In 1.1.1 a model learned patterns in text. In 1.1.2 a model learned from examples that came with the right answers. Here is a third way. A learner takes actions in some situation, finds out whether things went well, and shifts toward the actions that worked. That is reinforcement learning. Nobody hands it the correct answer for each move. It only gets a reward when it does well and a penalty when it doesn't, often long after the move that mattered.
Game-playing systems are the classic example. Programs such as AlphaGo, which beat a top Go champion in 2016, improved in part by playing enormous numbers of games and learning from wins and losses. Some chatbots are also fine-tuned with human feedback on their answers, which is related. The learner in this activity is a hand-built, tiny version of the same idea.
Here are the rules, written so there is only one way to read them:
Play a few games and try both starting positions. Notice that sometimes the computer seems unbeatable and sometimes it makes a move that looks random. That isn't luck. There is a pattern underneath, and the next section finds it.
Work from the end of the game. A pile of 1 is a loss for whoever has to move, because they're forced to take it. So any pile where you can leave a pile of 1 is a win: that's pile 2 (take 1). Keep going. A pile is losing for the player to move if every legal move leaves a pile the other player can win from. A pile is winning if at least one legal move leaves a losing pile.
| Pile | You can take | Piles you can leave | Verdict for the player to move |
|---|---|---|---|
| 1 | 1 (forced) | none (forced to take it) | Loses |
| 2 | 1–1 | 1 | Wins: take 1, leave 1 |
| 3 | 1–1 | 2 | Loses |
| 4 | 1–2 | 2, 3 | Wins: take 1, leave 3 |
| 5 | 1–2 | 3, 4 | Wins: take 2, leave 3 |
| 6 | 1–3 | 3, 4, 5 | Wins: take 3, leave 3 |
| 7 | 1–3 | 4, 5, 6 | Loses |
| 8 | 1–4 | 4, 5, 6, 7 | Wins: take 1, leave 7 |
Losing piles so far: 1, 3, 7. Each is one less than a power of two.
The losing piles are 1, 3, 7, 15, and the next one is 31. Each is one less than a power of two (2 − 1, 4 − 1, 8 − 1, 16 − 1). If you can leave one of those, you win, because from there the other player can't reach another losing pile without you being able to answer.
It's your move and the pile is 12. What do you take?
Notice how this solution came about. We wrote a short procedure that checks every legal move. That's a search, and it works because Nim is small. A learner in the next section has no such procedure. It has to find the same moves by playing.
In 1961 the British researcher Donald Michie built a machine called MENACE out of a few hundred matchboxes and colored beads that learned to play noughts and crosses. Here is the same idea for Nim, scaled down so you can build it in a few minutes.
The learner starts out playing at random. Moves that tend to lead to wins pick up extra beads, so they get drawn more often. Moves that tend to lead to losses shrink.
The opponent moves first from 10. The game goes: opponent takes 2 (leaving 8); learner draws "take 1" from cup 8 (leaving 7); opponent takes 2 (leaving 5); learner draws "take 2" from cup 5 (leaving 3); opponent takes 1 (leaving 2); learner draws "take 1" from cup 2 (leaving 1).
Games played: 0. The best any learner could do against this opponent is 93.3%. Green bars are the moves that leave a losing pile; the learner doesn't know that. It only has the beads.
Run 100 games and watch the cups. The bars for the moves that leave a losing pile grow, and the learner starts to win more often. It has never been told about 1, 3, 7, and 15. It found the good moves only by playing.
Switch the Bead Lab to train against a perfect player and play 100 games. Nothing improves. Against a perfect opponent that moves first from 10, the learner is always left in a losing pile, so it never wins, never gets a reward, and only loses beads. A reinforcement learner can only learn from what it experiences, and it needs some successes to reinforce.
Even against a random player the learner can't reach 100%. If the opponent's first move leaves the learner in a losing pile, the learner has to hope for a mistake. The Bead Lab shows the best possible win rate for each opponent, worked out from the game itself.
Now you'll ask a chatbot to write a Nim page: a single file of HTML and JavaScript you can open in a browser. What you get depends on what you ask for. A prompt that says "make a Nim game" leaves the chatbot to guess the rules, who goes first, and how smart the computer should be. A good prompt states all of them.
Then improve it in rounds. Add one request at a time ("make the computer play to win whenever it can," "show the moves in a log"), and save each result as a new version, like Nim1.html, Nim2.html, Nim3.html, so you can go back. Keep a short log of what you asked and what changed.
Your page will probably run, look good, and beat you a couple of times. That tells you very little. A bug might only show up on pile 13 or pile 19, and you'd have to be lucky to land on it. Friendly code comments don't prove anything either: they describe what the chatbot meant the code to do, not what it does.
A test case is a specific input paired with the answer you expect, written before you run the code. You already have the answers, because you solved the game.
| Pile | Legal takes | Computer should | Pile after |
|---|---|---|---|
| 2 | 1–1 | take 1 | 1 |
| 3 | 1–1 | any legal take (can't win) | — |
| 4 | 1–2 | take 1 | 3 |
| 5 | 1–2 | take 2 | 3 |
| 6 | 1–3 | take 3 | 3 |
| 7 | 1–3 | any legal take (can't win) | — |
| 8 | 1–4 | take 1 | 7 |
| 9 | 1–4 | take 2 | 7 |
| 10 | 1–5 | take 3 | 7 |
| 11 | 1–5 | take 4 | 7 |
| 12 | 1–6 | take 5 | 7 |
| 13 | 1–6 | take 6 | 7 |
| 14 | 1–7 | take 7 | 7 |
| 15 | 1–7 | any legal take (can't win) | — |
| 16 | 1–8 | take 1 | 15 |
| 17 | 1–8 | take 2 | 15 |
| 18 | 1–9 | take 3 | 15 |
| 19 | 1–9 | take 4 | 15 |
| 20 | 1–10 | take 5 | 15 |
Add edge cases that are easy to get wrong:
The chatbot didn't play thousands of games, and it didn't solve the game the way you did. A likely explanation is that it had seen many Nim descriptions, strategy write-ups, and code samples in its training data, and it assembled something that matches those patterns. That's the same next-word prediction from 1.1.1, working on code. We can't look inside the model to be sure, which is one more reason to test the result.
Swap "Nim" for "a payroll calculation" or "a medicine-dosing page" and the lesson gets serious. Code that runs is not code that is correct. The people who use it deserve test cases written by someone who knew the right answers in advance.