From d439bd8200115d96d7af41c023a4272dae82edbd Mon Sep 17 00:00:00 2001 From: Francisco Salgueiro Date: Sat, 7 Dec 2024 21:12:49 +0000 Subject: [PATCH] clock refactor --- src/components/boards/Board.tsx | 94 +++------------ src/components/boards/BoardGame.tsx | 36 ++++-- src/components/boards/Clock.tsx | 40 ++++++- src/components/boards/EvalListener.tsx | 4 +- src/utils/chess.ts | 39 +------ src/utils/clock.ts | 155 +++++++++++++++++++++++++ src/utils/treeReducer.ts | 2 + 7 files changed, 241 insertions(+), 129 deletions(-) create mode 100644 src/utils/clock.ts diff --git a/src/components/boards/Board.tsx b/src/components/boards/Board.tsx index ad71c311..54307143 100644 --- a/src/components/boards/Board.tsx +++ b/src/components/boards/Board.tsx @@ -19,17 +19,13 @@ import { import { keyMapAtom } from "@/state/keybinds"; import { chessboard } from "@/styles/Chessboard.css"; import { ANNOTATION_INFO, isBasicAnnotation } from "@/utils/annotation"; -import { - type TimeControlField, - getMaterialDiff, - getVariationLine, - parseTimeControl, -} from "@/utils/chess"; +import { getMaterialDiff, getVariationLine } from "@/utils/chess"; import { chessopsError, forceEnPassant, positionFromFen, } from "@/utils/chessops"; +import { type TimeControlField, getClockInfo } from "@/utils/clock"; import { getNodeAtPath } from "@/utils/treeReducer"; import { ActionIcon, @@ -107,8 +103,6 @@ interface ChessboardProps { canTakeBack?: boolean; whiteTime?: number; blackTime?: number; - whiteTc?: TimeControlField; - blackTc?: TimeControlField; practicing?: boolean; } @@ -125,8 +119,6 @@ function Board({ canTakeBack, whiteTime, blackTime, - whiteTc, - blackTc, practicing, }: ChessboardProps) { const { t } = useTranslation(); @@ -331,6 +323,13 @@ function Board({ shapes = shapes.concat(currentNode.shapes); } + const hasClock = + whiteTime !== undefined || + blackTime !== undefined || + headers.time_control !== undefined || + headers.white_time_control !== undefined || + headers.black_time_control !== undefined; + function changeTabType() { setCurrentTab((t) => { return { @@ -494,73 +493,6 @@ function Board({ const lightColor = theme.colors[color][6]; const darkColor = theme.colors[color][8]; - const timeControl = headers.time_control - ? parseTimeControl(headers.time_control) - : null; - let { whiteSeconds, blackSeconds } = match(pos?.turn) - .with("white", () => ({ - whiteSeconds: getNodeAtPath(root, position.slice(0, -1))?.clock, - blackSeconds: currentNode.clock, - })) - .with("black", () => ({ - whiteSeconds: currentNode.clock, - blackSeconds: getNodeAtPath(root, position.slice(0, -1))?.clock, - })) - .otherwise(() => { - return { - whiteSeconds: undefined, - blackSeconds: undefined, - }; - }); - if (position.length <= 1 && timeControl) { - if (timeControl.length > 0) { - const seconds = timeControl[0].seconds / 1000; - if (!whiteSeconds) { - whiteSeconds = seconds; - } - if (!blackSeconds) { - blackSeconds = seconds; - } - } - } - if (whiteTime) { - whiteSeconds = whiteTime / 1000; - } - if (blackTime) { - blackSeconds = blackTime / 1000; - } - - function calculateProgress(clock?: number, tc?: TimeControlField) { - if (!clock) { - return 0; - } - if (tc) { - return clock / (tc.seconds / 1000); - } - if (timeControl) { - return clock / (timeControl[0].seconds / 1000); - } - if (root.children.length > 0 && root.children[0].clock) { - return clock / root.children[0].clock; - } - return 0; - } - - const hasClock = - whiteTime !== undefined || - blackTime !== undefined || - headers.time_control !== undefined || - whiteTc !== undefined || - blackTc !== undefined; - - const topClock = orientation === "black" ? whiteSeconds : blackSeconds; - const topTc = orientation === "black" ? whiteTc : blackTc; - const topProgress = calculateProgress(topClock, topTc); - - const bottomClock = orientation === "black" ? blackSeconds : whiteSeconds; - const bottomTc = orientation === "black" ? blackTc : whiteTc; - const bottomProgress = calculateProgress(bottomClock, bottomTc); - const [enableBoardScroll] = useAtom(enableBoardScrollAtom); const [snapArrows] = useAtom(snapArrowsAtom); @@ -620,10 +552,10 @@ function Board({ {hasClock && ( )} {hasClock && ( )} diff --git a/src/components/boards/Clock.tsx b/src/components/boards/Clock.tsx index f23ae443..42ccdf90 100644 --- a/src/components/boards/Clock.tsx +++ b/src/components/boards/Clock.tsx @@ -1,17 +1,49 @@ +import { positionFromFen } from "@/utils/chessops"; +import { getClockInfo } from "@/utils/clock"; import { Paper, Progress, Text } from "@mantine/core"; +import { useContext } from "react"; +import { match } from "ts-pattern"; +import { useStore } from "zustand"; +import { TreeStateContext } from "../common/TreeStateContext"; import * as classes from "./Clock.css"; function Clock({ color, turn, - progress, - clock, + whiteTime, + blackTime, }: { color: "white" | "black"; turn: "white" | "black"; - progress: number; - clock: number | undefined; + whiteTime?: number; + blackTime?: number; }) { + const store = useContext(TreeStateContext)!; + const root = useStore(store, (s) => s.root); + const position = useStore(store, (s) => s.position); + const headers = useStore(store, (s) => s.headers); + const currentNode = useStore(store, (s) => s.currentNode()); + const [pos, error] = positionFromFen(currentNode.fen); + + const { white, black } = getClockInfo({ + headers, + root, + currentClock: currentNode.clock, + position, + pos, + whiteTime, + blackTime, + }); + + const clock = match(color) + .with("white", () => white.value) + .with("black", () => black.value) + .otherwise(() => undefined); + const progress = match(color) + .with("white", () => white.progress) + .with("black", () => black.progress) + .otherwise(() => 0); + return ( = { [key in Color]: T; }; diff --git a/src/utils/clock.ts b/src/utils/clock.ts new file mode 100644 index 00000000..a9dac604 --- /dev/null +++ b/src/utils/clock.ts @@ -0,0 +1,155 @@ +import type { Chess } from "chessops"; +import { match } from "ts-pattern"; +import { type GameHeaders, type TreeNode, getNodeAtPath } from "./treeReducer"; + +function calculateProgress( + root: TreeNode, + timeControl: TimeControl | null, + clock: number | null, + tc: TimeControlField | null, +) { + if (!clock) { + return 0; + } + if (tc) { + return clock / (tc.seconds / 1000); + } + if (timeControl) { + return clock / (timeControl[0].seconds / 1000); + } + if (root.children.length > 0 && root.children[0].clock) { + return clock / root.children[0].clock; + } + return 0; +} + +export type TimeControlField = { + seconds: number; + increment?: number; + moves?: number; +}; + +type TimeControl = TimeControlField[]; + +type ClockInfo = { + progress: number; + value: number | undefined; +}; + +function parseTimeControl(timeControl: string): TimeControl { + const fields = timeControl.split(":"); + const timeControlFields: TimeControl = []; + for (const field of fields) { + const match = field.match(/(?:(\d+)\/)?(\d+)(?:\+(\d+))?/); + if (!match) { + continue; + } + const moves = match[1]; + const seconds = match[2]; + const increment = match[3]; + const timeControlField: TimeControlField = { + seconds: Number.parseInt(seconds) * 1000, + }; + if (increment) { + timeControlField.increment = Number.parseInt(increment) * 1000; + } + if (moves) { + timeControlField.moves = Number.parseInt(moves); + } + timeControlFields.push(timeControlField); + } + return timeControlFields; +} + +export function getClockInfo({ + headers, + root, + currentClock, + pos, + position, + whiteTime, + blackTime, +}: { + headers: GameHeaders; + root: TreeNode; + currentClock: number | undefined; + pos: Chess | null; + position: number[]; + whiteTime?: number; + blackTime?: number; +}): { + white: ClockInfo; + black: ClockInfo; +} { + const timeControl = headers.time_control + ? parseTimeControl(headers.time_control) + : null; + + let whiteTc: TimeControlField | null = null; + let blackTc: TimeControlField | null = null; + + if (headers.white_time_control) { + whiteTc = parseTimeControl(headers.white_time_control)[0]; + } else if (timeControl) { + whiteTc = timeControl[0]; + } + if (headers.black_time_control) { + blackTc = parseTimeControl(headers.black_time_control)[0]; + } else if (timeControl) { + blackTc = timeControl[0]; + } + + let { whiteSeconds, blackSeconds } = match(pos?.turn) + .with("white", () => ({ + whiteSeconds: getNodeAtPath(root, position.slice(0, -1))?.clock, + blackSeconds: currentClock, + })) + .with("black", () => ({ + whiteSeconds: currentClock, + blackSeconds: getNodeAtPath(root, position.slice(0, -1))?.clock, + })) + .otherwise(() => { + return { + whiteSeconds: undefined, + blackSeconds: undefined, + }; + }); + if (position.length <= 1 && timeControl) { + if (timeControl.length > 0) { + const seconds = timeControl[0].seconds / 1000; + if (!whiteSeconds) { + whiteSeconds = seconds; + } + if (!blackSeconds) { + blackSeconds = seconds; + } + } + } + if (whiteTime) { + whiteSeconds = whiteTime / 1000; + } + if (blackTime) { + blackSeconds = blackTime / 1000; + } + + return { + white: { + value: whiteSeconds, + progress: calculateProgress( + root, + timeControl, + whiteSeconds ?? null, + whiteTc, + ), + }, + black: { + value: blackSeconds, + progress: calculateProgress( + root, + timeControl, + blackSeconds ?? null, + blackTc, + ), + }, + }; +} diff --git a/src/utils/treeReducer.ts b/src/utils/treeReducer.ts index c7d5a3d9..882af7f5 100644 --- a/src/utils/treeReducer.ts +++ b/src/utils/treeReducer.ts @@ -145,6 +145,8 @@ export type GameHeaders = { black_elo?: number | null; result: Outcome; time_control?: string | null; + white_time_control?: string | null; + black_time_control?: string | null; eco?: string | null; variant?: string | null; // Repertoire headers