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