Account-free chess matches with shareable invite links
  • TypeScript 83.5%
  • JavaScript 15.5%
  • Python 0.6%
  • CSS 0.2%
  • HTML 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-15 13:16:46 -04:00
.audit Replace vendor chess code with native TypeScript implementations 2026-10-15 12:16:02 -04:00
.zed Enable multithreaded Stockfish and refine game layouts 2026-10-14 13:11:01 -04:00
public Enhance board interactions and validate saved preferences 2026-10-14 14:53:09 -04:00
reference Replace vendor chess code with native TypeScript implementations 2026-10-15 12:16:02 -04:00
scripts Animate the king when placed in check 2026-10-15 13:16:46 -04:00
server Apply lint fixes and use non-mutating array sorting 2026-10-15 12:24:33 -04:00
service-worker Replace vendor chess code with native TypeScript implementations 2026-10-15 12:16:02 -04:00
src Animate the king when placed in check 2026-10-15 13:16:46 -04:00
tests Animate the king when placed in check 2026-10-15 13:16:46 -04:00
worker Replace vendor chess code with native TypeScript implementations 2026-10-15 12:16:02 -04:00
.gitignore Remove unnecessary code comments 2026-10-14 13:22:06 -04:00
.oxfmtignore Bundle the chess client from source modules 2026-10-14 00:05:08 -04:00
.oxfmtrc.json Replace vendor chess code with native TypeScript implementations 2026-10-15 12:16:02 -04:00
.oxlintignore Replace vendor chess code with native TypeScript implementations 2026-10-15 12:16:02 -04:00
.oxlintrc.json Apply lint fixes and use non-mutating array sorting 2026-10-15 12:24:33 -04:00
bun.lock Replace vendor chess code with native TypeScript implementations 2026-10-15 12:16:02 -04:00
bunfig.toml Create Openboard account-free chess matches 2026-10-13 23:57:06 -04:00
GLOSSARY.md Separate game sessions, analysis, and tunable bots 2026-10-14 14:05:30 -04:00
package.json Replace vendor chess code with native TypeScript implementations 2026-10-15 12:16:02 -04:00
README.md Replace vendor chess code with native TypeScript implementations 2026-10-15 12:16:02 -04:00
tsconfig.json Create Openboard account-free chess matches 2026-10-13 23:57:06 -04:00
tsconfig.service-worker.json Add offline support, chat, and chess enhancements 2026-10-14 10:52:42 -04:00
tsconfig.worker.json Separate game sessions, analysis, and tunable bots 2026-10-14 14:05:30 -04:00
wrangler.jsonc Rename the Wrangler project to openboard 2026-10-14 11:29:38 -04:00

Openboard

Chess matches: create a game, share /game/<id>, and let a friend claim the other seat.

bun install
bun run dev

Open http://localhost:3000. For opponents on another device, run the server at a URL they can reach and share that URL's invite link. PORT and HOST configure the listener. Production: bun run build, then bun run start to serve the built frontend with offline support.

Matches persist in data/openboard.sqlite; override with DATABASE_PATH. Anonymous player tokens stay in browser storage. Returning players need the same browser. Opening a taken match from another browser gives spectator access.

Install Openboard

On the HTTPS site, use your browser's install option to open Openboard in its own app window. On iPhone or iPad, use Safari's Share → Add to Home Screen. Installation support varies by browser.

The production app caches its interface, board themes, and pieces after the first online visit. Same-device games then work offline. Start a computer game online once to download and cache Stockfish before playing against it offline. Multiplayer and chat still require a connection. Local and computer games remain in the current window and are not saved when it closes. Browsers may evict cached files when storage is limited.

New releases install in the background and activate after all Openboard windows close, so updates do not interrupt games. Offline caching is disabled in the development server to keep hot reload working.

The web app manifest and icons live in public/. Both the Bun server and production build serve them at root-relative URLs, including when opening a game invite.

Cloudflare Workers

bun run deploy builds the frontend and publishes the Worker configured in wrangler.jsonc. Hosted matches use one SQLite-backed Durable Object per game; the Bun server remains available for local development. Deployment requires Wrangler access to the configured Cloudflare account.

To check the Worker runtime locally:

bun run build
bunx wrangler deploy --dry-run --outdir dist-worker
bun run test:worker

The Worker verification uses Miniflare's supported Node runtime. It checks invite assets, anonymous seats, moves, and persistence across runtime restarts.

Client integration

The board uses a native TypeScript DOM renderer and rules engine, with the extracted Chess.com play client 2026.10.3 board CSS and locally served Neo, Classic, and Alpha pieces. It does not load Chess.com's full web client or connect to Chess.com's services. React supplies the invite UI and accessible input layer; Bun/SQLite supplies match state, seat ownership, and clocks, with moves validated by the native rules engine.

The upstream JavaScript downloads are retained locally in the Git-ignored reference/chess-com/ directory for auditing; they are not runtime dependencies. The original board stylesheet is tracked in reference/chess-client/ and regenerates the scoped CSS with bun run extract:client. The upstream rules engine remains as a test-only parity oracle under src/vendor/chess-client/; no app runtime module imports its JavaScript.

src/vendor/chess-client/ contains the generated scoped board CSS and retained upstream JavaScript oracle fixtures used only by parity tests. App runtime code is TypeScript. public/chess-assets/ contains only the board textures and Neo, Classic, and Alpha piece images used by the app. The piece PNGs come from https://www.chess.com/chess-themes/pieces/{style}/150/{piece}.png. Full-site source stylesheets are not shipped.

The renderer handles move and capture animations, reverse moves (including two-ply takebacks), legal-move hints, drag destination outlines, and hint arrows. React retains keyboard interaction, move validation, annotations, and promotion controls. Board settings persist the piece style and inside, outside, every-square, or hidden coordinates. Animations respect reduced-motion preferences.

Regenerate the scoped board CSS from the tracked upstream stylesheet:

bun run extract:client

Checks

bun run check

Checks include application and Worker TypeScript, Oxlint, Oxfmt, match/API tests, original-renderer lifecycle tests, HTTP-origin player tokens, and a React invite-to-move test. WebSocket tests exercise the Bun and Cloudflare runtimes, private chat, spectator access, reconnects, command retries, and clock expiry. The Cloudflare socket test compiles an isolated test fixture and runs Miniflare with Node; it does not require a release build.

Module ownership

Source code is grouped by feature:

  • src/app/: application composition, layout, player rows, and dialogs.
  • src/board/: board interaction, renderer integration, assets, settings, premoves, and sounds.
  • src/bots/: opponent move selection, strength presets, pacing, and opening preferences.
  • src/engine/: Stockfish loading, workers, UCI commands, cancellation, and score parsing.
  • src/analysis/: position analysis, hints, and advantage-meter presentation.
  • src/game/: game state, shared transitions, and the local game-session lifecycle.
  • src/offline/: browser offline registration and readiness.
  • src/chess/: chess types, rules, history replay, position scores, and material accounting shared with the server.
  • src/matches/: match contracts, HTTP/WebSocket transport, synchronization, and the React subscription.
  • src/chat/: chat contracts and UI.
  • src/openings/: opening detection, generated book, and its React hook.
  • src/vendor/: generated upstream code, kept separate from application code.

Only the HTML, styles, and React bootstrap live directly under src/. Imports point to the owning module; there are no re-export barrels.

src/game/transitionGame.ts owns moves, clock advancement, and chess outcomes for both same-device and online matches. server/engine.ts adds seat authorization, match lifecycle, and persistence through the existing Bun and Durable Object storage adapters.

src/game/GameSession.ts owns one same-device or computer game: creation, engine preparation, clocks, bot turns, takeback checkpoints, resignation, and disposal. src/game/useGameSession.ts subscribes React to its snapshot; App sends player intent without managing workers or pending searches. Online games continue through the authoritative server and match synchronization module.

src/engine/stockfish.ts exposes structured searches over UCI move histories. It normalizes evaluations to White's perspective and hides protocol text from its callers. Bot behavior and position analysis share this engine capability but use separate workers and search settings. src/analysis/PositionAnalysis.ts accepts a move history without a match or player seat, reuses completed suggestions, and cancels stale searches when the displayed history changes. src/analysis/AdvantageMeter.tsx presents its score. Engine preparation and analysis load Stockfish only when needed.

src/chess/chessTypes.ts owns project chess vocabulary. src/chess/chessRules.ts hides the vendor rules engine and supplies detached, read-only position snapshots; move history remains the source for repetition and match replay. src/board/chessAssets.ts owns board themes and image paths.

src/board/chessClient.ts exposes renderer sessions through readiness, position-frame updates, and disposal. It retains the latest frame while loading. src/board/internal/renderer.ts privately owns vendor initialization, piece conversion, and native update ordering. src/board/Board.tsx uses a read-only rules view for player interaction. src/app/PlayLayout.tsx owns shared viewport sizing for setup, invites, and active games. Domain terms are recorded in GLOSSARY.md.

Match synchronization

src/matches/MatchSync.ts owns reconnection and the pending-command queue. src/matches/MatchSocket.ts carries match commands and private chat over one WebSocket per open online game; the server pushes personalized snapshots after changes. Match creation still uses HTTP. src/matches/useMatch.ts subscribes React to the engine and resumes synchronization on browser focus, visibility, and online events.

A pending command stays in browser storage until the server confirms or rejects it. Retries use the original request ID and board version. The server stores up to 128 accepted action receipts with each match. An older retry whose receipt was removed still fails the version check, so it cannot repeat an accepted action.

The server remains authoritative for moves, seats, and clocks. The browser preserves the last confirmed board during a disconnect and blocks new commands while an earlier command is uncertain. Pending commands resume when the same match is reopened in the same browser. Socket authentication sends the player token in the first message, never in the URL; upgrades require a matching Origin. Heartbeats detect stalled connections and retries use exponential backoff. Clocks continue while the tab is closed, so a delayed command can be rejected after timeout. Bun schedules clock expiry for connected games; Cloudflare uses a Durable Object alarm and hibernating WebSockets so idle games do not require polling or a continuous timer. Reconnecting restores the latest match and chat history.

Computer games

Choose Computer, choose a side, then Start playing. Selecting Computer downloads and initializes Stockfish so Start playing can reuse the ready worker. Switching away from computer setup releases an unused worker. Same-device and online matches do not load Stockfish. Games use Stockfish 19 lite with up to two search threads per worker on cross-origin-isolated browsers with shared memory and at least four logical cores. Other browsers, or a failed threaded startup, use the single-threaded lite build. Cloudflare production assets and the Bun production server send the required isolation headers; Bun’s HTML development routes use the single-threaded fallback. Computer setup offers Casual, Club, and Expert bot presets (Stockfish skill levels 0, 6, and 14). These describe relative strength, not calibrated human ratings. Tune strength exposes Skill Level (0–20) or an Elo limit (1320–3190), search time (0.1–5 seconds), and hash memory (16–128 MB). Settings save in this browser and apply to new games; existing saved engine settings are preserved as a Custom bot. Defaults remain Skill Level 0, 0.3 seconds of engine search, and 16 MB.

Move pace sets a base reply range independently of engine search: Quick uses 0.5–0.7 seconds, Natural (the default) 2–5 seconds, and Relaxed 4–8 seconds. Natural adds a 3-second pause on 15% of replies; Relaxed adds 4 seconds on 20%. Tune behavior retains the pause range, longer-pause probability and duration, and faster forced replies. A forced reply means there is exactly one legal move.

Under the hood, pacing responds to checks, tactical choices, close alternatives, and changing engine evaluations. Clear recaptures and familiar sound openings can be faster. The bot gradually follows the median of your latest six move times, filtering premoves and very long interruptions; the timing adjustment builds over four samples and stays between 0.79× and 1.3×. It learns only within the current game, and takebacks discard timing samples from undone moves. Playing strength does not adapt. Position and rhythm adjustments remain bounded by the configured maximum pause plus extra pause. Engine calculation counts toward the reply time, both calculation and waiting shrink under clock pressure, and all elapsed time consumes the bot's clock.

Bots vary their openings automatically using a small weighted repertoire indexed by position, including transpositions, through five full moves. Replies use up to four alternatives within the existing search-time budget; full-strength play keeps a single line to preserve search depth. A repertoire move is eligible only when a complete set of evaluations at the newest reported depth, at depth six or greater, puts it within 60 centipawns of the engine's leading alternative. Preferences are weighted by repertoire choice and evaluation loss; these are curated weights, not game-frequency statistics. Newer incomplete results, shallow results, mate scores, and positions outside the repertoire fall back to the engine's chosen move. Search budgets include time spent draining a cancelled search. Opening-name detection remains separate. Opening variety is part of the bot experience and has no separate setting.

src/bots/Bot.ts owns opponent move selection and pacing. It accepts move history and an optional remaining-clock budget, and consumes the shared SearchEngine interface. GameSession independently validates and applies its replies. src/bots/botSettings.ts owns presets and validated behavior settings. Stockfish remains the engine; hints use a separate full-strength worker without bot pacing. Takebacks cancel both search and waiting, and reopening a finished game restores the original bot settings.

Use Hint on your turn to show a suggested move with an arrow and chess notation. A separate full-strength Stockfish worker stays warm throughout the game and analyzes each position for up to one second. Hint reuses that analysis without changing opponent settings or playing the move. A vertical advantage bar beside the board follows the board’s orientation and shows evaluations from White’s perspective: positive scores favor White, negative scores favor Black, and M indicates mate. Its bar represents the evaluation, not a winning probability. Analysis follows the displayed position during move review; changing that position clears its previous score and suggestion. Both workers stop when the game ends or you leave it. Computer games hide the chat panel.

On desktop, the board and game controls fit within the viewport. Online games share one side panel with Moves, Game info, and Chat tabs; histories and conversations scroll inside the panel, with the chat composer fixed at its bottom.

Game results appear in a floating panel over the board. Dismiss it to inspect the final position, or choose Create another match.

Use Take back to return to your previous turn, undoing your move and any computer reply. It restores clocks, captures, and opening names, clears premoves, and cancels an in-flight search. You can take back after a game ends; when playing Black, the computer’s initial White move stays in place. Computer games live in the current tab and are not saved. The worker stops when the game ends or you leave it. Loading failures let you retry from setup. Bun serves the engine assets from the pinned dependency; the production build copies both lite engine variants, license, README, and exact source reference into dist/stockfish/.

Board controls

Drag a piece to move it; it follows your mouse or touch until released. Illegal drops and drops outside the board return the piece to its starting square. You can also click a piece and its destination.

Board settings save your theme, sounds, premoves, and auto-queen preference in this browser. Sounds distinguish moves, captures, check, and game end; opening a match or reviewing earlier positions does not replay notifications.

Right-click a square to toggle a highlight, or right-drag between squares to toggle an arrow. The pencil button enables drawing with touch or left-drag. Normal board clicks and Escape clear annotations; a new position clears them automatically.

In computer and online games, select or drag your own piece during the opponent’s turn to queue one premove. Blue squares mark the queued move. Use Cancel premove, right-click, or Escape to cancel it. The move executes only if legal after the opponent moves; entering move review or disabling premoves clears the queue.

Match chat

Chat fills the remaining right-hand space on desktop and stacks below the game on smaller screens. Only seated players can read or send messages; spectators and computer/same-device games have no chat access. Messages remain available after a game ends and persist with the match. The latest 100 messages are retained, with a 500-character limit per message.

Chat shares the match WebSocket, is pushed only to seated players, and never changes the board version. A failed send keeps the draft and its message ID for a safe retry.

Opening names

The move panel shows the recognized opening and ECO code, including common openings and major variations. Detection uses board positions to recognize transpositions and retains the last, deepest match when play leaves the opening book. Reviewing moves shows the opening recognized at that point. The book loads after the first move and runs locally; source and regeneration details.