$ npm install @echecs/game @echecs/fen @echecs/san

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.

React
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.