# A small game

From A Little Lisp for a Small World, the Plazmi Lisp book (https://plazmi.gg/book). Checked against Plazmi Lisp version 2, 12 November 2026. The whole book as one file: https://plazmi.gg/book.md

A game is a toy with rules, a state for each player, and a way to say no. This chapter builds one from start to finish: a pile of stones you play against, where each player has a game of their own and the stone pile keeps a table of wins.

## The rules

Fifteen stones. You and the pile take turns; on each turn you take one, two or three. Whoever takes the last stone wins. There is a way to win every time, if you go second and know it, and the pile knows it: after its turn it always tries to leave a multiple of four.

That gives the toy three jobs:

- `start` begins a game for whoever plays it.
- Listening: a player says how many they take, to the pile.
- `wins` reads out the table.

## Each player's own game

Two visitors can be playing at once, so the state keeps a game for each player, in a map from `who` to the stones left in their game, and a map of wins:

```clojure
{:games {"@mira" 11}
 :wins {"@mira" 0 "pile" 2}}
```

Planning the state first, as a small map with a key for each thing to remember, makes the rest of the toy easy to write: each verb reads one or two keys and puts one or two back.

## Saying no

`refuse` is `say` for a verb that won't do what was asked. Its line is said, and nothing the verb did is kept: no `put!` stands. In a chat app, the reply is marked as not done. Use it when a player asks for something the rules don't allow:

```clojure
(refuse self "There's no game of yours on. Start one with start stone pile.")
```

## The whole toy

```clojure
(defobject stone-pile
  {:name "Stone pile"
   :state {:games {} :wins {}}
   :verbs {:start (fn [self who]
                    (put! self :games (assoc (:games self) who 15))
                    (say self "Fifteen stones. Take 1, 2 or 3 a turn; whoever takes the last wins. Say to the stone pile how many you take."))
           :wins (fn [self who]
                   (let [table (:wins self)]
                     (if (empty? table)
                       (say self "Nobody has won yet.")
                       (say self (join ", " (map (fn [[k v]] (str k ": " v)) table))))))
           :give-up (fn [self who]
                      (if (contains? (:games self) who)
                        (do (put! self :games (dissoc (:games self) who))
                            (say self "The stones go back in a heap."))
                        (refuse self "There's no game of yours on.")))}
   :on-hear (fn [self who text]
              (when (:spoken-to self)
                (let [left (get (:games self) who)
                      n (parse-long (or (re-find #"[0-9]+" text) ""))
                      won (fn [k] (put! self :wins (update (:wins self) k (fnil inc 0))))
                      over (fn [] (put! self :games (dissoc (:games self) who)))]
                  (cond
                    (nil? left) (say self "There's no game of yours on. Start one with start stone pile.")
                    (not (and n (<= 1 n 3) (<= n left))) (say self (str "Take 1, 2 or 3, and no more than the " left " left."))
                    (= n left) (do (won who) (over) (say self "You take the last stone. You win."))
                    :else (let [after-you (- left n)
                                m (mod after-you 4)
                                mine (if (zero? m) 1 m)
                                after-me (- after-you mine)]
                            (if (zero? after-me)
                              (do (won "pile") (over)
                                  (say self (str "You take " n "; " after-you " left. I take the last " mine ". I win.")))
                              (do (put! self :games (assoc (:games self) who after-me))
                                  (say self (str "You take " n "; " after-you " left. I take " mine "; " after-me " left.")))))))))})
```

```text
The stone pile is ready.
```

Read it from the bottom up. When a player takes some stones, the pile works out what's left (`after-you`), and takes enough to leave a multiple of four, or one stone when it can't. If that empties the pile, the pile has won. `won` adds a win to the table, and `over` ends that player's game; both are small functions named in the `let`, so each branch of the `cond` reads as what it does.

Mira plays, and loses:

```text
> start stone pile
[Juniper's stone pile] Fifteen stones. Take 1, 2 or 3 a turn; whoever takes the last wins. Say to the stone pile how many you take.
> say to the stone pile: I'll take 2
[Juniper's stone pile] You take 2; 13 left. I take 1; 12 left.
> say to the stone pile: 4
[Juniper's stone pile] Take 1, 2 or 3, and no more than the 12 left.
> say to the stone pile: 3
[Juniper's stone pile] You take 3; 9 left. I take 1; 8 left.
> say to the stone pile: 3
[Juniper's stone pile] You take 3; 5 left. I take 1; 4 left.
> say to the stone pile: 1
[Juniper's stone pile] You take 1; 3 left. I take the last 3. I win.
```

Then she works out the trick. Going first from 15, a player who leaves the pile a multiple of four each time wins instead:

```text
> start stone pile
[Juniper's stone pile] Fifteen stones. …
> say to the stone pile: 3
[Juniper's stone pile] You take 3; 12 left. I take 1; 11 left.
> say to the stone pile: 3
[Juniper's stone pile] You take 3; 8 left. I take 1; 7 left.
> say to the stone pile: 3
[Juniper's stone pile] You take 3; 4 left. I take 1; 3 left.
> say to the stone pile: 3
[Juniper's stone pile] You take the last stone. You win.
> wins stone pile
[Juniper's stone pile] pile: 1, Mira: 1
> give-up stone pile
[Juniper's stone pile] There's no game of yours on.
```

The last line was a refusal: `give-up` with no game on said no, and kept nothing. And `"@mira"` in the table of wins was said aloud as her name.

## What the game teaches

- **Plan the state first.** `{:games {} :wins {}}`, keyed by `who`, let two players play at once without a line of extra code.
- **Small named helpers.** `won` and `over` are functions named in the `let`, so each branch reads as what it does.
- **Play it as a stranger would.** Mira's "4" and "I'll take 2" were checked: a toy that takes words should expect any words.
- **Refuse what the rules don't allow,** so nothing half-done is kept.

## Try it

1. Let a player choose the size of the pile: `start` gives 15, and a verb `start-big` gives 21.
2. Make the pile say how many games it has played in all, kept in its state.
3. Write a coin-guessing game: `flip` picks heads or tails at random and keeps it for that player; saying "heads" or "tails" to the toy says whether they were right.
4. Make `wins` list the winners best first, with `sort-by`.

## Recap

- A game keeps a state for each player, in a map keyed by `who`.
- Words said to it carry the moves; verbs start, report and stop.
- `refuse` says no and keeps nothing.
- Functions named in a `let` keep a long toy readable.

Next: pictures and dances.


---

Before: [A toy with a memory](https://plazmi.gg/book/memory.md). Next: [Pictures and dances](https://plazmi.gg/book/designs-and-dances.md). Contents: https://plazmi.gg/book.md
