Commit 875e48fc authored by timel's avatar timel

feat: timer

parent 00aa816c
Pipeline #22590 failed with stages
in 15 minutes and 7 seconds
......@@ -19,7 +19,7 @@ const defaultConfig: DefaultsConfig = {
const aiModeConfig: DefaultsConfig = {
...defaultConfig,
defaultDeck: VITE_AI_MODE_DEFAULT_DECK || "Hero",
defaultPlayer: `AiKiller${Math.random().toString(36).slice(2)}}`,
defaultPlayer: `AiKiller-${Math.random().toString(36).slice(2, 6)}}`,
defaultPassword: "AI",
};
......
......@@ -41,7 +41,11 @@ body {
margin: 0;
place-items: center;
min-width: 320px;
min-height: 100vh;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
a {
......
......@@ -14,7 +14,7 @@ import {
SortCardModal,
YesNoModal,
} from "./Message";
import { LifeBar, Mat, Menu, Timer } from "./PlayMat";
import { LifeBar, Mat, Menu } from "./PlayMat";
const NeosDuel = () => {
return (
......@@ -23,7 +23,6 @@ const NeosDuel = () => {
<Alert />
<Menu />
<LifeBar />
<Timer />
<Mat />
<CardModal />
<CardListModal />
......
......@@ -3,7 +3,7 @@ import "./index.scss";
import { animated, to, useSpring } from "@react-spring/web";
import { Dropdown, type MenuProps } from "antd";
import classnames from "classnames";
import React, { type CSSProperties, useEffect, useState, useRef } from "react";
import React, { type CSSProperties, useEffect, useRef, useState } from "react";
import { useSnapshot } from "valtio";
import type { CardMeta } from "@/api";
......
......@@ -3,20 +3,23 @@
display: flex;
top: 0;
left: 0;
height: 100vh; // FIXME: 100% on safari
bottom: 0;
flex-direction: column;
justify-content: space-between;
padding: 20px 35px;
padding: 20px;
margin-left: 10px;
pointer-events: none;
z-index: 100;
--bg-color: #323232;
width: 200px;
}
.life-bar {
width: 160px;
position: relative;
width: 100%;
color: white;
background-color: #323232;
background-color: var(--bg-color);
font-family: var(--theme-font);
border: 1px solid #222;
padding: 1rem;
padding-bottom: 0.6rem;
border-radius: 8px;
......@@ -32,3 +35,16 @@
font-size: 1.8rem;
}
}
.timer-container {
background-color: var(--bg-color);
border-radius: 4px;
padding: 0.4rem 1rem;
font-size: 0.8rem;
font-weight: bold;
font-family: var(--theme-font);
width: fit-content;
color: white;
display: flex;
gap: 8px;
align-items: center;
}
import "./index.scss";
import { Progress, Space } from "antd";
import classNames from "classnames";
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import AnimatedNumbers from "react-animated-numbers";
import { useSnapshot } from "valtio";
import { useEnv } from "@/hook";
import { matStore, playerStore } from "@/stores";
// 三个候选方案
// https://snack.expo.dev/?platform=web
// https://github.com/heyman333/react-animated-numbers
......@@ -28,28 +29,89 @@ export const LifeBar: React.FC = () => {
setOpLife(snap.op.life);
}, [snap.op.life]);
const snapTimeLimit = useSnapshot(matStore.timeLimits);
const [myTimeLimit, setMyTimeLimit] = useState(snapTimeLimit.me);
const [opTimeLimit, setOpTimeLimit] = useState(snapTimeLimit.op);
useEffect(() => {
setMyTimeLimit(snapTimeLimit.me);
}, [snapTimeLimit.me]);
useEffect(() => {
setOpTimeLimit(snapTimeLimit.op);
}, [snapTimeLimit.op]);
useEffect(() => {
setInterval(() => {
setMyTimeLimit((time) => time - 1);
setOpTimeLimit((time) => time - 1);
}, 1000);
}, []);
useEffect(() => {
if (useEnv().VITE_IS_AI_MODE) {
// 如果是AI模式
// FIXME: 探索一个优雅的、判断当前是不是AI模式的方法,用户手动输入AI也是AI模式
setMyTimeLimit(240);
setOpTimeLimit(240);
}
}, [currentPlayer]);
return (
<div id="life-bar-container">
<LifeBarItem
active={!matStore.isMe(currentPlayer)}
name={snapPlayer.getOpPlayer().name ?? "?"}
life={opLife}
timeLimit={opTimeLimit}
isMe={false}
/>
<LifeBarItem
active={matStore.isMe(currentPlayer)}
name={snapPlayer.getMePlayer().name ?? "?"}
life={meLife}
timeLimit={myTimeLimit}
isMe={true}
/>
</div>
);
};
const LifeBarItem: React.FC<{
active: boolean;
name: string;
life: number;
timeLimit: number;
isMe: boolean;
}> = ({ active, name, life, timeLimit, isMe }) => {
const mm = Math.floor(timeLimit / 60);
const ss = timeLimit % 60;
const timeText = `${mm < 10 ? "0" + mm : mm}:${ss < 10 ? "0" + ss : ss}`;
return (
<Space
direction="vertical"
style={{
flexDirection: isMe ? "column-reverse" : "column",
}}
size={12}
>
<div
className={classNames("life-bar", {
"life-bar-activated": matStore.isMe(currentPlayer),
"life-bar-activated": active,
})}
>
<div className="name">{snapPlayer.getOpPlayer().name}</div>
<div className="life">
{<AnimatedNumbers animateToNumber={opLife} />}
</div>
<div className="name">{name}</div>
<div className="life">{<AnimatedNumbers animateToNumber={life} />}</div>
</div>
<div
className={classNames("life-bar", {
"life-bar-activated": matStore.isMe(currentPlayer),
})}
>
<div className="name">{snapPlayer.getMePlayer().name}</div>
<div className="life">
<AnimatedNumbers animateToNumber={meLife} />
{active && (
<div className="timer-container">
<Progress
type="circle"
percent={(timeLimit / 240) * 100}
strokeWidth={20}
size={14}
/>
<div className="timer-text">{timeText}</div>
</div>
</div>
</div>
)}
</Space>
);
};
......@@ -28,6 +28,7 @@ import {
} from "@/api";
import { cardStore, matStore } from "@/stores";
import PhaseType = ygopro.StocGameMessage.MsgNewPhase.PhaseType;
import { Timer } from "../Timer";
const { phase } = matStore;
const { useToken } = theme;
......
#timer-container {
position: fixed;
display: flex;
top: 0;
right: 0;
height: 100vh;
padding: 20px 35px;
flex-direction: column;
pointer-events: none;
}
.timer {
width: 100px;
color: white;
background-color: #323232;
font-family: var(--theme-font);
border: 1px solid #222;
padding: 1rem;
padding-bottom: 0.6rem;
border-radius: 8px;
text-align: center;
display: flex;
flex-direction: column;
font-size: 1.2rem;
}
import "./index.scss";
import React, { useEffect, useState } from "react";
import { useSnapshot } from "valtio";
import { matStore } from "@/stores";
export const Timer: React.FC = () => {
const [time, setTime] = useState(0);
const snap = useSnapshot(matStore);
useEffect(() => {
const interval = setInterval(() => {
if (time > 0) {
setTime((time) => time - 1);
}
}, 1000);
return () => clearInterval(interval);
}, [time]);
useEffect(() => {
setTime(snap.timeLimits.me);
}, [snap.timeLimits.me]);
useEffect(() => {
setTime(snap.timeLimits.op);
}, [snap.timeLimits.op]);
return (
<div id="timer-container">
<div className="timer">{time}</div>
</div>
);
};
export * from "./LifeBar";
export * from "./Mat";
export * from "./Menu";
export * from "./Timer";
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