Chess engine as a Go package, HTTP server, CLI and WebAssembly binary: parse, convert and play chess in any notation.
Notation Converter · Match Parser · Play vs AI · AI Auto-play
All four run the engine as WebAssembly in your browser and are redeployed on every merge to master.
- Full rules engine on bitboards: legal move generation (perft-validated), castling, en passant, promotions, check/checkmate/stalemate, double and discovered check detection.
- Complete draw handling: threefold repetition, insufficient material, the 50-move rule, and claimable/agreed draws.
- Six notations, parsed and printed: Algebraic (SAN), Figurine, Descriptive, Coordinate, ICCF and Smith, plus PGN with tag pairs, comments, NAGs and
(...)variations. - Notation auto-detection with partial-parse tolerance: invalid input still yields the valid prefix, the most likely notation, and why parsing stopped.
- Conversion of a game from any notation to any notation, optionally from a custom starting position (FEN or board strings).
- AI move selection:
random, or minimax with alpha-beta pruning ateasy/medium/hard. - Battle-tested: 5,553 real PGN games in CI, plus ostinato's engine and notation corpora (Chernev endings, six-notation suite).
Six functions, identical across the Go package, HTTP server, CLI and WASM binary:
DefaultGame() OutputGame
ParseGame(game InputGame) (OutputGame, error)
DoAction(game InputGame, action InputAction) (OutputGame, OutputAction, error)
// Auto-detects the notation: Algebraic (incl. Figurine and PGN), Coordinate, Descriptive, ICCF, Smith
ParseNotation(game InputGame, notationString string) (OutputGame, OutputParseResult, error)
// Auto-detects the source notation and re-renders every move in the target notation:
// one of {Algebraic|Figurine|Descriptive|Coordinate|ICCF|Smith}
ConvertNotation(game InputGame, notationString string, targetNotation string) (OutputGame, OutputParseResult, error)
// mode is one of {random|easy|medium|hard}; ok is false when the game is over
AIMove(game InputGame, mode string) (game OutputGame, action OutputAction, ok bool, err error)All examples assume:
import "github.com/marianogappa/cheesse/api"
a := api.New()Convert a game between notations
// The source notation is auto-detected
_, result, _ := a.ConvertNotation(api.InputGame{}, "1. P-K4 P-K3\n2. P-Q4 P-Q4", "ICCF")
fmt.Println(result.NotationName) // Descriptive Notation
for _, step := range result.Steps {
fmt.Print(step.ActionString, " ") // 5254 5756 4244 4745
}Parse a game, auto-detecting the notation
_, result, _ := a.ParseNotation(api.InputGame{}, "1. e4 e5 2. Nf3")
fmt.Println(result.NotationName) // Algebraic Notation
fmt.Println(result.ValidActionCount) // 3
for _, step := range result.Steps {
fmt.Print(step.ActionString, " ") // e4 e5 Nf3
}Parse a PGN with headers, comments and variations
pgn := `[Event "F/S Return Match"]
[White "Fischer, Robert J."]
1. e4 e5 2. Nf3 {main line} Nc6 (2... d6) 3. Bb5 1/2-1/2`
_, result, _ := a.ParseNotation(api.InputGame{}, pgn)
fmt.Println(result.NotationName) // PGN
fmt.Println(result.Metadata["White"]) // Fischer, Robert J.Make a move and inspect the position
game, action, _ := a.DoAction(api.InputGame{}, api.InputAction{FromSquare: "e2", ToSquare: "e4"})
fmt.Println(action.ActionString) // e4
fmt.Println(game.FENString) // rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1Ask the AI for a move
game, action, ok, _ := a.AIMove(api.InputGame{}, "hard") // random|easy|medium|hard
fmt.Println(ok, action.ActionString) // true Nc3
fmt.Println(game.FENString) // rnbqkbnr/pppppppp/8/8/8/2N5/PPPPPPPP/R1BQKBNR b KQkq - 1 1Load any position from FEN
game, _ := a.ParseGame(api.InputGame{FENString: "rnb1kbnr/pppp1ppp/8/4p3/6Pq/5P2/PPPPP2P/RNBQKBNR w KQkq - 1 3"})
fmt.Println(game.IsCheckmate) // trueTolerate a partially valid game
_, result, _ := a.ParseNotation(api.InputGame{}, "1. e4 e5 2. Nf3 Nc6 3. Zz9")
fmt.Println(result.ParseWasSuccessful) // false
fmt.Println(result.ValidActionCount) // 4 (the valid prefix is in result.Steps)
fmt.Println(result.Error) // at move 3: unexpected input (no token matched)Enumerate every available action
game := a.DefaultGame()
fmt.Println(len(game.Actions)) // 22: 20 legal moves, plus resign and draw offer
first := game.Actions[0]
fmt.Println(first.FromPieceSquare, first.ToSquare) // a2 a4cheesse -serve 8080One endpoint per API function (/defaultGame, /parseGame, /doAction, /parseNotation, /convertNotation, /aiMove); requests and responses are JSON.
Get the default game as a Unicode board
curl -s localhost:8080/defaultGame | jq .game.board.board[
"♜♞♝♛♚♝♞♜",
"♟♟♟♟♟♟♟♟",
" ",
" ",
" ",
" ",
"♙♙♙♙♙♙♙♙",
"♖♘♗♕♔♗♘♖"
]Ask the AI for a move
curl -s localhost:8080/aiMove -d '{"game":{},"mode":"hard"}' | jq .action.actionString"Nc3"Convert a game over HTTP
curl -s localhost:8080/convertNotation -d '{"game":{},"notationString":"1. P-K4 P-K3","targetNotation":"Smith"}' | jq -c '[.parseResult.steps[].actionString]'["e2e4","e7e6"]Same JSON in, JSON out, one flag per API function.
Detect the notation of a pasted game
cheesse -parseNotation '{"game":{},"notationString":"1. e4 e5 2. Nf3"}' | jq .parseResult.notationName"Algebraic Notation"Convert a game to Smith notation
cheesse -convertNotation '{"game":{},"notationString":"1. P-K4 P-K3","targetNotation":"Smith"}' | jq -c '[.parseResult.steps[].actionString]'["e2e4","e7e6"]Prebuilt cheesse.wasm, cheesse.js and wasm_exec.js are attached to every release (with checksums.txt), or build from source:
GOOS=js GOARCH=wasm go build -tags tinygo -o cheesse.wasm .Call the API from JavaScript
The binary exposes the full API as synchronous JS globals. Every function takes a
Uint8Array containing a JSON request and returns a Uint8Array containing a JSON
response (same shapes as the HTTP endpoints; {"error": "..."} on failure):
const enc = new TextEncoder(), dec = new TextDecoder();
const call = (fn, obj) => JSON.parse(dec.decode(fn(enc.encode(JSON.stringify(obj)))));
JSON.parse(dec.decode(cheesseDefaultGame()));
call(cheesseParseGame, {game: {fenString: "..."}});
call(cheesseDoAction, {game: {}, action: {fromSquare: "e2", toSquare: "e4"}});
call(cheesseParseNotation, {game: {}, notationString: "1. e4 e5"});
call(cheesseConvertNotation, {game: {}, notationString: "1. e4 e5", targetNotation: "ICCF"});
call(cheesseAIMove, {game: {}, mode: "random"}); // random|easy|medium|hardBenchmark results (auto-updated on every push)
Updated by CI on the next merge to master.
That's roughly how kiwi people pronounce chess.