Your first game
@echecs/game tracks board state and enforces FIDE rules.
@echecs/san resolves human-readable notation like
e4 or Nf3 to concrete moves. Together,
you can play a full game in seven lines.
import { Game } from '@echecs/game';
import { parse } from '@echecs/san';
const game = new Game();
// Scholar's mate
for (const san of ['e4','e5','Qh5','Nc6','Bc4','Nf6','Qxf7']) {
game.move(parse(san, game.position()));
}
game.isCheckmate(); // true
game.turn(); // 'black'
parse() knows which piece can reach which square —
disambiguation, captures, castling, promotion are all handled.
Game validates every move; illegal ones throw.
Positions & notation
@echecs/fen parses and serializes Forsyth-Edwards
Notation — the standard format for storing chess positions.
@echecs/san converts between human notation and
concrete moves. Together with @echecs/game, you can
play a game and snapshot the result.
import { Game } from '@echecs/game';
import { parse } from '@echecs/san';
import { stringify } from '@echecs/fen';
const game = new Game();
// Play the Ruy Lopez opening
for (const san of ['e4','e5','Nf3','Nc6','Bb5']) {
game.move(parse(san, game.position()));
}
// Snapshot the position as a FEN string
const pos = game.position();
stringify({
board: pos.pieces(),
turn: pos.turn,
castlingRights: pos.castlingRights,
enPassantSquare: pos.enPassantSquare,
halfmoveClock: pos.halfmoveClock,
fullmoveNumber: pos.fullmoveNumber,
});
// 'r1bqkbnr/pppp1ppp/2n5/1B2p3/...'
parse() resolves ambiguous notation —
it knows which knight can reach f3, which bishop can reach b5.
stringify() produces a FEN string for storage or
transmission.
Run a tournament
@echecs/swiss generates pairings following the FIDE Dutch system.
@echecs/elo updates ratings after each game.
Feed results back in and pair the next round.
import { pair } from '@echecs/swiss';
import { update } from '@echecs/elo';
const players = [
{ id: 'anna', rating: 1800 },
{ id: 'boris', rating: 1750 },
{ id: 'clara', rating: 1600 },
{ id: 'david', rating: 1550 },
];
// Round 1 pairings
const round1 = pair(players, []);
// { pairings: [{ white: 'anna', black: 'clara' },
// { white: 'boris', black: 'david' }] }
// After games are played, update ratings
const [annaNew, claraNew] = update(
1800, 1600, 1 // anna wins
);
The tiebreak packages (@echecs/buchholz,
@echecs/sonneborn-berger, and six others) plug into
@echecs/tournament to compute final standings after
all rounds are complete.
Build the UI
@echecs/react-board renders an interactive chessboard with
drag-and-drop, animation, and legal move indicators.
@echecs/react-movesheet displays a PGN move list with
click navigation and variation support.
import Board from '@echecs/react-board';
import { Game } from '@echecs/game';
import { stringify } from '@echecs/fen';
const game = new Game();
function ChessApp() {
return (
<Board
position={stringify(game.position())}
movable
legalMoves={game.moves()}
onMove={(move) => {
game.move(move);
return true;
}}
/>
);
}
Pass a FEN string or a position map. The board handles piece rendering, square highlighting, and promotion dialogs. You handle the game logic.
Next steps
Each package has its own documentation with full API reference, types, and edge cases.