Commit 5226ac18 authored by timel's avatar timel

feat: auto mode

parent d43799ce
export * from "./useApp";
export * from "./useAutoMode";
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;
......
/*
* 旨在跳过登录界面,直接进入游戏(和AI对战、后续可能增加自动的PVP)。
* 仅在开发环境下生效,便于开发者快速调节决斗界面。
*/
interface AutoModeConfig {
defaultPlayer?: string;
defaultDeck?: string;
defaultPassword?: string;
defaultMora?: string;
isAiMode?: boolean;
isAiFirst?: boolean;
}
const autoModeConfig: AutoModeConfig = {
defaultPlayer: "",
defaultDeck: "",
defaultPassword: "",
};
const aiModeConfig: AutoModeConfig = {
defaultPlayer: "AiKiller",
defaultDeck: import.meta.env.VITE_AI_MODE_DEFAULT_DECK ?? "hero",
defaultPassword: "AI",
defaultMora: "scissors",
isAiMode: true,
isAiFirst: import.meta.env.VITE_IS_AI_FIRST === "true",
};
export function useAutoMode(): AutoModeConfig {
if (!import.meta.env.DEV) return {};
if (import.meta.env.VITE_IS_AI_MODE === "true") {
return aiModeConfig;
}
return autoModeConfig;
}
......@@ -7,16 +7,18 @@
*
* */
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 { useAutoMode } from "../hook";
const serverConfig = NeosConfig.servers;
const { isAiMode, defaultPlayer, defaultPassword } = useAutoMode();
export default function Login() {
const [player, setPlayer] = useState("");
const [passWd, setPasswd] = useState("");
const [player, setPlayer] = useState(isAiMode ? defaultPlayer : "");
const [passWd, setPasswd] = useState(isAiMode ? defaultPassword : "");
const [ip, setIp] = useState(`${serverConfig[0].ip}:${serverConfig[0].port}`);
const navigate = useNavigate();
......@@ -30,13 +32,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 { useAppSelector, useAutoMode } from "../hook";
import {
selectHandSelectAble,
unSelectHandAble,
......@@ -18,6 +18,8 @@ import {
TableOutlined,
} from "@ant-design/icons";
const { isAiMode, isAiFirst, defaultMora } = useAutoMode();
const Mora = () => {
const dispatch = store.dispatch;
const selectHandAble = useAppSelector(selectHandSelectAble);
......@@ -37,6 +39,18 @@ const Mora = () => {
}
}, [duelHsStart]);
useEffect(() => {
if (isAiMode) {
handleSelectMora(defaultMora!);
}
}, [selectHandAble]);
useEffect(() => {
if (isAiMode && !selectHandAble && selectTpAble) {
handleSelectTp(!isAiFirst);
}
}, [selectHandAble, selectTpAble]);
const handleSelectMora = (selected: string) => {
sendHandResult(selected);
dispatch(unSelectHandAble());
......
......@@ -19,7 +19,7 @@ import {
selectPlayer0,
selectPlayer1,
} from "../reducers/playerSlice";
import { useAppSelector } from "../hook";
import { useAppSelector, useAutoMode } from "../hook";
import { selectJoined } from "../reducers/joinSlice";
import { selectChat } from "../reducers/chatSlice";
import { fetchDeck, IDeck } from "../api/deck";
......@@ -50,6 +50,8 @@ import { initStrings } from "../api/strings";
const READY_STATE = "ready";
const { isAiMode, defaultDeck } = useAutoMode();
const WaitRoom = () => {
const params = useParams<{
player?: string;
......@@ -90,7 +92,20 @@ const WaitRoom = () => {
await rustInit(url);
};
init();
/** 如果是开发者模式下的人机对战,应该自动选择卡组,并自动准备和开始 */
const runAiMode = async () => {
await new Promise((resolve) => setTimeout(resolve, 500));
await handleChoseDeck(defaultDeck!);
handleChoseReady();
handleChoseStart();
};
(async () => {
await init();
if (isAiMode) {
await runAiMode();
}
})();
}
}, []);
......@@ -103,12 +118,14 @@ const WaitRoom = () => {
const duelStart = useAppSelector(selectDuelStart);
const [api, contextHolder] = notification.useNotification();
const [deckTitle, setDeckTitle] = useState("请选择卡组");
// FIXME: 这些数据应该从`store`中获取
// TODO: 云卡组
const DEFAULT_DECK = "hero";
const decks: MenuProps["items"] = [
{
label: "hero",
key: "hero",
label: DEFAULT_DECK,
key: DEFAULT_DECK,
},
];
const [uploadState, setUploadState] = useState("");
......@@ -145,9 +162,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 +172,8 @@ const WaitRoom = () => {
const handleChoseDeck = async (deckName: string) => {
const deck = await fetchDeck(deckName);
onDeckReady(deck);
await onDeckReady(deck);
setDeckTitle(deckName);
};
const handleChoseReady = () => {
......@@ -263,10 +280,7 @@ const WaitRoom = () => {
<Dropdown
menu={{
items: decks,
onClick: async ({ key }) => {
await handleChoseDeck(key);
setDeckTitle(key);
},
onClick: ({ key }) => handleChoseDeck(key),
}}
>
<a onClick={(e) => e.preventDefault()}>
......
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