Commit bf4cb9b9 authored by Chunchi Che's avatar Chunchi Che

finish waitroom logic

parent d8217948
Pipeline #22981 failed with stages
in 16 minutes and 18 seconds
...@@ -19,6 +19,7 @@ import StocChat from "./stoc/stocChat"; ...@@ -19,6 +19,7 @@ import StocChat from "./stoc/stocChat";
import StocDeckCount from "./stoc/stocDeckCount"; import StocDeckCount from "./stoc/stocDeckCount";
import StocDuelStart from "./stoc/stocDuelStart"; import StocDuelStart from "./stoc/stocDuelStart";
import StocGameMsg from "./stoc/stocGameMsg/mod"; import StocGameMsg from "./stoc/stocGameMsg/mod";
import StocHandResult from "./stoc/stocHandResult";
import StocHsPlayerChange from "./stoc/stocHsPlayerChange"; import StocHsPlayerChange from "./stoc/stocHsPlayerChange";
import StocHsPlayerEnter from "./stoc/stocHsPlayerEnter"; import StocHsPlayerEnter from "./stoc/stocHsPlayerEnter";
import StocHsWatchChange from "./stoc/stocHsWatchChange"; import StocHsWatchChange from "./stoc/stocHsWatchChange";
...@@ -78,7 +79,7 @@ export function adaptStoc(packet: YgoProPacket): ygopro.YgoStocMsg { ...@@ -78,7 +79,7 @@ export function adaptStoc(packet: YgoProPacket): ygopro.YgoStocMsg {
break; break;
} }
case STOC_HAND_RESULT: { case STOC_HAND_RESULT: {
// TODO pb = new StocHandResult(packet).upcast();
break; break;
} }
......
import { BufferReader } from "../../../../../rust-src/pkg/rust_src";
import { ygopro } from "../../idl/ocgcore";
import { StocAdapter, YgoProPacket } from "../packet";
/*
* STOC HandResult
*
* @usage - 后端告诉前端玩家们的猜拳选择
* */
export default class SelectHand implements StocAdapter {
packet: YgoProPacket;
constructor(packet: YgoProPacket) {
this.packet = packet;
}
upcast(): ygopro.YgoStocMsg {
const reader = new BufferReader(this.packet.exData);
const meResult = reader.readUint8();
const opResult = reader.readUint8();
return new ygopro.YgoStocMsg({
stoc_hand_result: new ygopro.StocHandResult({
meResult,
opResult,
}),
});
}
}
...@@ -6,7 +6,7 @@ import { subscribeKey } from "valtio/utils"; ...@@ -6,7 +6,7 @@ import { subscribeKey } from "valtio/utils";
import { fetchCard, ygopro } from "@/api"; import { fetchCard, ygopro } from "@/api";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { sleep } from "@/infra"; import { sleep } from "@/infra";
import { cardStore, CardType, matStore } from "@/stores"; import { cardStore, CardType, matStore, RoomStage, roomStore } from "@/stores";
import { replayStart } from "@/ui/Match/ReplayModal"; import { replayStart } from "@/ui/Match/ReplayModal";
const TOKEN_SIZE = 13; // 每人场上最多就只可能有13个token const TOKEN_SIZE = 13; // 每人场上最多就只可能有13个token
...@@ -82,6 +82,9 @@ export default async (start: ygopro.StocGameMessage.MsgStart) => { ...@@ -82,6 +82,9 @@ export default async (start: ygopro.StocGameMessage.MsgStart) => {
// 初始化完后,sleep 1s,让UI初始化完成, // 初始化完后,sleep 1s,让UI初始化完成,
// 否则在和AI对战时,由于后端给传给前端的`MSG`频率太高,会导致一些问题。 // 否则在和AI对战时,由于后端给传给前端的`MSG`频率太高,会导致一些问题。
await sleep(useConfig().startDelay); await sleep(useConfig().startDelay);
// 通知房间页面决斗开始
roomStore.stage = RoomStage.DUEL_START;
}; };
// 自动从code推断出occupant // 自动从code推断出occupant
......
import { ygopro } from "@/api"; import { ygopro } from "@/api";
import { eventbus, Task } from "@/infra";
import { RoomStage, roomStore } from "@/stores"; import { RoomStage, roomStore } from "@/stores";
export default function handleSelectHand(_: ygopro.YgoStocMsg) { export default async function handleSelectHand(_: ygopro.YgoStocMsg) {
roomStore.stage = RoomStage.SELECT_HAND; roomStore.stage = RoomStage.HAND_SELECTING;
await eventbus.call(Task.Mora);
} }
import { ygopro } from "@/api"; import { ygopro } from "@/api";
import { eventbus, Task } from "@/infra";
import { RoomStage, roomStore } from "@/stores"; import { RoomStage, roomStore } from "@/stores";
export default function handleSelectTp(_: ygopro.YgoStocMsg) { export default async function handleSelectTp(_: ygopro.YgoStocMsg) {
roomStore.stage = RoomStage.SELECT_TP; roomStore.stage = RoomStage.TP_SELECTING;
await eventbus.call(Task.Tp);
} }
...@@ -13,6 +13,7 @@ import handleSelectHand from "./mora/selectHand"; ...@@ -13,6 +13,7 @@ import handleSelectHand from "./mora/selectHand";
import handleSelectTp from "./mora/selectTp"; import handleSelectTp from "./mora/selectTp";
import handleChat from "./room/chat"; import handleChat from "./room/chat";
import handleDuelStart from "./room/duelStart"; import handleDuelStart from "./room/duelStart";
import handleHandResult from "./room/handResult";
import handleHsPlayerChange from "./room/hsPlayerChange"; import handleHsPlayerChange from "./room/hsPlayerChange";
import handleHsPlayerEnter from "./room/hsPlayerEnter"; import handleHsPlayerEnter from "./room/hsPlayerEnter";
import handleHsWatchChange from "./room/hsWatchChange"; import handleHsWatchChange from "./room/hsWatchChange";
...@@ -60,18 +61,17 @@ export default async function handleSocketMessage(e: MessageEvent) { ...@@ -60,18 +61,17 @@ export default async function handleSocketMessage(e: MessageEvent) {
break; break;
} }
case "stoc_select_hand": { case "stoc_select_hand": {
handleSelectHand(pb); await handleSelectHand(pb);
break; break;
} }
case "stoc_hand_result": { case "stoc_hand_result": {
// TODO handleHandResult(pb);
console.log("TODO: handle STOC HandResult.");
break; break;
} }
case "stoc_select_tp": { case "stoc_select_tp": {
handleSelectTp(pb); await handleSelectTp(pb);
break; break;
} }
......
import { ygopro } from "@/api";
import { roomStore } from "@/stores";
export default function handResult(pb: ygopro.YgoStocMsg) {
const msg = pb.stoc_hand_result;
const me = roomStore.getMePlayer();
const op = roomStore.getOpPlayer();
if (me && op) {
me.moraResult = msg.meResult;
op.moraResult = msg.opResult;
} else {
console.error("<HandResult>me or op is undefined");
}
}
...@@ -4,11 +4,13 @@ import { proxy } from "valtio"; ...@@ -4,11 +4,13 @@ import { proxy } from "valtio";
import { ygopro } from "@/api"; import { ygopro } from "@/api";
import StocHsPlayerChange = ygopro.StocHsPlayerChange; import StocHsPlayerChange = ygopro.StocHsPlayerChange;
import SelfType = ygopro.StocTypeChange.SelfType; import SelfType = ygopro.StocTypeChange.SelfType;
import HandType = ygopro.HandType;
import { type NeosStore } from "./shared"; import { type NeosStore } from "./shared";
export interface Player { export interface Player {
name: string; // 玩家的昵称 name: string; // 玩家的昵称
state: StocHsPlayerChange.State; // 玩家当前状态 state: StocHsPlayerChange.State; // 玩家当前状态
moraResult?: HandType; // 玩家的猜拳结果
deckInfo?: DeckInfo; deckInfo?: DeckInfo;
} }
...@@ -22,9 +24,12 @@ interface DeckInfo { ...@@ -22,9 +24,12 @@ interface DeckInfo {
// 房间内当前的阶段 // 房间内当前的阶段
export enum RoomStage { export enum RoomStage {
WAITING = 0, // 正在准备 WAITING = 0, // 正在准备
MORA = 1, // 正在猜拳 MORA = 1, // 进入猜拳阶段,但还未选择猜拳
SELECT_HAND = 2, // 选择猜拳 HAND_SELECTING = 2, // 正在选择猜拳
SELECT_TP = 2, // 选边 HAND_SELECTED = 3, // 选择完猜拳,等待后端返回结果
TP_SELECTING = 4, // 正在选边
TP_SELECTED = 5, // 选边完成,等待后端返回结果
DUEL_START = 6, // 决斗开始
} }
class RoomStore implements NeosStore { class RoomStore implements NeosStore {
......
...@@ -15,24 +15,24 @@ import { ...@@ -15,24 +15,24 @@ import {
Space, Space,
type ThemeConfig, type ThemeConfig,
} from "antd"; } from "antd";
import classNames from "classnames";
import { memo, useEffect, useState } from "react"; import { memo, useEffect, useState } from "react";
import { LoaderFunction } from "react-router-dom";
import { v4 as v4uuid } from "uuid";
import { useSnapshot } from "valtio"; import { useSnapshot } from "valtio";
import { subscribeKey } from "valtio/utils"; import { subscribeKey } from "valtio/utils";
import { type CardMeta, fetchCard, searchCards } from "@/api";
import { deckStore, type IDeck, initStore } from "@/stores"; import { deckStore, type IDeck, initStore } from "@/stores";
import { Background, Loading, ScrollableArea, YgoCard } from "@/ui/Shared"; import { Background, Loading, ScrollableArea, YgoCard } from "@/ui/Shared";
import { CardDetail } from "./CardDetail"; import { CardDetail } from "./CardDetail";
import { DeckSelect } from "./DeckSelect"; import { DeckSelect } from "./DeckSelect";
import styles from "./index.module.scss"; import styles from "./index.module.scss";
import { LoaderFunction } from "react-router-dom";
import { searchCards, type CardMeta, fetchCard } from "@/api";
import { v4 as v4uuid } from "uuid";
import classNames from "classnames";
import { import {
iDeckToEditingDeck,
type EditingDeck, type EditingDeck,
editingDeckToIDeck, editingDeckToIDeck,
iDeckToEditingDeck,
} from "./utils"; } from "./utils";
const theme: ThemeConfig = { const theme: ThemeConfig = {
......
...@@ -9,7 +9,7 @@ import { useSnapshot } from "valtio"; ...@@ -9,7 +9,7 @@ import { useSnapshot } from "valtio";
import { CookieKeys, getCookie } from "@/api"; import { CookieKeys, getCookie } from "@/api";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { accountStore, deckStore, type User, initStore } from "@/stores"; import { accountStore, deckStore, initStore, type User } from "@/stores";
import styles from "./index.module.scss"; import styles from "./index.module.scss";
import { initSqlite } from "./utils"; import { initSqlite } from "./utils";
......
...@@ -4,11 +4,9 @@ import React, { useEffect, useState } from "react"; ...@@ -4,11 +4,9 @@ import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { proxy, useSnapshot } from "valtio"; import { proxy, useSnapshot } from "valtio";
import { useConfig } from "@/config";
import { matStore } from "@/stores"; import { matStore } from "@/stores";
import { init } from "./util"; import { init } from "./util";
const NeosConfig = useConfig();
const localStore = proxy({ const localStore = proxy({
open: false, open: false,
...@@ -60,7 +58,7 @@ export const ReplayModal: React.FC = () => { ...@@ -60,7 +58,7 @@ export const ReplayModal: React.FC = () => {
if (hasStart) { if (hasStart) {
setLoading(false); setLoading(false);
// 跳转 // 跳转
navigate(`/duel/neos/replay/${NeosConfig.replayUrl}`); navigate(`/duel`);
} }
}, [hasStart]); }, [hasStart]);
......
...@@ -13,7 +13,7 @@ const router = createBrowserRouter([ ...@@ -13,7 +13,7 @@ const router = createBrowserRouter([
lazy: () => import("./Start"), lazy: () => import("./Start"),
}, },
{ {
path: "/match", path: "/match/*",
lazy: () => import("./Match"), lazy: () => import("./Match"),
}, },
{ {
...@@ -29,7 +29,7 @@ const router = createBrowserRouter([ ...@@ -29,7 +29,7 @@ const router = createBrowserRouter([
lazy: () => import("./WaitRoom"), lazy: () => import("./WaitRoom"),
}, },
{ {
path: "/duel/:ip/:player/:passWd", path: "/duel",
lazy: () => import("./Duel/Main"), lazy: () => import("./Duel/Main"),
}, },
], ],
......
...@@ -2,6 +2,7 @@ import classNames from "classnames"; ...@@ -2,6 +2,7 @@ import classNames from "classnames";
import styles from "./index.module.scss"; import styles from "./index.module.scss";
// TODO: SpecialButton能不能做个Loading?
export const SpecialButton: React.FC< export const SpecialButton: React.FC<
React.PropsWithChildren<React.ComponentProps<"span">> & { React.PropsWithChildren<React.ComponentProps<"span">> & {
disabled?: boolean; disabled?: boolean;
......
...@@ -6,8 +6,8 @@ import { eventEmitter, Task } from "@/infra"; ...@@ -6,8 +6,8 @@ import { eventEmitter, Task } from "@/infra";
import { IconFont } from "../Shared"; import { IconFont } from "../Shared";
export enum Mora { export enum Mora {
Rock = "rock",
Scissors = "scissors", Scissors = "scissors",
Rock = "rock",
Paper = "paper", Paper = "paper",
} }
......
import { CheckCircleFilled } from "@ant-design/icons"; import { CheckCircleFilled, LoadingOutlined } from "@ant-design/icons";
import HandType = ygopro.HandType;
import { import {
sendHandResult,
sendHsNotReady, sendHsNotReady,
sendHsReady, sendHsReady,
sendHsStart,
sendHsToDuelList, sendHsToDuelList,
sendHsToObserver, sendHsToObserver,
sendTpResult,
sendUpdateDeck, sendUpdateDeck,
ygopro, ygopro,
} from "@/api"; } from "@/api";
...@@ -13,12 +16,19 @@ import PlayerState = ygopro.StocHsPlayerChange.State; ...@@ -13,12 +16,19 @@ import PlayerState = ygopro.StocHsPlayerChange.State;
import SelfType = ygopro.StocTypeChange.SelfType; import SelfType = ygopro.StocTypeChange.SelfType;
import { Avatar, Button, ConfigProvider, Skeleton, Space } from "antd"; import { Avatar, Button, ConfigProvider, Skeleton, Space } from "antd";
import classNames from "classnames"; import classNames from "classnames";
import { useState } from "react"; import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useSnapshot } from "valtio"; import { useSnapshot } from "valtio";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { accountStore, deckStore, IDeck, Player, roomStore } from "@/stores"; import {
accountStore,
deckStore,
IDeck,
Player,
RoomStage,
roomStore,
} from "@/stores";
import { Background, IconFont, Select, SpecialButton } from "@/ui/Shared"; import { Background, IconFont, Select, SpecialButton } from "@/ui/Shared";
import { Chat } from "./Chat"; import { Chat } from "./Chat";
...@@ -49,9 +59,16 @@ export const Component: React.FC = () => { ...@@ -49,9 +59,16 @@ export const Component: React.FC = () => {
const { decks } = useSnapshot(deckStore); const { decks } = useSnapshot(deckStore);
const [deck, setDeck] = useState<IDeck>(JSON.parse(JSON.stringify(decks[0]))); const [deck, setDeck] = useState<IDeck>(JSON.parse(JSON.stringify(decks[0])));
const room = useSnapshot(roomStore); const room = useSnapshot(roomStore);
const me = room.getMePlayer();
const op = room.getOpPlayer();
const navigate = useNavigate();
const [myMora, setMyMora] = useState<Mora>(); useEffect(() => {
const [opponentMora, setOpponentMora] = useState<Mora>(); if (room.stage == RoomStage.DUEL_START) {
// 决斗开始,跳转决斗页面
navigate("/duel");
}
}, [room.stage]);
return ( return (
<ConfigProvider theme={theme}> <ConfigProvider theme={theme}>
...@@ -84,18 +101,15 @@ export const Component: React.FC = () => { ...@@ -84,18 +101,15 @@ export const Component: React.FC = () => {
<div className={styles["both-side-container"]}> <div className={styles["both-side-container"]}>
<PlayerZone <PlayerZone
who={Who.Me} who={Who.Me}
player={room.getMePlayer()} player={me}
avatar={user?.avatar_url} avatar={user?.avatar_url}
btn={ btn={
<> room.stage == RoomStage.WAITING ? (
{/* 根据stage显示结果 */} <Button
{/* <Button
size="large" size="large"
className={styles["btn-join"]} className={styles["btn-join"]}
onClick={() => { onClick={() => {
if ( if (me?.state === PlayerState.NO_READY) {
room.getMePlayer()?.state === PlayerState.NO_READY
) {
sendUpdateDeck(deck); sendUpdateDeck(deck);
window.myExtraDeckCodes = [...deck.extra]; window.myExtraDeckCodes = [...deck.extra];
sendHsReady(); sendHsReady();
...@@ -104,42 +118,45 @@ export const Component: React.FC = () => { ...@@ -104,42 +118,45 @@ export const Component: React.FC = () => {
} }
}} }}
> >
{room.getMePlayer()?.state === PlayerState.NO_READY {me?.state === PlayerState.NO_READY
? "决斗准备" ? "决斗准备"
: "取消准备"} : "取消准备"}
</Button> */} </Button>
<div style={{ marginLeft: "auto" }}> ) : (
{/* 根据是否有了猜拳结果而显示 */} <MoraAvatar
<Avatar mora={
style={{ marginLeft: "auto" }} me?.moraResult !== undefined &&
size={48} me.moraResult !== HandType.UNKNOWN
icon={<IconFont type="icon-hand-rock" />} ? Object.values(Mora)[me.moraResult - 1]
: undefined
}
/> />
{/* <Skeleton.Avatar active size={48} /> */} )
</div>
</>
} }
/> />
<PlayerZone <PlayerZone
who={Who.Op} who={Who.Op}
player={room.getOpPlayer()} player={op}
btn={ btn={
<div style={{ marginLeft: "auto" }}> <MoraAvatar
{/* 根据是否有了猜拳结果而显示 */} mora={
{/* <Avatar op?.moraResult !== undefined &&
style={{ marginLeft: "auto" }} op.moraResult !== HandType.UNKNOWN
size={48} ? Object.values(Mora)[op.moraResult - 1]
icon={<IconFont type="icon-hand-rock" />} : undefined
/> */} }
<Skeleton.Avatar active size={48} /> />
</div>
} }
/> />
</div> </div>
<ActionButton <ActionButton
onMoraSelect={setMyMora} onMoraSelect={(mora) => {
onTpSelect={() => { sendHandResult(mora);
// 暂时不知道需不需要 先放着 roomStore.stage = RoomStage.HAND_SELECTED;
}}
onTpSelect={(tp) => {
sendTpResult(tp == Tp.First);
roomStore.stage = RoomStage.TP_SELECTED;
}} }}
/> />
</div> </div>
...@@ -191,6 +208,21 @@ const PlayerZone: React.FC<{ ...@@ -191,6 +208,21 @@ const PlayerZone: React.FC<{
); );
}; };
// 展示猜拳结果的组件
const MoraAvatar: React.FC<{ mora?: Mora }> = ({ mora }) => (
<div style={{ marginLeft: "auto" }}>
{mora ? (
<Avatar
style={{ marginLeft: "auto" }}
size={48}
icon={<IconFont type={`icon-hand-${mora}`} />}
/>
) : (
<Skeleton.Avatar active size={48} />
)}
</div>
);
const Controller: React.FC<{ onDeckChange: (deckName: string) => void }> = ({ const Controller: React.FC<{ onDeckChange: (deckName: string) => void }> = ({
onDeckChange, onDeckChange,
}) => { }) => {
...@@ -281,32 +313,45 @@ const ActionButton: React.FC<{ ...@@ -281,32 +313,45 @@ const ActionButton: React.FC<{
onMoraSelect: (mora: Mora) => void; onMoraSelect: (mora: Mora) => void;
onTpSelect: (tp: Tp) => void; onTpSelect: (tp: Tp) => void;
}> = ({ onMoraSelect, onTpSelect }) => { }> = ({ onMoraSelect, onTpSelect }) => {
const { stage } = useSnapshot(roomStore);
return ( return (
<MoraPopover onSelect={onMoraSelect}> <MoraPopover onSelect={onMoraSelect}>
<TpPopover onSelect={onTpSelect}> <TpPopover onSelect={onTpSelect}>
<SpecialButton className={styles["btns-action"]} disabled={false}> <SpecialButton
className={styles["btns-action"]}
disabled={stage != RoomStage.WAITING}
onClick={() => {
sendHsStart();
}}
>
{stage == RoomStage.WAITING ? (
<> <>
<IconFont type="icon-play" size={12} /> <IconFont type="icon-play" size={12} />
开始游戏 开始游戏
</> </>
{/* <> ) : stage == RoomStage.HAND_SELECTING ? (
<>
<IconFont type="icon-mora" size={18} /> <IconFont type="icon-mora" size={18} />
<span>请猜拳</span> <span>请猜拳</span>
</> */} </>
) : stage == RoomStage.HAND_SELECTED ? (
<> <>
{/* 这里要把disabled设为true */} <LoadingOutlined size={20} />
{/* <LoadingOutlined size={20} /> <span>等待对方猜拳</span>
<span>等待对方猜拳</span> */}
</> </>
{/* <> ) : stage == RoomStage.TP_SELECTING ? (
<>
<IconFont type="icon-one" size={18} /> <IconFont type="icon-one" size={18} />
<span>请选择先后手</span> <span>请选择先后手</span>
</> */} </>
) : stage == RoomStage.TP_SELECTED ? (
<> <>
{/* 这里要把disabled设为true */} <LoadingOutlined size={20} />
{/* <LoadingOutlined size={20} /> <span>等待游戏开始</span>
<span>等待选择先后手</span> */}
</> </>
) : (
<></>
)}
</SpecialButton> </SpecialButton>
</TpPopover> </TpPopover>
</MoraPopover> </MoraPopover>
......
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