Skip to content

Commit b09892a

Browse files
committed
refactor(board): optimize rendering, refine interactions, and fix state bugs
This commit introduces a major overhaul to the chessboard rendering logic, significantly improving performance, fixing interaction bugs, and adding new shortcut features for a smoother gameplay and analysis experience. - **Custom Piece Rendering**: Replaced the default piece renderer with a highly optimized `CustomPiece` component. This prevents unnecessary re-renders across the board during interactions and uses hardware acceleration (`translateZ`) for z-index management. - **Preloaded Assets**: Added `<link rel="preload">` to `_document.tsx` for the default `maestro` king and pawn SVGs, drastically cutting down the perceived initial load time. - **Zero-Latency Drops**: Dynamically disables piece transition animations when a piece is dropped to ensure instantaneous visual snapping. - **Jotai State Granularity**: Refactored `currentPositionAtom` usage to derive primitive values, preventing the board from re-rendering every time the complex engine evaluation object updates. - **Drag Cancellation Fix**: Fixed a bug where a right-click during a piece drag would incorrectly drop the piece if it's a movable square. Implemented a custom ghost-piece system `animateReturnFlight()` to smoothly fly the piece back to its origin square upon cancellation. - **Annotation Modifiers**: - `Ctrl + Right Click: Draws **red** arrows with **yellow** squares. - `Alt` + Right Click: Draws **blue** arrows with **blue** squares. - Default Right Click remains **yellow** arrows with **red** squares. - **Spacebar Best Move Shortcut**: Implemented a global keyboard listener (ignoring input fields) that allows users to instantly execute the engine's current top recommended move by pressing the spacebar. - **Check Visuals and Audio**: Added a red drop-shadow `filter` to the King when in check. Also integrated a new `check.mp3` sound effect to trigger on checking moves. - **Stale State Prevention**: Added FEN tracking to the `CurrentPosition` type. This ensures that old engine lines and previous opening names don't briefly flash on the UI during rapid, multi-move sequences. - **CSS Improvements**: Added `board.css` to remove mobile swipe defaults (`touch-action: none`), improve piece dragging cursors, and fix z-index clipping issues with the promotion dialog. - Added `bench.js` at the root directory for profiling `chess.js` history and PGN loading performance. - Added a circle indicator to the capturable pieces
1 parent c8007e4 commit b09892a

File tree

26 files changed

+1483
-243
lines changed

26 files changed

+1483
-243
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ tsconfig.tsbuildinfo
2020
.cdk.staging
2121
cdk.out
2222
cdk.context.json
23+
24+
/docs

bench.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const { Chess } = require('chess.js');
2+
const c = new Chess();
3+
for(let i=0; i<40; i++) {
4+
const moves = c.moves();
5+
c.move(moves[Math.floor(Math.random() * moves.length)]);
6+
}
7+
const pgn = c.pgn();
8+
const history = c.history({verbose:true});
9+
10+
console.time('loadPgn');
11+
for(let i=0; i<100; i++) {
12+
const c2 = new Chess();
13+
c2.loadPgn(pgn);
14+
}
15+
console.timeEnd('loadPgn');
16+
17+
console.time('replayHistory');
18+
for(let i=0; i<100; i++) {
19+
const c3 = new Chess();
20+
for(const m of history) {
21+
c3.move(m);
22+
}
23+
}
24+
console.timeEnd('replayHistory');

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"private": true,
55
"license": "GPL-3.0-only",
66
"scripts": {
7-
"dev": "next dev --turbo",
7+
"dev": "SENTRY_SUPPRESS_TURBOPACK_WARNING=1 next dev --turbo",
88
"build": "next build",
99
"start": "next start",
1010
"lint": "next lint && tsc --noEmit",

public/sounds/check.mp3

5.98 KB
Binary file not shown.

src/components/board/board.css

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/* Override react-chessboard's piece slide animation easing.
2+
ease-out-cubic: snappy initial velocity, smooth deceleration. */
3+
[data-boardid] div[style*="transition: transform"] {
4+
transition-timing-function: cubic-bezier(0.33, 1, 0.68, 1) !important;
5+
z-index: 300 !important;
6+
}
7+
8+
/* Temporarily disable animations for zero-latency drops */
9+
.disable-piece-animations div[style*="transition: transform"] {
10+
transition: none !important;
11+
}
12+
13+
/* Piece cursor: hand on hover */
14+
[data-piece] {
15+
cursor: grab !important;
16+
touch-action: none !important; /* Prevent mobile page swipe on pieces */
17+
-webkit-user-drag: none !important; /* Prevent browser-default image drag */
18+
}
19+
20+
/* Disable text/element selection on the board */
21+
[data-boardid],
22+
[data-boardid] * {
23+
user-select: none !important;
24+
-webkit-user-select: none !important;
25+
}
26+
27+
/* Piece return flight */
28+
.piece-return-ghost {
29+
position: absolute;
30+
pointer-events: none;
31+
z-index: 15;
32+
will-change: transform;
33+
transition: transform 150ms cubic-bezier(0.33, 1, 0.68, 1);
34+
}
35+
36+
/* Ensure react-chessboard promotion dialog rises above hardware-accelerated custom pieces */
37+
[data-boardid] > div:not([data-piece]):not([data-square]) {
38+
z-index: 1000 !important;
39+
transform: translateZ(1px);
40+
}
41+
42+
/* Prevent the board container from cutting off the overflowing promotion dialog */
43+
[data-boardid],
44+
[data-boardid] div {
45+
overflow: visible !important;
46+
clip-path: none !important;
47+
transform-style: preserve-3d !important;
48+
}

src/components/board/capturedPieces.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { getCapturedPieces, getMaterialDifference } from "@/lib/chess";
22
import { Color } from "@/types/enums";
33
import { Box, Grid2 as Grid, Stack, Typography } from "@mui/material";
4-
import { ReactElement, useMemo } from "react";
4+
import { ReactElement, useMemo, memo } from "react";
55

66
export interface Props {
77
fen: string;
@@ -10,7 +10,7 @@ export interface Props {
1010

1111
const PIECE_SCALE = 0.55;
1212

13-
export default function CapturedPieces({ fen, color }: Props) {
13+
const CapturedPieces = memo(function CapturedPieces({ fen, color }: Props) {
1414
const piecesComponents = useMemo(() => {
1515
const capturedPieces = getCapturedPieces(fen, color);
1616
return capturedPieces.map(({ piece, count }) =>
@@ -46,7 +46,7 @@ export default function CapturedPieces({ fen, color }: Props) {
4646
)}
4747
</Grid>
4848
);
49-
}
49+
});
5050

5151
const getCapturedPiecesComponents = (
5252
pieceSymbol: string,
@@ -74,3 +74,5 @@ const getCapturedPiecesComponents = (
7474
</Stack>
7575
);
7676
};
77+
78+
export default CapturedPieces;

src/components/board/evaluationBar.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Box, Grid2 as Grid, Typography } from "@mui/material";
22
import { PrimitiveAtom, atom, useAtomValue } from "jotai";
3-
import { useEffect, useState } from "react";
3+
import { useEffect, useState, memo } from "react";
44
import { getEvaluationBarValue } from "@/lib/chess";
55
import { Color } from "@/types/enums";
66
import { CurrentPosition } from "@/types/eval";
@@ -11,7 +11,7 @@ interface Props {
1111
currentPositionAtom?: PrimitiveAtom<CurrentPosition>;
1212
}
1313

14-
export default function EvaluationBar({
14+
const EvaluationBar = memo(function EvaluationBar({
1515
height,
1616
boardOrientation,
1717
currentPositionAtom = atom({}),
@@ -107,4 +107,6 @@ export default function EvaluationBar({
107107
</Box>
108108
</Grid>
109109
);
110-
}
110+
});
111+
112+
export default EvaluationBar;

0 commit comments

Comments
 (0)