Our strategy up to this point is just so-so. It looks just one move ahead. To play a perfect game, you have to consider the move after that (your own move) and the move after that (the opponent's move).

The rules we gave you so far are obvious. There's more than one possible way to finish the strategy program, but we'll go through one possible choice for the third rule.

Finding a Fork

It's not good enough just to look for a winning move for yourself two moves from now. If you can see such a move, so can your opponent, who'll move to block you. So what you have to find is a fork: two triples in which you have one appearance and your opponent has none, and which have a free square in common. This is much easier to see with a picture:

board with fork

X opened the game with the standard opening move, in the center. O responded very badly, on the top edge, and is therefore about to lose the game. X played in the top left corner. O had to respond in the bottom right corner in order to block an immediate win for X. It's X's turn. There is no winning combination with two Xs, nor with two Os. But X can find two winning combinations, the ones marked with the red lines, both of which have one X and two free squares, with one of the free squares in common. In the picture, the common square is 4, the left edge square, and so that's where X should move.

Trick question to make sure you're paying attention: Wouldn't it be better to find a fork in which X has two apperances in each triple? Discuss this with your partner if you don't have a good answer.

The point of the fork is that if X moves in square 4, then X has two winning combinations for next time. It's O's turn, but O can block only one of the two wins, and so X fills out the remaining winning combination two moves from now. For example, if O moves in square 6 to block the center row, then X moves in square 7 to win in the left column. Conversely, if O moves in 7, then X moves in 6 and wins.

The straightforward way to find a fork would be this mess:

really complicated fork script

Don't strain too hard to understand this; it's complicated, because (unlike the usual situation with HOFs) you have to look at every possible pair of triples, to find a number in common between two triples. In this script, singles is a list of those singleton triples with exactly one X (if that's our letter) and two free squares. Therefore, we have to nest two HOF calls. For each item of singles, we make a list forks containing those other triples that share a free square with this one. If that list is nonempty, we've found at least one fork, and so we take the first fork (it's unlikely that there will be two forks at once, but keep doesn't know that, so it reports a list, as usual) and report the number in common.

Luckily, there's an easier way, although it's a little tricky. It depends on noticing that, in the end, we don't really care about the two triples in a fork, but only about the pivot, the number that appears in two triples. So we're going to find the pivot directly, without finding the triples, by "collapsing" the triple structure. Here's the plan:

1. As in the previous version, find the singleton triples.

find singles with keep

2. This gives us a list of triples—that is, a list of lists. Make one big list of all the letters and numbers from the triples:

flatten triples

You don't have the append block in your palette, but it's in one of the Snap! libraries. Click on the file menu icon, and choose "Libraries..." at the bottom of the menu, then choose "List utilities" from the new menu that appears.

(The word atom in computer science means a value that isn't a list. Another name for an atom is a scalar. The use of combine here turns a list of lists into a list of its component atoms.)

Now find a number that appears more than once in atoms. This part is easy:

for loop to find pivot

That should do it!

Take the Offensive

You might think that the fourth rule should be to look for a possible fork for the opponent, and block its pivot. You can do that (if you did all the work above, just use opponent in the call to find fork) easily enough to experiment with the resulting program. What you'll see is that there are situations in which the opponent has two possible forks, and you can block only one of them.

Instead, take the offensive. Just find a singleton triple (one with one of your letter and the other squares free) and pick either square. That will force the opponent to block.

However, it's really important to make sure that you don't force your opponent into a square that creates a fork! If that would happen, just choose another singleton triple in which to attack.

We're not going to go through this rule in any further detail; try it on your own.