Lonely Chess
A legal PGN chess game, read as source code.
Every move in the game file below is an instruction: a variable declaration, a binary digit, a loop, an arithmetic step, or a print. The runner on this page is a real, independent implementation of the language — not a recording of the Python original.
white to move
ordinary chess rules. every game is also a program — run yours and see what it computes.
Four things worth knowing before you run one
The mechanism, in short.
- A PGN file is the program
- Standard Portable Game Notation — the same format Chess.com and Lichess export — is parsed directly. No custom file format, no preprocessing.
- The board is memory
- Interpreter state lives in named slots like
p_h2andp_g2, one per pawn's home square. - Pieces carry syntax
- Knights, rooks, bishops, kings, and queens each perform a fixed programming role — declaration, encoding, looping, branching, printing.
- Two implementations, one language
- A Python reference implementation ships in the repository; this page runs a TypeScript port held to the same rules — every sample and regression case prints identical output in both, checked by
npm test.
Run it yourself
Paste a PGN program, or load one of the samples below. This parses moves, tracks interpreter state, and prints only when the program reaches an explicit print instruction — the same contract as the Python runtime.
Write a message, get a chess game
Type a number from −127 to 127, or a short line of plain text. The compiler writes the Lonely Chess program that prints it, then replays it on a real chess engine to check that every move is legal — so it can actually be played over a board.
[Event "Compiled Lonely Chess program"]
[Site "lonely-chess compiler"]
[Result "*"]
{ String p_d2 = "gg"; print(p_d2) }
1. Nc3 a5 { begin string }
2. d3 a4 { declare p_d2 }
3. d4 Ra6 { rook enters rank 6 }
4. Ne4 Rb6
5. Nc3 Rc6
6. Ne4 Rf6
7. Nc3 Rg6
8. Ne4 Rh6
9. Nc3 Ra6 { 'g' = 103 }
10. Ne4 Ra8 { next character }
11. Nc3 Ra6
12. Ne4 Rb6
13. Nc3 Rc6
14. Ne4 Rf6
15. Nc3 Rg6
16. Ne4 Rh6
17. Nc3 Ra6 { 'g' = 103 }
18. Nb1 Ra8 { commit p_d2 (2 chars) }
19. d5 Nc6 { d-pawn: select p_d2 }
20. Qd2 Nb8 { queen out: print }
21. Qd1 Nc6 { queen home: output "gg" }
*
How the language reads
Every chess move either changes the interpreter's mode, writes data, performs arithmetic, controls flow, or prints — never more than one at a time.
Integer mode
White Knight to a3 begins an integer declaration.
String mode
White Knight to c3 begins a string declaration instead.
Variable name
A pawn move selects a variable by file — the h-file pawn becomes p_h2.
Binary encoding
The Black rook walks rank 6 to write 7-bit values, most significant bit first.
Loop
King to e2 starts or advances the loop; king to e1 prints the current iteration and clears the buffer.
If + modulo
Bishop opens the if-block, the a-rook opens modulo, the black rook bounces to count the divisor, and the a-rook returns to a1 to evaluate the match.
Real programs, unabridged
Six sample files straight from the repository, shown in full — no shortcut ellipses. The last one is a fully legal chess game that prints 100.
[Event "Live Chess"]
[Site "Chess.com"]
[Date "2026.04.29"]
[Round "?"]
[White "Lessrtrer"]
[Black "6nicor"]
[Result "0-1"]
[TimeControl "600"]
[WhiteElo "507"]
[BlackElo "998"]
[Termination "6nicor won - game abandoned"]
[ECO "A00"]
[EndTime "1:11:17 GMT+0000"]
[Link "https://www.chess.com/game/live/167970268718"]
{ === LONELY CHESS: int p_h2 = 100 then print(p_h2) === }
{ --- SETUP PHASE --- }
{ Move 1: W Knight b1->a3 signals: begin INTEGER instantiation mode }
{ Move 1: B pawn a7->a5 filler move }
1. Na3 a5
{ Move 2: W pawn h2->h3 declares variable name p_h2 (based on pawn home square h2) }
{ Move 2: B pawn a5->a4 filler move }
2. h3 a4
{ Move 3: W pawn h3->h4 second pawn push, no-op (var already declared) }
{ Move 3: B rook a8->a6 arms encoding mode (rook reaches 6th rank) }
3. h4 Ra6
{ --- ENCODING PHASE: writing 1100100 = 100 in 7-bit binary --- }
{ b6=bit6, c6=bit5, d6=bit4, e6=bit3, f6=bit2, g6=bit1, h6=bit0 }
{ Move 4: W Knight a3->c4 no-op wait }
{ Move 4: B rook a6->b6 sets bit6 -> acc = 1000000 }
4. Nc4 Rb6
{ Move 5: W Knight c4->a3 no-op wait }
{ Move 5: B rook b6->c6 sets bit5 -> acc = 1100000 }
5. Na3 Rc6
{ Move 6: W Knight a3->c4 no-op wait }
{ Move 6: B rook c6->f6 sets bit2 -> acc = 1100100 = 100 }
6. Nc4 Rf6
{ Move 7: W Knight c4->a3 no-op wait }
{ Move 7: B rook f6->a6 finalises value: 1100100 = 100 }
7. Na3 Ra6
{ --- FINALISE PHASE --- }
{ Move 8: W Knight a3->b1 commits p_h2 = 100 }
{ Move 8: B rook a6->a8 encoding complete, resets to start position }
8. Nb1 Ra8
{ --- PRINT PHASE: print(p_h2) --- }
{ Move 9: W pawn h4->h5 selects p_h2 for printing (pawn home = h2) }
{ Move 9: B pawn e7->e6 filler }
9. h5 e6
{ Move 10: W Queen d1->d2 initiates print (path now clear after pawn moved) }
{ Move 10: B pawn e6->e5 filler }
10. Qd2 e5
{ Move 11: W Queen d2->d1 finalises print -> OUTPUT: p_h2 = 100 }
{ Move 11: B pawn e5->e4 filler }
11. Qd1 e4
0-1
Run it locally
Pure Python 3.6+, no dependencies.
python3 lonely_chess_runtime.py fizzbuzz_100.pgn
python3 lonely_chess_interpreter.py sample_int100.pgnRuntime vs. interpreter
lonely_chess_runtime.py prints only final program output. lonely_chess_interpreter.py annotates every move — better for debugging and for watching each chess move change the program state.