@react-chess-tools/react-chess-game 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/index.d.mts +16 -1
- package/dist/index.mjs +60 -47
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/ChessGame/parts/Board.tsx +50 -49
- package/src/index.ts +1 -0
- package/src/utils/__tests__/board.test.ts +256 -1
- package/src/utils/board.ts +49 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @react-chess-tools/react-chess-game
|
|
2
2
|
|
|
3
|
+
## 0.5.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 9d33423: Fix PuzzleBoard options merging
|
|
8
|
+
|
|
9
|
+
## 0.5.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- f50ac0f: feat: improve chessboard options merging with deep merge utility
|
|
14
|
+
|
|
3
15
|
## 0.5.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/dist/index.d.mts
CHANGED
|
@@ -140,4 +140,19 @@ declare const getGameInfo: (game: Chess, orientation: Color) => {
|
|
|
140
140
|
};
|
|
141
141
|
type GameInfo = ReturnType<typeof getGameInfo>;
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Smart deep merge for ChessboardOptions that handles different property types appropriately:
|
|
145
|
+
* - Functions: Overwrite (custom functions replace base functions)
|
|
146
|
+
* - Objects: Deep merge (nested objects merge recursively)
|
|
147
|
+
* - Primitives: Overwrite (custom values replace base values)
|
|
148
|
+
*
|
|
149
|
+
* This ensures that computed options (like squareStyles with move highlighting) are preserved
|
|
150
|
+
* while allowing custom options to extend or override them intelligently.
|
|
151
|
+
*
|
|
152
|
+
* @param baseOptions - The computed base options (e.g., computed squareStyles, event handlers)
|
|
153
|
+
* @param customOptions - Custom options provided by the user
|
|
154
|
+
* @returns Intelligently merged ChessboardOptions
|
|
155
|
+
*/
|
|
156
|
+
declare const deepMergeChessboardOptions: (baseOptions: ChessboardOptions, customOptions?: Partial<ChessboardOptions>) => ChessboardOptions;
|
|
157
|
+
|
|
158
|
+
export { ChessGame, type ChessGameContextType, type ChessGameProps, type GameInfo, KeyboardControls, type RootProps, type Sound, type Sounds, type SoundsProps, deepMergeChessboardOptions, useChessGame, useChessGameContext, type useChessGameProps };
|
package/dist/index.mjs
CHANGED
|
@@ -231,6 +231,7 @@ import {
|
|
|
231
231
|
} from "react-chessboard";
|
|
232
232
|
|
|
233
233
|
// src/utils/board.ts
|
|
234
|
+
import { merge } from "lodash";
|
|
234
235
|
var LAST_MOVE_COLOR = "rgba(255, 255, 0, 0.5)";
|
|
235
236
|
var CHECK_COLOR = "rgba(255, 0, 0, 0.5)";
|
|
236
237
|
var getCustomSquareStyles = (game, info, activeSquare) => {
|
|
@@ -271,6 +272,24 @@ var getCustomSquareStyles = (game, info, activeSquare) => {
|
|
|
271
272
|
}
|
|
272
273
|
return customSquareStyles;
|
|
273
274
|
};
|
|
275
|
+
var deepMergeChessboardOptions = (baseOptions, customOptions) => {
|
|
276
|
+
if (!customOptions) {
|
|
277
|
+
return { ...baseOptions };
|
|
278
|
+
}
|
|
279
|
+
const result = merge({}, baseOptions, customOptions, {
|
|
280
|
+
customizer: (_objValue, srcValue) => {
|
|
281
|
+
if (typeof srcValue === "function") {
|
|
282
|
+
return srcValue;
|
|
283
|
+
}
|
|
284
|
+
if (Array.isArray(srcValue)) {
|
|
285
|
+
return srcValue;
|
|
286
|
+
}
|
|
287
|
+
return void 0;
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
delete result.customizer;
|
|
291
|
+
return result;
|
|
292
|
+
};
|
|
274
293
|
|
|
275
294
|
// src/components/ChessGame/parts/Board.tsx
|
|
276
295
|
var Board = ({ options = {} }) => {
|
|
@@ -354,54 +373,47 @@ var Board = ({ options = {} }) => {
|
|
|
354
373
|
orientation === "b" ? "black" : "white"
|
|
355
374
|
);
|
|
356
375
|
}, [promotionMove, squareWidth, orientation]);
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
dropSquareStyle: {
|
|
374
|
-
backgroundColor: "rgba(255, 255, 0, 0.4)"
|
|
375
|
-
},
|
|
376
|
-
onPieceDrag: ({ piece, square }) => {
|
|
377
|
-
if (piece.pieceType[0] === turn) {
|
|
378
|
-
setActiveSquare(square);
|
|
379
|
-
}
|
|
380
|
-
},
|
|
381
|
-
onPieceDrop: ({ sourceSquare, targetSquare }) => {
|
|
382
|
-
setActiveSquare(null);
|
|
383
|
-
const moveData = {
|
|
384
|
-
from: sourceSquare,
|
|
385
|
-
to: targetSquare
|
|
386
|
-
};
|
|
387
|
-
if (requiresPromotion(game, { ...moveData, promotion: "q" })) {
|
|
388
|
-
setPromotionMove(moveData);
|
|
389
|
-
return false;
|
|
390
|
-
}
|
|
391
|
-
return makeMove(moveData);
|
|
392
|
-
},
|
|
393
|
-
onSquareClick: ({ square }) => {
|
|
394
|
-
if (square.match(/^[a-h][1-8]$/)) {
|
|
395
|
-
onSquareClick(square);
|
|
396
|
-
}
|
|
397
|
-
},
|
|
398
|
-
onSquareRightClick,
|
|
399
|
-
allowDrawingArrows: true,
|
|
400
|
-
animationDurationInMs: game.history().length === 0 ? 0 : 300,
|
|
401
|
-
...options
|
|
376
|
+
const baseOptions = {
|
|
377
|
+
squareStyles: getCustomSquareStyles(game, info, activeSquare),
|
|
378
|
+
boardOrientation: orientation === "b" ? "black" : "white",
|
|
379
|
+
position: currentFen,
|
|
380
|
+
showNotation: true,
|
|
381
|
+
showAnimations: isLatestMove,
|
|
382
|
+
canDragPiece: ({ piece }) => {
|
|
383
|
+
if (isGameOver) return false;
|
|
384
|
+
return piece.pieceType[0] === turn;
|
|
385
|
+
},
|
|
386
|
+
dropSquareStyle: {
|
|
387
|
+
backgroundColor: "rgba(255, 255, 0, 0.4)"
|
|
388
|
+
},
|
|
389
|
+
onPieceDrag: ({ piece, square }) => {
|
|
390
|
+
if (piece.pieceType[0] === turn) {
|
|
391
|
+
setActiveSquare(square);
|
|
402
392
|
}
|
|
403
|
-
}
|
|
404
|
-
|
|
393
|
+
},
|
|
394
|
+
onPieceDrop: ({ sourceSquare, targetSquare }) => {
|
|
395
|
+
setActiveSquare(null);
|
|
396
|
+
const moveData = {
|
|
397
|
+
from: sourceSquare,
|
|
398
|
+
to: targetSquare
|
|
399
|
+
};
|
|
400
|
+
if (requiresPromotion(game, { ...moveData, promotion: "q" })) {
|
|
401
|
+
setPromotionMove(moveData);
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
return makeMove(moveData);
|
|
405
|
+
},
|
|
406
|
+
onSquareClick: ({ square }) => {
|
|
407
|
+
if (square.match(/^[a-h][1-8]$/)) {
|
|
408
|
+
onSquareClick(square);
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
onSquareRightClick,
|
|
412
|
+
allowDrawingArrows: true,
|
|
413
|
+
animationDurationInMs: game.history().length === 0 ? 0 : 300
|
|
414
|
+
};
|
|
415
|
+
const mergedOptions = deepMergeChessboardOptions(baseOptions, options);
|
|
416
|
+
return /* @__PURE__ */ React4.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React4.createElement(Chessboard, { options: mergedOptions }), promotionMove && /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(
|
|
405
417
|
"div",
|
|
406
418
|
{
|
|
407
419
|
onClick: () => setPromotionMove(null),
|
|
@@ -579,6 +591,7 @@ var ChessGame = {
|
|
|
579
591
|
};
|
|
580
592
|
export {
|
|
581
593
|
ChessGame,
|
|
594
|
+
deepMergeChessboardOptions,
|
|
582
595
|
useChessGame,
|
|
583
596
|
useChessGameContext
|
|
584
597
|
};
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/ChessGame/parts/Root.tsx","../src/hooks/useChessGame.ts","../src/utils/chess.ts","../src/hooks/useChessGameContext.ts","../src/components/ChessGame/parts/Board.tsx","../src/utils/board.ts","../src/components/ChessGame/parts/Sounds.tsx","../src/assets/sounds.ts","../src/hooks/useBoardSounds.ts","../src/hooks/useKeyboardControls.ts","../src/components/ChessGame/parts/KeyboardControls.tsx","../src/components/ChessGame/index.ts"],"sourcesContent":["import React from \"react\";\nimport { Color } from \"chess.js\";\nimport { useChessGame } from \"../../../hooks/useChessGame\";\nimport { ChessGameContext } from \"../../../hooks/useChessGameContext\";\n\nexport interface RootProps {\n fen?: string;\n orientation?: Color;\n}\n\nexport const Root: React.FC<React.PropsWithChildren<RootProps>> = ({\n fen,\n orientation,\n children,\n}) => {\n const context = useChessGame({ fen, orientation });\n return (\n <ChessGameContext.Provider value={context}>\n {children}\n </ChessGameContext.Provider>\n );\n};\n","import React, { useEffect } from \"react\";\nimport { Chess, Color } from \"chess.js\";\nimport { cloneGame, getCurrentFen, getGameInfo } from \"../utils/chess\";\n\nexport type useChessGameProps = {\n fen?: string;\n orientation?: Color;\n};\n\nexport const useChessGame = ({\n fen,\n orientation: initialOrientation,\n}: useChessGameProps = {}) => {\n const [game, setGame] = React.useState(new Chess(fen));\n\n useEffect(() => {\n setGame(new Chess(fen));\n }, [fen]);\n\n const [orientation, setOrientation] = React.useState<Color>(\n initialOrientation ?? \"w\",\n );\n const [currentMoveIndex, setCurrentMoveIndex] = React.useState(-1);\n\n const history = React.useMemo(() => game.history(), [game]);\n const isLatestMove = React.useMemo(\n () => currentMoveIndex === history.length - 1 || currentMoveIndex === -1,\n [currentMoveIndex, history.length],\n );\n\n const info = React.useMemo(\n () => getGameInfo(game, orientation),\n [game, orientation],\n );\n\n const currentFen = React.useMemo(\n () => getCurrentFen(fen, game, currentMoveIndex),\n [game, currentMoveIndex],\n );\n\n const currentPosition = React.useMemo(\n () => game.history()[currentMoveIndex],\n [game, currentMoveIndex],\n );\n\n const setPosition = React.useCallback((fen: string, orientation: Color) => {\n const newGame = new Chess();\n newGame.load(fen);\n setOrientation(orientation);\n setGame(newGame);\n setCurrentMoveIndex(-1);\n }, []);\n\n const makeMove = React.useCallback(\n (move: Parameters<Chess[\"move\"]>[0]): boolean => {\n // Only allow moves when we're at the latest position\n if (!isLatestMove) {\n return false;\n }\n\n try {\n const copy = cloneGame(game);\n copy.move(move);\n setGame(copy);\n setCurrentMoveIndex(copy.history().length - 1);\n return true;\n } catch (e) {\n return false;\n }\n },\n [isLatestMove, game],\n );\n\n const flipBoard = React.useCallback(() => {\n setOrientation((orientation) => (orientation === \"w\" ? \"b\" : \"w\"));\n }, []);\n\n const goToMove = React.useCallback(\n (moveIndex: number) => {\n if (moveIndex < -1 || moveIndex >= history.length) return;\n setCurrentMoveIndex(moveIndex);\n },\n [history.length],\n );\n\n const goToStart = React.useCallback(() => goToMove(-1), []);\n const goToEnd = React.useCallback(\n () => goToMove(history.length - 1),\n [history.length],\n );\n const goToPreviousMove = React.useCallback(\n () => goToMove(currentMoveIndex - 1),\n [currentMoveIndex],\n );\n const goToNextMove = React.useCallback(\n () => goToMove(currentMoveIndex + 1),\n [currentMoveIndex],\n );\n\n const methods = React.useMemo(\n () => ({\n makeMove,\n setPosition,\n flipBoard,\n goToMove,\n goToStart,\n goToEnd,\n goToPreviousMove,\n goToNextMove,\n }),\n [\n makeMove,\n setPosition,\n flipBoard,\n goToMove,\n goToStart,\n goToEnd,\n goToPreviousMove,\n goToNextMove,\n ],\n );\n\n return {\n game,\n currentFen,\n currentPosition,\n orientation,\n currentMoveIndex,\n isLatestMove,\n info,\n methods,\n };\n};\n","import { Chess, Color, Square } from \"chess.js\";\nimport _ from \"lodash\";\n\n/**\n * Creates a clone of the given Chess.js instance. This is needed to update the state\n * of react-chessboard component\n * @param game - The Chess.js instance to clone.\n * @returns A new Chess.js instance with the same state as the original.\n */\nexport const cloneGame = (game: Chess) => {\n const copy = new Chess();\n copy.loadPgn(game.pgn());\n return copy;\n};\n\n/**\n * Returns an object with information about the current state of the game. This can be determined\n * using chess.js instance, but this function is provided for convenience.\n * @param game - The Chess.js instance representing the game.\n * @returns An object with information about the current state of the game.\n */\n\nexport const getGameInfo = (game: Chess, orientation: Color) => {\n const turn = game.turn();\n const isPlayerTurn = turn === orientation;\n const isOpponentTurn = !isPlayerTurn;\n const moveNumber = game.history().length;\n const lastMove = _.last(game.history({ verbose: true }));\n const isCheck = game.isCheck();\n const isCheckmate = game.isCheckmate();\n const isDraw = game.isDraw();\n const isStalemate = game.isStalemate();\n const isThreefoldRepetition = game.isThreefoldRepetition();\n const isInsufficientMaterial = game.isInsufficientMaterial();\n const isGameOver = game.isGameOver();\n const hasPlayerWon = isOpponentTurn && isGameOver && !isDraw;\n const hasPlayerLost = isPlayerTurn && isGameOver && !isDraw;\n const isDrawn = game.isDraw();\n return {\n turn,\n isPlayerTurn,\n isOpponentTurn,\n moveNumber,\n lastMove,\n isCheck,\n isCheckmate,\n isDraw,\n isStalemate,\n isThreefoldRepetition,\n isInsufficientMaterial,\n isGameOver,\n isDrawn,\n hasPlayerWon,\n hasPlayerLost,\n };\n};\n\nexport type GameInfo = ReturnType<typeof getGameInfo>;\n\nexport const isLegalMove = (\n game: Chess,\n move: Parameters<Chess[\"move\"]>[0],\n) => {\n try {\n const copy = cloneGame(game);\n copy.move(move);\n return true;\n } catch (e) {\n return false;\n }\n};\n\nexport const requiresPromotion = (\n game: Chess,\n move: Parameters<Chess[\"move\"]>[0],\n) => {\n try {\n const copy = cloneGame(game);\n const result = copy.move(move);\n\n return result.flags.indexOf(\"p\") !== -1;\n } catch (e) {\n if (e instanceof Error && e.message.includes(\"Invalid move\")) {\n return false;\n }\n throw e;\n }\n};\n\nexport const getDestinationSquares = (game: Chess, square: Square) => {\n const moves = game.moves({ square, verbose: true });\n return moves.map((move) => move.to);\n};\n\nexport const getCurrentFen = (\n fen: string | undefined,\n game: Chess,\n currentMoveIndex: number,\n) => {\n const tempGame = new Chess();\n if (currentMoveIndex === -1) {\n if (fen) {\n tempGame.load(fen);\n }\n } else {\n const moves = game.history().slice(0, currentMoveIndex + 1);\n\n if (fen) {\n tempGame.load(fen);\n }\n moves.forEach((move) => tempGame.move(move));\n }\n return tempGame.fen();\n};\n","import React from \"react\";\nimport { useChessGame } from \"./useChessGame\";\n\nexport const ChessGameContext = React.createContext<ReturnType<\n typeof useChessGame\n> | null>(null);\n\nexport const useChessGameContext = () => {\n const context = React.useContext(ChessGameContext);\n if (!context) {\n throw new Error(\n `useChessGameContext must be used within a ChessGame component. Make sure your component is wrapped with <ChessGame.Root> or ensure the ChessGame component is properly rendered in the component tree.`,\n );\n }\n return context;\n};\n\nexport type ChessGameContextType = ReturnType<typeof useChessGame>;\n","import React from \"react\";\nimport {\n Chessboard,\n ChessboardOptions,\n defaultPieces,\n chessColumnToColumnIndex,\n} from \"react-chessboard\";\nimport { Move, Square } from \"chess.js\";\nimport { getCustomSquareStyles } from \"../../../utils/board\";\nimport { isLegalMove, requiresPromotion } from \"../../../utils/chess\";\nimport { useChessGameContext } from \"../../../hooks/useChessGameContext\";\n\nexport interface ChessGameProps {\n options?: ChessboardOptions;\n}\n\nexport const Board: React.FC<ChessGameProps> = ({ options = {} }) => {\n const gameContext = useChessGameContext();\n\n if (!gameContext) {\n throw new Error(\"ChessGameContext not found\");\n }\n\n const {\n game,\n currentFen,\n orientation,\n info,\n isLatestMove,\n methods: { makeMove },\n } = gameContext;\n\n const { turn, isGameOver } = info;\n\n const [activeSquare, setActiveSquare] = React.useState<Square | null>(null);\n\n const [promotionMove, setPromotionMove] =\n React.useState<Partial<Move> | null>(null);\n\n const onSquareClick = (square: Square) => {\n if (isGameOver) {\n return;\n }\n\n if (activeSquare === null) {\n const squadreInfo = game.get(square);\n if (squadreInfo && squadreInfo.color === turn) {\n return setActiveSquare(square);\n }\n return;\n }\n\n if (\n !isLegalMove(game, {\n from: activeSquare,\n to: square,\n promotion: \"q\",\n })\n ) {\n return setActiveSquare(null);\n }\n\n if (\n requiresPromotion(game, {\n from: activeSquare,\n to: square,\n promotion: \"q\",\n })\n ) {\n return setPromotionMove({\n from: activeSquare,\n to: square,\n });\n }\n\n setActiveSquare(null);\n makeMove({\n from: activeSquare,\n to: square,\n });\n };\n\n const onPromotionPieceSelect = (piece: string): void => {\n if (promotionMove?.from && promotionMove?.to) {\n makeMove({\n from: promotionMove.from,\n to: promotionMove.to,\n promotion: piece.toLowerCase(),\n });\n setPromotionMove(null);\n }\n };\n\n const onSquareRightClick = () => {\n setActiveSquare(null);\n setPromotionMove(null);\n };\n\n // Calculate square width for precise positioning\n const squareWidth = React.useMemo(() => {\n if (typeof document === \"undefined\") return 80;\n const squareElement = document.querySelector(`[data-square]`);\n return squareElement?.getBoundingClientRect()?.width ?? 80;\n }, [promotionMove]);\n\n // Calculate promotion square position\n const promotionSquareLeft = React.useMemo(() => {\n if (!promotionMove?.to) return 0;\n const column = promotionMove.to.match(/^[a-h]/)?.[0] ?? \"a\";\n return (\n squareWidth *\n chessColumnToColumnIndex(\n column,\n 8,\n orientation === \"b\" ? \"black\" : \"white\",\n )\n );\n }, [promotionMove, squareWidth, orientation]);\n\n return (\n <div style={{ position: \"relative\" }}>\n <Chessboard\n options={{\n squareStyles: {\n ...getCustomSquareStyles(game, info, activeSquare),\n ...options.squareStyles,\n },\n boardOrientation: orientation === \"b\" ? \"black\" : \"white\",\n position: currentFen,\n showNotation: true,\n showAnimations: isLatestMove,\n canDragPiece: ({ piece }) => {\n if (isGameOver) return false;\n return piece.pieceType[0] === turn;\n },\n dropSquareStyle: {\n backgroundColor: \"rgba(255, 255, 0, 0.4)\",\n },\n onPieceDrag: ({ piece, square }) => {\n if (piece.pieceType[0] === turn) {\n setActiveSquare(square as Square);\n }\n },\n onPieceDrop: ({ sourceSquare, targetSquare }) => {\n setActiveSquare(null);\n const moveData = {\n from: sourceSquare as Square,\n to: targetSquare as Square,\n };\n\n // Check if promotion is needed\n if (requiresPromotion(game, { ...moveData, promotion: \"q\" })) {\n setPromotionMove(moveData);\n return false; // Prevent the move until promotion is selected\n }\n\n return makeMove(moveData);\n },\n onSquareClick: ({ square }) => {\n if (square.match(/^[a-h][1-8]$/)) {\n onSquareClick(square as Square);\n }\n },\n onSquareRightClick: onSquareRightClick,\n allowDrawingArrows: true,\n animationDurationInMs: game.history().length === 0 ? 0 : 300,\n ...options,\n }}\n />\n {promotionMove && (\n <>\n {/* Backdrop overlay - click to cancel */}\n <div\n onClick={() => setPromotionMove(null)}\n onContextMenu={(e) => {\n e.preventDefault();\n setPromotionMove(null);\n }}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n backgroundColor: \"rgba(0, 0, 0, 0.1)\",\n zIndex: 1000,\n }}\n />\n {/* Promotion piece selection */}\n <div\n style={{\n position: \"absolute\",\n top: promotionMove.to?.[1]?.includes(\"8\") ? 0 : \"auto\",\n bottom: promotionMove.to?.[1].includes(\"1\") ? 0 : \"auto\",\n left: promotionSquareLeft,\n backgroundColor: \"white\",\n width: squareWidth,\n zIndex: 1001,\n display: \"flex\",\n flexDirection: \"column\",\n boxShadow: \"0 0 10px 0 rgba(0, 0, 0, 0.5)\",\n }}\n >\n {[\"q\", \"r\", \"n\", \"b\"].map((piece) => (\n <button\n key={piece}\n onClick={() => onPromotionPieceSelect(piece)}\n onContextMenu={(e) => {\n e.preventDefault();\n }}\n style={{\n width: \"100%\",\n aspectRatio: \"1\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n padding: 0,\n border: \"none\",\n cursor: \"pointer\",\n backgroundColor: \"white\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.backgroundColor = \"#f0f0f0\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.backgroundColor = \"white\";\n }}\n >\n {defaultPieces[\n `${turn}${piece.toUpperCase()}` as keyof typeof defaultPieces\n ]()}\n </button>\n ))}\n </div>\n </>\n )}\n </div>\n );\n};\n","import { type Chess, type Square } from \"chess.js\";\nimport { type CSSProperties } from \"react\";\nimport { getDestinationSquares, type GameInfo } from \"./chess\";\n\nconst LAST_MOVE_COLOR = \"rgba(255, 255, 0, 0.5)\";\nconst CHECK_COLOR = \"rgba(255, 0, 0, 0.5)\";\n\nexport const getCustomSquareStyles = (\n game: Chess,\n info: GameInfo,\n activeSquare: Square | null,\n) => {\n const customSquareStyles: Record<string, CSSProperties> = {};\n\n const { lastMove, isCheck, turn } = info;\n\n if (lastMove) {\n customSquareStyles[lastMove.from] = {\n backgroundColor: LAST_MOVE_COLOR,\n };\n customSquareStyles[lastMove.to] = {\n backgroundColor: LAST_MOVE_COLOR,\n };\n }\n\n if (activeSquare) {\n customSquareStyles[activeSquare] = {\n backgroundColor: LAST_MOVE_COLOR,\n };\n }\n\n if (activeSquare) {\n const destinationSquares = getDestinationSquares(game, activeSquare);\n destinationSquares.forEach((square) => {\n customSquareStyles[square] = {\n background:\n game.get(square) && game.get(square)?.color !== turn\n ? \"radial-gradient(circle, rgba(1, 0, 0, 0.1) 85%, transparent 85%)\"\n : \"radial-gradient(circle, rgba(0,0,0,.1) 25%, transparent 25%)\",\n };\n });\n }\n\n if (isCheck) {\n game.board().forEach((row) => {\n return row.forEach((square) => {\n if (square?.type === \"k\" && square?.color === info.turn) {\n customSquareStyles[square.square] = {\n backgroundColor: CHECK_COLOR,\n };\n }\n });\n });\n }\n return customSquareStyles;\n};\n","import { useMemo } from \"react\";\nimport { defaultSounds, type Sound } from \"../../../assets/sounds\";\nimport { useBoardSounds } from \"../../../hooks/useBoardSounds\";\n\nexport type SoundsProps = {\n sounds?: Partial<Record<Sound, string>>;\n};\n\nexport const Sounds: React.FC<SoundsProps> = ({ sounds }) => {\n const customSoundsAudios = useMemo(() => {\n if (typeof window === \"undefined\" || typeof Audio === \"undefined\") {\n return {} as Record<Sound, HTMLAudioElement>;\n }\n\n return Object.entries({ ...defaultSounds, sounds }).reduce(\n (acc, [name, base64]) => {\n acc[name as Sound] = new Audio(`data:audio/wav;base64,${base64}`);\n return acc;\n },\n {} as Record<Sound, HTMLAudioElement>,\n );\n }, [sounds]);\n useBoardSounds(customSoundsAudios);\n return null;\n};\n","export type Sound = \"check\" | \"move\" | \"capture\" | \"gameOver\";\nexport type Sounds = Record<Sound, HTMLAudioElement>;\n\nconst SILENCE = \"Li4vU2lsZW5jZS5vZ2c=\";\n\nexport const defaultSounds: Record<Sound, string> = {\n move: \"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\",\n capture:\n \"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\",\n check: SILENCE,\n gameOver:\n \"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\",\n};\n","import { useEffect } from \"react\";\nimport { useChessGameContext } from \"./useChessGameContext\";\nimport { type Sound } from \"../assets/sounds\";\n\nconst playSound = async (audioElement: HTMLAudioElement) => {\n try {\n await audioElement.play();\n } catch (error) {\n console.warn(\"Failed to play sound:\", (error as Error).message);\n }\n};\n\nexport const useBoardSounds = (sounds: Record<Sound, HTMLAudioElement>) => {\n const {\n info: { lastMove, isCheckmate },\n } = useChessGameContext();\n\n useEffect(() => {\n if (Object.keys(sounds).length === 0) {\n return;\n }\n\n if (isCheckmate && sounds.gameOver) {\n playSound(sounds.gameOver);\n return;\n }\n\n if (lastMove?.captured && sounds.capture) {\n playSound(sounds.capture);\n return;\n }\n\n if (lastMove && sounds.move) {\n playSound(sounds.move);\n }\n }, [lastMove]);\n};\n","import { useEffect } from \"react\";\nimport {\n defaultKeyboardControls,\n KeyboardControls,\n} from \"../components/ChessGame/parts/KeyboardControls\";\nimport { useChessGameContext } from \"./useChessGameContext\";\n\nexport const useKeyboardControls = (controls?: KeyboardControls) => {\n const gameContext = useChessGameContext();\n if (!gameContext) {\n throw new Error(\"ChessGameContext not found\");\n }\n const keyboardControls = { ...defaultKeyboardControls, ...controls };\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n const handler = keyboardControls[event.key];\n if (handler) {\n event.preventDefault();\n handler(gameContext);\n }\n };\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => {\n window.removeEventListener(\"keydown\", handleKeyDown);\n };\n }, [gameContext]);\n return null;\n};\n","import {\n ChessGameContextType,\n useChessGameContext,\n} from \"../../../hooks/useChessGameContext\";\nimport { useKeyboardControls } from \"../../../hooks/useKeyboardControls\";\n\nexport type KeyboardControls = Record<\n string,\n (context: ChessGameContextType) => void\n>;\n\nexport const defaultKeyboardControls: KeyboardControls = {\n ArrowLeft: (context) => context.methods.goToPreviousMove(),\n ArrowRight: (context) => context.methods.goToNextMove(),\n ArrowUp: (context) => context.methods.goToStart(),\n ArrowDown: (context) => context.methods.goToEnd(),\n};\n\ntype KeyboardControlsProps = {\n controls?: KeyboardControls;\n};\n\nexport const KeyboardControls: React.FC<KeyboardControlsProps> = ({\n controls,\n}) => {\n const gameContext = useChessGameContext();\n if (!gameContext) {\n throw new Error(\"ChessGameContext not found\");\n }\n const keyboardControls = { ...defaultKeyboardControls, ...controls };\n useKeyboardControls(keyboardControls);\n return null;\n};\n","import { Root } from \"./parts/Root\";\nimport { Board } from \"./parts/Board\";\nimport { Sounds } from \"./parts/Sounds\";\nimport { KeyboardControls } from \"./parts/KeyboardControls\";\n\nexport const ChessGame = {\n Root,\n Board,\n Sounds,\n KeyboardControls,\n};\n"],"mappings":"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","names":["React","Chess","Chess","fen","orientation","React","React","React","React","_a","useEffect","useEffect","useEffect","useEffect","KeyboardControls","KeyboardControls"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/ChessGame/parts/Root.tsx","../src/hooks/useChessGame.ts","../src/utils/chess.ts","../src/hooks/useChessGameContext.ts","../src/components/ChessGame/parts/Board.tsx","../src/utils/board.ts","../src/components/ChessGame/parts/Sounds.tsx","../src/assets/sounds.ts","../src/hooks/useBoardSounds.ts","../src/hooks/useKeyboardControls.ts","../src/components/ChessGame/parts/KeyboardControls.tsx","../src/components/ChessGame/index.ts"],"sourcesContent":["import React from \"react\";\nimport { Color } from \"chess.js\";\nimport { useChessGame } from \"../../../hooks/useChessGame\";\nimport { ChessGameContext } from \"../../../hooks/useChessGameContext\";\n\nexport interface RootProps {\n fen?: string;\n orientation?: Color;\n}\n\nexport const Root: React.FC<React.PropsWithChildren<RootProps>> = ({\n fen,\n orientation,\n children,\n}) => {\n const context = useChessGame({ fen, orientation });\n return (\n <ChessGameContext.Provider value={context}>\n {children}\n </ChessGameContext.Provider>\n );\n};\n","import React, { useEffect } from \"react\";\nimport { Chess, Color } from \"chess.js\";\nimport { cloneGame, getCurrentFen, getGameInfo } from \"../utils/chess\";\n\nexport type useChessGameProps = {\n fen?: string;\n orientation?: Color;\n};\n\nexport const useChessGame = ({\n fen,\n orientation: initialOrientation,\n}: useChessGameProps = {}) => {\n const [game, setGame] = React.useState(new Chess(fen));\n\n useEffect(() => {\n setGame(new Chess(fen));\n }, [fen]);\n\n const [orientation, setOrientation] = React.useState<Color>(\n initialOrientation ?? \"w\",\n );\n const [currentMoveIndex, setCurrentMoveIndex] = React.useState(-1);\n\n const history = React.useMemo(() => game.history(), [game]);\n const isLatestMove = React.useMemo(\n () => currentMoveIndex === history.length - 1 || currentMoveIndex === -1,\n [currentMoveIndex, history.length],\n );\n\n const info = React.useMemo(\n () => getGameInfo(game, orientation),\n [game, orientation],\n );\n\n const currentFen = React.useMemo(\n () => getCurrentFen(fen, game, currentMoveIndex),\n [game, currentMoveIndex],\n );\n\n const currentPosition = React.useMemo(\n () => game.history()[currentMoveIndex],\n [game, currentMoveIndex],\n );\n\n const setPosition = React.useCallback((fen: string, orientation: Color) => {\n const newGame = new Chess();\n newGame.load(fen);\n setOrientation(orientation);\n setGame(newGame);\n setCurrentMoveIndex(-1);\n }, []);\n\n const makeMove = React.useCallback(\n (move: Parameters<Chess[\"move\"]>[0]): boolean => {\n // Only allow moves when we're at the latest position\n if (!isLatestMove) {\n return false;\n }\n\n try {\n const copy = cloneGame(game);\n copy.move(move);\n setGame(copy);\n setCurrentMoveIndex(copy.history().length - 1);\n return true;\n } catch (e) {\n return false;\n }\n },\n [isLatestMove, game],\n );\n\n const flipBoard = React.useCallback(() => {\n setOrientation((orientation) => (orientation === \"w\" ? \"b\" : \"w\"));\n }, []);\n\n const goToMove = React.useCallback(\n (moveIndex: number) => {\n if (moveIndex < -1 || moveIndex >= history.length) return;\n setCurrentMoveIndex(moveIndex);\n },\n [history.length],\n );\n\n const goToStart = React.useCallback(() => goToMove(-1), []);\n const goToEnd = React.useCallback(\n () => goToMove(history.length - 1),\n [history.length],\n );\n const goToPreviousMove = React.useCallback(\n () => goToMove(currentMoveIndex - 1),\n [currentMoveIndex],\n );\n const goToNextMove = React.useCallback(\n () => goToMove(currentMoveIndex + 1),\n [currentMoveIndex],\n );\n\n const methods = React.useMemo(\n () => ({\n makeMove,\n setPosition,\n flipBoard,\n goToMove,\n goToStart,\n goToEnd,\n goToPreviousMove,\n goToNextMove,\n }),\n [\n makeMove,\n setPosition,\n flipBoard,\n goToMove,\n goToStart,\n goToEnd,\n goToPreviousMove,\n goToNextMove,\n ],\n );\n\n return {\n game,\n currentFen,\n currentPosition,\n orientation,\n currentMoveIndex,\n isLatestMove,\n info,\n methods,\n };\n};\n","import { Chess, Color, Square } from \"chess.js\";\nimport _ from \"lodash\";\n\n/**\n * Creates a clone of the given Chess.js instance. This is needed to update the state\n * of react-chessboard component\n * @param game - The Chess.js instance to clone.\n * @returns A new Chess.js instance with the same state as the original.\n */\nexport const cloneGame = (game: Chess) => {\n const copy = new Chess();\n copy.loadPgn(game.pgn());\n return copy;\n};\n\n/**\n * Returns an object with information about the current state of the game. This can be determined\n * using chess.js instance, but this function is provided for convenience.\n * @param game - The Chess.js instance representing the game.\n * @returns An object with information about the current state of the game.\n */\n\nexport const getGameInfo = (game: Chess, orientation: Color) => {\n const turn = game.turn();\n const isPlayerTurn = turn === orientation;\n const isOpponentTurn = !isPlayerTurn;\n const moveNumber = game.history().length;\n const lastMove = _.last(game.history({ verbose: true }));\n const isCheck = game.isCheck();\n const isCheckmate = game.isCheckmate();\n const isDraw = game.isDraw();\n const isStalemate = game.isStalemate();\n const isThreefoldRepetition = game.isThreefoldRepetition();\n const isInsufficientMaterial = game.isInsufficientMaterial();\n const isGameOver = game.isGameOver();\n const hasPlayerWon = isOpponentTurn && isGameOver && !isDraw;\n const hasPlayerLost = isPlayerTurn && isGameOver && !isDraw;\n const isDrawn = game.isDraw();\n return {\n turn,\n isPlayerTurn,\n isOpponentTurn,\n moveNumber,\n lastMove,\n isCheck,\n isCheckmate,\n isDraw,\n isStalemate,\n isThreefoldRepetition,\n isInsufficientMaterial,\n isGameOver,\n isDrawn,\n hasPlayerWon,\n hasPlayerLost,\n };\n};\n\nexport type GameInfo = ReturnType<typeof getGameInfo>;\n\nexport const isLegalMove = (\n game: Chess,\n move: Parameters<Chess[\"move\"]>[0],\n) => {\n try {\n const copy = cloneGame(game);\n copy.move(move);\n return true;\n } catch (e) {\n return false;\n }\n};\n\nexport const requiresPromotion = (\n game: Chess,\n move: Parameters<Chess[\"move\"]>[0],\n) => {\n try {\n const copy = cloneGame(game);\n const result = copy.move(move);\n\n return result.flags.indexOf(\"p\") !== -1;\n } catch (e) {\n if (e instanceof Error && e.message.includes(\"Invalid move\")) {\n return false;\n }\n throw e;\n }\n};\n\nexport const getDestinationSquares = (game: Chess, square: Square) => {\n const moves = game.moves({ square, verbose: true });\n return moves.map((move) => move.to);\n};\n\nexport const getCurrentFen = (\n fen: string | undefined,\n game: Chess,\n currentMoveIndex: number,\n) => {\n const tempGame = new Chess();\n if (currentMoveIndex === -1) {\n if (fen) {\n tempGame.load(fen);\n }\n } else {\n const moves = game.history().slice(0, currentMoveIndex + 1);\n\n if (fen) {\n tempGame.load(fen);\n }\n moves.forEach((move) => tempGame.move(move));\n }\n return tempGame.fen();\n};\n","import React from \"react\";\nimport { useChessGame } from \"./useChessGame\";\n\nexport const ChessGameContext = React.createContext<ReturnType<\n typeof useChessGame\n> | null>(null);\n\nexport const useChessGameContext = () => {\n const context = React.useContext(ChessGameContext);\n if (!context) {\n throw new Error(\n `useChessGameContext must be used within a ChessGame component. Make sure your component is wrapped with <ChessGame.Root> or ensure the ChessGame component is properly rendered in the component tree.`,\n );\n }\n return context;\n};\n\nexport type ChessGameContextType = ReturnType<typeof useChessGame>;\n","import React from \"react\";\nimport {\n Chessboard,\n ChessboardOptions,\n defaultPieces,\n chessColumnToColumnIndex,\n} from \"react-chessboard\";\nimport { Move, Square } from \"chess.js\";\nimport {\n getCustomSquareStyles,\n deepMergeChessboardOptions,\n} from \"../../../utils/board\";\nimport { isLegalMove, requiresPromotion } from \"../../../utils/chess\";\nimport { useChessGameContext } from \"../../../hooks/useChessGameContext\";\n\nexport interface ChessGameProps {\n options?: ChessboardOptions;\n}\n\nexport const Board: React.FC<ChessGameProps> = ({ options = {} }) => {\n const gameContext = useChessGameContext();\n\n if (!gameContext) {\n throw new Error(\"ChessGameContext not found\");\n }\n\n const {\n game,\n currentFen,\n orientation,\n info,\n isLatestMove,\n methods: { makeMove },\n } = gameContext;\n\n const { turn, isGameOver } = info;\n\n const [activeSquare, setActiveSquare] = React.useState<Square | null>(null);\n\n const [promotionMove, setPromotionMove] =\n React.useState<Partial<Move> | null>(null);\n\n const onSquareClick = (square: Square) => {\n if (isGameOver) {\n return;\n }\n\n if (activeSquare === null) {\n const squadreInfo = game.get(square);\n if (squadreInfo && squadreInfo.color === turn) {\n return setActiveSquare(square);\n }\n return;\n }\n\n if (\n !isLegalMove(game, {\n from: activeSquare,\n to: square,\n promotion: \"q\",\n })\n ) {\n return setActiveSquare(null);\n }\n\n if (\n requiresPromotion(game, {\n from: activeSquare,\n to: square,\n promotion: \"q\",\n })\n ) {\n return setPromotionMove({\n from: activeSquare,\n to: square,\n });\n }\n\n setActiveSquare(null);\n makeMove({\n from: activeSquare,\n to: square,\n });\n };\n\n const onPromotionPieceSelect = (piece: string): void => {\n if (promotionMove?.from && promotionMove?.to) {\n makeMove({\n from: promotionMove.from,\n to: promotionMove.to,\n promotion: piece.toLowerCase(),\n });\n setPromotionMove(null);\n }\n };\n\n const onSquareRightClick = () => {\n setActiveSquare(null);\n setPromotionMove(null);\n };\n\n // Calculate square width for precise positioning\n const squareWidth = React.useMemo(() => {\n if (typeof document === \"undefined\") return 80;\n const squareElement = document.querySelector(`[data-square]`);\n return squareElement?.getBoundingClientRect()?.width ?? 80;\n }, [promotionMove]);\n\n // Calculate promotion square position\n const promotionSquareLeft = React.useMemo(() => {\n if (!promotionMove?.to) return 0;\n const column = promotionMove.to.match(/^[a-h]/)?.[0] ?? \"a\";\n return (\n squareWidth *\n chessColumnToColumnIndex(\n column,\n 8,\n orientation === \"b\" ? \"black\" : \"white\",\n )\n );\n }, [promotionMove, squareWidth, orientation]);\n\n const baseOptions: ChessboardOptions = {\n squareStyles: getCustomSquareStyles(game, info, activeSquare),\n boardOrientation: orientation === \"b\" ? \"black\" : \"white\",\n position: currentFen,\n showNotation: true,\n showAnimations: isLatestMove,\n canDragPiece: ({ piece }) => {\n if (isGameOver) return false;\n return piece.pieceType[0] === turn;\n },\n dropSquareStyle: {\n backgroundColor: \"rgba(255, 255, 0, 0.4)\",\n },\n onPieceDrag: ({ piece, square }) => {\n if (piece.pieceType[0] === turn) {\n setActiveSquare(square as Square);\n }\n },\n onPieceDrop: ({ sourceSquare, targetSquare }) => {\n setActiveSquare(null);\n const moveData = {\n from: sourceSquare as Square,\n to: targetSquare as Square,\n };\n\n // Check if promotion is needed\n if (requiresPromotion(game, { ...moveData, promotion: \"q\" })) {\n setPromotionMove(moveData);\n return false; // Prevent the move until promotion is selected\n }\n\n return makeMove(moveData);\n },\n onSquareClick: ({ square }) => {\n if (square.match(/^[a-h][1-8]$/)) {\n onSquareClick(square as Square);\n }\n },\n onSquareRightClick: onSquareRightClick,\n allowDrawingArrows: true,\n animationDurationInMs: game.history().length === 0 ? 0 : 300,\n };\n\n const mergedOptions = deepMergeChessboardOptions(baseOptions, options);\n\n return (\n <div style={{ position: \"relative\" }}>\n <Chessboard options={mergedOptions} />\n {promotionMove && (\n <>\n {/* Backdrop overlay - click to cancel */}\n <div\n onClick={() => setPromotionMove(null)}\n onContextMenu={(e) => {\n e.preventDefault();\n setPromotionMove(null);\n }}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n backgroundColor: \"rgba(0, 0, 0, 0.1)\",\n zIndex: 1000,\n }}\n />\n {/* Promotion piece selection */}\n <div\n style={{\n position: \"absolute\",\n top: promotionMove.to?.[1]?.includes(\"8\") ? 0 : \"auto\",\n bottom: promotionMove.to?.[1].includes(\"1\") ? 0 : \"auto\",\n left: promotionSquareLeft,\n backgroundColor: \"white\",\n width: squareWidth,\n zIndex: 1001,\n display: \"flex\",\n flexDirection: \"column\",\n boxShadow: \"0 0 10px 0 rgba(0, 0, 0, 0.5)\",\n }}\n >\n {[\"q\", \"r\", \"n\", \"b\"].map((piece) => (\n <button\n key={piece}\n onClick={() => onPromotionPieceSelect(piece)}\n onContextMenu={(e) => {\n e.preventDefault();\n }}\n style={{\n width: \"100%\",\n aspectRatio: \"1\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n padding: 0,\n border: \"none\",\n cursor: \"pointer\",\n backgroundColor: \"white\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.backgroundColor = \"#f0f0f0\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.backgroundColor = \"white\";\n }}\n >\n {defaultPieces[\n `${turn}${piece.toUpperCase()}` as keyof typeof defaultPieces\n ]()}\n </button>\n ))}\n </div>\n </>\n )}\n </div>\n );\n};\n","import { type Chess, type Square } from \"chess.js\";\nimport { type CSSProperties } from \"react\";\nimport { merge } from \"lodash\";\nimport type { ChessboardOptions } from \"react-chessboard\";\nimport { getDestinationSquares, type GameInfo } from \"./chess\";\n\nconst LAST_MOVE_COLOR = \"rgba(255, 255, 0, 0.5)\";\nconst CHECK_COLOR = \"rgba(255, 0, 0, 0.5)\";\n\nexport const getCustomSquareStyles = (\n game: Chess,\n info: GameInfo,\n activeSquare: Square | null,\n) => {\n const customSquareStyles: Record<string, CSSProperties> = {};\n\n const { lastMove, isCheck, turn } = info;\n\n if (lastMove) {\n customSquareStyles[lastMove.from] = {\n backgroundColor: LAST_MOVE_COLOR,\n };\n customSquareStyles[lastMove.to] = {\n backgroundColor: LAST_MOVE_COLOR,\n };\n }\n\n if (activeSquare) {\n customSquareStyles[activeSquare] = {\n backgroundColor: LAST_MOVE_COLOR,\n };\n }\n\n if (activeSquare) {\n const destinationSquares = getDestinationSquares(game, activeSquare);\n destinationSquares.forEach((square) => {\n customSquareStyles[square] = {\n background:\n game.get(square) && game.get(square)?.color !== turn\n ? \"radial-gradient(circle, rgba(1, 0, 0, 0.1) 85%, transparent 85%)\"\n : \"radial-gradient(circle, rgba(0,0,0,.1) 25%, transparent 25%)\",\n };\n });\n }\n\n if (isCheck) {\n game.board().forEach((row) => {\n return row.forEach((square) => {\n if (square?.type === \"k\" && square?.color === info.turn) {\n customSquareStyles[square.square] = {\n backgroundColor: CHECK_COLOR,\n };\n }\n });\n });\n }\n return customSquareStyles;\n};\n\n/**\n * Smart deep merge for ChessboardOptions that handles different property types appropriately:\n * - Functions: Overwrite (custom functions replace base functions)\n * - Objects: Deep merge (nested objects merge recursively)\n * - Primitives: Overwrite (custom values replace base values)\n *\n * This ensures that computed options (like squareStyles with move highlighting) are preserved\n * while allowing custom options to extend or override them intelligently.\n *\n * @param baseOptions - The computed base options (e.g., computed squareStyles, event handlers)\n * @param customOptions - Custom options provided by the user\n * @returns Intelligently merged ChessboardOptions\n */\nexport const deepMergeChessboardOptions = (\n baseOptions: ChessboardOptions,\n customOptions?: Partial<ChessboardOptions>,\n): ChessboardOptions => {\n if (!customOptions) {\n return { ...baseOptions }; // Return a new object even when no custom options\n }\n\n const result = merge({}, baseOptions, customOptions, {\n customizer: (_objValue: unknown, srcValue: unknown) => {\n // Functions should always overwrite (not merge)\n // This is important for event handlers like onSquareClick, onPieceDrop, etc.\n if (typeof srcValue === \"function\") {\n return srcValue;\n }\n\n // For arrays, we typically want to overwrite rather than merge\n // This avoids unexpected behavior with array concatenation\n if (Array.isArray(srcValue)) {\n return srcValue;\n }\n\n // Let lodash handle objects with default deep merge behavior\n // This will properly merge nested objects like squareStyles, dropSquareStyle, etc.\n return undefined; // Use default merge behavior\n },\n });\n\n // Clean up any unwanted properties that lodash might add\n delete (result as Record<string, unknown>).customizer;\n\n return result;\n};\n","import { useMemo } from \"react\";\nimport { defaultSounds, type Sound } from \"../../../assets/sounds\";\nimport { useBoardSounds } from \"../../../hooks/useBoardSounds\";\n\nexport type SoundsProps = {\n sounds?: Partial<Record<Sound, string>>;\n};\n\nexport const Sounds: React.FC<SoundsProps> = ({ sounds }) => {\n const customSoundsAudios = useMemo(() => {\n if (typeof window === \"undefined\" || typeof Audio === \"undefined\") {\n return {} as Record<Sound, HTMLAudioElement>;\n }\n\n return Object.entries({ ...defaultSounds, sounds }).reduce(\n (acc, [name, base64]) => {\n acc[name as Sound] = new Audio(`data:audio/wav;base64,${base64}`);\n return acc;\n },\n {} as Record<Sound, HTMLAudioElement>,\n );\n }, [sounds]);\n useBoardSounds(customSoundsAudios);\n return null;\n};\n","export type Sound = \"check\" | \"move\" | \"capture\" | \"gameOver\";\nexport type Sounds = Record<Sound, HTMLAudioElement>;\n\nconst SILENCE = \"Li4vU2lsZW5jZS5vZ2c=\";\n\nexport const defaultSounds: Record<Sound, string> = {\n move: \"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\",\n capture:\n \"T2dnUwACAAAAAAAAAADPNAAAAAAAADwcwn8BHgF2b3JiaXMAAAAAAUSsAAAAAAAAAHcBAAAAAAC4AU9nZ1MAAAAAAAAAAAAAzzQAAAEAAAAWcbXCEJ///////////////////8kDdm9yYmlzKwAAAFhpcGguT3JnIGxpYlZvcmJpcyBJIDIwMTIwMjAzIChPbW5pcHJlc2VudCkDAAAAHgAAAFRJVExFPVdvb2RlbiBwaWVjZSAtIHNoYXJwIGhpdCcAAABDb3B5cmlnaHQ9Q29weXJpZ2h0IDIwMDAsIFNvdW5kZG9ncy5jb20TAAAAU29mdHdhcmU9QXdDKysgdjIuMQEFdm9yYmlzKUJDVgEACAAAADFMIMWA0JBVAAAQAABgJCkOk2ZJKaWUoSh5mJRISSmllMUwiZiUicUYY4wxxhhjjDHGGGOMIDRkFQAABACAKAmOo+ZJas45ZxgnjnKgOWlOOKcgB4pR4DkJwvUmY26mtKZrbs4pJQgNWQUAAAIAQEghhRRSSCGFFGKIIYYYYoghhxxyyCGnnHIKKqigggoyyCCDTDLppJNOOumoo4466ii00EILLbTSSkwx1VZjrr0GXXxzzjnnnHPOOeecc84JQkNWAQAgAAAEQgYZZBBCCCGFFFKIKaaYcgoyyIDQkFUAACAAgAAAAABHkRRJsRTLsRzN0SRP8ixREzXRM0VTVE1VVVVVdV1XdmXXdnXXdn1ZmIVbuH1ZuIVb2IVd94VhGIZhGIZhGIZh+H3f933f930gNGQVACABAKAjOZbjKaIiGqLiOaIDhIasAgBkAAAEACAJkiIpkqNJpmZqrmmbtmirtm3LsizLsgyEhqwCAAABAAQAAAAAAKBpmqZpmqZpmqZpmqZpmqZpmqZpmmZZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZlmVZQGjIKgBAAgBAx3Ecx3EkRVIkx3IsBwgNWQUAyAAACABAUizFcjRHczTHczzHczxHdETJlEzN9EwPCA1ZBQAAAgAIAAAAAABAMRzFcRzJ0SRPUi3TcjVXcz3Xc03XdV1XVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVYHQkFUAAAQAACGdZpZqgAgzkGEgNGQVAIAAAAAYoQhDDAgNWQUAAAQAAIih5CCa0JrzzTkOmuWgqRSb08GJVJsnuamYm3POOeecbM4Z45xzzinKmcWgmdCac85JDJqloJnQmnPOeRKbB62p0ppzzhnnnA7GGWGcc85p0poHqdlYm3POWdCa5qi5FJtzzomUmye1uVSbc84555xzzjnnnHPOqV6czsE54Zxzzonam2u5CV2cc875ZJzuzQnhnHPOOeecc84555xzzglCQ1YBAEAAAARh2BjGnYIgfY4GYhQhpiGTHnSPDpOgMcgppB6NjkZKqYNQUhknpXSC0JBVAAAgAACEEFJIIYUUUkghhRRSSCGGGGKIIaeccgoqqKSSiirKKLPMMssss8wyy6zDzjrrsMMQQwwxtNJKLDXVVmONteaec645SGultdZaK6WUUkoppSA0ZBUAAAIAQCBkkEEGGYUUUkghhphyyimnoIIKCA1ZBQAAAgAIAAAA8CTPER3RER3RER3RER3RER3P8RxREiVREiXRMi1TMz1VVFVXdm1Zl3Xbt4Vd2HXf133f141fF4ZlWZZlWZZlWZZlWZZlWZZlCUJDVgEAIAAAAEIIIYQUUkghhZRijDHHnINOQgmB0JBVAAAgAIAAAAAAR3EUx5EcyZEkS7IkTdIszfI0T/M00RNFUTRNUxVd0RV10xZlUzZd0zVl01Vl1XZl2bZlW7d9WbZ93/d93/d93/d93/d939d1IDRkFQAgAQCgIzmSIimSIjmO40iSBISGrAIAZAAABACgKI7iOI4jSZIkWZImeZZniZqpmZ7pqaIKhIasAgAAAQAEAAAAAACgaIqnmIqniIrniI4oiZZpiZqquaJsyq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7ruq7rukBoyCoAQAIAQEdyJEdyJEVSJEVyJAcIDVkFAMgAAAgAwDEcQ1Ikx7IsTfM0T/M00RM90TM9VXRFFwgNWQUAAAIACAAAAAAAwJAMS7EczdEkUVIt1VI11VItVVQ9VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV1TRN0zSB0JCVAAAZAAAjQQYZhBCKcpBCbj1YCDHmJAWhOQahxBiEpxAzDDkNInSQQSc9uJI5wwzz4FIoFURMg40lN44gDcKmXEnlOAhCQ1YEAFEAAIAxyDHEGHLOScmgRM4xCZ2UyDknpZPSSSktlhgzKSWmEmPjnKPSScmklBhLip2kEmOJrQAAgAAHAIAAC6HQkBUBQBQAAGIMUgophZRSzinmkFLKMeUcUko5p5xTzjkIHYTKMQadgxAppRxTzinHHITMQeWcg9BBKAAAIMABACDAQig0ZEUAECcA4HAkz5M0SxQlSxNFzxRl1xNN15U0zTQ1UVRVyxNV1VRV2xZNVbYlTRNNTfRUVRNFVRVV05ZNVbVtzzRl2VRV3RZV1bZl2xZ+V5Z13zNNWRZV1dZNVbV115Z9X9ZtXZg0zTQ1UVRVTRRV1VRV2zZV17Y1UXRVUVVlWVRVWXZlWfdVV9Z9SxRV1VNN2RVVVbZV2fVtVZZ94XRVXVdl2fdVWRZ+W9eF4fZ94RhV1dZN19V1VZZ9YdZlYbd13yhpmmlqoqiqmiiqqqmqtm2qrq1bouiqoqrKsmeqrqzKsq+rrmzrmiiqrqiqsiyqqiyrsqz7qizrtqiquq3KsrCbrqvrtu8LwyzrunCqrq6rsuz7qizruq3rxnHrujB8pinLpqvquqm6um7runHMtm0co6rqvirLwrDKsu/rui+0dSFRVXXdlF3jV2VZ921fd55b94WybTu/rfvKceu60vg5z28cubZtHLNuG7+t+8bzKz9hOI6lZ5q2baqqrZuqq+uybivDrOtCUVV9XZVl3zddWRdu3zeOW9eNoqrquirLvrDKsjHcxm8cuzAcXds2jlvXnbKtC31jyPcJz2vbxnH7OuP2daOvDAnHjwAAgAEHAIAAE8pAoSErAoA4AQAGIecUUxAqxSB0EFLqIKRUMQYhc05KxRyUUEpqIZTUKsYgVI5JyJyTEkpoKZTSUgehpVBKa6GU1lJrsabUYu0gpBZKaS2U0lpqqcbUWowRYxAy56RkzkkJpbQWSmktc05K56CkDkJKpaQUS0otVsxJyaCj0kFIqaQSU0mptVBKa6WkFktKMbYUW24x1hxKaS2kEltJKcYUU20txpojxiBkzknJnJMSSmktlNJa5ZiUDkJKmYOSSkqtlZJSzJyT0kFIqYOOSkkptpJKTKGU1kpKsYVSWmwx1pxSbDWU0lpJKcaSSmwtxlpbTLV1EFoLpbQWSmmttVZraq3GUEprJaUYS0qxtRZrbjHmGkppraQSW0mpxRZbji3GmlNrNabWam4x5hpbbT3WmnNKrdbUUo0txppjbb3VmnvvIKQWSmktlNJiai3G1mKtoZTWSiqxlZJabDHm2lqMOZTSYkmpxZJSjC3GmltsuaaWamwx5ppSi7Xm2nNsNfbUWqwtxppTS7XWWnOPufVWAADAgAMAQIAJZaDQkJUAQBQAAEGIUs5JaRByzDkqCULMOSepckxCKSlVzEEIJbXOOSkpxdY5CCWlFksqLcVWaykptRZrLQAAoMABACDABk2JxQEKDVkJAEQBACDGIMQYhAYZpRiD0BikFGMQIqUYc05KpRRjzknJGHMOQioZY85BKCmEUEoqKYUQSkklpQIAAAocAAACbNCUWByg0JAVAUAUAABgDGIMMYYgdFQyKhGETEonqYEQWgutddZSa6XFzFpqrbTYQAithdYySyXG1FpmrcSYWisAAOzAAQDswEIoNGQlAJAHAEAYoxRjzjlnEGLMOegcNAgx5hyEDirGnIMOQggVY85BCCGEzDkIIYQQQuYchBBCCKGDEEIIpZTSQQghhFJK6SCEEEIppXQQQgihlFIKAAAqcAAACLBRZHOCkaBCQ1YCAHkAAIAxSjkHoZRGKcYglJJSoxRjEEpJqXIMQikpxVY5B6GUlFrsIJTSWmw1dhBKaS3GWkNKrcVYa64hpdZirDXX1FqMteaaa0otxlprzbkAANwFBwCwAxtFNicYCSo0ZCUAkAcAgCCkFGOMMYYUYoox55xDCCnFmHPOKaYYc84555RijDnnnHOMMeecc845xphzzjnnHHPOOeecc44555xzzjnnnHPOOeecc84555xzzgkAACpwAAAIsFFkc4KRoEJDVgIAqQAAABFWYowxxhgbCDHGGGOMMUYSYowxxhhjbDHGGGOMMcaYYowxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGFtrrbXWWmuttdZaa6211lprrQBAvwoHAP8HG1ZHOCkaCyw0ZCUAEA4AABjDmHOOOQYdhIYp6KSEDkIIoUNKOSglhFBKKSlzTkpKpaSUWkqZc1JSKiWlllLqIKTUWkottdZaByWl1lJqrbXWOgiltNRaa6212EFIKaXWWostxlBKSq212GKMNYZSUmqtxdhirDGk0lJsLcYYY6yhlNZaazHGGGstKbXWYoy1xlprSam11mKLNdZaCwDgbnAAgEiwcYaVpLPC0eBCQ1YCACEBAARCjDnnnHMQQgghUoox56CDEEIIIURKMeYcdBBCCCGEjDHnoIMQQgghhJAx5hx0EEIIIYQQOucchBBCCKGEUkrnHHQQQgghlFBC6SCEEEIIoYRSSikdhBBCKKGEUkopJYQQQgmllFJKKaWEEEIIoYQSSimllBBCCKWUUkoppZQSQgghlFJKKaWUUkIIoZRQSimllFJKCCGEUkoppZRSSgkhhFBKKaWUUkopIYQSSimllFJKKaUAAIADBwCAACPoJKPKImw04cIDUGjISgCADAAAcdhq6ynWyCDFnISWS4SQchBiLhFSijlHsWVIGcUY1ZQxpRRTUmvonGKMUU+dY0oxw6yUVkookYLScqy1dswBAAAgCAAwECEzgUABFBjIAIADhAQpAKCwwNAxXAQE5BIyCgwKx4Rz0mkDABCEyAyRiFgMEhOqgaJiOgBYXGDIB4AMjY20iwvoMsAFXdx1IIQgBCGIxQEUkICDE2544g1PuMEJOkWlDgIAAAAAAAEAHgAAkg0gIiKaOY4Ojw+QEJERkhKTE5QAAAAAAOABgA8AgCQFiIiIZo6jw+MDJERkhKTE5AQlAAAAAAAAAAAACAgIAAAAAAAEAAAACAhPZ2dTAAQAPwAAAAAAAM80AAACAAAAFGk+EBoBD3QmJS0tLy+1JistsZ2ZimMjGRgWEhEPAQDaZfyfI31BABsAAAAAAADWZfz3MF9KgbGpIfaKQOIRSc+3AAAA96+6YDvaPzAM5YN8sTLkMwHryfJLdf8mCfz/3v//f0tjUwBKWgD4SC8H4B5tRwIAgE30+hOTbAUA2igCgHtSz7YC8HgwjgjQDjzx5VqhnWMTfNJUCWt0OHZ4rAD3J/QccRdd0nrHhNJ82DVLEVgBr215KCG955L6fk9UxM+g2BU5lzsBDB31eyEUdQHJG+YxEwOMAgZaL0QilsokPZ8R/0bS1f/vPa4VACQdiz9kUV3pkpkKBJD9z0SCUsCgMc+1D+v61st2+tIsNvQlaH+gc5ETuNu/AMQdy34qRfrGP6XgndK3FVYt/B+3j4n1+IA4zrmhu3ucDqqSM9+nqZ0MiNwqALRuC3/IiO/DoysmKPvgIhWcDWDc+nxt/ffy+s3EAOhGf9A8+JQWvGXiLbFUN00ALHqvvHne/CX/wakSrmIgA8xSC2zDbn+MT9WF2xujR93zHPrW2Wa2qm6jh7eTDgBSuNzA/aPi+r5YzHU4VEsoGz8+t/sOZO48B9hGIF2pAknY5sJUIVuiTQAAcAf0YPDpeetZfX3+TD2Sba8dvv/apU3z85eybX5+/kJ1iReaIAJ4MtLa/IVXvjo8/dqfozOKnmjhFZRkXSj/SSd2OpaQNTOzAs+UZvemvo/S6wNyu/4igIbILXsaQHJurNZa6Q3QUPoW2hWEs96gwAkWpTCNM+xfnpewJa9NExvXBr2oVQAAUBYA3B09T2IcO70Ia/F2Hx+AQeFuiK04HIdXksauN0p2ZbvMTL7OoAP0Hf2ftkHPnpyKoP+3AKsWwvJRE+/pVPcelFzQufjsJW/R4vR0InwdMyAAbFqLvz3S2vUvdJ+8r1YjqhY2Zd9Phb3sul++A8efFazJB92dyO/LLZr/EgAAumcMiO863MewuPptlJUi8HMbT7d4me5gkLEdMI4Z9G0M5GwJkhISAADhYrtHuK20o5mfnrOM0YWf9aHvazJ7tunKhU0CS5s/u/Lr/1e/sOYE2srDEMZgD91F5jpbfPqzl1JSUuLs80oBMHd3n+u83woFOHr5pfUG+QDALngGmEyV6EAW7lIjguHzyo1RDFg4A7ggjJo6zvIgAW6xzyW1xkTCiP8pDgAqDB0K8B0DAAAAfmYkwWeXX9IviqePuWCh7kclSBDaEIFxTKUEyYoQAQCa5gHX0NPXm5Aq6vW8lf2U72h/VObN3sb7R2diK/csA/aPx7GhGG+Nv0MCCRbKfH3E+9laEMxZJ9SlQN8JO6QPN5XAA6XgClTM/96SMZTYYmm4NcBg5LDQd7tusHCCqcq/v5ald7iUpl5AYd8cY+8jBR7AmcPvBkCBCosAAF5mnMUtcndDVT29fYT7uMKzOqDaLiESPUoAANgf3pWzl99mRq7l2exfyrcx5j/bnI1LYnp6ejc9rY/eg5T8rLBs3FsR9ZeO4HE991YNLSuDNaloeTgPf0cA301bZaJZA1gNdczLq7eQCLW91ZFqDVFotrlp5arKWv23FYL5CZa06IACRw+rsyWYmqL9Fy2b8FUUWIEbX4mdAN5lfMt9oKog2kg5tTf96AMYu1QJCWYKAOAs/4X5dzp47pxeTyz3bM2w9SZ1fSnfqJPmqXi+Hl+XkkDTxkVxaME2aQ++6psX1NaoSTxAlyCxAPjNpwmKRZlkEQCt8gU1cRqD7zMBQFVJTk5fVam/Wrj2L+8nLBo8YveSTn6o7iVbds562OtgA2ieAH5l/F3qvTUFbOBUGfv2S4BsbSkTUAYAgDFfWibq/84qiQ98lXl2y35j31syL1utJar5dhaHrI1YIsGWTI6s+3UV0gtspTffgx+IYaKQYIf8CTg260VNmwFAZ9+iAB3wdIAgAJ5l/HuUO00BG/AoBQBQAAC2jZs53uWe3dPsoAO3WYPwAJAAvmX8556+oIANMAAATAEAgGIWEoDEBE0mAN5l/J8jfUEBGwAAEBAQUAAAJgBMAP3QBt5l/J8jfUEBGwAAgDIBAABgwwSAhwTeZfyfI31BAQcAAAAEAAAA2BveZfyfI31BARsAAIACAAAAG95l/O8sX0oBGwAAAAAAAA4=\",\n check: SILENCE,\n gameOver:\n \"T2dnUwACAAAAAAAAAABMML4BAAAAAPoxzZMBHgF2b3JiaXMAAAAAAUSsAAAAAAAAAHcBAAAAAAC4AU9nZ1MAAAAAAAAAAAAATDC+AQEAAADnLs1GEf9G///////////////////JA3ZvcmJpczUAAABYaXBoLk9yZyBsaWJWb3JiaXMgSSAyMDE4MDMxNiAoTm93IDEwMCUgZmV3ZXIgc2hlbGxzKQMAAABjAAAAaVR1bk5PUk09IDAwMDAwMEU2IDAwMDAwMDA2IDAwMDAwNTI2IDAwMDAwMDI1IDAwMDAwMDFBIDAwMDAwMDFBIDAwMDAyNTQ2IDAwMDAwQkI2IDAwMDAwMDAwIDAwMDAwMDAwfQAAAGlUdW5TTVBCPSAwMDAwMDAwMCAwMDAwMDIxMCAwMDAwMDg4RiAwMDAwMDAwMDAwMDA1Q0UxIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwIDAwMDAwMDAwFAAAAFRJVExFPWluY29taW5nLXJqcy0xAQV2b3JiaXMpQkNWAQAIAAAAMUwgxYDQkFUAABAAAGAkKQ6TZkkppZShKHmYlEhJKaWUxTCJmJSJxRhjjDHGGGOMMcYYY4wgNGQVAAAEAIAoCY6j5klqzjlnGCeOcqA5aU44pyAHilHgOQnC9SZjbqa0pmtuziklCA1ZBQAAAgBASCGFFFJIIYUUYoghhhhiiCGHHHLIIaeccgoqqKCCCjLIIINMMumkk0466aijjjrqKLTQQgsttNJKTDHVVmOuvQZdfHPOOeecc84555xzzglCQ1YBACAAAARCBhlkEEIIIYUUUogppphyCjLIgNCQVQAAIACAAAAAAEeRFEmxFMuxHM3RJE/yLFETNdEzRVNUTVVVVVV1XVd2Zdd2ddd2fVmYhVu4fVm4hVvYhV33hWEYhmEYhmEYhmH4fd/3fd/3fSA0ZBUAIAEAoCM5luMpoiIaouI5ogOEhqwCAGQAAAQAIAmSIimSo0mmZmquaZu2aKu2bcuyLMuyDISGrAIAAAEABAAAAAAAoGmapmmapmmapmmapmmapmmapmmaZlmWZVmWZVmWZVmWZVmWZVmWZVmWZVmWZVmWZVmWZVmWZVmWZVlAaMgqAEACAEDHcRzHcSRFUiTHciwHCA1ZBQDIAAAIAEBSLMVyNEdzNMdzPMdzPEd0RMmUTM30TA8IDVkFAAACAAgAAAAAAEAxHMVxHMnRJE9SLdNyNVdzPddzTdd1XVdVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVgdCQVQAABAAAIZ1mlmqACDOQYSA0ZBUAgAAAABihCEMMCA1ZBQAABAAAiKHkIJrQmvPNOQ6a5aCpFJvTwYlUmye5qZibc84555xszhnjnHPOKcqZxaCZ0JpzzkkMmqWgmdCac855EpsHranSmnPOGeecDsYZYZxzzmnSmgep2Vibc85Z0JrmqLkUm3POiZSbJ7W5VJtzzjnnnHPOOeecc86pXpzOwTnhnHPOidqba7kJXZxzzvlknO7NCeGcc84555xzzjnnnHPOCUJDVgEAQAAABGHYGMadgiB9jgZiFCGmIZMedI8Ok6AxyCmkHo2ORkqpg1BSGSeldILQkFUAACAAAIQQUkghhRRSSCGFFFJIIYYYYoghp5xyCiqopJKKKsoos8wyyyyzzDLLrMPOOuuwwxBDDDG00kosNdVWY4215p5zrjlIa6W11lorpZRSSimlIDRkFQAAAgBAIGSQQQYZhRRSSCGGmHLKKaegggoIDVkFAAACAAgAAADwJM8RHdERHdERHdERHdERHc/xHFESJVESJdEyLVMzPVVUVVd2bVmXddu3hV3Ydd/Xfd/XjV8XhmVZlmVZlmVZlmVZlmVZlmUJQkNWAQAgAAAAQgghhBRSSCGFlGKMMcecg05CCYHQkFUAACAAgAAAAABHcRTHkRzJkSRLsiRN0izN8jRP8zTRE0VRNE1TFV3RFXXTFmVTNl3TNWXTVWXVdmXZtmVbt31Ztn3f933f933f933f933f13UgNGQVACABAKAjOZIiKZIiOY7jSJIEhIasAgBkAAAEAKAojuI4jiNJkiRZkiZ5lmeJmqmZnumpogqEhqwCAAABAAQAAAAAAKBoiqeYiqeIiueIjiiJlmmJmqq5omzKruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6ruu6QGjIKgBAAgBAR3IkR3IkRVIkRXIkBwgNWQUAyAAACADAMRxDUiTHsixN8zRP8zTREz3RMz1VdEUXCA1ZBQAAAgAIAAAAAADAkAxLsRzN0SRRUi3VUjXVUi1VVD1VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVXVNE3TNIHQkJUAABkAACNBBhmEEIpykEJuPVgIMeYkBaE5BqHEGISnEDMMOQ0idJBBJz24kjnDDPPgUigVREyDjSU3jiANwqZcSeU4CEJDVgQAUQAAgDHIMcQYcs5JyaBEzjEJnZTIOSelk9JJKS2WGDMpJaYSY+Oco9JJyaSUGEuKnaQSY4mtAACAAAcAgAALodCQFQFAFAAAYgxSCimFlFLOKeaQUsox5RxSSjmnnFPOOQgdhMoxBp2DECmlHFPOKccchMxB5ZyD0EEoAAAgwAEAIMBCKDRkRQAQJwDgcCTPkzRLFCVLE0XPFGXXE03XlTTNNDVRVFXLE1XVVFXbFk1VtiVNE01N9FRVE0VVFVXTlk1VtW3PNGXZVFXdFlXVtmXbFn5XlnXfM01ZFlXV1k1VtXXXln1f1m1dmDTNNDVRVFVNFFXVVFXbNlXXtjVRdFVRVWVZVFVZdmVZ91VX1n1LFFXVU03ZFVVVtlXZ9W1Vln3hdFVdV2XZ91VZFn5b14Xh9n3hGFXV1k3X1XVVln1h1mVht3XfKGmaaWqiqKqaKKqqqaq2baqurVui6KqiqsqyZ6qurMqyr6uubOuaKKquqKqyLKqqLKuyrPuqLOu2qKq6rcqysJuuq+u27wvDLOu6cKqurquy7PuqLOu6revGceu6MHymKcumq+q6qbq6buu6ccy2bRyjquq+KsvCsMqy7+u6L7R1IVFVdd2UXeNXZVn3bV93nlv3hbJtO7+t+8px67rS+DnPbxy5tm0cs24bv637xvMrP2E4jqVnmrZtqqqtm6qr67JuK8Os60JRVX1dlWXfN11ZF27fN45b142iquq6Ksu+sMqyMdzGbxy7MBxd2zaOW9edsq0LfWPI9wnPa9vGcfs64/Z1o68MCcePAACAAQcAgAATykChISsCgDgBAAYh5xRTECrFIHQQUuogpFQxBiFzTkrFHJRQSmohlNQqxiBUjknInJMSSmgplNJSB6GlUEproZTWUmuxptRi7SCkFkppLZTSWmqpxtRajBFjEDLnpGTOSQmltBZKaS1zTkrnoKQOQkqlpBRLSi1WzEnJoKPSQUippBJTSam1UEprpaQWS0oxthRbbjHWHEppLaQSW0kpxhRTbS3GmiPGIGTOScmckxJKaS2U0lrlmJQOQkqZg5JKSq2VklLMnJPSQUipg45KSSm2kkpMoZTWSkqxhVJabDHWnFJsNZTSWkkpxpJKbC3GWltMtXUQWgultBZKaa21VmtqrcZQSmslpRhLSrG1FmtuMeYaSmmtpBJbSanFFluOLcaaU2s1ptZqbjHmGlttPdaac0qt1tRSjS3GmmNtvdWae+8gpBZKaS2U0mJqLcbWYq2hlNZKKrGVklpsMebaWow5lNJiSanFklKMLcaaW2y5ppZqbDHmmlKLtebac2w19tRarC3GmlNLtdZac4+59VYAAMCAAwBAgAlloNCQlQBAFAAAQYhSzklpEHLMOSoJQsw5J6lyTEIpKVXMQQgltc45KSnF1jkIJaUWSyotxVZrKSm1FmstAACgwAEAIMAGTYnFAQoNWQkARAEAIMYgxBiEBhmlGIPQGKQUYxAipRhzTkqlFGPOSckYcw5CKhljzkEoKYRQSiophRBKSSWlAgAAChwAAAJs0JRYHKDQkBUBQBQAAGAMYgwxhiB0VDIqEYRMSiepgRBaC6111lJrpcXMWmqttNhACK2F1jJLJcbUWmatxJhaKwAA7MABAOzAQig0ZCUAkAcAQBijFGPOOWcQYsw56Bw0CDHmHIQOKsacgw5CCBVjzkEIIYTMOQghhBBC5hyEEEIIoYMQQgillNJBCCGEUkrpIIQQQimldBBCCKGUUgoAACpwAAAIsFFkc4KRoEJDVgIAeQAAgDFKOQehlEYpxiCUklKjFGMQSkmpcgxCKSnFVjkHoZSUWuwglNJabDV2EEppLcZaQ0qtxVhrriGl1mKsNdfUWoy15pprSi3GWmvNuQAA3AUHALADG0U2JxgJKjRkJQCQBwCAIKQUY4wxhhRiijHnnEMIKcWYc84pphhzzjnnlGKMOeecc4wx55xzzjnGmHPOOeccc84555xzjjnnnHPOOeecc84555xzzjnnnHPOCQAAKnAAAAiwUWRzgpGgQkNWAgCpAAAAEVZijDHGGBsIMcYYY4wxRhJijDHGGGNsMcYYY4wxxphijDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYW2uttdZaa6211lprrbXWWmutAEC/CgcA/wcbVkc4KRoLLDRkJQAQDgAAGMOYc445Bh2EhinopIQOQgihQ0o5KCWEUEopKXNOSkqlpJRaSplzUlIqJaWWUuogpNRaSi211loHJaXWUmqttdY6CKW01FprrbXYQUgppdZaiy3GUEpKrbXYYow1hlJSaq3F2GKsMaTSUmwtxhhjrKGU1lprMcYYay0ptdZijLXGWmtJqbXWYos11loLAOBucACASLBxhpWks8LR4EJDVgIAIQEABEKMOeeccxBCCCFSijHnoIMQQgghREox5hx0EEIIIYSMMeeggxBCCCGEkDHmHHQQQgghhBA65xyEEEIIoYRSSuccdBBCCCGUUELpIIQQQgihhFJKKR2EEEIooYRSSiklhBBCCaWUUkoppYQQQgihhBJKKaWUEEIIpZRSSimllBJCCCGUUkoppZRSQgihlFBKKaWUUkoIIYRSSimllFJKCSGEUEoppZRSSikhhBJKKaWUUkoppQAAgAMHAIAAI+gko8oibDThwgNQaMhKAIAMAABx2GrrKdbIIMWchJZLhJByEGIuEVKKOUexZUgZxRjVlDGlFFNSa+icYoxRT51jSjHDrJRWSiiRgtJyrLV2zAEAACAIADAQITOBQAEUGMgAgAOEBCkAoLDA0DFcBATkEjIKDArHhHPSaQMAEITIDJGIWAwSE6qBomI6AFhcYMgHgAyNjbSLC+gywAVd3HUghCAEIYjFARSQgIMTbnjiDU+4wQk6RaUOAgAAAAAAAQAeAACSDSAiIpo5jg6PD5AQkRGSEpMTlAAAAAAA4AGADwCAJAWIiIhmjqPD4wMkRGSEpMTkBCUAAAAAAAAAAAAICAgAAAAAAAQAAAAICE9nZ1MAAEBiAAAAAAAATDC+AQIAAABDQUjMISfG1ycqHFNMS1FTZn6IpLnI2ygmMC8wupGMmrXHxdLIzDwBpwDQ6w9iomaBwEjOWf2eNBVfI/GUjNVn7Ikz33q6t+WgvB3tOrpF/JziT0CIIC3IvXBaVZ5mrS2A77Cd1p6RUJju15TbuEtv+jN6SJktC3S0vKh1kCZ8Yj6O6a5JPUk7DKM0vNuJKakxvSUeb8MZ+3jRn3v2nZOXDhPKU9PJXr55Q0KSVrGSd4Gw7xkfqDuFNPsxYWRBqUWBql4Q49qlhlHRTNZS/5Bk2YUEc5ZZUb4dub2eMLeDbJEPm68V6gNOh0tWutjI7O9kNNtvN9zg1TuMdMWIvyJvOf30UJJj5T+/50JZlrC2VUM6EHY1XAdaZEFcEiamV+c85zTO69z5OK0BJcBYFWMiG81aAQu9Uc2DafSBQdPVNwv6TfO2/rwz9xaWjXFqz/C+uWMkYNP1Z3rSk9aTSKbcB8n1C2cU6AUluqFLTheCOS2pl+71EuZSoizXaXMEXMCW6QqvSsRvT5jRYLYbYoHiTuFKYjXRgHwXiksn6rIoJO4yUuq/pN7WUpXi0lS5QKcGQhJsWQIq1nk6Cw9y8xRDRV+g0JL1upTpfKn4PrT3epbPbrUSSwXuL6zBSbq0rzRB9S4rNMWOTXYFHAKfst3uJtXJ0zgD6JT4LtDGa1bSQlUep7mQQAXJZEEF536GhUsB1G5f/+YZgAsONXdqwANB1bDFqYvNOdUkQ4YMGWJUiMsX8Fr8V2dQjNEhDJNfd/EMc++CKW4VYwABBYwmTXWEImUMnZf/E9oafvCvoUJMeE8h8AcA8AEASAcAkI6kAIA1z/Mc8xzBw2lR3QEmsDVXe3tQSin1Y9Jd933ywn2d7Dn0bxd/3XG808w67wQAXgaAGwagCsC9AtwF/hne2s8FYsNzAn8AELwGIIB0gKWgnnQAgCEBoBo8O0sBAFYHAMx2CSdDTV9XR7W07nxBSDLPAcCbFgCgARxiHQC2IvE9EoByawFAAD4ZXgceC8SA7wZ+AAB2CUgHkNIBLB0AoB4AN9cgdywAsHsGgNIvn4ommhUNJY37X3UrabwI4HZAAABUoQwAfIJ9pLg6rjsAHgNJAB4Jnode+1Qx4D2BPwCAD4ASSAcC6RgEHgMAAODpinT4VgQAqBgKAHrXcRO1L2Jri57GyjROEOWLAgA6oAEwBigAoCTwmE/wPPw2NME67v8EAD75XbOPHUSD7wb+AGC8BgCQDgRMiCBEZgAAANzsnyaNjRIA8LoKABjinEMvqVZn1SmF7MVaO/OZ+AEAcOIDOG7CIgAAACwCjq8A7NjLgkPrO+sA/sidcs+tVA34NmE5/gHAKBIQIIuAxwTseRQAAMCkVG3wag4ADkICAHUc/vTabw49x+gFd76o92EJxAMAgJDUsx9D9NDAsHTukgTApyN0YMcF0GLE0hCaWB83RbkMufMJmlgEAHA/vshdcs+iKxnwLOD7A4AxBghAQYiVkJ62FgDQAGtK6v3RSgAAGgEAKXXztWM7hxQOyb22ro8RgAF2UgpNJOAw4MIcRLHP6HhXss48hYSEDkEZAfjWBAIw/MXIORInbKWlb10IT1TsPOHWvHmbpYxWQCfrXy6Ar8QdANAGIx0Anphds7ex1BWkJ7z8AADsAABZjWcVWwYAAGtyqskiAOwKgPFK5RfObzLUdiUpnEObD+hN+TPov/uGhPh7ZlQmFP4EDVfZTFbds2pvAMeVNii/HFCbY5qwT65FY7ALhelcthuKhQNdlMhV34fD0wQh54iVnFbKcl0MsylPfQOg5RWnuk8dlgWYAB443TNHuXbXAOAHAGANAIFcfqKipwwFsAAuk9NkwAFgtAKwVnZlnMpJsuNzCjn1l0lEnYW5tI5u0JIbAjmLKfIb0aBIwo++QFog7P/Q9wJ7zKtrbqaxC2EuBJAc2ClKO9bEvX6aULmLG2bQgu5UiyWT0fyoVTFWuGVLCArAcXn2imkPzUDvT7SW5IE19+oKHU0SIYvFPXa2T1cRIyQSAFeuAqgDHqi82VPuhQAwP8wJ+Hq/6QEEUmlMyivnaQqQaPFpYVtZbRPBuY2xdSgLAGUCgE3jlEGIgFzJNhu/pJLIoeNjmvgtYUxEmDV7AiqjoqGif0mRNFwjUXDBvgL9E8kMSt0ipNwRUYO/pUGEkViFaPEKvZkMMTYmkgdaBqb7WkibwTQpty891NcuHdiji3qRBDcxHRpvrHpJZyXNZQuvrVUi9VY/HbC2RxkZdHa9/pall2bB/MNTr8+nLAOeaPwX6AAEgP0DAHAAyVWen/hR9j0KAMC/N8TXbAGgpQBwq132BSqtvCX9MROqp7neI6GWqb7XO++n2VPnRZNGpXbfe3pA0YoASVcvqmkXfkMufWmmrYQGiGgn7lvX9K/xG+rmTTG2WbxH9Eq1srl0mBUfzzHLG5z0HfFldVjQVqLSzlvmCt+Y4uqxQpODpNDuVsTcZUkmEP8zbCWSEXYH2zHx7YclDCiE++HOyAWt4sMxU3aEkNAZM2RgtJvkZ5OxUSL82sIxOnZ27Pnx7EA+JsDa7wMAVGm5qjztGemAXZbPzVjWZWgonjTQ67SoatZMu3/vS2rWb734/ogHP3/w4MHPHyiAxrq3nxp2I4H/Fz/tda17if/QYeAq+pKGSeYtG9TR6739bcv/mZn6Zv/PTEV5q6PNZ8byOMG51in9MFApS504bFChR2Dm2BLwKYboKvkxGy3l6R26qtxJs+jknkPz70izxFYhcmP5nkhIPxVBDVvvjz+t0L+VLrWs1d+oQDmZU62ek9tLSKQKWPvMxVKvYfZ2ksJaPGSerOYRZPhOASwSlxb0y6x0IWSnfgWMiv89R4AmtTVGKXDvSE/uEs+d4JF39MRdCgAsFtfJUxfIDjF1jBIYyfE0T6RFKmsaxkg5z1kueg8v/13Qw4rqAiwa131Qr0A+InfqqwXrTPtxMFndt6K74hXBe+A/jN7Kp8vsbLq7K+mWBBuH6uyFADwa0/AEYMLYbBVuuf3YtrzExtT67VdhH5Wb4QauXzrzb+tb/9wkqqzNT0s7wkoATELflkQuUaLmHP3xkiCdxlWLXqZWform67WS4lKwpHHE/LLdo9J8zT/jGezyHLAAekcds9snmmkXBuAHAMwEaParSmutPAARsjCD9VAjHU0znJ6emZmexDROFTV3ev35ibftNuzkX/c+urV/fctwXCA2FYtcVGhE2gWonAX1EeQ+mpKkOYA/BncV2H5BFO9d37VwT8qpUJns8Ad7pFRynBceTJ2/KM1D/dI/UHiOVEaKOwIM6tlpbZZCCt+73Kzq+67FhiXc943BEys88Xu+07iRo1LtZKTcTmc7bIiaX0KcN/CoBrwAH68A/kedBq/7cjfhCbIIPwAADwABWYnRRnnSAcACeD2hrmwFgPEEwFgRnBK9JfL1lLLXVgI+AqLNl+DzUdIsvbDJmZTIwlMcIZiqf5hCBi5fhl1jMKLVyiO1Gctrobk7BRarCIxNcA2vIjzBd8MuKYz6HWhSGSNSMo/t1PIHOgGE4dds0ffep0AFWIFvPDRAE4BqAF54XQcv+x7mCc8E/AAAHCSUQDTRKu2R0gGAADwMpQ6lAwBpACiqWrRt/czCYyJDOHSRGpgAHugA/kz1owddt7L8sOAoFMzU2fWgMuoy1I3i2EQveY6ME2guje1As9S7nN/ue0qCJsrxHGrKPYK32LL/wgDb6rGMNkYCpsk4CIh7ka9KkTRgvlwAAKAC3jddBi77CtPgZHD8AwAwASAQFZ2JwngAAOChPPCfBQCWCQBznfwrdOyAiBTMevXrxBKC/exix/ozg1R0J1KBR4W8m9fZZrz1VYt64Idocv8OM1VJOvrm0KyLYnh49sUMUyfARQjol927yI7xFVMrGHPlre3HQdm/FwoqRjq8UX50sXePj0aCST86iutzJRQxLITURQUGBKAaAB7nnAfW7RzVAOAHAKADmKxYZbTWHgVwAVj7hnrVCgCLDQAz6l1vj+vSJhPo6dGuVaKBBPpUlM63pldxjfCtLUAiWlqIfIz+mnhcgrP2jqiZSYLCEQ/nq/XR5KQueWrcGa4JF5Uv7w7FvZ0Kd6Apfpa+OnEVAqUtTLGddvxdvnlJxctx6dJG9Zw/SQ+a/v7q1/8LiLYRENZ9FTpbloqAKTiNSxArAIWmnSp72zg0+YV0zQF16AB+V/wMfG+gUclollh/AAAO4FXFWHLU2jAAAL0Xvw4AeCYA3mym1q5BWbiS/qPzdADMP/cWnPLdnpM9SA9P5kENeGyWaWKGtpV7BbsUQ3yZJFMZoxHYpYKTL0yX2imUhFYvoGVLD8PvF8CIFgN4YXnP4dLUvdxZuudJqqeAbnyrIM2RpFWLqFxGLJfLR6ArN/wlKlYBfCNeMrStpv6epgeNkH6wQuJbafjz+4oERn89aQ10T3lUjYalIuzHqliM88iZh9IIMBsAPja8Dhr4eAHgvUgXwPUOD2irslOttdbaAizJ/ichpBWB/g4HhBQAcCsKwMDvB8MBqMnuos11bMzCNbC9OfAKoY4899pz4RxOHe4xWcIl/PD2odxhNyObQlLglEitYRTlbeoslZx2L2/KFWlbejRU4yS0NB5EEo5cm26PEtPPRHfFXTJ5f0r5fDju8FRInrbI3Ll59XWxGYtaRt9VdmBNzX4r6QqCmP9Cs9Id5ZcbocxCUNLloouM8Io9yb2xYIAtaAYa0AHeNnwQDvHwORrAe6dmAf9aAMYmVlXUUXOOouetzz4afGcyxbRbz3PBJ3MeQdFpkk6W05tt1wgxFWCddB7+XEKz3ew4KcVUTZSMeMciUw8Zn5ibuJ78Mbadw4SEefajAUeW1VyZcLNm0cvBM5Z+X74caneqXq9pXYm3iMSJuIPGtHWfun9ts6REfLb/Ur/FYPSO2+6Lu5KPeFhKt7zp7nDEI/G/eQPtdxORRRTi88UHA+WXTuoa0oyrvFM/7hg+QRa3d7vdJsZnmWfBHgAggQigIADeNdzaGl4+DIC9T0EDXGcnPJqqxItaG0ohXNBSvgiAg2TVCoFfBQCw0a4ibd95c+FUsesgKPDBus+WOST0wUWkDpnnlNLIhDB+RHGv4SBjZvzGMppPGFVfau6FAiP1kyGgQGoLg7/y6gU8Web12IhbPvzuMMaNMFq4mBPU3KagB4kFar76yIWmqTGOinRAHiVhSyc4psy1CQtEc8DKXU38j31iYyButXydJtPnwzqEaFqIvleAj4/Bg1Y0WZQ0jXbsFC5+RzRsAD42vPBOsLH4HAQQ3zfTAP3leEBVVSwvaqJm4EWDRRoSm1jImR8dTOUDZZXwebMvoAfq66vDxbH7wnGgPy0O1BCfAl0ivOv/wtnaaSq9ZVJv8zJG1mhlxWVaxyJc/UMjT+kcTp7WumgFqHzNazze/pV6P6/hbihRJaZuUESX+klphduFMNKppuitYQvZHCbiH+nIWvU542nxo3Zokb6orpOqi5FywZZlr2xVJ5OtXOxX7ljol1udO5k3PVZNh6JoWK/V6QjKCnJTOAdMAE9nZ1MABABsAAAAAAAATDC+AQMAAACTiNSjA8awXb41vBonIEAoPzMBwdwluE21iV/RL9kI4zEAcNDM4tO8/GAMTsBfa73NP2XPdYpf0yuqZgKLTciDfPNV0TJ9yC/HUHplqamEfn8v7KDWiqK8nO6ZAvMBXlEVWO3p2MQE8DSHsBxkRUFKuB/acj8ngy0XFVIECJSCv4Ut8SahqHunhi0utcVF5Rqiin8Xxe+eR5mKHxdZMJ2TB54HRjukTQGWoWaf1/bj2U0A1sUzALixpY+9Dz+XmLk7BsyaNeBl7B7ffWlQAN41/Nq6b4GAAUM7mpD2/bKsHVMAAK2UU88yisSF0r/buFPSLtsfdGRuWNGtjPX3KNIUBVujMiaHysn8dqO/1IhSoVNgEQDbGtrqyjRfz1jt/o9yr2BOBf5w+DXgRCfEhnNXTEe5bJ/uLdxuLB9D737M+ZQDr26GpOg+c4HdiAyZlnqvue9Ym7XBFlapWO3nzIB+D24hNWkyX7tkFkLso76nVXkjhpSuBth8lJAT2IAG3mX87yxfSgEbUKwNs2BmAgoAADRMTR0H1wfHMRRUr4uTDS3c2mRKfmkrn+Jb878I8iDftwZMOgkkvjaBUzLCBLeVBIQRJvyiQQYYcazrA74GYANsAKo6w7k+wK0G\",\n};\n","import { useEffect } from \"react\";\nimport { useChessGameContext } from \"./useChessGameContext\";\nimport { type Sound } from \"../assets/sounds\";\n\nconst playSound = async (audioElement: HTMLAudioElement) => {\n try {\n await audioElement.play();\n } catch (error) {\n console.warn(\"Failed to play sound:\", (error as Error).message);\n }\n};\n\nexport const useBoardSounds = (sounds: Record<Sound, HTMLAudioElement>) => {\n const {\n info: { lastMove, isCheckmate },\n } = useChessGameContext();\n\n useEffect(() => {\n if (Object.keys(sounds).length === 0) {\n return;\n }\n\n if (isCheckmate && sounds.gameOver) {\n playSound(sounds.gameOver);\n return;\n }\n\n if (lastMove?.captured && sounds.capture) {\n playSound(sounds.capture);\n return;\n }\n\n if (lastMove && sounds.move) {\n playSound(sounds.move);\n }\n }, [lastMove]);\n};\n","import { useEffect } from \"react\";\nimport {\n defaultKeyboardControls,\n KeyboardControls,\n} from \"../components/ChessGame/parts/KeyboardControls\";\nimport { useChessGameContext } from \"./useChessGameContext\";\n\nexport const useKeyboardControls = (controls?: KeyboardControls) => {\n const gameContext = useChessGameContext();\n if (!gameContext) {\n throw new Error(\"ChessGameContext not found\");\n }\n const keyboardControls = { ...defaultKeyboardControls, ...controls };\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n const handler = keyboardControls[event.key];\n if (handler) {\n event.preventDefault();\n handler(gameContext);\n }\n };\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => {\n window.removeEventListener(\"keydown\", handleKeyDown);\n };\n }, [gameContext]);\n return null;\n};\n","import {\n ChessGameContextType,\n useChessGameContext,\n} from \"../../../hooks/useChessGameContext\";\nimport { useKeyboardControls } from \"../../../hooks/useKeyboardControls\";\n\nexport type KeyboardControls = Record<\n string,\n (context: ChessGameContextType) => void\n>;\n\nexport const defaultKeyboardControls: KeyboardControls = {\n ArrowLeft: (context) => context.methods.goToPreviousMove(),\n ArrowRight: (context) => context.methods.goToNextMove(),\n ArrowUp: (context) => context.methods.goToStart(),\n ArrowDown: (context) => context.methods.goToEnd(),\n};\n\ntype KeyboardControlsProps = {\n controls?: KeyboardControls;\n};\n\nexport const KeyboardControls: React.FC<KeyboardControlsProps> = ({\n controls,\n}) => {\n const gameContext = useChessGameContext();\n if (!gameContext) {\n throw new Error(\"ChessGameContext not found\");\n }\n const keyboardControls = { ...defaultKeyboardControls, ...controls };\n useKeyboardControls(keyboardControls);\n return null;\n};\n","import { Root } from \"./parts/Root\";\nimport { Board } from \"./parts/Board\";\nimport { Sounds } from \"./parts/Sounds\";\nimport { KeyboardControls } from \"./parts/KeyboardControls\";\n\nexport const ChessGame = {\n Root,\n Board,\n Sounds,\n KeyboardControls,\n};\n"],"mappings":"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","names":["React","Chess","Chess","fen","orientation","React","React","React","React","_a","useEffect","useEffect","useEffect","useEffect","KeyboardControls","KeyboardControls"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-chess-tools/react-chess-game",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "react-chess-game is a React component bridging chess.js with react-chessboard to offer a full-featured, ready-to-integrate chess board experience.",
|
|
5
5
|
"main": "dist/index.mjs",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -6,7 +6,10 @@ import {
|
|
|
6
6
|
chessColumnToColumnIndex,
|
|
7
7
|
} from "react-chessboard";
|
|
8
8
|
import { Move, Square } from "chess.js";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
getCustomSquareStyles,
|
|
11
|
+
deepMergeChessboardOptions,
|
|
12
|
+
} from "../../../utils/board";
|
|
10
13
|
import { isLegalMove, requiresPromotion } from "../../../utils/chess";
|
|
11
14
|
import { useChessGameContext } from "../../../hooks/useChessGameContext";
|
|
12
15
|
|
|
@@ -117,56 +120,54 @@ export const Board: React.FC<ChessGameProps> = ({ options = {} }) => {
|
|
|
117
120
|
);
|
|
118
121
|
}, [promotionMove, squareWidth, orientation]);
|
|
119
122
|
|
|
123
|
+
const baseOptions: ChessboardOptions = {
|
|
124
|
+
squareStyles: getCustomSquareStyles(game, info, activeSquare),
|
|
125
|
+
boardOrientation: orientation === "b" ? "black" : "white",
|
|
126
|
+
position: currentFen,
|
|
127
|
+
showNotation: true,
|
|
128
|
+
showAnimations: isLatestMove,
|
|
129
|
+
canDragPiece: ({ piece }) => {
|
|
130
|
+
if (isGameOver) return false;
|
|
131
|
+
return piece.pieceType[0] === turn;
|
|
132
|
+
},
|
|
133
|
+
dropSquareStyle: {
|
|
134
|
+
backgroundColor: "rgba(255, 255, 0, 0.4)",
|
|
135
|
+
},
|
|
136
|
+
onPieceDrag: ({ piece, square }) => {
|
|
137
|
+
if (piece.pieceType[0] === turn) {
|
|
138
|
+
setActiveSquare(square as Square);
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
onPieceDrop: ({ sourceSquare, targetSquare }) => {
|
|
142
|
+
setActiveSquare(null);
|
|
143
|
+
const moveData = {
|
|
144
|
+
from: sourceSquare as Square,
|
|
145
|
+
to: targetSquare as Square,
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Check if promotion is needed
|
|
149
|
+
if (requiresPromotion(game, { ...moveData, promotion: "q" })) {
|
|
150
|
+
setPromotionMove(moveData);
|
|
151
|
+
return false; // Prevent the move until promotion is selected
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return makeMove(moveData);
|
|
155
|
+
},
|
|
156
|
+
onSquareClick: ({ square }) => {
|
|
157
|
+
if (square.match(/^[a-h][1-8]$/)) {
|
|
158
|
+
onSquareClick(square as Square);
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
onSquareRightClick: onSquareRightClick,
|
|
162
|
+
allowDrawingArrows: true,
|
|
163
|
+
animationDurationInMs: game.history().length === 0 ? 0 : 300,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const mergedOptions = deepMergeChessboardOptions(baseOptions, options);
|
|
167
|
+
|
|
120
168
|
return (
|
|
121
169
|
<div style={{ position: "relative" }}>
|
|
122
|
-
<Chessboard
|
|
123
|
-
options={{
|
|
124
|
-
squareStyles: {
|
|
125
|
-
...getCustomSquareStyles(game, info, activeSquare),
|
|
126
|
-
...options.squareStyles,
|
|
127
|
-
},
|
|
128
|
-
boardOrientation: orientation === "b" ? "black" : "white",
|
|
129
|
-
position: currentFen,
|
|
130
|
-
showNotation: true,
|
|
131
|
-
showAnimations: isLatestMove,
|
|
132
|
-
canDragPiece: ({ piece }) => {
|
|
133
|
-
if (isGameOver) return false;
|
|
134
|
-
return piece.pieceType[0] === turn;
|
|
135
|
-
},
|
|
136
|
-
dropSquareStyle: {
|
|
137
|
-
backgroundColor: "rgba(255, 255, 0, 0.4)",
|
|
138
|
-
},
|
|
139
|
-
onPieceDrag: ({ piece, square }) => {
|
|
140
|
-
if (piece.pieceType[0] === turn) {
|
|
141
|
-
setActiveSquare(square as Square);
|
|
142
|
-
}
|
|
143
|
-
},
|
|
144
|
-
onPieceDrop: ({ sourceSquare, targetSquare }) => {
|
|
145
|
-
setActiveSquare(null);
|
|
146
|
-
const moveData = {
|
|
147
|
-
from: sourceSquare as Square,
|
|
148
|
-
to: targetSquare as Square,
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
// Check if promotion is needed
|
|
152
|
-
if (requiresPromotion(game, { ...moveData, promotion: "q" })) {
|
|
153
|
-
setPromotionMove(moveData);
|
|
154
|
-
return false; // Prevent the move until promotion is selected
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return makeMove(moveData);
|
|
158
|
-
},
|
|
159
|
-
onSquareClick: ({ square }) => {
|
|
160
|
-
if (square.match(/^[a-h][1-8]$/)) {
|
|
161
|
-
onSquareClick(square as Square);
|
|
162
|
-
}
|
|
163
|
-
},
|
|
164
|
-
onSquareRightClick: onSquareRightClick,
|
|
165
|
-
allowDrawingArrows: true,
|
|
166
|
-
animationDurationInMs: game.history().length === 0 ? 0 : 300,
|
|
167
|
-
...options,
|
|
168
|
-
}}
|
|
169
|
-
/>
|
|
170
|
+
<Chessboard options={mergedOptions} />
|
|
170
171
|
{promotionMove && (
|
|
171
172
|
<>
|
|
172
173
|
{/* Backdrop overlay - click to cancel */}
|
package/src/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ export type { KeyboardControls } from "./components/ChessGame/parts/KeyboardCont
|
|
|
16
16
|
|
|
17
17
|
// Utility Types
|
|
18
18
|
export type { GameInfo } from "./utils/chess";
|
|
19
|
+
export { deepMergeChessboardOptions } from "./utils/board";
|
|
19
20
|
|
|
20
21
|
// Component Props
|
|
21
22
|
export type { ChessGameProps } from "./components/ChessGame/parts/Board";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Chess } from "chess.js";
|
|
2
|
-
import { getCustomSquareStyles } from "../board";
|
|
2
|
+
import { getCustomSquareStyles, deepMergeChessboardOptions } from "../board";
|
|
3
3
|
import { getGameInfo } from "../chess";
|
|
4
4
|
|
|
5
5
|
describe("Board Utilities", () => {
|
|
@@ -140,4 +140,259 @@ describe("Board Utilities", () => {
|
|
|
140
140
|
expect(styles.c3.background).toContain("25%");
|
|
141
141
|
});
|
|
142
142
|
});
|
|
143
|
+
|
|
144
|
+
describe("deepMergeChessboardOptions", () => {
|
|
145
|
+
it("should deeply merge nested object options without overriding computed values", () => {
|
|
146
|
+
const baseOptions = {
|
|
147
|
+
squareStyles: {
|
|
148
|
+
e4: {
|
|
149
|
+
backgroundColor: "rgba(255, 255, 0, 0.5)", // Computed move highlighting
|
|
150
|
+
background:
|
|
151
|
+
"radial-gradient(circle, rgba(0,0,0,.1) 25%, transparent 25%)", // Move dot
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
dropSquareStyle: {
|
|
155
|
+
backgroundColor: "rgba(255, 255, 0, 0.4)",
|
|
156
|
+
border: "2px dashed yellow",
|
|
157
|
+
},
|
|
158
|
+
showNotation: true,
|
|
159
|
+
allowDrawingArrows: true,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const customOptions = {
|
|
163
|
+
squareStyles: {
|
|
164
|
+
e4: {
|
|
165
|
+
border: "2px solid red", // Should be added without removing background
|
|
166
|
+
},
|
|
167
|
+
a1: {
|
|
168
|
+
backgroundColor: "blue", // New square style
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
dropSquareStyle: {
|
|
172
|
+
backgroundColor: "rgba(0, 255, 0, 0.6)", // Should override backgroundColor but preserve border
|
|
173
|
+
},
|
|
174
|
+
showNotation: false, // Should override primitive
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const result = deepMergeChessboardOptions(baseOptions, customOptions);
|
|
178
|
+
|
|
179
|
+
// squareStyles should deep merge
|
|
180
|
+
expect(result.squareStyles?.e4).toEqual({
|
|
181
|
+
backgroundColor: "rgba(255, 255, 0, 0.5)",
|
|
182
|
+
background:
|
|
183
|
+
"radial-gradient(circle, rgba(0,0,0,.1) 25%, transparent 25%)",
|
|
184
|
+
border: "2px solid red",
|
|
185
|
+
});
|
|
186
|
+
expect(result.squareStyles?.a1).toEqual({
|
|
187
|
+
backgroundColor: "blue",
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// dropSquareStyle should deep merge
|
|
191
|
+
expect(result.dropSquareStyle).toEqual({
|
|
192
|
+
backgroundColor: "rgba(0, 255, 0, 0.6)",
|
|
193
|
+
border: "2px dashed yellow",
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Primitives should override
|
|
197
|
+
expect(result.showNotation).toBe(false);
|
|
198
|
+
expect(result.allowDrawingArrows).toBe(true);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("should handle function properties by overwriting them", () => {
|
|
202
|
+
const baseOnSquareClick = jest.fn();
|
|
203
|
+
const baseCanDragPiece = jest.fn();
|
|
204
|
+
const customOnSquareClick = jest.fn();
|
|
205
|
+
|
|
206
|
+
const baseOptions = {
|
|
207
|
+
onSquareClick: baseOnSquareClick,
|
|
208
|
+
canDragPiece: baseCanDragPiece,
|
|
209
|
+
showNotation: true,
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const customOptions = {
|
|
213
|
+
onSquareClick: customOnSquareClick,
|
|
214
|
+
// canDragPiece not provided - should keep base function
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const result = deepMergeChessboardOptions(baseOptions, customOptions);
|
|
218
|
+
|
|
219
|
+
// Custom function should replace base function
|
|
220
|
+
expect(result.onSquareClick).toBe(customOnSquareClick);
|
|
221
|
+
expect(result.onSquareClick).not.toBe(baseOnSquareClick);
|
|
222
|
+
|
|
223
|
+
// Base function should be preserved when not overridden
|
|
224
|
+
expect(result.canDragPiece).toBe(baseCanDragPiece);
|
|
225
|
+
|
|
226
|
+
// Other properties should merge normally
|
|
227
|
+
expect(result.showNotation).toBe(true);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("should handle nested object properties by deep merging them", () => {
|
|
231
|
+
const baseOptions = {
|
|
232
|
+
darkSquareStyle: {
|
|
233
|
+
backgroundColor: "#8B4513",
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const customOptions = {
|
|
238
|
+
darkSquareStyle: {
|
|
239
|
+
backgroundColor: "#654321",
|
|
240
|
+
border: "1px solid black",
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const result = deepMergeChessboardOptions(baseOptions, customOptions);
|
|
245
|
+
|
|
246
|
+
expect(result.darkSquareStyle).toEqual({
|
|
247
|
+
backgroundColor: "#654321",
|
|
248
|
+
border: "1px solid black",
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("should handle undefined custom options gracefully", () => {
|
|
253
|
+
const baseOptions = {
|
|
254
|
+
squareStyles: {
|
|
255
|
+
e4: { backgroundColor: "yellow" },
|
|
256
|
+
},
|
|
257
|
+
showNotation: true,
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const result = deepMergeChessboardOptions(baseOptions, undefined);
|
|
261
|
+
|
|
262
|
+
expect(result).toEqual(baseOptions);
|
|
263
|
+
expect(result).not.toBe(baseOptions); // Should be a new object
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("should handle empty custom options", () => {
|
|
267
|
+
const baseOptions = {
|
|
268
|
+
squareStyles: {
|
|
269
|
+
e4: { backgroundColor: "yellow" },
|
|
270
|
+
},
|
|
271
|
+
showNotation: true,
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const result = deepMergeChessboardOptions(baseOptions, {});
|
|
275
|
+
|
|
276
|
+
expect(result).toEqual(baseOptions);
|
|
277
|
+
expect(result).not.toBe(baseOptions); // Should be a new object
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("should preserve complex nested object structures", () => {
|
|
281
|
+
const baseOptions = {
|
|
282
|
+
squareStyles: {
|
|
283
|
+
e4: {
|
|
284
|
+
backgroundColor: "rgba(255, 255, 0, 0.5)",
|
|
285
|
+
background:
|
|
286
|
+
"radial-gradient(circle, rgba(0,0,0,.1) 25%, transparent 25%)",
|
|
287
|
+
border: "1px solid black",
|
|
288
|
+
},
|
|
289
|
+
d5: {
|
|
290
|
+
backgroundColor: "rgba(0, 255, 0, 0.3)",
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const customOptions = {
|
|
296
|
+
squareStyles: {
|
|
297
|
+
e4: {
|
|
298
|
+
borderRadius: "4px", // Add new property
|
|
299
|
+
backgroundColor: "rgba(255, 0, 0, 0.5)", // Override existing
|
|
300
|
+
// background should be preserved from base
|
|
301
|
+
},
|
|
302
|
+
f6: {
|
|
303
|
+
backgroundColor: "blue", // New square
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const result = deepMergeChessboardOptions(baseOptions, customOptions);
|
|
309
|
+
|
|
310
|
+
expect(result.squareStyles?.e4).toEqual({
|
|
311
|
+
backgroundColor: "rgba(255, 0, 0, 0.5)", // Overridden
|
|
312
|
+
background:
|
|
313
|
+
"radial-gradient(circle, rgba(0,0,0,.1) 25%, transparent 25%)", // Preserved
|
|
314
|
+
border: "1px solid black", // Preserved
|
|
315
|
+
borderRadius: "4px", // Added
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
expect(result.squareStyles?.d5).toEqual({
|
|
319
|
+
backgroundColor: "rgba(0, 255, 0, 0.3)", // Preserved from base
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
expect(result.squareStyles?.f6).toEqual({
|
|
323
|
+
backgroundColor: "blue", // Added from custom
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it("should handle real-world Board component use case", () => {
|
|
328
|
+
// Simulate the actual use case in Board component
|
|
329
|
+
const computedSquareStyles = {
|
|
330
|
+
e2: {
|
|
331
|
+
backgroundColor: "rgba(255, 255, 0, 0.5)", // Active square highlight
|
|
332
|
+
},
|
|
333
|
+
e3: {
|
|
334
|
+
background:
|
|
335
|
+
"radial-gradient(circle, rgba(0,0,0,.1) 25%, transparent 25%)", // Move dot
|
|
336
|
+
},
|
|
337
|
+
e4: {
|
|
338
|
+
background:
|
|
339
|
+
"radial-gradient(circle, rgba(0,0,0,.1) 25%, transparent 25%)", // Move dot
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const baseOptions = {
|
|
344
|
+
squareStyles: computedSquareStyles,
|
|
345
|
+
boardOrientation: "white" as const,
|
|
346
|
+
position: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
|
|
347
|
+
showNotation: true,
|
|
348
|
+
onSquareClick: jest.fn(),
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const userCustomOptions = {
|
|
352
|
+
squareStyles: {
|
|
353
|
+
e4: {
|
|
354
|
+
border: "2px solid red", // User wants to add border to a square that has move dots
|
|
355
|
+
},
|
|
356
|
+
a1: {
|
|
357
|
+
backgroundColor: "lightblue", // User wants to highlight corner square
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
showNotation: false, // User wants to hide notation
|
|
361
|
+
onSquareClick: jest.fn(), // User provides custom click handler
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const result = deepMergeChessboardOptions(baseOptions, userCustomOptions);
|
|
365
|
+
|
|
366
|
+
// Move highlighting should be preserved while adding user's border
|
|
367
|
+
expect(result.squareStyles?.e4).toEqual({
|
|
368
|
+
background:
|
|
369
|
+
"radial-gradient(circle, rgba(0,0,0,.1) 25%, transparent 25%)",
|
|
370
|
+
border: "2px solid red",
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// User's new square style should be added
|
|
374
|
+
expect(result.squareStyles?.a1).toEqual({
|
|
375
|
+
backgroundColor: "lightblue",
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// Other computed styles should be preserved
|
|
379
|
+
expect(result.squareStyles?.e2).toEqual({
|
|
380
|
+
backgroundColor: "rgba(255, 255, 0, 0.5)",
|
|
381
|
+
});
|
|
382
|
+
expect(result.squareStyles?.e3).toEqual({
|
|
383
|
+
background:
|
|
384
|
+
"radial-gradient(circle, rgba(0,0,0,.1) 25%, transparent 25%)",
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Primitives should be overridden
|
|
388
|
+
expect(result.showNotation).toBe(false);
|
|
389
|
+
expect(result.position).toBe(
|
|
390
|
+
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
// Functions should be replaced
|
|
394
|
+
expect(result.onSquareClick).toBe(userCustomOptions.onSquareClick);
|
|
395
|
+
expect(result.onSquareClick).not.toBe(baseOptions.onSquareClick);
|
|
396
|
+
});
|
|
397
|
+
});
|
|
143
398
|
});
|
package/src/utils/board.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type Chess, type Square } from "chess.js";
|
|
2
2
|
import { type CSSProperties } from "react";
|
|
3
|
+
import { merge } from "lodash";
|
|
4
|
+
import type { ChessboardOptions } from "react-chessboard";
|
|
3
5
|
import { getDestinationSquares, type GameInfo } from "./chess";
|
|
4
6
|
|
|
5
7
|
const LAST_MOVE_COLOR = "rgba(255, 255, 0, 0.5)";
|
|
@@ -54,3 +56,50 @@ export const getCustomSquareStyles = (
|
|
|
54
56
|
}
|
|
55
57
|
return customSquareStyles;
|
|
56
58
|
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Smart deep merge for ChessboardOptions that handles different property types appropriately:
|
|
62
|
+
* - Functions: Overwrite (custom functions replace base functions)
|
|
63
|
+
* - Objects: Deep merge (nested objects merge recursively)
|
|
64
|
+
* - Primitives: Overwrite (custom values replace base values)
|
|
65
|
+
*
|
|
66
|
+
* This ensures that computed options (like squareStyles with move highlighting) are preserved
|
|
67
|
+
* while allowing custom options to extend or override them intelligently.
|
|
68
|
+
*
|
|
69
|
+
* @param baseOptions - The computed base options (e.g., computed squareStyles, event handlers)
|
|
70
|
+
* @param customOptions - Custom options provided by the user
|
|
71
|
+
* @returns Intelligently merged ChessboardOptions
|
|
72
|
+
*/
|
|
73
|
+
export const deepMergeChessboardOptions = (
|
|
74
|
+
baseOptions: ChessboardOptions,
|
|
75
|
+
customOptions?: Partial<ChessboardOptions>,
|
|
76
|
+
): ChessboardOptions => {
|
|
77
|
+
if (!customOptions) {
|
|
78
|
+
return { ...baseOptions }; // Return a new object even when no custom options
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const result = merge({}, baseOptions, customOptions, {
|
|
82
|
+
customizer: (_objValue: unknown, srcValue: unknown) => {
|
|
83
|
+
// Functions should always overwrite (not merge)
|
|
84
|
+
// This is important for event handlers like onSquareClick, onPieceDrop, etc.
|
|
85
|
+
if (typeof srcValue === "function") {
|
|
86
|
+
return srcValue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// For arrays, we typically want to overwrite rather than merge
|
|
90
|
+
// This avoids unexpected behavior with array concatenation
|
|
91
|
+
if (Array.isArray(srcValue)) {
|
|
92
|
+
return srcValue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Let lodash handle objects with default deep merge behavior
|
|
96
|
+
// This will properly merge nested objects like squareStyles, dropSquareStyle, etc.
|
|
97
|
+
return undefined; // Use default merge behavior
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Clean up any unwanted properties that lodash might add
|
|
102
|
+
delete (result as Record<string, unknown>).customizer;
|
|
103
|
+
|
|
104
|
+
return result;
|
|
105
|
+
};
|