Skip to content

bmarini/jchess

Repository files navigation

jchess

A TypeScript chess library: PGN parser, move generator, position model, and a navigable game player. Zero runtime dependencies.

Live demo: bmarini.github.io/jchess

Features

  • PGN parser — handles annotations, NAGs, recursive variations (RAV), and [%clk]/[%eval] metadata
  • Position model — FEN parsing, move application, en passant, castling, promotion, check detection
  • SAN ↔ coordinatestoSAN() for board moves, parseSAN() for SAN strings, full disambiguation
  • Legal move generationlegalMovesFrom(), pin detection, checkmate/stalemate
  • Game navigationGamePlayer with forward/backward/jump, variation entry/exit, makeMove
  • PGN export — round-trip serialization with annotations, variations, and metadata
  • Strict typesSquare is a template literal of all 64 valid squares; nothing slips past the type checker
  • Zero dependencies — pure TypeScript, ~38kB compressed

Installation

npm install jchess

Quick start

import { Position, parsePGN, buildTransitions, GamePlayer } from 'jchess'

// Parse a PGN
const game = parsePGN(`
[White "Fischer"]
[Black "Spassky"]

1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 *
`)

// Build a navigable player
const result = buildTransitions(game, Position.starting())
const player = new GamePlayer(result)

// Navigate
player.stepForward()        // play e4
player.stepForward()        // play e5
console.log(player.currentSAN)         // 'e5'
console.log(player.positionAt(2).toFEN())

Apply a move directly

import { Position } from 'jchess'

const pos = Position.starting()
const after = pos.applyMove('e4')
console.log(after?.position.get('e4'))  // { color: 'w', type: 'P', id: ... }

Check for checkmate

import { Position } from 'jchess/board'
import { hasAnyLegalMove } from 'jchess/movegen'

// Fool's mate position (black to move, white just played Qh5#)
const pos = Position.fromFEN('rnb1kbnr/pppp1Qpp/8/4p3/4P3/8/PPPP1PPP/RNB1KBNR b KQkq - 0 2')
console.log(pos.isInCheck())          // true
console.log(hasAnyLegalMove(pos))     // false → checkmate

Convert board moves to SAN

import { Position } from 'jchess/board'
import { toSAN } from 'jchess/movegen'

const pos = Position.starting()
console.log(toSAN(pos, 'g1', 'f3'))   // 'Nf3'
console.log(toSAN(pos, 'e2', 'e4'))   // 'e4'

Export back to PGN

import { parsePGN, buildTransitions, exportPGN, Position } from 'jchess'

const game = parsePGN('1. e4 e5 2. Nf3 Nc6 *')
const { transitions } = buildTransitions(game, Position.starting())
console.log(exportPGN(game.headers, transitions, game.preAnnotation))

Public API

The package is organized into focused modules. Import from the barrel for everything, or from subpaths for clarity:

// Barrel — everything
import { Position, parsePGN, GamePlayer } from 'jchess'

// Subpaths
import { Position, STARTING_FEN, opponent } from 'jchess/board'
import { toSAN, legalMovesFrom, hasAnyLegalMove } from 'jchess/movegen'
import { parsePGN, tokenize } from 'jchess/pgn'
import { exportPGN } from 'jchess/export'
import { buildTransitions, GamePlayer, Line } from 'jchess/transitions'
import { parseAnnotation, serializeAnnotation, extractNAG } from 'jchess/annotation'
import type { Square, Color, PieceType, Piece, Board, ParsedGame, Transition } from 'jchess/types'
Module Exports
jchess/board Position, STARTING_FEN, squareToCoord, coordToSquare, boardGet, boardSet, parseSAN, findMoveSource, opponent, toSquare
jchess/movegen toSAN, legalMovesFrom, hasAnyLegalMove
jchess/pgn parsePGN, tokenize
jchess/export exportPGN
jchess/transitions buildTransitions, GamePlayer, Line, buildSingleTransition
jchess/annotation parseAnnotation, serializeAnnotation, extractNAG, hasDisplayText
jchess/types Square, Color, PieceType, Piece, Board, CastlingRights, Transition, ParsedGame, ParsedMove, MoveMetadata

Optional DOM viewer

The package also bundles a vanilla DOM-based viewer (ChessViewer) under a separate subpath. React/Vue consumers don't need it; tree-shaking keeps it out of your bundle unless you import it explicitly.

import { ChessViewer } from 'jchess/viewer'

const viewer = new ChessViewer(document.getElementById('board')!, {
  pgn: '1. e4 e5 2. Nf3 *',
})
viewer.next()

Architecture

Layered design

types.ts        ← shared types (Square, Piece, Board, etc.)
   ↑
board.ts        ← Layer 1: Position class, FEN, applyMove, isInCheck
   ↑
movegen.ts      ← Layer 2: toSAN, legalMovesFrom, hasAnyLegalMove
   ↑
transitions.ts  ← Layer 3: buildTransitions, GamePlayer, Line

pgn.ts, export.ts, and annotation.ts are independent modules that work alongside the layers.

Position is a rich domain object

Position owns all move logic and is immutableapplyMove returns a new Position. This makes testing straightforward and lets the transition list pre-compute every position in a game without worrying about shared state.

Transition list

When a PGN is loaded, buildTransitions walks every move and records { forward, backward } command lists per half-move. Single-step navigation replays these in O(1). GamePlayer manages a stack of Line instances — the main line plus any variations the user has entered.

Stable piece IDs

Each Piece has a stable numeric id. This lets a renderer track DOM elements across captures, promotions, and variation switches without re-rendering the whole board. Transitions carry piece IDs, so the renderer always knows which element to move/remove/add.

Annotation metadata

PGN commands like [%clk 0:30:00] and [%eval +1.5] are parsed into structured MoveMetadata separately from display text. The exporter re-embeds them for round-trip fidelity.

Coordinate system

  • board[0] = rank 8 (black's back rank)
  • board[7] = rank 1 (white's back rank)
  • board[row][col], col 0 = a-file, col 7 = h-file
  • squareToCoord('e4')[4, 4]
  • coordToSquare(4, 4)'e4'

Square is a template literal type — 'a1' | 'a2' | ... | 'h8'. Constructing a square from string concatenation requires toSquare(file, rank) or an explicit cast.

TypeScript

jchess ships with .d.ts files generated from source. Strict mode and noUncheckedIndexedAccess are enabled — every public API is fully typed.

Development

The repo also contains a Next.js demo app (web/) that consumes the library directly via path aliases. To work on jchess locally:

git clone https://github.com/benmarini/jchess
cd jchess
npm install
npm test                # run all 217 tests
npm run build           # build to dist/

# Run the demo
cd web
npm install
npm run dev             # http://localhost:3000

The demo includes Stockfish WASM analysis, opening detection, full-game review, bot play, and PGN sharing — see the live site for what's possible on top of jchess.

License

MIT — see LICENSE.

The bundled demo additionally uses Stockfish (GPL-3.0), the Lichess chess-openings database (CC0), and Phosphor icons (MIT). These are not part of the npm package.

About

A jQuery-based javascript library for parsing and displaying chess games.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors