A full-stack chess application built with Next.js + React in a pnpm monorepo. It implements standard FIDE chess rules, supports both two-player mode and play-against-computer mode (Stockfish), and provides a fully interactive board UI.
This project is the result of a ground-up refactor undertaken after studying Design Patterns and SOLID Principles. The goal was to turn a stateful, hard-to-extend codebase into a system with a clear architecture that is easy to read, easy to test, and easy to extend with new features.
- Monorepo Structure
- Class Diagram
- SOLID Principles in Practice
- Design Patterns Applied
- Game Flow
- Playing Against Stockfish
- Getting Started
- Scripts
- What Has Been Accomplished
- Architecture Notes
| Package | Role |
|---|---|
@chess/core |
Interfaces, constants, types, enums. Declares contracts only; contains no implementation logic. |
@chess/domain |
All chess domain logic: board, pieces, moves, history, notation. |
client |
Next.js 15 + React 19 UI: interactive board, clock, move history, play vs Stockfish. |
ChessGame/
├── packages/
│ ├── core/ # @chess/core - contracts / abstractions
│ └── domain/ # @chess/domain - chess domain logic
├── client/ # Next.js 15 + React 19 + Tailwind 4
└── pnpm-workspace.yaml
| Principle | Application in this project |
|---|---|
| S - Single Responsibility | Each class has one job: MoveGenerator only generates candidate moves, MoveValidator only validates legality, MoveNotationBuilder only builds notation, MoveExecutor only executes a move. |
| O - Open/Closed | Adding a new piece type requires only a new XxxMovement class plus a PieceFactory entry. Adding a new move type requires only a new XxxMoveExecutor. Existing code is not modified. |
| L - Liskov Substitution | All pieces extend Piece / Movement; every executor extends MoveExecutor. They are interchangeable through the IPiece, IMovement, and executor interfaces. |
| I - Interface Segregation | @chess/core splits small interfaces: IBoard, ICell, IPiece, IMove, etc. Each consumer depends only on the part it needs. |
| D - Dependency Inversion | domain and client depend only on interfaces from core, never on concrete implementation classes. |
PieceFactory- creates all piece types (Pawn, Rook, Knight, Bishop, Queen, King) for either color.ColorFactory- creates color objects (white / black).MoveExecutorFactory- selects the right executor by move type (NORMAL,CASTLING,EN_PASSANT,PROMOTION) via a registry map.
IPlayerStrategywithHumanPlayer/EnginePlayer- the strategy for who moves next.GameControllerdoes not need to know who is playing.Movementhierarchy (PawnMovement,SlidingMovementextending toBishopMovement,RookMovement,QueenMovement) - different movement strategies per piece type.
MoveExecutor- defines the sharedmovePiece()scaffold; subclasses (NormalMoveExecutor,CastlingMoveExecutor,PromotionMoveExecutor,EnPassantMoveExecutor) only overrideexecute().SlidingMovement- provides the sharedgetSlidingMoves()used by all sliding pieces (Queen, Rook, Bishop).
Board.getBoardInstance()- a single shared board across the application.GameState.getInstance()- a single shared game state.MoveValidator.getInstance()- a single shared validator.PawnMovement.of(side)- cached per color to avoid wasteful re-creation.
BoardIteratorplusBoard implements Iterable<ICell>- iterate the board with a standardfor...ofloop.
GameController.subscribe()+notify()- React components subscribe and re-render when the snapshot changes.GameClock.setOnTick()- the clock notifies on every update.
- The player selects or drags a piece;
GameController.tryMove(from, to)is called. Game.getLegalMoves()->MoveGenerator.generate(piece)produces candidates;MoveValidator.isValidMove()validates legality (check, checkmate, castling, en passant, promotion).- If the move is a promotion, the
PromotionOverlayis shown and the player picks a piece. MoveExecutorFactorycreates the correct executor, which applies the move to the board.MoveNotationBuilderandPgnBuilderrecord the notation;MoveHistorystores the history.GameClockswaps the turn;GameController.notify()updates the UI.- In computer mode, when it is the engine's turn,
EnginePlayer.computeMove()calls the/api/engineUCI endpoint, anduciToMove()feeds the move back into the game.
client/src/game/stockfishEngine.ts- fetch wrapper around the engine API with cancellation support (AbortController).client/src/app/api/engine/route.ts- a Next.js Route Handler that spawns the Stockfish binary (UCI protocol), readsbestmove, with a safety timeout.client/src/game/uci.ts- converts between UCI strings (for examplee2e4) andIPosition.
Note: only the binary client/public/stockfish-engine/stockfish-linux-x86-64-universal is tracked by git; the rest of the Stockfish source tree is ignored via .gitignore. The route handler runs on the Node.js runtime.
Requirements: Node.js 20+, pnpm 11+.
# 1. Install dependencies
pnpm install
# 2. Build core & domain (client imports from dist)
pnpm build
# 3. Run the dev server
pnpm dev:clientOpen http://localhost:3000.
| Command | Description |
|---|---|
pnpm build |
Builds core + domain + client |
pnpm dev:client |
Builds core/domain, then starts the Next.js dev server |
pnpm typecheck |
Builds core/domain + typechecks client |
pnpm test |
Runs tests across all three packages |
pnpm format |
Formats code (Prettier) |
pnpm format:check |
Checks formatting |
pnpm prepare |
Installs Husky (pre-commit hook) |
The Husky pre-commit hook automatically runs: format:check, then typecheck, then test, then lint.
- Clean monorepo separation -
core(contracts),domain(logic),client(UI); each package builds and tests independently. - Complete chess rule engine - move generation, legality validation, castling, en passant, promotion, check and checkmate detection.
- Flexible piece movement - one
Movementstrategy class per piece type with sliding movement shared by Queen, Rook, and Bishop. - Pluggable move execution - each move type has its own executor selected through a factory.
- Configurable promotion - the promoted piece is selectable (Queen, Rook, Bishop, Knight) instead of always promoting to Queen.
- Computer opponent - Stockfish integration via a UCI endpoint, with player-mode and engine move-time controls.
- Interactive board UI - drag-and-drop and click-to-move, legal-move highlights, move history, game clock, and game-over overlay.
- Quality gates - Husky pre-commit hook enforces formatting, typechecking, tests, and linting on every commit.
@chess/corecontains contracts only - separating interfaces from implementations keepsdomainindependent of concrete details and makes the client easy to mock.domainhas no React dependency - all logic is plain TypeScript, which is easy to unit test.- The client uses a Compound Component + Context pattern (
Board,Board.Files,useBoard()) to compose the board flexibly.

