Commit f15c1195 authored by timel's avatar timel Committed by Chunchi Che

feat: automode

parent d43799ce
#created by ygopro2
#main
14124483
9411399
9411399
18094166
18094166
18094166
40044918
40044918
59392529
50720316
50720316
27780618
27780618
16605586
16605586
22865492
22865492
23434538
23434538
14558127
14558127
13650422
83965310
81439173
8949584
8949584
32807846
52947044
45906428
24094653
21143940
21143940
21143940
48130397
24224830
24224830
12071500
24299458
24299458
10045474
#extra
86165817
41209827
29095552
40854197
60461804
60461804
22908820
58481572
58481572
89870349
46759931
63813056
1948619
58004362
58004362
!side
27204311
34267821
34267821
43534808
43534808
94145021
94145021
18144506
54693926
54693926
43898403
43898403
65681983
23002292
#created by ygopro2
#main
27204311
33331231
37351133
37351133
26077387
26077387
26077387
23434538
23434538
23434538
14558127
14558127
14558127
97268402
97268402
18144506
25955749
99550630
35261759
35261759
73628505
63166095
63166095
46271408
67169062
32807846
70368879
70368879
51227866
9726840
9726840
52340444
24224830
24224830
98338152
98338152
24299458
24010609
24010609
97616504
50005218
67616300
10045474
10045474
#extra
85289965
86066372
49202162
38342335
45819647
2857636
50588353
75147529
12421694
90673288
90673288
90673288
63288573
8491308
8491308
!side
27204311
33331231
34267821
34267821
24508238
73642296
59438930
59438930
5133471
24299458
83326048
83326048
69452756
69452756
23002292
This diff is collapsed.
......@@ -3,33 +3,33 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@ant-design/pro-components": "^2.3.49",
"@ant-design/pro-components": "^2.4.4",
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/sortable": "^7.0.2",
"@react-spring/shared": "^9.6.1",
"@react-spring/types": "^9.6.1",
"@react-spring/web": "^9.6.1",
"@reduxjs/toolkit": "^1.8.6",
"@react-spring/shared": "^9.7.2",
"@react-spring/types": "^9.7.2",
"@react-spring/web": "^9.7.2",
"@reduxjs/toolkit": "^1.9.3",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.3.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.11.48",
"@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6",
"@types/react-redux": "^7.1.24",
"@types/node": "^16.18.23",
"@types/react": "^18.0.33",
"@types/react-dom": "^18.0.11",
"@types/react-redux": "^7.1.25",
"@types/sql.js": "^1.4.4",
"antd": "^5.2.4",
"antd": "^5.4.0",
"axios": "^0.27.2",
"google-protobuf": "^3.21.0",
"google-protobuf": "^3.21.2",
"react": "^18.2.0",
"react-babylonjs": "^3.1.15",
"react-dom": "^18.2.0",
"react-draggable": "^4.4.5",
"react-redux": "^8.0.4",
"react-router-dom": "^6.4.0",
"react-scripts": "^2.1.3",
"socket.io-client": "^4.5.1",
"react-redux": "^8.0.5",
"react-router-dom": "^6.10.0",
"react-scripts": "^2.1.8",
"socket.io-client": "^4.6.1",
"sql.js": "^1.8.0",
"vite-plugin-svgr": "^2.4.0",
"web-vitals": "^2.1.4",
......@@ -65,19 +65,20 @@
]
},
"devDependencies": {
"@babylonjs/core": "^5.29.0",
"@babylonjs/gui": "^5.35.1",
"@babylonjs/core": "^5.54.0",
"@babylonjs/gui": "^5.54.0",
"@types/google-protobuf": "^3.15.6",
"@typescript-eslint/eslint-plugin": "^5.40.1",
"@typescript-eslint/parser": "^5.40.1",
"@vitejs/plugin-react": "^2.1.0",
"eslint": "^8.26.0",
"eslint-config-prettier": "^8.5.0",
"@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.57.1",
"@vitejs/plugin-react": "^3.1.0",
"eslint": "^8.38.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"prettier": "^2.7.1",
"sass": "^1.58.0",
"typescript": "^4.8.4",
"vite": "^3.1.0",
"vite-plugin-wasm-pack": "^0.1.12"
"prettier": "^2.8.7",
"sass": "^1.61.0",
"typescript": "^4.9.5",
"vite": "^4.2.1",
"vite-plugin-wasm-pack": "^0.1.12",
"vite-ydk-loader": "^0.0.2"
}
}
{
"hero": {
"main": [
14124483,
9411399,
9411399,
18094166,
18094166,
18094166,
40044918,
40044918,
59392529,
50720316,
50720316,
27780618,
27780618,
16605586,
16605586,
22865492,
22865492,
23434538,
23434538,
14558127,
14558127,
13650422,
83965310,
81439173,
8949584,
8949584,
32807846,
52947044,
45906428,
24094653,
21143940,
21143940,
21143940,
48130397,
24224830,
24224830,
12071500,
24299458,
24299458,
10045474
],
"extra": [
86165817,
41209827,
29095552,
40854197,
60461804,
60461804,
22908820,
58481572,
58481572,
89870349,
46759931,
63813056,
1948619,
58004362,
58004362
],
"side": [
27204311,
34267821,
34267821,
43534808,
43534808,
94145021,
94145021,
18144506,
54693926,
54693926,
43898403,
43898403,
65681983,
23002292
]
}
}
// import axios from "axios";
import DECKS from "./deck.json";
const DECKS: Record<string, { default: IDeck }> = import.meta.glob(
"/neos-assets/structure-decks/*.ydk",
{
eager: true,
}
);
const DeckManager = _objToMap(DECKS);
export interface IDeck {
main?: number[];
extra?: number[];
side?: number[];
}
export const DeckManager = _objToMap(
Object.keys(DECKS).map((key) => ({
...DECKS[key].default,
deckName: key.split("/").pop()?.split(".")[0] ?? "undefined",
}))
);
/*
* 返回卡组资源。
......@@ -20,13 +23,20 @@ export interface IDeck {
export async function fetchDeck(deck: string): Promise<IDeck> {
const res = DeckManager.get(deck);
return res || { main: [], extra: [], side: [] };
return res ?? { deckName: "undefined", main: [], extra: [], side: [] };
}
function _objToMap(object: any): Map<string, IDeck> {
let map = new Map();
function _objToMap(object: IDeck[]): Map<string, IDeck> {
const map: Map<string, IDeck> = new Map();
Object.keys(object).forEach((key) => map.set(key, object[key]));
object.forEach((value) => map.set(value.deckName, value));
return map;
}
export interface IDeck {
deckName: string;
main: number[];
extra: number[];
side: number[];
}
export * from "./deckManager";
import { useEnv } from "../hook";
const { DEV, VITE_IS_AI_MODE, VITE_IS_AI_FIRST } = useEnv();
interface AutomationConfig {
isAiMode: boolean;
isAiFirst: boolean;
}
const defaultConfig: AutomationConfig = {
isAiMode: false,
isAiFirst: false,
};
const aiModeConfig: AutomationConfig = {
isAiMode: true,
isAiFirst: VITE_IS_AI_FIRST,
};
const genAutomationConfig = () => {
if (DEV) {
if (VITE_IS_AI_MODE) {
return aiModeConfig;
}
// 待拓展
}
return defaultConfig;
};
export const automationConfig: AutomationConfig = genAutomationConfig();
import { useEnv } from "../hook";
const { DEV, VITE_IS_AI_MODE, VITE_AI_MODE_DEFAULT_DECK } = useEnv();
interface DefaultsConfig {
defaultPlayer: string;
defaultDeck: string;
defaultPassword: string;
defaultMora: string;
}
const defaultConfig: DefaultsConfig = {
defaultPlayer: "",
// 无需考虑undefined的情况,如果为undefined,界面上会显示【请选择】
defaultDeck: VITE_AI_MODE_DEFAULT_DECK,
defaultPassword: "",
defaultMora: "scissors",
};
const aiModeConfig: DefaultsConfig = {
...defaultConfig,
defaultPlayer: `AiKiller${Math.random().toString(36).slice(2)}}`,
defaultPassword: "AI",
};
const genDefaultsConfig = () => {
if (DEV) {
if (VITE_IS_AI_MODE) {
return aiModeConfig;
}
// 待拓展
}
return defaultConfig;
};
export const defaultsConfig: DefaultsConfig = genDefaultsConfig();
import { useEnv } from "../hook";
import NeosDevConfig from "../../neos.config.json";
import NeosProdConfig from "../../neos.config.prod.json";
const { DEV } = useEnv();
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y
? 1
: 2
? true
: false;
type Expect<T extends true> = T;
/**
* 确保两个json文件的结构一致,不一致会报错
*/
type _ = Expect<Equal<typeof NeosDevConfig, typeof NeosProdConfig>>;
export const envConfig = DEV ? NeosDevConfig : NeosProdConfig;
import { automationConfig } from "./automation";
import { defaultsConfig } from "./defaults";
import { envConfig } from "./env";
export const useConfig = () =>
({
automation: automationConfig,
defaults: defaultsConfig,
...envConfig,
} satisfies Record<string, unknown>);
/// <reference types="react-scripts" />
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_IS_AI_MODE: boolean;
readonly VITE_IS_AI_FIRST: boolean;
readonly VITE_AI_MODE_DEFAULT_DECK: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
export * from "./useApp";
export * from "./useEnv";
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import type { RootState, AppDispatch } from "./store";
import type { RootState, AppDispatch } from "../store";
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch;
......
/**
* @description 将 vite 的原始环境变量转成正确的类型
* @returns 转换成正确类型的 vite 环境变量
*/
export const useEnv = (): ImportMetaEnv => {
const env = import.meta.env;
const ret: any = {};
Object.keys(env).forEach((envKey) => {
// 转成正确的布尔类型
ret[envKey] =
env[envKey] === "true"
? true
: env[envKey] === "false"
? false
: env[envKey];
});
return ret;
};
/// <reference types="react-scripts" />
......@@ -7,16 +7,21 @@
*
* */
import { Input } from "antd";
import React, { useState, ChangeEvent } from "react";
import React, { useState, ChangeEvent, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import "../styles/core.scss";
import NeosConfig from "../../neos.config.json";
import { useConfig } from "../config";
const serverConfig = NeosConfig.servers;
const {
defaults: { defaultPlayer, defaultPassword },
automation: { isAiMode },
} = useConfig();
export default function Login() {
const [player, setPlayer] = useState("");
const [passWd, setPasswd] = useState("");
const [player, setPlayer] = useState(defaultPlayer);
const [passWd, setPasswd] = useState(defaultPassword);
const [ip, setIp] = useState(`${serverConfig[0].ip}:${serverConfig[0].port}`);
const navigate = useNavigate();
......@@ -30,13 +35,19 @@ export default function Login() {
setIp(event.target.value);
};
const handleSubmit = () => navigate(`/room/${player}/${passWd}/${ip}`);
useEffect(() => {
// 如果开启了AI模式,直接进入房间
if (isAiMode) {
handleSubmit();
}
}, []);
return (
<div className="container">
<div id="login">
<form
className="login-form"
onSubmit={() => navigate(`/room/${player}/${passWd}/${ip}`)}
>
<form className="login-form" onSubmit={handleSubmit}>
<span className="fa fa-user"></span>
<Input
autoFocus
......
import React from "react";
import { sendHandResult, sendTpResult } from "../api/ocgcore/ocgHelper";
import { useAppSelector } from "../hook";
import { useConfig } from "../config";
import {
selectHandSelectAble,
unSelectHandAble,
......@@ -18,6 +19,11 @@ import {
TableOutlined,
} from "@ant-design/icons";
const {
automation: { isAiMode, isAiFirst },
defaults: { defaultMora },
} = useConfig();
const Mora = () => {
const dispatch = store.dispatch;
const selectHandAble = useAppSelector(selectHandSelectAble);
......@@ -30,13 +36,6 @@ const Mora = () => {
ip?: string;
}>();
useEffect(() => {
// 若对局已经开始,自动跳转
if (duelHsStart) {
navigate(`/duel/${player}/${passWd}/${ip}`);
}
}, [duelHsStart]);
const handleSelectMora = (selected: string) => {
sendHandResult(selected);
dispatch(unSelectHandAble());
......@@ -46,6 +45,25 @@ const Mora = () => {
dispatch(unSelectTpAble());
};
useEffect(() => {
// 若对局已经开始,自动跳转
if (duelHsStart) {
navigate(`/duel/${player}/${passWd}/${ip}`);
}
}, [duelHsStart]);
useEffect(() => {
if (isAiMode) {
handleSelectMora(defaultMora);
}
}, [selectHandAble]);
useEffect(() => {
if (isAiMode && !selectHandAble && selectTpAble) {
handleSelectTp(!isAiFirst);
}
}, [selectHandAble, selectTpAble]);
return (
<>
<Modal title="请选择猜拳" open={selectHandAble} footer={<></>}>
......
......@@ -4,10 +4,10 @@ import {
Avatar,
Space,
Button,
Dropdown,
notification,
Upload,
message,
Select,
} from "antd";
import { useNavigate } from "react-router-dom";
import React, { useState, useEffect } from "react";
......@@ -20,9 +20,10 @@ import {
selectPlayer1,
} from "../reducers/playerSlice";
import { useAppSelector } from "../hook";
import { useConfig } from "../config";
import { selectJoined } from "../reducers/joinSlice";
import { selectChat } from "../reducers/chatSlice";
import { fetchDeck, IDeck } from "../api/deck";
import { fetchDeck, type IDeck, DeckManager } from "../api/deck";
import {
sendUpdateDeck,
sendHsReady,
......@@ -34,22 +35,26 @@ import {
LoginOutlined,
LogoutOutlined,
SendOutlined,
DownOutlined,
TagOutlined,
UploadOutlined,
} from "@ant-design/icons";
import { initMeExtraDeckMeta } from "../reducers/duel/extraDeckSlice";
import type { MenuProps, UploadProps } from "antd";
import type { UploadProps } from "antd";
import { useParams } from "react-router-dom";
import { selectDuelStart } from "../reducers/moraSlice";
import NeosConfig from "../../neos.config.json";
import YGOProDeck from "ygopro-deck-encode";
//@ts-ignore
// @ts-ignore
import rustInit from "rust-src";
import { initStrings } from "../api/strings";
const READY_STATE = "ready";
const {
defaults: { defaultDeck },
automation: { isAiMode },
} = useConfig();
const WaitRoom = () => {
const params = useParams<{
player?: string;
......@@ -102,15 +107,13 @@ const WaitRoom = () => {
const player1 = useAppSelector(selectPlayer1);
const duelStart = useAppSelector(selectDuelStart);
const [api, contextHolder] = notification.useNotification();
const [deckTitle, setDeckTitle] = useState("请选择卡组");
// FIXME: 这些数据应该从`store`中获取
// TODO: 云卡组
const decks: MenuProps["items"] = [
{
label: "hero",
key: "hero",
},
];
const decks = [...DeckManager.keys()].map((deckName) => ({
value: deckName,
label: deckName,
}));
const [uploadState, setUploadState] = useState("");
const uploadProps: UploadProps = {
name: "file",
......@@ -135,8 +138,7 @@ const WaitRoom = () => {
) {
// YDK解析成功
message.success(`${file.name}解析成功`);
onDeckReady(deck);
onDeckReady({ deckName: file.name, ...deck });
} else {
message.error(`${file.name}解析失败`);
setUploadState("ERROR");
......@@ -145,9 +147,9 @@ const WaitRoom = () => {
},
};
const onDeckReady = (deck: IDeck) => {
const onDeckReady = async (deck: IDeck) => {
sendUpdateDeck(deck);
dispatch(
await dispatch(
initMeExtraDeckMeta({ controler: 0, codes: deck.extra?.reverse() || [] })
);
setChoseDeck(true);
......@@ -155,8 +157,7 @@ const WaitRoom = () => {
const handleChoseDeck = async (deckName: string) => {
const deck = await fetchDeck(deckName);
onDeckReady(deck);
await onDeckReady(deck);
};
const handleChoseReady = () => {
......@@ -172,6 +173,18 @@ const WaitRoom = () => {
useEffect(() => {
if (joined) {
api.info({ message: "成功加入房间!", placement: "top" });
/** 如果是开发者模式下的人机对战,应该自动选择卡组,并自动准备和开始 */
const runAiMode = async () => {
await new Promise((resolve) => setTimeout(resolve, 500));
await handleChoseDeck(defaultDeck!);
handleChoseReady();
handleChoseStart();
};
(async () => {
if (isAiMode) {
await runAiMode();
}
})();
}
}, [joined]);
useEffect(() => {
......@@ -260,22 +273,11 @@ const WaitRoom = () => {
)}
</Space>
<Space wrap size={16}>
<Dropdown
menu={{
items: decks,
onClick: async ({ key }) => {
await handleChoseDeck(key);
setDeckTitle(key);
},
}}
>
<a onClick={(e) => e.preventDefault()}>
<Space>
{deckTitle}
<DownOutlined />
</Space>
</a>
</Dropdown>
<Select
defaultValue={defaultDeck}
onChange={handleChoseDeck}
options={decks}
/>
</Space>
<Space>
<Upload {...uploadProps}>
......
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import svgr from 'vite-plugin-svgr'
import wasmPack from 'vite-plugin-wasm-pack';
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import svgr from "vite-plugin-svgr";
import wasmPack from "vite-plugin-wasm-pack";
import ydkLoader from "vite-ydk-loader";
// https://vitejs.dev/config/
export default defineConfig({
build: {
minify: false
minify: false,
},
plugins: [react(), svgr(), wasmPack('./rust-src')]
})
plugins: [react(), svgr(), wasmPack("./rust-src"), ydkLoader()],
resolve: {
extensions: [".js", ".json", ".ydk"],
},
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment