@react-chess-tools/react-chess-game 1.0.0 → 1.0.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 +14 -0
- package/README.md +569 -0
- package/dist/index.cjs +265 -184
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -4
- package/dist/index.d.ts +48 -4
- package/dist/index.js +271 -185
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/src/components/ChessGame/ChessGame.stories.helpers.tsx +181 -0
- package/src/components/ChessGame/ChessGame.stories.tsx +574 -35
- package/src/components/ChessGame/Clock/index.tsx +174 -0
- package/src/components/ChessGame/Theme.stories.tsx +2 -2
- package/src/components/ChessGame/index.ts +2 -0
- package/src/components/ChessGame/parts/Board.tsx +214 -205
- package/src/components/ChessGame/parts/KeyboardControls.tsx +9 -0
- package/src/components/ChessGame/parts/Root.tsx +15 -1
- package/src/components/ChessGame/parts/Sounds.tsx +9 -0
- package/src/components/ChessGame/parts/__tests__/Board.test.tsx +122 -0
- package/src/components/ChessGame/parts/__tests__/KeyboardControls.test.tsx +34 -0
- package/src/components/ChessGame/parts/__tests__/Root.test.tsx +50 -0
- package/src/components/ChessGame/parts/__tests__/Sounds.test.tsx +22 -0
- package/src/hooks/useChessGame.ts +50 -12
- package/src/index.ts +10 -0
- package/src/theme/__tests__/context.test.tsx +0 -15
- package/README.MD +0 -190
|
@@ -5,21 +5,33 @@ import { ChessGameContext } from "../../../hooks/useChessGameContext";
|
|
|
5
5
|
import { ThemeProvider } from "../../../theme/context";
|
|
6
6
|
import { mergeTheme } from "../../../theme/utils";
|
|
7
7
|
import type { PartialChessGameTheme } from "../../../theme/types";
|
|
8
|
+
import type { TimeControlConfig } from "@react-chess-tools/react-chess-clock";
|
|
8
9
|
|
|
9
10
|
export interface RootProps {
|
|
10
11
|
fen?: string;
|
|
11
12
|
orientation?: Color;
|
|
12
13
|
/** Optional theme configuration. Supports partial themes - only override the colors you need. */
|
|
13
14
|
theme?: PartialChessGameTheme;
|
|
15
|
+
/** Optional clock configuration to enable chess clock functionality */
|
|
16
|
+
timeControl?: TimeControlConfig;
|
|
17
|
+
/** Auto-switch clock on move (default: true) */
|
|
18
|
+
autoSwitchOnMove?: boolean;
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
export const Root: React.FC<React.PropsWithChildren<RootProps>> = ({
|
|
17
22
|
fen,
|
|
18
23
|
orientation,
|
|
19
24
|
theme,
|
|
25
|
+
timeControl,
|
|
26
|
+
autoSwitchOnMove,
|
|
20
27
|
children,
|
|
21
28
|
}) => {
|
|
22
|
-
const context = useChessGame({
|
|
29
|
+
const context = useChessGame({
|
|
30
|
+
fen,
|
|
31
|
+
orientation,
|
|
32
|
+
timeControl,
|
|
33
|
+
autoSwitchOnMove,
|
|
34
|
+
});
|
|
23
35
|
|
|
24
36
|
// Merge partial theme with defaults
|
|
25
37
|
const mergedTheme = React.useMemo(() => mergeTheme(theme), [theme]);
|
|
@@ -30,3 +42,5 @@ export const Root: React.FC<React.PropsWithChildren<RootProps>> = ({
|
|
|
30
42
|
</ChessGameContext.Provider>
|
|
31
43
|
);
|
|
32
44
|
};
|
|
45
|
+
|
|
46
|
+
Root.displayName = "ChessGame.Root";
|
|
@@ -2,6 +2,13 @@ import { useMemo } from "react";
|
|
|
2
2
|
import { defaultSounds, type Sound } from "../../../assets/sounds";
|
|
3
3
|
import { useBoardSounds } from "../../../hooks/useBoardSounds";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Props for the Sounds component
|
|
7
|
+
*
|
|
8
|
+
* Note: This is a logic-only component that returns null and does not render
|
|
9
|
+
* any DOM elements. It sets up board sounds via the useBoardSounds hook.
|
|
10
|
+
* Therefore, it does not accept HTML attributes like className, style, etc.
|
|
11
|
+
*/
|
|
5
12
|
export type SoundsProps = {
|
|
6
13
|
sounds?: Partial<Record<Sound, string>>;
|
|
7
14
|
};
|
|
@@ -23,3 +30,5 @@ export const Sounds: React.FC<SoundsProps> = ({ sounds }) => {
|
|
|
23
30
|
useBoardSounds(customSoundsAudios);
|
|
24
31
|
return null;
|
|
25
32
|
};
|
|
33
|
+
|
|
34
|
+
Sounds.displayName = "ChessGame.Sounds";
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render } from "@testing-library/react";
|
|
3
|
+
import "@testing-library/jest-dom";
|
|
4
|
+
import { ChessGame } from "../..";
|
|
5
|
+
import { Board } from "../Board";
|
|
6
|
+
|
|
7
|
+
describe("ChessGame.Board", () => {
|
|
8
|
+
it("should have correct displayName", () => {
|
|
9
|
+
expect(Board.displayName).toBe("ChessGame.Board");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should forward ref to div element", () => {
|
|
13
|
+
const ref = React.createRef<HTMLDivElement>();
|
|
14
|
+
|
|
15
|
+
render(
|
|
16
|
+
<ChessGame.Root>
|
|
17
|
+
<Board ref={ref} />
|
|
18
|
+
</ChessGame.Root>,
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
expect(ref.current).toBeInstanceOf(HTMLDivElement);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should apply custom className", () => {
|
|
25
|
+
const { container } = render(
|
|
26
|
+
<ChessGame.Root>
|
|
27
|
+
<Board className="custom-board-class" />
|
|
28
|
+
</ChessGame.Root>,
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const board = container.querySelector(".custom-board-class");
|
|
32
|
+
expect(board).toBeInTheDocument();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should merge multiple className props", () => {
|
|
36
|
+
const { container } = render(
|
|
37
|
+
<ChessGame.Root>
|
|
38
|
+
<Board className="class-1 class-2" />
|
|
39
|
+
</ChessGame.Root>,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const board = container.querySelector(".class-1");
|
|
43
|
+
expect(board).toHaveClass("class-1");
|
|
44
|
+
expect(board).toHaveClass("class-2");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should apply custom style", () => {
|
|
48
|
+
const customStyle = { border: "2px solid red", margin: "10px" };
|
|
49
|
+
|
|
50
|
+
const { container } = render(
|
|
51
|
+
<ChessGame.Root>
|
|
52
|
+
<Board style={customStyle} />
|
|
53
|
+
</ChessGame.Root>,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const board = container.firstElementChild as HTMLElement;
|
|
57
|
+
expect(board).toHaveStyle({ border: "2px solid red" });
|
|
58
|
+
expect(board).toHaveStyle({ margin: "10px" });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should apply custom id", () => {
|
|
62
|
+
const { container } = render(
|
|
63
|
+
<ChessGame.Root>
|
|
64
|
+
<Board id="custom-board-id" />
|
|
65
|
+
</ChessGame.Root>,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const board = container.querySelector("#custom-board-id");
|
|
69
|
+
expect(board).toBeInTheDocument();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should apply data-* attributes", () => {
|
|
73
|
+
const { container } = render(
|
|
74
|
+
<ChessGame.Root>
|
|
75
|
+
<Board data-testid="board" data-custom="value" />
|
|
76
|
+
</ChessGame.Root>,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const board = container.querySelector("[data-custom='value']");
|
|
80
|
+
expect(board).toHaveAttribute("data-testid", "board");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should apply aria-* attributes", () => {
|
|
84
|
+
const { container } = render(
|
|
85
|
+
<ChessGame.Root>
|
|
86
|
+
<Board aria-label="Chess board" aria-describedby="board-desc" />
|
|
87
|
+
</ChessGame.Root>,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const board = container.firstElementChild as HTMLElement;
|
|
91
|
+
expect(board).toHaveAttribute("aria-label", "Chess board");
|
|
92
|
+
expect(board).toHaveAttribute("aria-describedby", "board-desc");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("should accept custom onClick handler", () => {
|
|
96
|
+
const handleClick = jest.fn();
|
|
97
|
+
|
|
98
|
+
const { container } = render(
|
|
99
|
+
<ChessGame.Root>
|
|
100
|
+
<Board onClick={handleClick} />
|
|
101
|
+
</ChessGame.Root>,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const board = container.firstElementChild as HTMLElement;
|
|
105
|
+
board.click();
|
|
106
|
+
|
|
107
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should throw error when used outside ChessGame.Root", () => {
|
|
111
|
+
// Suppress console.error for this test
|
|
112
|
+
const consoleError = jest
|
|
113
|
+
.spyOn(console, "error")
|
|
114
|
+
.mockImplementation(() => {});
|
|
115
|
+
|
|
116
|
+
expect(() => {
|
|
117
|
+
render(<Board />);
|
|
118
|
+
}).toThrow("useChessGameContext must be used within a ChessGame component");
|
|
119
|
+
|
|
120
|
+
consoleError.mockRestore();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render } from "@testing-library/react";
|
|
3
|
+
import "@testing-library/jest-dom";
|
|
4
|
+
import { ChessGame } from "../..";
|
|
5
|
+
import { KeyboardControls } from "../KeyboardControls";
|
|
6
|
+
|
|
7
|
+
describe("ChessGame.KeyboardControls", () => {
|
|
8
|
+
it("should have correct displayName", () => {
|
|
9
|
+
expect(KeyboardControls.displayName).toBe("ChessGame.KeyboardControls");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should render null (no DOM element)", () => {
|
|
13
|
+
const { container } = render(
|
|
14
|
+
<ChessGame.Root>
|
|
15
|
+
<ChessGame.KeyboardControls />
|
|
16
|
+
</ChessGame.Root>,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
// KeyboardControls should not render any DOM elements
|
|
20
|
+
expect(container.querySelector("*")).toBeNull();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should throw error when used outside ChessGame.Root", () => {
|
|
24
|
+
const consoleError = jest
|
|
25
|
+
.spyOn(console, "error")
|
|
26
|
+
.mockImplementation(() => {});
|
|
27
|
+
|
|
28
|
+
expect(() => {
|
|
29
|
+
render(<ChessGame.KeyboardControls />);
|
|
30
|
+
}).toThrow();
|
|
31
|
+
|
|
32
|
+
consoleError.mockRestore();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import "@testing-library/jest-dom";
|
|
4
|
+
import { ChessGame } from "../..";
|
|
5
|
+
import { Root } from "../Root";
|
|
6
|
+
|
|
7
|
+
describe("ChessGame.Root", () => {
|
|
8
|
+
it("should have correct displayName", () => {
|
|
9
|
+
expect(Root.displayName).toBe("ChessGame.Root");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should render children correctly", () => {
|
|
13
|
+
render(
|
|
14
|
+
<ChessGame.Root>
|
|
15
|
+
<div data-testid="child">Child Component</div>
|
|
16
|
+
</ChessGame.Root>,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
expect(screen.getByTestId("child")).toBeInTheDocument();
|
|
20
|
+
expect(screen.getByText("Child Component")).toBeInTheDocument();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should render multiple children", () => {
|
|
24
|
+
render(
|
|
25
|
+
<ChessGame.Root>
|
|
26
|
+
<div data-testid="child-1">Child 1</div>
|
|
27
|
+
<div data-testid="child-2">Child 2</div>
|
|
28
|
+
<div data-testid="child-3">Child 3</div>
|
|
29
|
+
</ChessGame.Root>,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
expect(screen.getByTestId("child-1")).toBeInTheDocument();
|
|
33
|
+
expect(screen.getByTestId("child-2")).toBeInTheDocument();
|
|
34
|
+
expect(screen.getByTestId("child-3")).toBeInTheDocument();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should NOT render a DOM element wrapper", () => {
|
|
38
|
+
const { container } = render(
|
|
39
|
+
<ChessGame.Root>
|
|
40
|
+
<div data-testid="child">Child</div>
|
|
41
|
+
</ChessGame.Root>,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// The child should be in the container
|
|
45
|
+
const child = screen.getByTestId("child");
|
|
46
|
+
expect(child).toBeInTheDocument();
|
|
47
|
+
// Root itself doesn't add any DOM elements - it's just a fragment/context provider
|
|
48
|
+
expect(container.innerHTML).toContain('data-testid="child"');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render } from "@testing-library/react";
|
|
3
|
+
import "@testing-library/jest-dom";
|
|
4
|
+
import { ChessGame } from "../..";
|
|
5
|
+
import { Sounds } from "../Sounds";
|
|
6
|
+
|
|
7
|
+
describe("ChessGame.Sounds", () => {
|
|
8
|
+
it("should have correct displayName", () => {
|
|
9
|
+
expect(Sounds.displayName).toBe("ChessGame.Sounds");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should render null (no DOM element)", () => {
|
|
13
|
+
const { container } = render(
|
|
14
|
+
<ChessGame.Root>
|
|
15
|
+
<ChessGame.Sounds />
|
|
16
|
+
</ChessGame.Root>,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
// Sounds should not render any DOM elements
|
|
20
|
+
expect(container.querySelector("*")).toBeNull();
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -1,22 +1,31 @@
|
|
|
1
|
-
import React, { useEffect } from "react";
|
|
1
|
+
import React, { useEffect, useRef } from "react";
|
|
2
2
|
import { Chess, Color } from "chess.js";
|
|
3
3
|
import { cloneGame, getCurrentFen, getGameInfo } from "../utils/chess";
|
|
4
|
+
import { useOptionalChessClock } from "@react-chess-tools/react-chess-clock";
|
|
5
|
+
import type { TimeControlConfig } from "@react-chess-tools/react-chess-clock";
|
|
4
6
|
|
|
5
7
|
export type useChessGameProps = {
|
|
6
8
|
fen?: string;
|
|
7
9
|
orientation?: Color;
|
|
10
|
+
/** Optional clock configuration to enable chess clock functionality */
|
|
11
|
+
timeControl?: TimeControlConfig;
|
|
12
|
+
/** Automatically switch the clock after each move (default: true).
|
|
13
|
+
* Set to false to let players manually press the clock, mimicking real-life over-the-board play. */
|
|
14
|
+
autoSwitchOnMove?: boolean;
|
|
8
15
|
};
|
|
9
16
|
|
|
10
17
|
export const useChessGame = ({
|
|
11
18
|
fen,
|
|
12
19
|
orientation: initialOrientation,
|
|
20
|
+
timeControl,
|
|
21
|
+
autoSwitchOnMove = true,
|
|
13
22
|
}: useChessGameProps = {}) => {
|
|
14
23
|
const [game, setGame] = React.useState(() => {
|
|
15
24
|
try {
|
|
16
25
|
return new Chess(fen);
|
|
17
26
|
} catch (e) {
|
|
18
27
|
console.error("Invalid FEN:", fen, e);
|
|
19
|
-
return new Chess();
|
|
28
|
+
return new Chess();
|
|
20
29
|
}
|
|
21
30
|
});
|
|
22
31
|
|
|
@@ -35,10 +44,8 @@ export const useChessGame = ({
|
|
|
35
44
|
const [currentMoveIndex, setCurrentMoveIndex] = React.useState(-1);
|
|
36
45
|
|
|
37
46
|
const history = React.useMemo(() => game.history(), [game]);
|
|
38
|
-
const isLatestMove =
|
|
39
|
-
|
|
40
|
-
[currentMoveIndex, history.length],
|
|
41
|
-
);
|
|
47
|
+
const isLatestMove =
|
|
48
|
+
currentMoveIndex === history.length - 1 || currentMoveIndex === -1;
|
|
42
49
|
|
|
43
50
|
const info = React.useMemo(
|
|
44
51
|
() => getGameInfo(game, orientation),
|
|
@@ -47,13 +54,18 @@ export const useChessGame = ({
|
|
|
47
54
|
|
|
48
55
|
const currentFen = React.useMemo(
|
|
49
56
|
() => getCurrentFen(fen, game, currentMoveIndex),
|
|
50
|
-
[game, currentMoveIndex],
|
|
57
|
+
[fen, game, currentMoveIndex],
|
|
51
58
|
);
|
|
52
59
|
|
|
53
|
-
const currentPosition =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
const currentPosition = game.history()[currentMoveIndex];
|
|
61
|
+
|
|
62
|
+
const clockState = useOptionalChessClock(timeControl);
|
|
63
|
+
|
|
64
|
+
// Keep clockState in a ref to avoid re-creating makeMove on every clock tick.
|
|
65
|
+
// The clock state object is recreated on every render (especially during active
|
|
66
|
+
// ticking), which would defeat the useCallback memoization.
|
|
67
|
+
const clockStateRef = useRef(clockState);
|
|
68
|
+
clockStateRef.current = clockState;
|
|
57
69
|
|
|
58
70
|
const setPosition = React.useCallback((fen: string, orientation: Color) => {
|
|
59
71
|
try {
|
|
@@ -74,17 +86,42 @@ export const useChessGame = ({
|
|
|
74
86
|
return false;
|
|
75
87
|
}
|
|
76
88
|
|
|
89
|
+
// Access clock state via ref to avoid stale closures while keeping
|
|
90
|
+
// the callback stable (not re-created on every clock tick)
|
|
91
|
+
const clock = clockStateRef.current;
|
|
92
|
+
|
|
93
|
+
// Don't allow moves after clock timeout
|
|
94
|
+
if (clock && clock.timeout !== null) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
77
98
|
try {
|
|
78
99
|
const copy = cloneGame(game);
|
|
79
100
|
copy.move(move);
|
|
80
101
|
setGame(copy);
|
|
81
102
|
setCurrentMoveIndex(copy.history().length - 1);
|
|
103
|
+
|
|
104
|
+
// Auto-start clock on first move
|
|
105
|
+
if (clock && clock.status === "idle") {
|
|
106
|
+
clock.methods.start();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Pause clock on game over (checked immediately after move)
|
|
110
|
+
if (clock && clock.status === "running" && copy.isGameOver()) {
|
|
111
|
+
clock.methods.pause();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Auto-switch clock after a move is made if enabled
|
|
115
|
+
if (autoSwitchOnMove && clock && clock.status !== "finished") {
|
|
116
|
+
clock.methods.switch();
|
|
117
|
+
}
|
|
118
|
+
|
|
82
119
|
return true;
|
|
83
120
|
} catch (e) {
|
|
84
121
|
return false;
|
|
85
122
|
}
|
|
86
123
|
},
|
|
87
|
-
[isLatestMove, game],
|
|
124
|
+
[isLatestMove, game, autoSwitchOnMove],
|
|
88
125
|
);
|
|
89
126
|
|
|
90
127
|
const flipBoard = React.useCallback(() => {
|
|
@@ -145,5 +182,6 @@ export const useChessGame = ({
|
|
|
145
182
|
isLatestMove,
|
|
146
183
|
info,
|
|
147
184
|
methods,
|
|
185
|
+
clock: clockState,
|
|
148
186
|
};
|
|
149
187
|
};
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
// Components
|
|
2
2
|
export { ChessGame } from "./components/ChessGame";
|
|
3
3
|
|
|
4
|
+
// Clock - Re-export from react-chess-clock for convenience
|
|
5
|
+
export { ChessClock } from "@react-chess-tools/react-chess-clock";
|
|
6
|
+
export type {
|
|
7
|
+
TimeControlConfig,
|
|
8
|
+
TimeControlInput,
|
|
9
|
+
TimingMethod,
|
|
10
|
+
ClockStartMode,
|
|
11
|
+
UseChessClockReturn,
|
|
12
|
+
} from "@react-chess-tools/react-chess-clock";
|
|
13
|
+
|
|
4
14
|
// Hooks & Context
|
|
5
15
|
export { useChessGameContext } from "./hooks/useChessGameContext";
|
|
6
16
|
export { useChessGame } from "./hooks/useChessGame";
|
|
@@ -38,21 +38,6 @@ describe("useChessGameTheme", () => {
|
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
describe("ThemeProvider", () => {
|
|
41
|
-
it("should render children", () => {
|
|
42
|
-
const TestComponent = () => {
|
|
43
|
-
const theme = useChessGameTheme();
|
|
44
|
-
return <div data-testid="theme-check">{theme.state.lastMove}</div>;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const { result } = renderHook(() => useChessGameTheme(), {
|
|
48
|
-
wrapper: ({ children }) => (
|
|
49
|
-
<ThemeProvider theme={defaultGameTheme}>{children}</ThemeProvider>
|
|
50
|
-
),
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
expect(result.current).toBeTruthy();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
41
|
it("should allow nested providers with inner provider winning", () => {
|
|
57
42
|
const outerTheme = {
|
|
58
43
|
...defaultGameTheme,
|
package/README.MD
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
<p>
|
|
3
|
-
|
|
4
|
-
</p>
|
|
5
|
-
<p>
|
|
6
|
-
<a href="https://github.com/Clariity/react-chessboard" target="blank">react-chessboard</a> + <a href="https://github.com/jhlywa/chess.js" target="blank">chess.js</a> + nice defaults
|
|
7
|
-
</p>
|
|
8
|
-
<p>
|
|
9
|
-
An easy-customizable, ready-to-use chess game component for React.
|
|
10
|
-
</div>
|
|
11
|
-
|
|
12
|
-
## Project Description
|
|
13
|
-
|
|
14
|
-
This project is a React-based chess game that allows users to play chess online. It is part of the `react-chess-tools` package and is designed to be easy to use and customizable. It is built on top of the `react-chessboard` and `chess.js` packages, and provides a nice default configuration for the chess game, including:
|
|
15
|
-
|
|
16
|
-
- Sounds
|
|
17
|
-
- Move-by-click
|
|
18
|
-
- Square highlighting
|
|
19
|
-
- Keyboard controls
|
|
20
|
-
|
|
21
|
-
It is built using an approach similar to the one used in the `radix-ui`, where the `ChessGame` component is built using a `ChessGameContext` that you can use to customize and enhance the component game. It also provides a set of default components that you can use to build your next chess app.
|
|
22
|
-
|
|
23
|
-
## Preview
|
|
24
|
-
|
|
25
|
-
Visit the [demo](https://react-chess-tools.vercel.app/) to see the `react-chess-game` component in action.
|
|
26
|
-
|
|
27
|
-
## Installation
|
|
28
|
-
|
|
29
|
-
To install the `react-chess-game` package, run the following command:
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
$ npm install @react-chess-tools/react-chess-game
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Usage
|
|
36
|
-
|
|
37
|
-
To use the `react-chess-game` package, you can import the `ChessGame` component and use it as follows:
|
|
38
|
-
|
|
39
|
-
```tsx
|
|
40
|
-
import { ChessGame } from "@react-chess-tools/react-chess-game";
|
|
41
|
-
|
|
42
|
-
const App = () => (
|
|
43
|
-
<ChessGame.Root>
|
|
44
|
-
<ChessGame.Board />
|
|
45
|
-
</ChessGame.Root>
|
|
46
|
-
);
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## Documentation
|
|
50
|
-
|
|
51
|
-
The `react-chess-game` package provides a set of components that you can use to build your chess app. The following sections describe the components and their usage.
|
|
52
|
-
|
|
53
|
-
### ChessGame.Root
|
|
54
|
-
|
|
55
|
-
The `ChessGame.Root` component is the root component of the `react-chess-game` package. It is used to provide the `ChessGameContext` to the rest of the components. It also provides a set of default values for the `ChessGameContext` that you can customize. It instantiates a `Chess` instance using the `fen` prop and provides it to the `ChessGameContext`.
|
|
56
|
-
|
|
57
|
-
#### Props
|
|
58
|
-
|
|
59
|
-
The `ChessGame.Root` component accepts the following props:
|
|
60
|
-
|
|
61
|
-
| Name | Type | Default | Description |
|
|
62
|
-
| ----------- | ---------- | ------- | ----------------------------------------------- |
|
|
63
|
-
| children | React.FC | | The children of the `ChessGame.Root` component. |
|
|
64
|
-
| fen | string | | The initial FEN of the chess game. |
|
|
65
|
-
| orientation | "w" \| "b" | "w" | The orientation of the chess game. |
|
|
66
|
-
|
|
67
|
-
### ChessGame.Board
|
|
68
|
-
|
|
69
|
-
The `ChessGame.Board` component is the main component of the `react-chess-game` package. It is used to render the chess board and the pieces. It uses the `ChessGameContext` to get the `Chess` instance and the `orientation` of the game.
|
|
70
|
-
|
|
71
|
-
This version targets `react-chessboard` v5 and exposes a single `options` prop instead of spreading all board props.
|
|
72
|
-
|
|
73
|
-
#### Props
|
|
74
|
-
|
|
75
|
-
Accepts a single prop:
|
|
76
|
-
|
|
77
|
-
| Name | Type | Description |
|
|
78
|
-
| -------- | ------------------- | -------------------------------------------------------------------------------------------------- |
|
|
79
|
-
| options? | `ChessboardOptions` | Forwarded to `react-chessboard` v5 `Chessboard({ options })`. Your values merge with the defaults. |
|
|
80
|
-
|
|
81
|
-
Quick example (custom styles and event handlers):
|
|
82
|
-
|
|
83
|
-
```tsx
|
|
84
|
-
<ChessGame.Root>
|
|
85
|
-
<ChessGame.Board
|
|
86
|
-
options={{
|
|
87
|
-
squareStyles: { e4: { boxShadow: "inset 0 0 0 2px #4f46e5" } },
|
|
88
|
-
onPieceDrop: ({ sourceSquare, targetSquare }) => {
|
|
89
|
-
// return boolean to accept/reject drop
|
|
90
|
-
return true;
|
|
91
|
-
},
|
|
92
|
-
showNotation: true,
|
|
93
|
-
animationDurationInMs: 300,
|
|
94
|
-
}}
|
|
95
|
-
/>
|
|
96
|
-
|
|
97
|
-
{/* Other parts like <ChessGame.Sounds /> or <ChessGame.KeyboardControls /> */}
|
|
98
|
-
</ChessGame.Root>
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### ChessGame.Sounds
|
|
102
|
-
|
|
103
|
-
The `ChessGame.Sounds` component is used to provide the sounds for the chess game. By default, it uses built-in sounds, but you can provide your own sounds. Sounds must be provided as base 64 encoded strings.
|
|
104
|
-
|
|
105
|
-
#### Props
|
|
106
|
-
|
|
107
|
-
The `ChessGame.Sounds` component accepts the following props:
|
|
108
|
-
|
|
109
|
-
| Name | Type | Default | Description |
|
|
110
|
-
| -------- | ------ | ------- | ------------------------------------------------- |
|
|
111
|
-
| check | string | | The sound to play when a player is in check. |
|
|
112
|
-
| move | string | | The sound to play when a player makes a move. |
|
|
113
|
-
| capture | string | | The sound to play when a player captures a piece. |
|
|
114
|
-
| gameOver | string | | The sound to play when the game is over. |
|
|
115
|
-
|
|
116
|
-
### ChessGame.KeyboardControls
|
|
117
|
-
|
|
118
|
-
The `ChessGame.KeyboardControls` component is used to provide keyboard controls for navigating through the chess game. By default, it enables arrow key controls for moving through game history, but you can customize the key bindings.
|
|
119
|
-
|
|
120
|
-
#### Props
|
|
121
|
-
|
|
122
|
-
The `ChessGame.KeyboardControls` component accepts the following props:
|
|
123
|
-
|
|
124
|
-
| Name | Type | Default | Description |
|
|
125
|
-
| -------- | ---------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
126
|
-
| controls | KeyboardControls | defaultKeyboardControls | Object mapping key names to handler functions. The default controls are: ArrowLeft (previous move), ArrowRight (next move), ArrowUp (go to start), ArrowDown (go to end) |
|
|
127
|
-
|
|
128
|
-
### useChessGameContext
|
|
129
|
-
|
|
130
|
-
The `useChessGameContext` hook is used to get the `ChessGameContext` from the `ChessGame.Root` component. It can be used to customize the chess game.
|
|
131
|
-
|
|
132
|
-
#### Returns
|
|
133
|
-
|
|
134
|
-
The `useChessGameContext` hook returns the following values:
|
|
135
|
-
|
|
136
|
-
| Name | Type | Description |
|
|
137
|
-
| ---------------- | ---------- | -------------------------------------------------------------------------------------- |
|
|
138
|
-
| game | Chess | The underlying `Chess.js` instance. |
|
|
139
|
-
| orientation | "w" \| "b" | The orientation of the chess game. |
|
|
140
|
-
| currentFen | string | The current FEN string representing the board position. |
|
|
141
|
-
| currentPosition | string | The current move in the game history. |
|
|
142
|
-
| currentMoveIndex | number | The index of the current move in the game history. |
|
|
143
|
-
| isLatestMove | boolean | Whether the current position is the latest move in the game. |
|
|
144
|
-
| methods | Methods | The methods you can use to interact with the chess game. |
|
|
145
|
-
| info | Info | The info of the chess game, calculated using the chess instance using chess.js methods |
|
|
146
|
-
|
|
147
|
-
#### Methods
|
|
148
|
-
|
|
149
|
-
The `useChessGameContext` hook also exposes these methods:
|
|
150
|
-
|
|
151
|
-
| Name | Type | Description |
|
|
152
|
-
| ---------------- | ------------------------------- | --------------------------------------------------------------- | -------------------------------------------------------------------- | --- | ------------------ | ------------------------------------------------------------------ |
|
|
153
|
-
| makeMove | `(move: string | { from: Square; to: Square; promotion?: "q" | "r" | "b" | "n" }) => boolean` | Attempts a move at the latest position; returns `true` if applied. |
|
|
154
|
-
| setPosition | `(fen: string, orientation: "w" | "b") => void` | Sets a new FEN and orientation, resets history navigation to latest. |
|
|
155
|
-
| flipBoard | `() => void` | Flips the board orientation. |
|
|
156
|
-
| goToMove | `(moveIndex: number) => void` | Jumps to a specific move index (`-1` is the starting position). |
|
|
157
|
-
| goToStart | `() => void` | Goes to the starting position. |
|
|
158
|
-
| goToEnd | `() => void` | Goes to the latest move. |
|
|
159
|
-
| goToPreviousMove | `() => void` | Goes to the previous move. |
|
|
160
|
-
| goToNextMove | `() => void` | Goes to the next move. |
|
|
161
|
-
|
|
162
|
-
#### Info
|
|
163
|
-
|
|
164
|
-
The `useChessGameContext` hook returns the following info:
|
|
165
|
-
|
|
166
|
-
| Name | Type | Description |
|
|
167
|
-
| ---------------------- | ---------- | -------------------------------------------------------------------------- |
|
|
168
|
-
| turn | "w" \| "b" | The turn of the chess game |
|
|
169
|
-
| isPlayerTurn | boolean | Whether it is the player's turn |
|
|
170
|
-
| isOpponentTurn | boolean | Whether it is the opponent's turn |
|
|
171
|
-
| moveNumber | number | The number of the current move |
|
|
172
|
-
| lastMove | Move | The last move made in the chess game |
|
|
173
|
-
| isCheck | boolean | Whether the player is in check |
|
|
174
|
-
| isCheckmate | boolean | Whether the player is in checkmate |
|
|
175
|
-
| isDraw | boolean | Whether the game is a draw |
|
|
176
|
-
| isStalemate | boolean | Whether the game is a stalemate |
|
|
177
|
-
| isThreefoldRepetition | boolean | Whether the game is a threefold repetition |
|
|
178
|
-
| isInsufficientMaterial | boolean | Whether the game is a insufficient material |
|
|
179
|
-
| isGameOver | boolean | Whether the game is over |
|
|
180
|
-
| isDrawn | boolean | Whether the game is drawn |
|
|
181
|
-
| hasPlayerWon | boolean | Whether the player (the side specified in the `orientation` prop) has won |
|
|
182
|
-
| hasPlayerLost | boolean | Whether the player (the side specified in the `orientation` prop) has lost |
|
|
183
|
-
|
|
184
|
-
## 📝 License
|
|
185
|
-
|
|
186
|
-
This project is [MIT](https://opensource.org/licenses/MIT) licensed.
|
|
187
|
-
|
|
188
|
-
## Show your support
|
|
189
|
-
|
|
190
|
-
Give a ⭐️ if this project helped you!
|