Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,7 @@
text-align: center;
}

.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}

@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}

.card {
padding: 2em;
}

.read-the-docs {
color: #888;
}

#moves {
#legend {
margin-top: 1em;
height: 3em;
}
Original file line number Diff line number Diff line change
@@ -1,95 +1,14 @@
import { useEffect, useRef } from 'react';
import { create } from 'zustand';
import { Chess } from 'chess.js';
import { Chessboard, PieceRenderObject } from 'react-chessboard';
import {
createReplayVisualizer,
processEpisodeData,
LegacyAdapter,
LegacyRendererOptions,
ReplayData,
ChessStep,
ChessPlayer,
} from '@kaggle-environments/core';
import StyledChessboard from './components/StyledChessboard';
import Controls from './components/Controls';
import Legend from './components/Legend';
import './App.css';

interface ChessStore {
chess: Chess;
setState: (options: LegacyRendererOptions<ChessStep[]>) => void;
}

const useChessStore = create<ChessStore>((set) => ({
chess: new Chess(),

setState: (options: LegacyRendererOptions<ChessStep[]>) => {
const step = options.steps.at(options.step);
const player = step!.players.find((element: ChessPlayer) => element.isTurn);

if (player) {
const history = options.replay.info!.stateHistory;
const chess = new Chess(history.at(options.step));
const move = chess.move(player.actionDisplayText!);

set({ chess });

console.log(player.thoughts);
console.log(`${player.name} (${move.color}): ${move.piece} ${move.from} -> ${move.to}`);
}
},
}));

function App() {
const { chess, setState } = useChessStore();
const controlsRef = useRef(null);

useEffect(() => {
const app = controlsRef.current!;
const adapter = new LegacyAdapter<ChessStep[]>(setState);
const transformer = (replay: ReplayData) => processEpisodeData(replay, 'open_spiel_chess');

createReplayVisualizer(app, adapter, { transformer });
}, [setState]);

const move = chess.history({ verbose: true })[0];

const style = { width: '100%', height: '100%' };

const pieces: PieceRenderObject = {
wP: () => <img src="images/wP.png" style={style} />,
wK: () => <img src="images/wK.png" style={style} />,
wQ: () => <img src="images/wQ.png" style={style} />,
wR: () => <img src="images/wR.png" style={style} />,
wB: () => <img src="images/wB.png" style={style} />,
wN: () => <img src="images/wN.png" style={style} />,
bP: () => <img src="images/bP.png" style={style} />,
bK: () => <img src="images/bK.png" style={style} />,
bQ: () => <img src="images/bQ.png" style={style} />,
bR: () => <img src="images/bR.png" style={style} />,
bB: () => <img src="images/bB.png" style={style} />,
bN: () => <img src="images/bN.png" style={style} />,
};

const lightSquareStyle = {
backgroundImage: 'url(/images/wBg.png)',
backgroundPosition: 'center',
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
backgroundColor: 'transparent',
};

const darkSquareStyle = {
backgroundImage: 'url(/images/bBg.png)',
backgroundPosition: 'center',
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
backgroundColor: 'transparent',
};

return (
<div className="container">
<Chessboard options={{ position: chess.fen(), pieces, lightSquareStyle, darkSquareStyle }} />
<div id="controls" ref={controlsRef} />
<div id="moves">{move ? `${move.piece} ${move.from} → ${move.to}` : `Loading...`}</div>
<StyledChessboard />
<Controls />
<Legend />
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useEffect, useRef } from 'react';
import { Chess } from 'chess.js';
import {
createReplayVisualizer,
processEpisodeData,
LegacyAdapter,
LegacyRendererOptions,
ReplayData,
ChessPlayer,
ChessStep,
} from '@kaggle-environments/core';
import useChessStore from '../stores/useChessStore';

export default function Controls() {
const containerRef = useRef(null);
const setState = useChessStore((state) => state.setState);

useEffect(() => {
const renderer = (options: LegacyRendererOptions<ChessStep[]>) => {
const step = options.steps.at(options.step);
const player = step!.players.find((player: ChessPlayer) => player.isTurn);

if (player) {
const history = options.replay.info!.stateHistory;
const fen = history.at(options.step);
const chess = new Chess(fen);
const move = chess.move(player.actionDisplayText!);

step!.players.map((p) => {
const opposite = { w: 'b', b: 'w' };
const color = p.isTurn ? move.color : opposite[move.color];
chess.setHeader(color, p.name);
});

console.log(player.thoughts);
console.log(`${player.name} (${move.color}): ${move.piece} ${move.from} -> ${move.to}`);

setState(chess);
}
};

const container = containerRef.current!;
const adapter = new LegacyAdapter<ChessStep[]>(renderer);
const transformer = (replay: ReplayData) => processEpisodeData(replay, 'open_spiel_chess');

createReplayVisualizer(container, adapter, { transformer });
}, [setState]);

return <div id="controls" ref={containerRef} />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import useChessStore from '../stores/useChessStore';

export default function Legend() {
const chess = useChessStore((state) => state.chess);
const move = chess.history({ verbose: true }).at(0);
const headers = chess.getHeaders();

return (
<div id="legend">{move && `${headers[move.color]} (${move.color}): ${move.piece} ${move.from} → ${move.to}`}</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Chessboard, PieceRenderObject } from 'react-chessboard';
import useChessStore from '../stores/useChessStore';

export default function StyledChessboard() {
const chess = useChessStore((state) => state.chess);
const position = chess.fen();

const style = { width: '100%', height: '100%' };
const pieces: PieceRenderObject = {
wP: () => <img src="./images/wP.png" style={style} />,
wK: () => <img src="./images/wK.png" style={style} />,
wQ: () => <img src="./images/wQ.png" style={style} />,
wR: () => <img src="./images/wR.png" style={style} />,
wB: () => <img src="./images/wB.png" style={style} />,
wN: () => <img src="./images/wN.png" style={style} />,
bP: () => <img src="./images/bP.png" style={style} />,
bK: () => <img src="./images/bK.png" style={style} />,
bQ: () => <img src="./images/bQ.png" style={style} />,
bR: () => <img src="./images/bR.png" style={style} />,
bB: () => <img src="./images/bB.png" style={style} />,
bN: () => <img src="./images/bN.png" style={style} />,
};

const lightSquareStyle = {
backgroundImage: 'url(./images/wBg.png)',
backgroundPosition: 'center',
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
backgroundColor: 'transparent',
};

const darkSquareStyle = {
backgroundImage: 'url(./images/bBg.png)',
backgroundPosition: 'center',
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
backgroundColor: 'transparent',
};

return <Chessboard options={{ position, pieces, lightSquareStyle, darkSquareStyle }} />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { create } from 'zustand';
import { Chess } from 'chess.js';

interface ChessStore {
chess: Chess;
setState: (chess: Chess) => void;
}

const useChessStore = create<ChessStore>((set) => ({
chess: new Chess(),
setState: (chess: Chess) => set({ chess }),
}));

export default useChessStore;