Commit 5b89ad0c authored by Chunchi Che's avatar Chunchi Che

YgoAgent

parent 52da069f
Pipeline #28420 passed with stages
in 13 minutes and 32 seconds
...@@ -388,24 +388,36 @@ export function convertPhase(phase: _Phase): Phase { ...@@ -388,24 +388,36 @@ export function convertPhase(phase: _Phase): Phase {
} }
export function parsePlayerFromMsg(msg: GM): number { export function parsePlayerFromMsg(msg: GM): number {
if ( switch (msg.gameMsg) {
msg instanceof GM.MsgSelectCard || case "select_card":
msg instanceof GM.MsgSelectTribute || return msg.select_card.player;
msg instanceof GM.MsgSelectSum || case "select_tribute":
msg instanceof GM.MsgSelectIdleCmd || return msg.select_tribute.player;
msg instanceof GM.MsgSelectChain || case "select_sum":
msg instanceof GM.MsgSelectPosition || return msg.select_sum.player;
msg instanceof GM.MsgSelectEffectYn || case "select_idle_cmd":
msg instanceof GM.MsgSelectYesNo || return msg.select_idle_cmd.player;
msg instanceof GM.MsgSelectBattleCmd || case "select_chain":
msg instanceof GM.MsgSelectUnselectCard || return msg.select_chain.player;
msg instanceof GM.MsgSelectOption || case "select_position":
msg instanceof GM.MsgSelectPlace || return msg.select_position.player;
msg instanceof GM.MsgAnnounce case "select_effect_yn":
) { return msg.select_effect_yn.player;
return msg.player; case "select_yes_no":
return msg.select_yes_no.player;
case "select_battle_cmd":
return msg.select_battle_cmd.player;
case "select_unselect_card":
return msg.select_unselect_card.player;
case "select_option":
return msg.select_option.player;
case "select_place":
return msg.select_place.player;
case "announce":
return msg.announce.player;
default:
throw new Error(`Unsupported message type: ${msg}`);
} }
throw new Error(`Unsupported message type: ${msg}`);
} }
function convertMsgSelectCard(msg: GM.MsgSelectCard): MsgSelectCard { function convertMsgSelectCard(msg: GM.MsgSelectCard): MsgSelectCard {
...@@ -697,68 +709,51 @@ function convertMsgAnnounceNumber(msg: GM.MsgAnnounce): MsgAnnounceNumber { ...@@ -697,68 +709,51 @@ function convertMsgAnnounceNumber(msg: GM.MsgAnnounce): MsgAnnounceNumber {
} }
export function convertActionMsg(msg: ygopro.StocGameMessage): ActionMsg { export function convertActionMsg(msg: ygopro.StocGameMessage): ActionMsg {
if (msg instanceof GM.MsgSelectCard) { switch (msg.gameMsg) {
return { case "select_card":
data: convertMsgSelectCard(msg),
};
} else if (msg instanceof GM.MsgSelectTribute) {
return {
data: convertMsgSelectTribute(msg),
};
} else if (msg instanceof GM.MsgSelectSum) {
return {
data: convertMsgSelectSum(msg),
};
} else if (msg instanceof GM.MsgSelectIdleCmd) {
return {
data: convertMsgSelectIdleCmd(msg),
};
} else if (msg instanceof GM.MsgSelectChain) {
return {
data: convertMsgSelectChain(msg),
};
} else if (msg instanceof GM.MsgSelectPosition) {
return {
data: convertMsgSelectPosition(msg),
};
} else if (msg instanceof GM.MsgSelectEffectYn) {
return {
data: convertMsgSelectEffectYn(msg),
};
} else if (msg instanceof GM.MsgSelectYesNo) {
return {
data: convertMsgSelectYesNo(msg),
};
} else if (msg instanceof GM.MsgSelectBattleCmd) {
return {
data: convertMsgSelectBattleCmd(msg),
};
} else if (msg instanceof GM.MsgSelectUnselectCard) {
return {
data: convertMsgSelectUnselectCard(msg),
};
} else if (msg instanceof GM.MsgSelectOption) {
return {
data: convertMsgSelectOption(msg),
};
} else if (msg instanceof GM.MsgSelectPlace) {
return {
data: convertMsgSelectPlace(msg),
};
} else if (msg instanceof GM.MsgAnnounce) {
if (msg.announce_type === GM.MsgAnnounce.AnnounceType.Attribute) {
return {
data: convertMsgAnnounceAttrib(msg),
};
} else if (msg.announce_type === GM.MsgAnnounce.AnnounceType.Number) {
return { return {
data: convertMsgAnnounceNumber(msg), data: convertMsgSelectCard(msg.select_card),
}; };
} else { case "select_tribute":
throw new Error(`Unsupported announce type: ${msg.announce_type}`); return { data: convertMsgSelectTribute(msg.select_tribute) };
case "select_sum":
return { data: convertMsgSelectSum(msg.select_sum) };
case "select_idle_cmd":
return { data: convertMsgSelectIdleCmd(msg.select_idle_cmd) };
case "select_chain":
return { data: convertMsgSelectChain(msg.select_chain) };
case "select_position":
return { data: convertMsgSelectPosition(msg.select_position) };
case "select_effect_yn":
return { data: convertMsgSelectEffectYn(msg.select_effect_yn) };
case "select_yes_no":
return { data: convertMsgSelectYesNo(msg.select_yes_no) };
case "select_battle_cmd":
return { data: convertMsgSelectBattleCmd(msg.select_battle_cmd) };
case "select_unselect_card":
return { data: convertMsgSelectUnselectCard(msg.select_unselect_card) };
case "select_option":
return { data: convertMsgSelectOption(msg.select_option) };
case "select_place":
return { data: convertMsgSelectPlace(msg.select_place) };
case "announce": {
const announce = msg.announce;
if (announce.announce_type === GM.MsgAnnounce.AnnounceType.Attribute) {
return {
data: convertMsgAnnounceAttrib(announce),
};
} else if (
announce.announce_type === GM.MsgAnnounce.AnnounceType.Number
) {
return {
data: convertMsgAnnounceNumber(announce),
};
} else {
throw new Error(`Unsupported announce type: ${announce.announce_type}`);
}
} }
} else { default:
throw new Error(`Unsupported message type: ${msg}`); throw new Error(`Unsupported message type: ${msg}`);
} }
} }
......
...@@ -6,6 +6,7 @@ import { ...@@ -6,6 +6,7 @@ import {
MatStore, MatStore,
PlaceStore, PlaceStore,
RoomStore, RoomStore,
SideStore,
} from "@/stores"; } from "@/stores";
interface ContextInitInfo { interface ContextInitInfo {
...@@ -14,6 +15,7 @@ interface ContextInitInfo { ...@@ -14,6 +15,7 @@ interface ContextInitInfo {
placeStore?: PlaceStore; placeStore?: PlaceStore;
roomStore?: RoomStore; roomStore?: RoomStore;
chatStore?: ChatStore; chatStore?: ChatStore;
sideStore?: SideStore;
} }
export class Context { export class Context {
...@@ -22,16 +24,18 @@ export class Context { ...@@ -22,16 +24,18 @@ export class Context {
public placeStore: PlaceStore; public placeStore: PlaceStore;
public roomStore: RoomStore; public roomStore: RoomStore;
public chatStore: ChatStore; public chatStore: ChatStore;
public sideStore: SideStore;
constructor(); constructor();
constructor(initInfo: ContextInitInfo); constructor(initInfo: ContextInitInfo);
constructor(initInfo?: ContextInitInfo) { constructor(initInfo?: ContextInitInfo) {
const { matStore, cardStore, placeStore, roomStore, chatStore } = const { matStore, cardStore, placeStore, roomStore, chatStore, sideStore } =
initInfo ?? {}; initInfo ?? {};
this.matStore = matStore ?? new MatStore(); this.matStore = matStore ?? new MatStore();
this.cardStore = cardStore ?? new CardStore(); this.cardStore = cardStore ?? new CardStore();
this.placeStore = placeStore ?? new PlaceStore(); this.placeStore = placeStore ?? new PlaceStore();
this.roomStore = roomStore ?? new RoomStore(); this.roomStore = roomStore ?? new RoomStore();
this.chatStore = chatStore ?? new ChatStore(); this.chatStore = chatStore ?? new ChatStore();
this.sideStore = sideStore ?? new SideStore();
} }
} }
...@@ -5,8 +5,20 @@ import { Context } from "./context"; ...@@ -5,8 +5,20 @@ import { Context } from "./context";
export class Container { export class Container {
public context: Context; public context: Context;
public conn: WebSocketStream; public conn: WebSocketStream;
// ref: https://yugioh.fandom.com/wiki/Kuriboh
private enableKuriboh: boolean = false;
constructor(context: Context, conn: WebSocketStream) { constructor(context: Context, conn: WebSocketStream) {
this.context = context; this.context = context;
this.conn = conn; this.conn = conn;
} }
public setEnableKuriboh(value: boolean) {
this.enableKuriboh = value;
}
public getEnableKuriboh(): boolean {
return this.enableKuriboh;
}
} }
import {
PredictReq,
sendSelectBattleCmdResponse,
sendSelectEffectYnResponse,
sendSelectIdleCmdResponse,
sendSelectMultiResponse,
sendSelectOptionResponse,
sendSelectPlaceResponse,
sendSelectPositionResponse,
sendSelectSingleResponse,
ygopro,
} from "@/api";
import { predictDuel } from "@/api/ygoAgent/predict";
import {
Global,
Input,
MsgSelectSum,
MultiSelectMsg,
} from "@/api/ygoAgent/schema";
import {
convertActionMsg,
convertCard,
convertDeckCard,
convertPhase,
convertPositionResponse,
parsePlayerFromMsg,
} from "@/api/ygoAgent/transaction";
import { Container } from "@/container";
import { cardStore, matStore } from "@/stores";
import { argmax, computeSetDifference } from "./util";
const { DECK, HAND, MZONE, SZONE, GRAVE, REMOVED, EXTRA } = ygopro.CardZone;
export function genAgentInput(msg: ygopro.StocGameMessage): Input {
const mat = matStore;
// TODO (ygo-agent): TZONE
const zones = [DECK, HAND, MZONE, SZONE, GRAVE, REMOVED, EXTRA];
const player = parsePlayerFromMsg(msg);
const opponent = 1 - player;
const cards = cardStore.inner
.filter(
(card) =>
zones.includes(card.location.zone) &&
!(card.location.zone === DECK && card.location.controller === player),
)
.map((card) => convertCard(card, player));
const cardCodesMe = cardStore.inner
.filter(
(card) =>
zones.includes(card.location.zone) &&
card.location.controller === player,
)
.map((card) => card.code);
const cardCodesMeDeck = computeSetDifference(mat.mainDeck, cardCodesMe);
const mainDeckCardMeta = mat.mainDeckCardMeta;
// TODO (ygo-agent): 临时方案,有很多边界情况未考虑
const deckCardsMe = cardCodesMeDeck.map((code) =>
convertDeckCard(mainDeckCardMeta.get(code)!),
);
const turnPlayer = mat.currentPlayer;
const global: Global = {
is_first: player === 0,
is_my_turn: turnPlayer === player,
my_lp: mat.initInfo.of(player).life,
op_lp: mat.initInfo.of(opponent).life,
phase: convertPhase(mat.phase.currentPhase),
turn: mat.turnCount,
};
const actionMsg = convertActionMsg(msg);
return {
global,
cards: deckCardsMe.concat(cards),
action_msg: actionMsg,
};
}
async function sendRequest(req: PredictReq) {
const duelId = matStore.duelId;
const resp = await predictDuel(duelId, req);
if (resp !== undefined) {
matStore.agentIndex = resp.index;
} else {
throw new Error("Failed to get predict response");
}
// TODO: 下面的逻辑需要封装一下,因为:
// 1. 现在实现的功能是AI托管,UI上不需要感知AI的预测结果;
// 2. 后面如果需要实现AI辅助功能,UI上需要感知AI的预测结果,
// 所以需要单独提供接口能力。
const preds = resp.predict_results.action_preds;
const actionIdx = argmax(preds, (r) => r.prob);
matStore.prevActionIndex = actionIdx;
const pred = preds[actionIdx];
return pred;
}
// TODO:
// 1. 逻辑需要拆分下
// 2. 这个函数在外面被各个 service 模块分散调用,
// 需要改成在`gameMsg.ts`调用,并通过`try..catch`正确处理错误。
export async function sendAIPredictAsResponse(
container: Container,
msg: ygopro.StocGameMessage,
) {
const conn = container.conn;
const input = genAgentInput(msg);
const msgName = input.action_msg.data.msg_type;
const multiSelectMsgs = ["select_card", "select_tribute", "select_sum"];
if (multiSelectMsgs.includes(msgName)) {
switch (msgName) {
case "select_tribute":
case "select_card": {
const msg_ = input.action_msg.data as MultiSelectMsg;
const selected = [];
const responses = [];
while (true) {
msg_.selected = selected;
const req = {
index: matStore.agentIndex,
input: input,
prev_action_idx: matStore.prevActionIndex,
};
const response = (await sendRequest(req)).response;
if (response !== -1) {
selected.push(matStore.prevActionIndex);
responses.push(response);
}
if (response === -1 || selected.length === msg_.max) {
sendSelectMultiResponse(conn, responses);
break;
}
}
break;
}
case "select_sum":
const msg_ = input.action_msg.data as MsgSelectSum;
const selected = [];
const responses = [];
for (const c of msg_.must_cards) {
responses.push(c.response);
}
while (true) {
msg_.selected = selected;
const req = {
index: matStore.agentIndex,
input: input,
prev_action_idx: matStore.prevActionIndex,
};
const pred = await sendRequest(req);
const idx = matStore.prevActionIndex;
selected.push(idx);
responses.push(pred.response);
if (pred.can_finish) {
sendSelectMultiResponse(conn, responses);
break;
}
}
break;
}
} else {
const req = {
index: matStore.agentIndex,
input: input,
prev_action_idx: matStore.prevActionIndex,
};
const response = (await sendRequest(req)).response;
switch (msgName) {
case "announce_attrib":
case "announce_number":
sendSelectOptionResponse(conn, response);
break;
case "select_battlecmd":
sendSelectBattleCmdResponse(conn, response);
break;
case "select_chain":
sendSelectSingleResponse(conn, response);
break;
case "select_yesno":
case "select_effectyn":
sendSelectEffectYnResponse(conn, response === 1);
break;
case "select_idlecmd":
sendSelectIdleCmdResponse(conn, response);
break;
case "select_option":
sendSelectOptionResponse(conn, response);
break;
case "select_position":
sendSelectPositionResponse(conn, convertPositionResponse(response));
break;
case "select_place": {
const place = (msg as unknown as ygopro.StocGameMessage.MsgSelectPlace)
.places[response];
sendSelectPlaceResponse(conn, {
controller: place.controller,
zone: place.zone,
sequence: place.sequence,
});
break;
}
case "select_unselect_card": {
if (response === -1) {
sendSelectSingleResponse(conn, -1);
} else {
sendSelectMultiResponse(conn, [response]);
}
break;
}
}
}
}
This diff is collapsed.
import { fetchStrings, Region, ygopro } from "@/api"; import { fetchStrings, Region, ygopro } from "@/api";
import { displayOptionModal } from "@/ui/Duel/Message"; import { displayOptionModal } from "@/ui/Duel/Message";
import MsgAnnounce = ygopro.StocGameMessage.MsgAnnounce; import MsgAnnounce = ygopro.StocGameMessage.MsgAnnounce;
import { Container } from "@/container";
import { sendAIPredictAsResponse } from "@/service/duel/agent";
import { matStore } from "@/stores";
import { displayAnnounceModal } from "@/ui/Duel/Message/AnnounceModal"; import { displayAnnounceModal } from "@/ui/Duel/Message/AnnounceModal";
export default async (container: Container, announce: MsgAnnounce) => { export default async (announce: MsgAnnounce) => {
if (matStore.autoSelect) {
// TODO: 如果是开启 AI 模式,不应该调用这个函数
console.log("intercept announce");
await sendAIPredictAsResponse(
container,
announce as unknown as ygopro.StocGameMessage,
);
return;
}
const type_ = announce.announce_type; const type_ = announce.announce_type;
let min = announce.min; let min = announce.min;
if ( if (
......
...@@ -3,6 +3,7 @@ import { Container } from "@/container"; ...@@ -3,6 +3,7 @@ import { Container } from "@/container";
import { replayStore } from "@/stores"; import { replayStore } from "@/stores";
import { showWaiting } from "@/ui/Duel/Message"; import { showWaiting } from "@/ui/Duel/Message";
import { YgoAgent } from "./agent";
import onAnnounce from "./announce"; import onAnnounce from "./announce";
import onMsgAttack from "./attack"; import onMsgAttack from "./attack";
import onMsgAttackDisable from "./attackDisable"; import onMsgAttackDisable from "./attackDisable";
...@@ -69,19 +70,6 @@ const ActiveList = [ ...@@ -69,19 +70,6 @@ const ActiveList = [
"select_battle_cmd", "select_battle_cmd",
"select_unselect_card", "select_unselect_card",
"select_yes_no", "select_yes_no",
];
const ReplayIgnoreMsg = [
"select_idle_cmd",
"select_place",
"select_card",
"select_chain",
"select_effect_yn",
"select_position",
"select_option",
"select_battle_cmd",
"select_unselect_card",
"select_yes_no",
"select_tribute", "select_tribute",
"select_counter", "select_counter",
"select_sum", "select_sum",
...@@ -93,21 +81,41 @@ const ReplayIgnoreMsg = [ ...@@ -93,21 +81,41 @@ const ReplayIgnoreMsg = [
export default async function handleGameMsg( export default async function handleGameMsg(
container: Container, container: Container,
pb: ygopro.YgoStocMsg, pb: ygopro.YgoStocMsg,
agent?: YgoAgent,
): Promise<void> { ): Promise<void> {
const msg = pb.stoc_game_msg; const msg = pb.stoc_game_msg;
if (ActiveList.includes(msg.gameMsg)) { if (ActiveList.includes(msg.gameMsg)) {
showWaiting(false); showWaiting(false);
}
if (replayStore.isReplay && ReplayIgnoreMsg.includes(msg.gameMsg)) return; if (replayStore.isReplay) return;
console.log(msg.gameMsg); if (agent && !agent.getDisable()) {
console.info(`Handling msg: ${msg.gameMsg} with YgoAgent`);
const enableKuriboh = container.getEnableKuriboh();
try {
await agent.sendAIPredictAsResponse(container.conn, msg, enableKuriboh);
if (enableKuriboh) return;
} catch (e) {
console.error(`Erros occurs when handling msg ${msg.gameMsg}: ${e}`);
container.setEnableKuriboh(false);
// TODO: I18N
container.context.matStore.error = `AI模型监测到场上存在它没见过的卡片,
因此需要关掉AI辅助功能。\n
请耐心等待开发团队对模型进行优化,感谢!`;
agent.setDisable(true);
}
}
}
switch (msg.gameMsg) { switch (msg.gameMsg) {
case "start": { case "start": {
await onMsgStart(msg.start); await onMsgStart(msg.start);
// We should init agent when the MSG_START reached.
if (agent) await agent.init();
break; break;
} }
case "draw": { case "draw": {
...@@ -131,7 +139,7 @@ export default async function handleGameMsg( ...@@ -131,7 +139,7 @@ export default async function handleGameMsg(
break; break;
} }
case "select_idle_cmd": { case "select_idle_cmd": {
onMsgSelectIdleCmd(container, msg.select_idle_cmd); onMsgSelectIdleCmd(msg.select_idle_cmd);
break; break;
} }
...@@ -155,12 +163,12 @@ export default async function handleGameMsg( ...@@ -155,12 +163,12 @@ export default async function handleGameMsg(
break; break;
} }
case "select_effect_yn": { case "select_effect_yn": {
await onMsgSelectEffectYn(container, msg.select_effect_yn); await onMsgSelectEffectYn(msg.select_effect_yn);
break; break;
} }
case "select_position": { case "select_position": {
await onMsgSelectPosition(container, msg.select_position); await onMsgSelectPosition(msg.select_position);
break; break;
} }
...@@ -175,7 +183,7 @@ export default async function handleGameMsg( ...@@ -175,7 +183,7 @@ export default async function handleGameMsg(
break; break;
} }
case "select_battle_cmd": { case "select_battle_cmd": {
onMsgSelectBattleCmd(container, msg.select_battle_cmd); onMsgSelectBattleCmd(msg.select_battle_cmd);
break; break;
} }
...@@ -185,12 +193,12 @@ export default async function handleGameMsg( ...@@ -185,12 +193,12 @@ export default async function handleGameMsg(
break; break;
} }
case "select_unselect_card": { case "select_unselect_card": {
await onMsgSelectUnselectCard(container, msg.select_unselect_card); await onMsgSelectUnselectCard(msg.select_unselect_card);
break; break;
} }
case "select_yes_no": { case "select_yes_no": {
await onMsgSelectYesNo(container, msg.select_yes_no); await onMsgSelectYesNo(msg.select_yes_no);
break; break;
} }
...@@ -220,12 +228,12 @@ export default async function handleGameMsg( ...@@ -220,12 +228,12 @@ export default async function handleGameMsg(
break; break;
} }
case "select_sum": { case "select_sum": {
onMsgSelectSum(container, msg.select_sum); onMsgSelectSum(msg.select_sum);
break; break;
} }
case "select_tribute": { case "select_tribute": {
onMsgSelectTribute(container, msg.select_tribute); onMsgSelectTribute(msg.select_tribute);
break; break;
} }
...@@ -309,7 +317,7 @@ export default async function handleGameMsg( ...@@ -309,7 +317,7 @@ export default async function handleGameMsg(
break; break;
} }
case "announce": { case "announce": {
await onAnnounce(container, msg.announce); await onAnnounce(msg.announce);
break; break;
} }
......
...@@ -7,30 +7,17 @@ import { ...@@ -7,30 +7,17 @@ import {
} from "@/stores"; } from "@/stores";
import MsgSelectBattleCmd = ygopro.StocGameMessage.MsgSelectBattleCmd; import MsgSelectBattleCmd = ygopro.StocGameMessage.MsgSelectBattleCmd;
import { Container } from "@/container";
import { sendAIPredictAsResponse } from "@/service/duel/agent";
export default async ( export default async (selectBattleCmd: MsgSelectBattleCmd) => {
container: Container,
selectBattleCmd: MsgSelectBattleCmd,
) => {
const player = selectBattleCmd.player; const player = selectBattleCmd.player;
const cmds = selectBattleCmd.battle_cmds; const cmds = selectBattleCmd.battle_cmds;
// 先清掉之前的互动性 // 先清掉之前的互动性
// TODO: 确认这里在AI托管的模式下是否需要
cardStore.inner.forEach((card) => { cardStore.inner.forEach((card) => {
card.idleInteractivities = []; card.idleInteractivities = [];
}); });
if (matStore.autoSelect) {
console.log("intercept selectBattleCmd");
await sendAIPredictAsResponse(
container,
selectBattleCmd as unknown as ygopro.StocGameMessage,
);
return;
}
cmds.forEach((cmd) => { cmds.forEach((cmd) => {
const interactType = battleTypeToInteracType(cmd.battle_type); const interactType = battleTypeToInteracType(cmd.battle_type);
......
...@@ -2,8 +2,6 @@ import { sendSelectMultiResponse, ygopro } from "@/api"; ...@@ -2,8 +2,6 @@ import { sendSelectMultiResponse, ygopro } from "@/api";
import MsgSelectCard = ygopro.StocGameMessage.MsgSelectCard; import MsgSelectCard = ygopro.StocGameMessage.MsgSelectCard;
import { Container } from "@/container"; import { Container } from "@/container";
import { sendAIPredictAsResponse } from "@/service/duel/agent";
import { matStore } from "@/stores";
import { displaySelectActionsModal } from "@/ui/Duel/Message/SelectActionsModal"; import { displaySelectActionsModal } from "@/ui/Duel/Message/SelectActionsModal";
import { fetchCheckCardMeta } from "../utils"; import { fetchCheckCardMeta } from "../utils";
...@@ -14,15 +12,6 @@ export default async (container: Container, selectCard: MsgSelectCard) => { ...@@ -14,15 +12,6 @@ export default async (container: Container, selectCard: MsgSelectCard) => {
// TODO: handle release_param // TODO: handle release_param
if (matStore.autoSelect) {
console.log("intercept selectCard");
await sendAIPredictAsResponse(
container,
selectCard as unknown as ygopro.StocGameMessage,
);
return;
}
if (!cancelable && cards.length === 1) { if (!cancelable && cards.length === 1) {
// auto send // auto send
sendSelectMultiResponse(conn, [cards[0].response]); sendSelectMultiResponse(conn, [cards[0].response]);
......
import { sendSelectSingleResponse, ygopro } from "@/api"; import { sendSelectSingleResponse, ygopro } from "@/api";
import { Container } from "@/container"; import { Container } from "@/container";
import { sendAIPredictAsResponse } from "@/service/duel/agent";
import { ChainSetting, fetchSelectHintMeta, matStore } from "@/stores"; import { ChainSetting, fetchSelectHintMeta, matStore } from "@/stores";
import { displaySelectActionsModal } from "@/ui/Duel/Message/SelectActionsModal"; import { displaySelectActionsModal } from "@/ui/Duel/Message/SelectActionsModal";
...@@ -69,16 +68,6 @@ export default async (container: Container, selectChain: MsgSelectChain) => { ...@@ -69,16 +68,6 @@ export default async (container: Container, selectChain: MsgSelectChain) => {
} }
case 2: // 处理多张 case 2: // 处理多张
case 3: { case 3: {
if (matStore.autoSelect) {
// TODO: 确认AI模型是否可以处理其他case的情况
console.log("intercept selectChain");
await sendAIPredictAsResponse(
container,
selectChain as unknown as ygopro.StocGameMessage,
);
return;
}
// 处理强制发动的卡 // 处理强制发动的卡
fetchSelectHintMeta({ fetchSelectHintMeta({
selectHintData: 203, selectHintData: 203,
......
import { fetchStrings, Region, type ygopro } from "@/api"; import { fetchStrings, Region, type ygopro } from "@/api";
import { CardMeta, fetchCard } from "@/api/cards"; import { CardMeta, fetchCard } from "@/api/cards";
import { Container } from "@/container";
import { sendAIPredictAsResponse } from "@/service/duel/agent";
import { matStore } from "@/stores";
import { displayYesNoModal } from "@/ui/Duel/Message"; import { displayYesNoModal } from "@/ui/Duel/Message";
type MsgSelectEffectYn = ygopro.StocGameMessage.MsgSelectEffectYn; type MsgSelectEffectYn = ygopro.StocGameMessage.MsgSelectEffectYn;
// 这里改成了 async 不知道有没有影响 // 这里改成了 async 不知道有没有影响
export default async ( export default async (selectEffectYn: MsgSelectEffectYn) => {
container: Container,
selectEffectYn: MsgSelectEffectYn,
) => {
if (matStore.autoSelect) {
console.log("intercept selectEffectYn");
await sendAIPredictAsResponse(
container,
selectEffectYn as unknown as ygopro.StocGameMessage,
);
return;
}
const code = selectEffectYn.code; const code = selectEffectYn.code;
const location = selectEffectYn.location; const location = selectEffectYn.location;
const effect_description = selectEffectYn.effect_description; const effect_description = selectEffectYn.effect_description;
......
import { ygopro } from "@/api"; import { ygopro } from "@/api";
import { sendAIPredictAsResponse } from "@/service/duel/agent";
import { import {
cardStore, cardStore,
type Interactivity, type Interactivity,
...@@ -8,29 +7,17 @@ import { ...@@ -8,29 +7,17 @@ import {
} from "@/stores"; } from "@/stores";
import MsgSelectIdleCmd = ygopro.StocGameMessage.MsgSelectIdleCmd; import MsgSelectIdleCmd = ygopro.StocGameMessage.MsgSelectIdleCmd;
import { Container } from "@/container";
export default async ( export default async (selectIdleCmd: MsgSelectIdleCmd) => {
container: Container,
selectIdleCmd: MsgSelectIdleCmd,
) => {
const player = selectIdleCmd.player; const player = selectIdleCmd.player;
const cmds = selectIdleCmd.idle_cmds; const cmds = selectIdleCmd.idle_cmds;
// 先清掉之前的互动性 // 先清掉之前的互动性
// TODO: 确认这里是否需要在AI托管的时候调用
cardStore.inner.forEach((card) => { cardStore.inner.forEach((card) => {
card.idleInteractivities = []; card.idleInteractivities = [];
}); });
if (matStore.autoSelect) {
console.log("intercept selectIdleCmd");
await sendAIPredictAsResponse(
container,
selectIdleCmd as unknown as ygopro.StocGameMessage,
);
return;
}
cmds.forEach((cmd) => { cmds.forEach((cmd) => {
const interactType = idleTypeToInteractType(cmd.idle_type); const interactType = idleTypeToInteractType(cmd.idle_type);
......
...@@ -6,8 +6,6 @@ import { ...@@ -6,8 +6,6 @@ import {
type ygopro, type ygopro,
} from "@/api"; } from "@/api";
import { Container } from "@/container"; import { Container } from "@/container";
import { sendAIPredictAsResponse } from "@/service/duel/agent";
import { matStore } from "@/stores";
import { displayOptionModal } from "@/ui/Duel/Message"; import { displayOptionModal } from "@/ui/Duel/Message";
export default async ( export default async (
...@@ -21,15 +19,6 @@ export default async ( ...@@ -21,15 +19,6 @@ export default async (
return; return;
} }
if (matStore.autoSelect) {
console.log("intercept selectOption");
await sendAIPredictAsResponse(
container,
selectOption as unknown as ygopro.StocGameMessage,
);
return;
}
if (options.length === 1) { if (options.length === 1) {
sendSelectOptionResponse(conn, options[0].response); sendSelectOptionResponse(conn, options[0].response);
return; return;
......
import { sendSelectPlaceResponse, ygopro } from "@/api"; import { sendSelectPlaceResponse, ygopro } from "@/api";
import { Container } from "@/container"; import { Container } from "@/container";
import { sendAIPredictAsResponse } from "@/service/duel/agent"; import { InteractType, placeStore } from "@/stores";
import { InteractType, matStore, placeStore } from "@/stores";
type MsgSelectPlace = ygopro.StocGameMessage.MsgSelectPlace; type MsgSelectPlace = ygopro.StocGameMessage.MsgSelectPlace;
...@@ -12,15 +11,6 @@ export default async (container: Container, selectPlace: MsgSelectPlace) => { ...@@ -12,15 +11,6 @@ export default async (container: Container, selectPlace: MsgSelectPlace) => {
return; return;
} }
if (matStore.autoSelect) {
console.log("intercept selectPlace");
await sendAIPredictAsResponse(
container,
selectPlace as unknown as ygopro.StocGameMessage,
);
return;
}
if (selectPlace.places.length === 1) { if (selectPlace.places.length === 1) {
const place = selectPlace.places[0]; const place = selectPlace.places[0];
sendSelectPlaceResponse(conn, { sendSelectPlaceResponse(conn, {
......
import { ygopro } from "@/api"; import { ygopro } from "@/api";
import { Container } from "@/container";
import { sendAIPredictAsResponse } from "@/service/duel/agent";
import { matStore } from "@/stores";
import { displayPositionModal } from "@/ui/Duel/Message"; import { displayPositionModal } from "@/ui/Duel/Message";
type MsgSelectPosition = ygopro.StocGameMessage.MsgSelectPosition; type MsgSelectPosition = ygopro.StocGameMessage.MsgSelectPosition;
export default async ( export default async (selectPosition: MsgSelectPosition) => {
container: Container,
selectPosition: MsgSelectPosition,
) => {
if (matStore.autoSelect) {
console.log("intercept selectPosition");
await sendAIPredictAsResponse(
container,
selectPosition as unknown as ygopro.StocGameMessage,
);
return;
}
const _player = selectPosition.player; const _player = selectPosition.player;
const positions = selectPosition.positions.map( const positions = selectPosition.positions.map(
(position) => position.position, (position) => position.position,
......
...@@ -4,20 +4,7 @@ import { displaySelectActionsModal } from "@/ui/Duel/Message/SelectActionsModal" ...@@ -4,20 +4,7 @@ import { displaySelectActionsModal } from "@/ui/Duel/Message/SelectActionsModal"
import { fetchCheckCardMeta } from "../utils"; import { fetchCheckCardMeta } from "../utils";
type MsgSelectSum = ygopro.StocGameMessage.MsgSelectSum; type MsgSelectSum = ygopro.StocGameMessage.MsgSelectSum;
import { Container } from "@/container"; export default async (selectSum: MsgSelectSum) => {
import { sendAIPredictAsResponse } from "@/service/duel/agent";
import { matStore } from "@/stores";
export default async (container: Container, selectSum: MsgSelectSum) => {
if (matStore.autoSelect) {
console.log("intercept selectSum");
await sendAIPredictAsResponse(
container,
selectSum as unknown as ygopro.StocGameMessage,
);
return;
}
const { const {
selecteds: selecteds1, selecteds: selecteds1,
mustSelects: mustSelect1, mustSelects: mustSelect1,
......
...@@ -4,23 +4,7 @@ import { displaySelectActionsModal } from "@/ui/Duel/Message/SelectActionsModal" ...@@ -4,23 +4,7 @@ import { displaySelectActionsModal } from "@/ui/Duel/Message/SelectActionsModal"
import { fetchCheckCardMeta } from "../utils"; import { fetchCheckCardMeta } from "../utils";
type MsgSelectTribute = ygopro.StocGameMessage.MsgSelectTribute; type MsgSelectTribute = ygopro.StocGameMessage.MsgSelectTribute;
import { Container } from "@/container"; export default async (selectTribute: MsgSelectTribute) => {
import { sendAIPredictAsResponse } from "@/service/duel/agent";
import { matStore } from "@/stores";
export default async (
container: Container,
selectTribute: MsgSelectTribute,
) => {
if (matStore.autoSelect) {
console.log("intercept selectTribute");
await sendAIPredictAsResponse(
container,
selectTribute as unknown as ygopro.StocGameMessage,
);
return;
}
const { selecteds, mustSelects, selectables } = await fetchCheckCardMeta( const { selecteds, mustSelects, selectables } = await fetchCheckCardMeta(
selectTribute.selectable_cards, selectTribute.selectable_cards,
); );
......
...@@ -6,22 +6,7 @@ import { fetchCheckCardMeta } from "../utils"; ...@@ -6,22 +6,7 @@ import { fetchCheckCardMeta } from "../utils";
import { isAllOnField } from "./util"; import { isAllOnField } from "./util";
type MsgSelectUnselectCard = ygopro.StocGameMessage.MsgSelectUnselectCard; type MsgSelectUnselectCard = ygopro.StocGameMessage.MsgSelectUnselectCard;
import { Container } from "@/container"; export default async (selectUnselectCards: MsgSelectUnselectCard) => {
import { sendAIPredictAsResponse } from "@/service/duel/agent";
export default async (
container: Container,
selectUnselectCards: MsgSelectUnselectCard,
) => {
if (matStore.autoSelect) {
console.log("intercept selectUnselectCards");
await sendAIPredictAsResponse(
container,
selectUnselectCards as unknown as ygopro.StocGameMessage,
);
return;
}
const { const {
finishable, finishable,
cancelable, cancelable,
......
import { getStrings, ygopro } from "@/api"; import { getStrings, ygopro } from "@/api";
import { Container } from "@/container";
import { sendAIPredictAsResponse } from "@/service/duel/agent";
import { matStore } from "@/stores";
import { displayYesNoModal } from "@/ui/Duel/Message"; import { displayYesNoModal } from "@/ui/Duel/Message";
type MsgSelectYesNo = ygopro.StocGameMessage.MsgSelectYesNo; type MsgSelectYesNo = ygopro.StocGameMessage.MsgSelectYesNo;
export default async (container: Container, selectYesNo: MsgSelectYesNo) => { export default async (selectYesNo: MsgSelectYesNo) => {
if (matStore.autoSelect) {
console.log("intercept selectYesNo");
await sendAIPredictAsResponse(
container,
selectYesNo as unknown as ygopro.StocGameMessage,
);
return;
}
const _player = selectYesNo.player; const _player = selectYesNo.player;
const effect_description = selectYesNo.effect_description; const effect_description = selectYesNo.effect_description;
......
import { fetchCard, ygopro } from "@/api"; import { fetchCard, ygopro } from "@/api";
import { matStore } from "@/stores";
import { displaySortCardModal } from "@/ui/Duel/Message"; import { displaySortCardModal } from "@/ui/Duel/Message";
type MsgSortCard = ygopro.StocGameMessage.MsgSortCard; type MsgSortCard = ygopro.StocGameMessage.MsgSortCard;
export default async (sortCard: MsgSortCard) => { export default async (sortCard: MsgSortCard) => {
if (matStore.autoSelect) {
console.log("intercept selectTribute");
// TODO (ygo-agent): don't sort, should response 255
}
const options = await Promise.all( const options = await Promise.all(
sortCard.options.map(async ({ code, response }) => { sortCard.options.map(async ({ code, response }) => {
const meta = fetchCard(code!); const meta = fetchCard(code!);
......
import { flatten } from "lodash-es"; import { flatten } from "lodash-es";
import { v4 as v4uuid } from "uuid"; import { v4 as v4uuid } from "uuid";
import { createDuel, fetchCard, ygopro } from "@/api"; import { ygopro } from "@/api";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { sleep } from "@/infra"; import { sleep } from "@/infra";
import { import {
...@@ -89,16 +89,6 @@ export default async (start: ygopro.StocGameMessage.MsgStart) => { ...@@ -89,16 +89,6 @@ export default async (start: ygopro.StocGameMessage.MsgStart) => {
// note: 额外卡组的卡会在对局开始后通过`UpdateData` msg更新 // note: 额外卡组的卡会在对局开始后通过`UpdateData` msg更新
const { duelId, index } = (await createDuel())!;
matStore.duelId = duelId;
matStore.agentIndex = index;
matStore.mainDeckCardMeta = matStore.mainDeck.reduce((map, item) => {
if (!map.has(item)) {
map.set(item, fetchCard(item));
}
return map;
}, new Map());
if (replayStore.isReplay) { if (replayStore.isReplay) {
replayStart(); replayStart();
} }
......
import { Container } from "@/container"; import { Container } from "@/container";
import { WebSocketStream } from "@/infra";
import { YgoAgent } from "./duel/agent";
import handleSocketMessage from "./onSocketMessage"; import handleSocketMessage from "./onSocketMessage";
export async function pollSocketLooper( export async function pollSocketLooper(container: Container) {
container: Container, await container.conn.execute((event) =>
conn: WebSocketStream, handleSocketMessage(container, event),
) { );
await conn.execute((event) => handleSocketMessage(container, event)); }
export async function pollSocketLooperWithAgent(container: Container) {
const agent = new YgoAgent();
agent.attachContext(container.context);
await container.conn.execute((event) =>
handleSocketMessage(container, event, agent),
);
} }
...@@ -7,6 +7,7 @@ import { YgoProPacket } from "@/api/ocgcore/ocgAdapter/packet"; ...@@ -7,6 +7,7 @@ import { YgoProPacket } from "@/api/ocgcore/ocgAdapter/packet";
import { Container } from "@/container"; import { Container } from "@/container";
import { replayStore } from "@/stores"; import { replayStore } from "@/stores";
import { YgoAgent } from "./duel/agent";
import handleGameMsg from "./duel/gameMsg"; import handleGameMsg from "./duel/gameMsg";
import handleTimeLimit from "./duel/timeLimit"; import handleTimeLimit from "./duel/timeLimit";
import handleDeckCount from "./mora/deckCount"; import handleDeckCount from "./mora/deckCount";
...@@ -36,12 +37,18 @@ let animation: Promise<void> = Promise.resolve(); ...@@ -36,12 +37,18 @@ let animation: Promise<void> = Promise.resolve();
export default async function handleSocketMessage( export default async function handleSocketMessage(
container: Container, container: Container,
e: MessageEvent, e: MessageEvent,
agent?: YgoAgent,
) { ) {
// 确保按序执行 // 确保按序执行
animation = animation.then(() => _handle(container, e)); animation = animation.then(() => _handle(container, e, agent));
} }
async function _handle(container: Container, e: MessageEvent) { // FIXME: 下面的所有`handler`中访问`Store`的时候都应该通过`Container`进行访问
async function _handle(
container: Container,
e: MessageEvent,
agent?: YgoAgent,
) {
const packets = YgoProPacket.deserialize(e.data); const packets = YgoProPacket.deserialize(e.data);
for (const packet of packets) { for (const packet of packets) {
...@@ -101,7 +108,7 @@ async function _handle(container: Container, e: MessageEvent) { ...@@ -101,7 +108,7 @@ async function _handle(container: Container, e: MessageEvent) {
// 如果不是回放模式,则记录回放数据 // 如果不是回放模式,则记录回放数据
replayStore.record(packet); replayStore.record(packet);
} }
await handleGameMsg(container, pb); await handleGameMsg(container, pb, agent);
break; break;
} }
......
...@@ -95,12 +95,7 @@ const initialState: Omit<MatState, "reset"> = { ...@@ -95,12 +95,7 @@ const initialState: Omit<MatState, "reset"> = {
// methods // methods
isMe, isMe,
turnCount: 0, turnCount: 0,
duelId: "", error: "",
agentIndex: 0,
prevActionIndex: 0,
mainDeck: [],
mainDeckCardMeta: new Map(),
autoSelect: false,
}; };
export class MatStore implements MatState, NeosStore { export class MatStore implements MatState, NeosStore {
...@@ -118,12 +113,7 @@ export class MatStore implements MatState, NeosStore { ...@@ -118,12 +113,7 @@ export class MatStore implements MatState, NeosStore {
selectUnselectInfo = initialState.selectUnselectInfo; selectUnselectInfo = initialState.selectUnselectInfo;
duelEnd = initialState.duelEnd; duelEnd = initialState.duelEnd;
turnCount = initialState.turnCount; turnCount = initialState.turnCount;
duelId = initialState.duelId; error = initialState.error;
agentIndex = initialState.agentIndex;
prevActionIndex = initialState.prevActionIndex;
mainDeck = initialState.mainDeck;
mainDeckCardMeta = initialState.mainDeckCardMeta;
autoSelect = initialState.autoSelect;
// methods // methods
isMe = initialState.isMe; isMe = initialState.isMe;
...@@ -154,12 +144,7 @@ export class MatStore implements MatState, NeosStore { ...@@ -154,12 +144,7 @@ export class MatStore implements MatState, NeosStore {
}; };
this.duelEnd = false; this.duelEnd = false;
this.turnCount = 0; this.turnCount = 0;
this.duelId = ""; this.error = initialState.error;
this.agentIndex = 0;
this.prevActionIndex = 0;
this.mainDeck = [];
this.mainDeckCardMeta = new Map();
this.autoSelect = false;
} }
} }
......
import type { CardMeta, ygopro } from "@/api"; import type { ygopro } from "@/api";
// >>> play mat state >>> // >>> play mat state >>>
...@@ -50,14 +50,8 @@ export interface MatState { ...@@ -50,14 +50,8 @@ export interface MatState {
/** 根据自己的先后手判断是否是自己 */ /** 根据自己的先后手判断是否是自己 */
isMe: (player: number) => boolean; isMe: (player: number) => boolean;
// 下面其中一些貌似可以封装成为`AgentInfo`
turnCount: number; turnCount: number;
duelId: string; error: string;
agentIndex: number;
prevActionIndex: number;
mainDeck: number[];
mainDeckCardMeta: Map<number, CardMeta>;
autoSelect: boolean;
} }
export interface InitInfo { export interface InitInfo {
......
...@@ -15,7 +15,7 @@ export enum SideStage { ...@@ -15,7 +15,7 @@ export enum SideStage {
WAITING = 8, // 观战者等待双方玩家 WAITING = 8, // 观战者等待双方玩家
} }
class SideStore implements NeosStore { export class SideStore implements NeosStore {
stage: SideStage = SideStage.NONE; stage: SideStage = SideStage.NONE;
// 因为在上一局可能会出现断线重连, // 因为在上一局可能会出现断线重连,
......
...@@ -18,6 +18,7 @@ export const HintNotification = () => { ...@@ -18,6 +18,7 @@ export const HintNotification = () => {
const toss = snap.tossResult; const toss = snap.tossResult;
const handResults = snap.handResults; const handResults = snap.handResults;
const currentPhase = snap.phase.currentPhase; const currentPhase = snap.phase.currentPhase;
const error = snap.error;
const [msgApi, msgContextHolder] = message.useMessage({ const [msgApi, msgContextHolder] = message.useMessage({
maxCount: NeosConfig.ui.hint.maxCount, maxCount: NeosConfig.ui.hint.maxCount,
...@@ -61,6 +62,12 @@ export const HintNotification = () => { ...@@ -61,6 +62,12 @@ export const HintNotification = () => {
} }
}, [currentPhase]); }, [currentPhase]);
useEffect(() => {
if (error !== "") {
msgApi.error(error);
}
}, [error]);
return <>{msgContextHolder}</>; return <>{msgContextHolder}</>;
}; };
......
...@@ -230,7 +230,10 @@ export const Menu = () => { ...@@ -230,7 +230,10 @@ export const Menu = () => {
const [phaseSwitchItems, setPhaseSwitchItems] = useState<MenuProps["items"]>( const [phaseSwitchItems, setPhaseSwitchItems] = useState<MenuProps["items"]>(
[], [],
); );
const [autoSelect, setAutoSelect] = useState(false);
const [enableKuriboh, setEnableKuriboh] = useState(
container.getEnableKuriboh(),
);
useEffect(() => { useEffect(() => {
const endResponse = [ const endResponse = [
...@@ -315,9 +318,9 @@ export const Menu = () => { ...@@ -315,9 +318,9 @@ export const Menu = () => {
const globalDisable = !matStore.isMe(currentPlayer); const globalDisable = !matStore.isMe(currentPlayer);
const switchAutoSelect = () => { const switchAutoSelect = () => {
const newAutoSelect = !autoSelect; const newValue = !enableKuriboh;
matStore.autoSelect = newAutoSelect; setEnableKuriboh(newValue);
setAutoSelect(newAutoSelect); container.setEnableKuriboh(newValue);
}; };
return ( return (
...@@ -348,7 +351,7 @@ export const Menu = () => { ...@@ -348,7 +351,7 @@ export const Menu = () => {
</DropdownWithTitle> </DropdownWithTitle>
<Tooltip title="AI"> <Tooltip title="AI">
<Button <Button
icon={autoSelect ? <RobotFilled /> : <RobotOutlined />} icon={enableKuriboh ? <RobotFilled /> : <RobotOutlined />}
onClick={switchAutoSelect} onClick={switchAutoSelect}
type="text" type="text"
></Button> ></Button>
......
...@@ -81,8 +81,8 @@ ...@@ -81,8 +81,8 @@
"Select": "请选择", "Select": "请选择",
"Minimum": "最小值", "Minimum": "最小值",
"Maximum": "最大值", "Maximum": "最大值",
"Confirm": "确 定", "Confirm": "确定",
"Cancel": "取 消" "Cancel": "取消"
}, },
"CardDetails": { "CardDetails": {
"Level": "等级", "Level": "等级",
...@@ -167,7 +167,9 @@ ...@@ -167,7 +167,9 @@
"UltraPreemptiveServer": "超先行服", "UltraPreemptiveServer": "超先行服",
"PlayerNickname": "玩家昵称", "PlayerNickname": "玩家昵称",
"RoomPasswordOptional": "房间密码(可选)", "RoomPasswordOptional": "房间密码(可选)",
"JoinRoom": "加入房间" "JoinRoom": "加入房间",
"EnableAIAssist": "启用AI辅助功能",
"BetaTest": "Beta测试"
}, },
"ReplayModal": { "ReplayModal": {
"SelectReplay": "选择回放", "SelectReplay": "选择回放",
......
...@@ -15,4 +15,16 @@ ...@@ -15,4 +15,16 @@
.select { .select {
margin: 0.25rem 0; margin: 0.25rem 0;
} }
.ai-assist-container {
display: flex;
align-items: center;
gap: 1rem;
margin: 0.25rem 0;
span {
font-size: 1rem;
color: #d7e70b;
}
}
} }
import { App, Button, Input, Modal } from "antd"; import { App, Button, Input, Modal, Switch } from "antd";
import React, { ChangeEvent, useEffect, useState } from "react"; import React, { ChangeEvent, useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
...@@ -43,6 +43,7 @@ export const MatchModal: React.FC = ({}) => { ...@@ -43,6 +43,7 @@ export const MatchModal: React.FC = ({}) => {
const [confirmLoading, setConfirmLoading] = useState(false); const [confirmLoading, setConfirmLoading] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
const { t: i18n } = useTranslation("MatchModal"); const { t: i18n } = useTranslation("MatchModal");
const [enableKuriboh, setEnableKuriBoh] = useState(false);
const handlePlayerChange = (event: ChangeEvent<HTMLInputElement>) => { const handlePlayerChange = (event: ChangeEvent<HTMLInputElement>) => {
setPlayer(event.target.value); setPlayer(event.target.value);
...@@ -60,6 +61,7 @@ export const MatchModal: React.FC = ({}) => { ...@@ -60,6 +61,7 @@ export const MatchModal: React.FC = ({}) => {
player, player,
ip: genServerAddress(serverId), ip: genServerAddress(serverId),
passWd: passwd, passWd: passwd,
enableKuriboh,
}); });
}; };
...@@ -116,6 +118,14 @@ export const MatchModal: React.FC = ({}) => { ...@@ -116,6 +118,14 @@ export const MatchModal: React.FC = ({}) => {
]} ]}
onChange={handleServerChange} onChange={handleServerChange}
/> />
<div className={styles["ai-assist-container"]}>
<span>{i18n("EnableAIAssist")}</span>
<span style={{ color: "red" }}>{`(${i18n("BetaTest")}!!)`}</span>
<Switch
value={enableKuriboh}
onChange={(value) => setEnableKuriBoh(value)}
/>
</div>
<Input <Input
className={styles.input} className={styles.input}
type="text" type="text"
......
...@@ -5,7 +5,10 @@ import { useConfig } from "@/config"; ...@@ -5,7 +5,10 @@ import { useConfig } from "@/config";
import { getUIContainer, initUIContainer } from "@/container/compat"; import { getUIContainer, initUIContainer } from "@/container/compat";
import { initReplaySocket, initSocket } from "@/middleware/socket"; import { initReplaySocket, initSocket } from "@/middleware/socket";
import sqliteMiddleWare, { sqliteCmd } from "@/middleware/sqlite"; import sqliteMiddleWare, { sqliteCmd } from "@/middleware/sqlite";
import { pollSocketLooper } from "@/service/executor"; import {
pollSocketLooper,
pollSocketLooperWithAgent,
} from "@/service/executor";
const NeosConfig = useConfig(); const NeosConfig = useConfig();
...@@ -14,6 +17,7 @@ export const connectSrvpro = async (params: { ...@@ -14,6 +17,7 @@ export const connectSrvpro = async (params: {
ip: string; ip: string;
player: string; player: string;
passWd: string; passWd: string;
enableKuriboh?: boolean;
replay?: boolean; replay?: boolean;
replayData?: ArrayBuffer; replayData?: ArrayBuffer;
}) => { }) => {
...@@ -50,7 +54,7 @@ export const connectSrvpro = async (params: { ...@@ -50,7 +54,7 @@ export const connectSrvpro = async (params: {
initUIContainer(conn); initUIContainer(conn);
// execute the event looper // execute the event looper
pollSocketLooper(getUIContainer(), conn); pollSocketLooper(getUIContainer());
} else { } else {
// connect to the ygopro Server // connect to the ygopro Server
const conn = initSocket(params); const conn = initSocket(params);
...@@ -59,6 +63,13 @@ export const connectSrvpro = async (params: { ...@@ -59,6 +63,13 @@ export const connectSrvpro = async (params: {
initUIContainer(conn); initUIContainer(conn);
// execute the event looper // execute the event looper
pollSocketLooper(getUIContainer(), conn);
if (params.enableKuriboh) {
const container = getUIContainer();
container.setEnableKuriboh(true);
pollSocketLooperWithAgent(container);
} else {
pollSocketLooper(getUIContainer());
}
} }
}; };
...@@ -28,7 +28,6 @@ import { ...@@ -28,7 +28,6 @@ import {
accountStore, accountStore,
deckStore, deckStore,
IDeck, IDeck,
matStore,
Player, Player,
resetUniverse, resetUniverse,
RoomStage, RoomStage,
...@@ -67,7 +66,6 @@ export const Component: React.FC = () => { ...@@ -67,7 +66,6 @@ export const Component: React.FC = () => {
const updateDeck = (deck: IDeck) => { const updateDeck = (deck: IDeck) => {
sendUpdateDeck(container.conn, deck); sendUpdateDeck(container.conn, deck);
matStore.mainDeck = deck.main;
// 设置side里面的卡组 // 设置side里面的卡组
sideStore.setSideDeck(deck); sideStore.setSideDeck(deck);
}; };
......
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