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_h2 and p_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.

sample_int100.pgn

Program output

waiting for Run program▍

Variables at end

{ }

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.

try
type
string · 2 characters
length
42 plies · 21 moves
chess
✓ every move is a legal chess move
» compiled.pgn
[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.

Na3

Integer mode

White Knight to a3 begins an integer declaration.

Nc3

String mode

White Knight to c3 begins a string declaration instead.

h2→h3

Variable name

A pawn move selects a variable by file — the h-file pawn becomes p_h2.

b6–h6

Binary encoding

The Black rook walks rank 6 to write 7-bit values, most significant bit first.

Ke2 / Ke1

Loop

King to e2 starts or advances the loop; king to e1 prints the current iteration and clears the buffer.

Bh3…Bf1

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.

» sample_int100.pgn
[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.

» terminal
python3 lonely_chess_runtime.py fizzbuzz_100.pgn

python3 lonely_chess_interpreter.py sample_int100.pgn

Runtime 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.