chessi
v0.2.3
Published
A complete, rule-compliant chess engine for React. Includes the useChessi hook with move validation, piece tracking, castling, en passant, and pawn promotion.
Readme
Chessi ♟️
A complete, rule-compliant chess engine for React. Export the useChessi hook and get instant access to a fully-functional chess game with move validation, piece tracking, and support for all special moves.
Features
- ✅ Full Chess Rules: Includes all standard moves, castling, en passant, and pawn promotion
- ✅ Move Validation: Automatic detection of legal moves and capture squares
- ✅ Check Detection: Prevents moves that put your own king in check
- ✅ Piece Tracking: Automatically tracks captured pieces for both players
- ✅ Castling Rights: Manages castling availability based on king and rook movement
- ✅ React Hook: Drop-in
useChessihook for seamless integration - ✅ TypeScript: Fully typed for safety and autocomplete
- ✅ Zero Configuration: Just import and use—no setup required
Installation
npm install chessior with yarn:
yarn add chessiQuick Start
import { useChessi } from "chessi";
function ChessApp() {
const {
boardState,
gameState,
possibleSquares,
possibleCaptureSquares,
handleMove,
onPieceSelected,
} = useChessi();
return (
<div>
{/* Render 8x8 grid */}
{Array.from({ length: 8 }).map((_, row) =>
Array.from({ length: 8 }).map((_, col) => {
const coord = `${row}${col}`;
const piece = boardState[coord];
return (
<button
key={coord}
onClick={() => {
if (piece && !selectedPiece) {
// Select a piece to show legal moves
onPieceSelected(coord);
setSelectedPiece(coord);
} else if (selectedPiece) {
// Move the piece
handleMove(selectedPiece, coord);
setSelectedPiece(null);
}
}}
style={{
backgroundColor: possibleSquares.includes(coord)
? "pink"
: possibleCaptureSquares.includes(coord)
? "green"
: "white",
}}
>
{piece}
</button>
);
}),
)}
<p>Current Turn: {gameState.turn}</p>
<p>Captured by White: {gameState.capturedByWhite.join(", ")}</p>
<p>Captured by Black: {gameState.capturedByBlack.join(", ")}</p>
</div>
);
}API Reference
useChessi()
Main hook that initializes and returns the chess game state and control functions.
Returns
{
boardState: BoardState;
gameState: GameState;
possibleSquares: Coordinate[];
possibleCaptureSquares: Coordinate[];
handleMove: (from: Coordinate, to: Coordinate) => void;
onPieceSelected: (square: Coordinate | null) => void;
}Return Values Explained
boardState- Object mapping board coordinates to pieces. Example:{ "10": "wr", "11": "wn", ... }. Empty squares arenull.gameState- Current game metadata:{ turn: 'white' | 'black', // Whose turn it is capturedByWhite: PieceName[], // Pieces captured by white capturedByBlack: PieceName[], // Pieces captured by black whiteCastlingRights: 'both' | 'long' | 'short' | null, // Castling availability blackCastlingRights: 'both' | 'long' | 'short' | null }possibleSquares- Array of coordinates where the selected piece can move (empty squares).possibleCaptureSquares- Array of coordinates where the selected piece can capture an opponent's piece.handleMove(from, to)- Executes a move from one square to another. Automatically:- Validates the move
- Updates board state
- Handles special moves (castling, en passant, promotion)
- Switches turns
- Updates captured pieces
onPieceSelected(square)- Selects a piece and calculates all legal moves. Passnullto deselect.
Usage Examples
Example 1: Basic Board Rendering
import { useChessi } from "chessi";
function SimpleBoard() {
const { boardState } = useChessi();
return (
<div style={{ display: "grid", gridTemplateColumns: "repeat(8, 1fr)" }}>
{Object.entries(boardState).map(([coord, piece]) => (
<div
key={coord}
style={{
border: "1px solid #ccc",
padding: "10px",
textAlign: "center",
}}
>
{piece || "·"}
</div>
))}
</div>
);
}Example 2: Interactive Chess Board with Move Highlighting
import { useState } from "react";
import { useChessi } from "chessi";
import { Coordinate } from "chessi/types";
function InteractiveBoard() {
const [selectedSquare, setSelectedSquare] = useState<Coordinate | null>(null);
const {
boardState,
possibleSquares,
possibleCaptureSquares,
handleMove,
onPieceSelected,
} = useChessi();
const handleSquareClick = (coord: Coordinate) => {
if (selectedSquare === null) {
// Select a piece
setSelectedSquare(coord);
onPieceSelected(coord);
} else if (selectedSquare === coord) {
// Deselect
setSelectedSquare(null);
onPieceSelected(null);
} else {
// Try to move
handleMove(selectedSquare, coord);
setSelectedSquare(null);
onPieceSelected(null);
}
};
return (
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(8, 1fr)",
gap: "0",
}}
>
{Array.from({ length: 8 }).map((_, row) =>
Array.from({ length: 8 }).map((_, col) => {
const coord: Coordinate = `${row}${col}`;
const piece = boardState[coord];
const isSelected = selectedSquare === coord;
const canMove = possibleSquares.includes(coord);
const canCapture = possibleCaptureSquares.includes(coord);
return (
<div
key={coord}
onClick={() => handleSquareClick(coord)}
style={{
width: "50px",
height: "50px",
backgroundColor: isSelected
? "#ffd700"
: canCapture
? "#ff6b6b"
: canMove
? "#ffb6b6"
: (row + col) % 2 === 0
? "#f0d9b5"
: "#baca44",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: "20px",
cursor: "pointer",
}}
>
{piece}
</div>
);
}),
)}
</div>
);
}Example 3: Game State Display
import { useChessi } from "chessi";
function GameInfo() {
const { gameState } = useChessi();
return (
<div>
<h3>Game Status</h3>
<p>
<strong>Current Turn:</strong> {gameState.turn.toUpperCase()}
</p>
<p>
<strong>White Pieces Captured:</strong>{" "}
{gameState.capturedByWhite.join(", ") || "None"}
</p>
<p>
<strong>Black Pieces Captured:</strong>{" "}
{gameState.capturedByBlack.join(", ") || "None"}
</p>
<p>
<strong>White Castling Rights:</strong>{" "}
{gameState.whiteCastlingRights || "None"}
</p>
<p>
<strong>Black Castling Rights:</strong>{" "}
{gameState.blackCastlingRights || "None"}
</p>
</div>
);
}Coordinate System
The board uses a numeric coordinate system from 00 to 77:
0 1 2 3 4 5 6 7
0 ♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜ (Row 0)
1 ♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟ (Row 1)
2 · · · · · · · · (Row 2)
3 · · · · · · · · (Row 3)
4 · · · · · · · · (Row 4)
5 · · · · · · · · (Row 5)
6 ♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙ (Row 6)
7 ♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖ (Row 7)- First digit: Row (0-7, top to bottom)
- Second digit: Column (0-7, left to right)
- Example:
"64"is row 6, column 4 (white's e2 in standard chess notation)
Piece Notation
Pieces are represented by a two-character code:
| Code | Piece | Color |
| ---- | ------ | ----- |
| wp | Pawn | White |
| wn | Knight | White |
| wb | Bishop | White |
| wr | Rook | White |
| wq | Queen | White |
| wk | King | White |
| bp | Pawn | Black |
| bn | Knight | Black |
| bb | Bishop | Black |
| br | Rook | Black |
| bq | Queen | Black |
| bk | King | Black |
Types
All TypeScript types are exported for full type safety:
import { Coordinate, PieceName, BoardState, GameState } from "chessi";
type Coordinate = `${number}${number}`; // e.g., "00", "42", "77"
type PieceName =
| "wp"
| "wn"
| "wb"
| "wr"
| "wq"
| "wk"
| "bp"
| "bn"
| "bb"
| "br"
| "bq"
| "bk";
type BoardState = Record<Coordinate, PieceName | null>;
type GameState = {
turn: "black" | "white";
capturedByWhite: PieceName[];
capturedByBlack: PieceName[];
blackCastlingRights: "both" | "long" | "short" | null;
whiteCastlingRights: "both" | "long" | "short" | null;
};Common Patterns
Detecting Valid Moves
const { possibleSquares, possibleCaptureSquares, onPieceSelected } =
useChessi();
// When user clicks a piece
const handlePieceClick = (coord: Coordinate) => {
onPieceSelected(coord);
// possibleSquares now contains empty squares this piece can move to
// possibleCaptureSquares contains opponent pieces that can be captured
const canMoveOrCapture = [...possibleSquares, ...possibleCaptureSquares];
console.log("Legal moves:", canMoveOrCapture);
};Undo Last Move
Unfortunately, chessi doesn't have built-in undo. However, you can implement it by:
import { useChessi } from 'chessi';
import { useState } from 'react';
function GameWithUndo() {
const [moveHistory, setMoveHistory] = useState([]);
const { boardState, gameState, handleMove } = useChessi();
const executeMove = (from: Coordinate, to: Coordinate) => {
const stateBefore = { board: { ...boardState }, game: { ...gameState } };
handleMove(from, to);
setMoveHistory([...moveHistory, stateBefore]);
};
return (
// Your game UI
);
}Reset Game
Since useChessi initializes a fresh game on mount, you can reset by:
function GameWithReset() {
const [resetKey, setResetKey] = useState(0);
const chessGame = useChessi();
return (
<>
<button onClick={() => setResetKey((prev) => prev + 1)}>
Reset Game
</button>
<Game key={resetKey} />
</>
);
}Troubleshooting
Moves aren't working
Make sure you:
- Call
onPieceSelected(coord)before trying to move (this validates the piece and calculates legal moves) - Only move to coordinates in
possibleSquaresorpossibleCaptureSquares - Check that it's the correct player's turn (validate
gameState.turn)
Piece highlights aren't updating
The state updates are synchronous. If using React DevTools, verify:
possibleSquaresandpossibleCaptureSquaresupdate when you callonPieceSelectedboardStateandgameStateupdate when you callhandleMove
Can't move into check
By design, chessi prevents illegal moves. If a move isn't in possibleSquares, it's illegal (including moves that would put your king in check).
Contributing
Found a bug or have a feature request? Please open an issue or PR on the GitHub repository.
License
MIT
Happy chess playing! ♟️♙
