iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🍫

Enjoy Chomp in Lean this Christmas

に公開

This is the article for Day 16 of the Theorem Prover Advent Calendar 2025.

However, this article will not deal with the intricacies of formal proof.

General-Purpose Programming Language Lean

The Lean language is a general-purpose functional programming language (that somehow also allows you to describe proofs of mathematical theorems). As such, you can write regular programs with it. It incorporates monads and their do notation, allowing you to write code in a style similar to imperative programming languages.
https://lean-lang.org/functional_programming_in_lean/

In this article, we will implement the Poison Chocolate Game (Chomp) as a command-line game using Lean and enjoy playing it.

Poison Chocolate Game: Chomp

The Poison Chocolate Game is a wonderful game for two players, where you take turns trying to force your opponent to eat the piece of chocolate that contains poison.
For detailed rules, please watch the video "Winning Strategy for the Poison Chocolate Game [Yukkuri Commentary]" on the YouTube channel "Ebima Lab."
https://youtu.be/agRtx_XREtA?si=9Zn_v44kdDU0Yo1P

Chomp Program (Generated by LLM)

Nowadays, programs of this scale can be created by generative AIs like ChatGPT and Gemini.
The following code was created by Gemini. I corrected two grammatical errors (specifically, how elements are retrieved from a list).

ChompGame.lean
/-- Game settings: width and height -/
def width : Nat := 5
def height : Nat := 4

/-- Board state
    A list of the heights (number of pieces) of chocolate remaining in each column -/
abbrev Board := List Nat

/-- Create initial board -/
def initBoard : Board := List.replicate width height

/-- Display the board -/
def printBoard (b : Board) : IO Unit := do
  IO.println "   0 1 2 3 4 (列)"
  IO.println "  -----------"
  for r in [0:height] do
    let mut line := s!"{r}| "
    for c in [0:width] do
      -- If the height of that column is greater than the current row r, there is chocolate
      if let some h := b[c]? then
        if r < h then
          line := line ++ "O "
        else
          line := line ++ ". "
      else
        line := line ++ "  "
    IO.println line
  IO.println ""

/-- Parse input -/
def parseInput (s : String) : Option (Nat × Nat) := do
  let parts := s.trim.splitOn " "
  match parts with
  | [rStr, cStr] =>
    let r ← rStr.toNat?
    let c ← cStr.toNat?
    some (r, c)
  | _ => none

/-- Move logic
    Removes all chocolate to the lower right of the specified (r, c).
    Traverses the list from the beginning and does nothing until the specified column c is reached.
    From column c onwards, limits the height by r (min). -/
def makeMove (b : Board) (r c : Nat) : Board :=
  match b with
  | [] => []
  | h :: t =>
    if c > 0 then
      -- To the left of the specified column, so height h remains as is. Move to the next column (decrement c)
      h :: makeMove t r (c - 1)
    else
      -- From the specified column onwards, height becomes min h r. Maintain c at 0 to apply to everything on the right
      min h r :: makeMove t r 0

/-- Main game loop -/
partial def gameLoop (b : Board) (player : Nat) : IO Unit := do
  printBoard b
  let playerNum := player + 1
  IO.println s!"プレイヤー {playerNum} の番です (行 列 を入力, 例: 1 2): "

  let stdin ← IO.getStdin
  let input ← stdin.getLine

  match parseInput input with
  | some (r, c) =>
    let colHeight := b[c]!
    if c >= width || r >= height then
      IO.println "エラー: 範囲外です。もう一度入力してください。"
      gameLoop b player
    else if r >= colHeight then
      IO.println "エラー: そこにはもうチョコがありません。"
      gameLoop b player
    else if r == 0 && c == 0 then
      IO.println s!"\n!!! 毒(0,0)を食べました! プレイヤー {playerNum} の負けです !!!"
    else
      let newBoard := makeMove b r c
      gameLoop newBoard (1 - player)
  | none =>
    IO.println "エラー: '行 列' の形式で数字を入力してください。"
    gameLoop b player

/-- Entry point -/
def main : IO Unit := do
  IO.println "=== CHOMP ゲームへようこそ ==="
  IO.println "左上の (0, 0) を食べると負けです。"
  IO.println "右下の領域を消していきます。\n"
  gameLoop initBoard 0

How to Play

  1. If you haven't installed Lean yet, please do so.
    https://lean-lang.org/install/

  2. Save the Lean source code above as ChompGame.lean.

  3. Run it using the lean command.

lean --run ChompGame.lean

Execution Example

$ lean --run ChompGame.lean
=== Welcome to the CHOMP Game ===
You lose if you eat the (0, 0) at the top-left.
We will clear out areas toward the bottom-right.

   0 1 2 3 4 (col)
  -----------
0| O O O O O
1| O O O O O
2| O O O O O
3| O O O O O

It is Player 1's turn (Enter row col, e.g., 1 2):
1 2
   0 1 2 3 4 (col)
  -----------
0| O O O O O
1| O O . . .
2| O O . . .
3| O O . . .

It is Player 2's turn (Enter row col, e.g., 1 2):
2 0
   0 1 2 3 4 (col)
  -----------
0| O O O O O
1| O O . . .
2| . . . . .
3| . . . . .

It is Player 1's turn (Enter row col, e.g., 1 2):
0 1
   0 1 2 3 4 (col)
  -----------
0| O . . . .
1| O . . . .
2| . . . . .
3| . . . . .

It is Player 2's turn (Enter row col, e.g., 1 2):
1 0
   0 1 2 3 4 (col)
  -----------
0| O . . . .
1| . . . . .
2| . . . . .
3| . . . . .

It is Player 1's turn (Enter row col, e.g., 1 2):
0 0

!!! You ate the poison (0,0)! Player 1 loses !!!

Is Chomp a First-Player Win?

By the way, according to the "Ebima Lab" video, this Poison Chocolate Game (Chomp) is a first-player win game. Since Lean is a theorem prover, I would like to prove this fact using it.
I plan to post an article about the proof at a later date. Please stay tuned.

(Update) I have proved it.
https://zenn.dev/rikitoro/articles/53e6b24364b985

Discussion