suntzuchess

module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 20, 2026 License: MIT

README

Sun Tzu Chess

A terminal program that teaches chess strategy through Sun Tzu's Art of War.

Each of the book's thirteen chapters becomes a campaign of lessons. Chapter IX catalogs the signs an army leaves behind, so its campaign is about reading what your opponent's last move gave away.

A lesson opens with a verse, then puts you in positions and asks for a move. Every answer gets a reply, right or wrong, and the mistakes you repeat turn up in a dossier. There is no free play and no computer opponent.

Six campaigns of the thirteen are written, five lessons each: Read the Board, on chapter IX, Know Yourself, on chapter III, Be Unbeatable First, on chapter IV, See Ahead, on chapter XIII, Do Not Dawdle, on chapter II, and Weak and Strong, on chapter VI. The map lists the other seven and marks them as not written.

SUN TZU CHESS                                     the art of war on 64 squares
──────────────────────────────────────────────────────────────────────────────
›  1. Read the Board         ch. IX                                       0/5
   2. Know Yourself          ch. III                                      0/5
   3. Be Unbeatable First    ch. IV                                       0/5
   4. See Ahead              ch. XIII                                     0/5
   5. Do Not Dawdle          ch. II                                       0/5
   6. Weak and Strong        ch. VI                                       0/5
   7. Direct and Indirect    ch. V                                not written
   8. The Devious Route      ch. VII                              not written
   9. The Ground Decides     ch. X                                not written
  10. Nine Grounds           ch. XI                               not written
  11. Attack by Fire         ch. XII                              not written
  12. The Whole Plan         ch. I                                not written
  13. When Rules Break       ch. VIII                             not written

  Chapter IX is a catalog of signs: dust, birds, the tone of an envoy's
  voice. Every chess move is a sign of the same kind. Learn to read the one
  that was just played before you reach for one of your own.
──────────────────────────────────────────────────────────────────────────────

↑↓ choose   enter open   p precepts   d dossier   s settings   q quit

A lesson puts the board beside the question, and names every marked square in the margin:

SUN TZU CHESS                                                    What the Last Move Did  3/6
────────────────────────────────────────────────────────────────────────────────────────────
 8  ♜  ·  ♝  ♛  ♚  ♝  ♞  ♜     Black has just played the knight from c6 to a5, attacking
 7  ♟  ♟  ♟  ♟  ·  ♟  ♟  ♟     your bishop. Read the whole move before you answer it, then
 6     ·   ~ ·     ·     ·     play your move.
 5  ♞~    ·     ♟     ·
 4     ·  ♗- · [♙] ·     ·     ~ c6 a5 the knight's journey
 3  ·     ·     ·  ♘  ·        - c4 your bishop, and what the move wants you to look
 2  ♙  ♙  ♙  ♙     ♙  ♙  ♙         at
 1  ♖  ♘  ♗  ♕  ♔     ·  ♖
    a  b  c  d  e  f  g  h     your move: enter picks up and puts down, : types it out

   White to move
────────────────────────────────────────────────────────────────────────────────────────────

hjkl move   enter select   : type   ? hint   esc lessons

The square the question turns on, e5, is not marked. It appears once the move has been played, because a label that names it answers the question.

Installing

go install github.com/sss7526/suntzuchess/cmd/suntzuchess@latest

From a clone, make build puts the binary in bin/, and go run ./cmd/suntzuchess runs it without installing anything.

Running it

suntzuchess
flag effect
-ascii start in the letters board style
-no-color no styling; the NO_COLOR environment variable does the same
-light assume a light terminal background instead of asking the terminal

The window needs at least 64 by 18. Below 71 columns the board moves above the text instead of beside it.

Where your progress is kept

Everything the program remembers about you goes in one file, progress.json: which lessons you have finished, how many questions you answered, got wrong and took a hint on, when you last played each one, the mistake tags behind the dossier, and your board style.

system file
Linux and BSD ~/.config/suntzuchess/progress.json, or $XDG_CONFIG_HOME/suntzuchess/ if you set that
macOS ~/Library/Application Support/suntzuchess/progress.json
Windows %AppData%\suntzuchess\progress.json

Set SUNTZUCHESS_HOME to a directory to keep it somewhere else. The file is plain JSON, so you can read it, copy it between machines, or delete it to start over. Nothing leaves your computer, and the program makes no network connections.

Keys

On the campaign map and the lesson list, arrows or hjkl move and enter opens; on the map, g and G also jump to the first campaign and the last. p opens the precepts index, d opens the dossier, s opens settings, q quits.

Inside a lesson:

key what it does
hjkl, arrows move the cursor around the board
enter or space pick up the piece under the cursor, then put it down
enter once you have answered the question, go on to the next one
: type the move instead: Nf3, exd5, O-O, or g1f3
, step a study back one move
1 to 9 answer a multiple-choice question
? hint
f turn the board around
esc back to the lesson list

A question that wants a move wants the move, not the square. Select the piece and send it to the target: to win the pawn on e5 you play the knight from f3, you do not point at e5. Putting the cursor on a square you cannot pick up names the pieces that could go there instead.

Board style

Terminal fonts disagree about the chess symbols. Some draw the outline set (♔♕♖♗♘♙) and the filled set (♚♛♜♝♞♟) as clearly different shapes, some draw them nearly alike, and some substitute a font that does neither. Press s on the campaign map to see a board drawn in each style, and take whichever one your terminal renders clearly. The program remembers the choice and uses it in every lesson.

style pieces
Diagram outline for White, filled for Black, both in one dark ink on tan squares
Contrast outline for White, filled for Black, kept light and dark on gray squares
Solid one filled set for both sides on gray squares, told apart by color
Solid Wood the same, on darker wooden squares
Letters uppercase for White, lowercase for Black

Diagram and Contrast tell the sides apart by shape, so they survive -no-color. The two Solid styles have only color to work with, and fall back to letters when color is off.

If both sides come out the same dark shape, your terminal is drawing the filled glyphs from a fallback font that colors them itself and ignores the foreground. VS Code's built-in terminal does this; GNOME Terminal does not. Pick Diagram, Contrast or Letters there.

The thirteen campaigns

They run in chess-difficulty order rather than the book's. Chapter I opens by weighing five factors, which a beginner cannot do until the factors have been taught, so it comes near the end. The precepts screen browses the same material in the book's own order.

# campaign chapter
1 Read the Board IX, The Army on the March
2 Know Yourself III, Attack by Stratagem
3 Be Unbeatable First IV, Tactical Dispositions
4 See Ahead XIII, The Use of Spies
5 Do Not Dawdle II, Waging War
6 Weak and Strong VI, Weak Points and Strong
7 Direct and Indirect V, Energy
8 The Devious Route VII, Manœuvering
9 The Ground Decides X, Terrain
10 Nine Grounds XI, The Nine Situations
11 Attack by Fire XII, The Attack by Fire
12 The Whole Plan I, Laying Plans
13 When Rules Break VIII, Variation of Tactics

The precepts index

p opens the whole of Giles' translation, a chapter at a time, with and to move between chapters and ↑↓ to scroll. All thirteen chapters are there in full, whether or not a campaign has been written for them, and a marks each verse a lesson quotes.

The dossier

Press d for the dossier. Every wrong answer you give carries tags naming the mistake, the tags add up across sessions, and the dossier lists the faults you keep repeating with an explanation of each. Chapter III tells you to know both sides. The dossier is the half about yourself.


The rest of this file is for anyone writing a lesson or working on the program.

Writing a lesson

Lessons are data. Adding one means writing YAML under corpus/, not Go.

corpus/
  faults.yaml                    the mistake tags and what each means
  book/giles.yaml                the whole translation, for the precepts index
  verses/giles.yaml              the shorter quotations lessons show
  campaigns/01-read-the-board.yaml
  campaigns/02-know-yourself.yaml
  campaigns/03-be-unbeatable-first.yaml
  campaigns/04-see-ahead.yaml
  campaigns/05-do-not-dawdle.yaml
  campaigns/06-weak-and-strong.yaml

A campaign holds lessons, a lesson holds an ordered list of beats, and a beat is one of seven kinds:

  • precept quotes a verse and says what it means on a board
  • study walks an authored line with commentary while the player watches
  • challenge asks for one move
  • gauntlet asks for several in turn, answering each with the opponent's reply
  • drill asks one move in each of several unrelated positions
  • choice asks a judgment question that no single move answers
  • debrief closes the lesson

A gauntlet threads one game through its steps, so only the beat sets a fen. A drill sets one on every step, because its questions have nothing to do with each other; that is what makes it the beat for repeating a shape until it is a habit.

A challenge, with the shape of an answer key:

- type: challenge
  fen: "r1bqkbnr/pppp1ppp/8/n3p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 4 4"
  ask: What did the knight stop defending when it left?
  marks:
    - squares: [e5]
      role: target
      label: the knight on c6 was the only guard here
  reveal:
    - squares: [e5]
      role: target
      label: the knight on c6 was the only guard here
  answers:
    - move: Nxe5
      say: A clean pawn.
  mistakes:
    - move: Bd5
      say: You answered the threat and never asked what it cost them.
      tags: [reactive-play]
  otherwise: That move does not touch e5.
  otherwise_tags: [reactive-play]
  hint: Count the defenders of e5 now that the knight has left c6.

marks show with the question and reveal waits until it has been answered. A label that names the loose piece, the weak square or the count answers the question it sits beside, so it goes in reveal. Every step of a gauntlet or a drill may reveal, because an answered question keeps the screen until the player moves on.

otherwise_tags file a fault for a wrong move the step did not name, which is most of them. Keep them general: this catches everything, so a specific tag would be a guess.

A beat starts from any position. Chapter X is about pawn structure and chapter XI about cramped and closed positions, so those campaigns want middlegames, not openings.

mistakes names wrong moves worth answering specifically; otherwise catches everything else. List every sound move you can find as an answer, partial: true where it is not the best one: an unlisted good move is scored wrong and told to try again, which is the worst thing the format does. Mark roles are weak, target, attacker, path, good and bad. Every mark also prints in the legend beside the board, so no annotation depends on color.

Write prose in YAML folded scalars (>-). A blank line between two of them becomes a paragraph break on screen.

Checking it
make validate          # or: go run ./cmd/suntzuchess validate

This replays the whole corpus against the rules of chess and refuses anything that would strand a player or teach them something the board does not do: an unparseable FEN, an authored move the position does not allow, a move that is both accepted and refuted, a gauntlet step with no opponent reply before the next question, a precept quoting a chapter its campaign does not teach, a mistake tag missing from faults.yaml, a lesson that does not end in a debrief, and a good deal more. The same walk runs as a Go test, so bad content breaks the build rather than a player's session.

With a UCI engine on PATH, make validate-engine also checks that every accepted answer is within a pawn of the engine's choice, that no move called a mistake comes that close, and that no partial outscores the answer above it. It looks for stockfish, lc0 and komodo, and SUNTZUCHESS_ENGINE names one directly.

Where a lesson has to walk the second-best move, because the position after it is what the next question needs, the answer says teaching_line: true and validate prints the list. Without it that pair reads as graded the wrong way round, which is the mistake the check exists to catch.

This is an authoring tool. Lessons ship with authored answer keys and run identically with no engine installed.

Building and testing

make on its own lists every target.

target what it does
make ci the whole gate: formatting, vet, tests, lint, govulncheck, the corpus check, a build, and a smoke test
make test the test suite
make cover-report per-package coverage as a bar chart
make build the binary in bin/, with the version from git describe
make dist cross-compiled archives and checksums for Linux, macOS and Windows on amd64 and arm64

Nothing beyond the Go toolchain needs installing. go run fetches golangci-lint and govulncheck at pinned versions.

Layout

cmd/suntzuchess/    entry point, flags, the validate subcommand
corpus/             the lesson content, embedded into the binary
internal/game/      the chess rules library, wrapped for what lessons need
internal/board/     Render(position, overlay, options) string
internal/content/   beat types, the YAML loader, and the linter
internal/lesson/    the beat runtime: judges a move, produces the text
internal/profile/   progress and mistake tags on disk
internal/engine/    the optional UCI check
internal/ui/        the Bubble Tea front end

board.Render turns a position and an overlay into a string and touches nothing else, and internal/lesson judges a move without drawing anything. Neither needs a terminal to test. Tests drive internal/ui by feeding it key messages and reading the rendered screen back.

Built on

corentings/chess for the rules, Bubble Tea and Lip Gloss for the terminal, and goccy/go-yaml for the content files.

The text

The text is Sun Tzu on the Art of War, translated by Lionel Giles in 1910 and long in the public domain. book/giles.yaml carries the treatise entire, 383 passages over thirteen chapters, keeping Giles' spelling and his verse numbers, including the headings where he printed two verses together. His running commentary and his footnotes are left out.

A lesson shows a shorter quotation, cut at a sentence boundary where a long verse turns to a subject the lesson does not use. Those live in verses/giles.yaml, and the linter checks each one against the passage it came from, so the two cannot drift apart. It also refuses a passage that reads like Giles' commentary rather than the treatise, because the plain-text source does not always mark where one ends and the other begins.

License

MIT. See LICENSE.

Directories

Path Synopsis
cmd
suntzuchess command
Command suntzuchess teaches chess strategy through Sun Tzu's Art of War.
Command suntzuchess teaches chess strategy through Sun Tzu's Art of War.
Package corpus carries the lesson content compiled into the binary.
Package corpus carries the lesson content compiled into the binary.
internal
board
Package board draws a position as terminal text.
Package board draws a position as terminal text.
content
Package content holds the lesson corpus: the verses quoted from the Art of War, and the campaigns that apply them to positions.
Package content holds the lesson corpus: the verses quoted from the Art of War, and the campaigns that apply them to positions.
engine
Package engine is the optional authoring check: it asks a UCI engine whether the answers a lesson accepts really are the best moves, and whether the moves it calls mistakes really are worse.
Package engine is the optional authoring check: it asks a UCI engine whether the answers a lesson accepts really are the best moves, and whether the moves it calls mistakes really are worse.
game
Package game wraps the chess rules library with the operations lessons need: resolving a move a player typed or clicked, listing where a selected piece may go, and replaying an authored line.
Package game wraps the chess rules library with the operations lessons need: resolving a move a player typed or clicked, listing where a selected piece may go, and replaying an authored line.
lesson
Package lesson runs a lesson: it holds the position, decides what a player's move or choice was worth, and produces the text to show them.
Package lesson runs a lesson: it holds the position, decides what a player's move or choice was worth, and produces the text to show them.
profile
Package profile stores what a player has done: which lessons they finished, and which mistakes they keep making.
Package profile stores what a player has done: which lessons they finished, and which mistakes they keep making.
ui
Package ui is the terminal front end: a campaign map, the lesson screen, a browsable index of the verses, and the dossier of the player's own mistakes.
Package ui is the terminal front end: a campaign map, the lesson screen, a browsable index of the verses, and the dossier of the player's own mistakes.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL