Skip to content
Puzzle #5: Tic Tac Toe
Share
Explore
Puzzle solution

icon picker
How we did it

The solution, step-by-step
Let’s get to what you probably came here for — how to solve the puzzle. The puzzle breaks down into three parts: setting up the game board, making moves, and handling win conditions.

Setting up the board

First things first, you need a game board. You know from the start that you’re going to need nine buttons to play with. The trick is to realize that the button itself can’t store whether the square is empty, or an X, or an O. So you’ll need either hidden columns or a separate table for that.
With that in mind, there were two ways we thought to set this up. One possibility is to use a grouped table with nine rows, like this:
Another option is to use a regular table with three rows and three columns, and store the values of the buttons in either three hidden columns, or an entirely separate table. That might look like this:
The advantage of the three-row solution is that you can get rid of the visual clutter of the group header cells in the final board. But, the grouped solution makes the rest of the work simpler. For example, if you have three separate columns, you have to configure the buttons for each column separately. In the grouping solution, since there’s only one button column, you can configure all the buttons at the same time. In your solutions, we saw several working examples of both approaches. But, because of the simplicity, the grouping solution is our favorite and the one we’ll walk through for the rest of this post.
Before moving on, we’d also like to give a quick shout-out to a puzzler who surprised us with a creative solution: . Instead of storing the moves in a fixed table, Martin stored the moves for his game by adding a row to a ‘Plays’ table every time a move was made:
He then used a Filter formula on the main board to find and display the matching move for each square.
Neat!

Making Moves

Once you have your board set up, you need to configure your buttons to play properly. Most of you figured out that this needs a Modify Rows action which writes either an X or an O to the corresponding value cell, depending on whose turn it is. In the grouping solution, that would look like this:
Now, you have to figure out whose turn it is. Most of you used a canvas formula to do this, and did it by comparing the number of X’s and O’s on the board, with a formula like
= If(Board.CountIf(value = "X") <= Board.CountIf(value = "O"), "X", "O")
If you didn’t know we had a CountIf formula, you may have used Filter followed by a Count instead. Another clever solution we saw was to get the parity of the total number of moves played, like so:
= If(Board.CountIf(value.isNotBlank()) % 2 = 0, "X", "O")
Finally, you could even use our Modified() formula to figure out what the last move was, and then set the next move to the opposite:
Last move = Board.filter(value.isNotBlank()).Sort(false, [Modified on]).First().valueNext move = If([Last move] = "X", "O", "X")
Another detail is how to make sure you can only play in empty squares. The most straightforward way to do that, which almost all of you did, is by disabling the buttons if the value is non-empty:
Finally, once you get all that down, you need to figure out how to reset the board. You can do this by making a button which modifies every row in the table to have an empty value:
Putting that all together, you get something like this:
And there you have it. You can now play Tic Tac Toe to your heart’s desire. But as you know, the puzzle didn’t stop there. After all, any real computer game would tell you when you’ve won, right?

Handling winners

Luckily, there are only eight win conditions in Tic Tac Toe, so you can enumerate them all pretty easily. We saw a few ways of doing that. Some people put the logic almost entirely in the conditional formats. Others used a hidden column in the Board table to calculate whether each square was a winning square, and then wrote a simple conditional format that highlighted the winning squares. One solution even parsed the board into a 9-character string like “XXOOOXOOX” and used splices and concatenation to figure out if there was any winning “XXX” or “OOO” combination. But, our favorite solutions are those that used a separate table with a lookup column to list out the winning combinations. This requires some overhead, but we love it because it makes the logic clear and reusable, and makes all the formulas simple. That table looks something like this:
The Squares column is a multiselect lookup to the board table, and the formulas on the X win and O win columns are:
Squares.Value.CountIf(CurrentValue = "X") = 3
Squares.Value.CountIf(CurrentValue = "O") = 3
It’s worth noting that, to make the lookup column usable, I had to make sure the board had a useful display column. To that end, I added a Name column to the original board, which concatenates the row and column of each cell. That’s why the rows show up nicely as 1A, 1B, 1C, 2A, etc.
Once that’s all in place, the conditional format on the main board is relatively simple. It just says, color me green if there’s a winning solution which contains me:
Putting that all together, you get a solution that looks like this:
Hide those extra columns, tidy up the formatting a little bit, and there you have it- puzzle solved! 🙌

Bonus Round: CodaBot

*SPOILER ALERT* if you want to try your hand at building your own AI player, don’t read the following!
Now that we’ve got the main puzzle out of the way, it’s time for the really fun part: building an AI player. There are a lot of details here, but it boils down to two basic parts: how the AI makes a move, and how to program it to make a good move. Let’s start with the former.

How to make a move

The theme of this puzzle is buttons, so, to do this, you guessed it — we’ll use a button! First, we’ll choose a default move for the computer to make, like the first open square. To do that, we’ll put the following in a canvas formula called Bot move:
= Board.filter(value.isBlank()).first()
Now, let’s add the button. To make things easier, let’s assume we only want the computer to play as O. Then, the button will read the value of Bot move, find the corresponding cell on the board, and write an O to that square. Here’s the fully configured button:
At this point, we technically have a working bot. But, who wants to play a bot that lets you win every time? Certainly not our Coda puzzle solvers. So, let’s program the bot to play intelligently.

Programming your Bot

First, we need to figure out how we want the bot to play. This can be a pretty deep rabbit hole if you want to program an optimal AI (more on that later), but for now, let’s assume we’re happy with giving the bot a few solid rules of thumb. A few rules come to mind:
If you can win this turn, do it
If the other player can win next turn, stop them
Prefer the middle square, then the corner squares, then the edge squares, in that order

That seems like a pretty good start. So, how to make it happen?
To implement this, we’ll pull a trick from puzzler Tomas Jansson, who programmed his AI using a helper table with one row per square on the board. His solution worked a little differently than the one I’m going to show here in that he relied on a clever numeric system for evaluating both each win condition and each possible move. But the idea is similar: for each possible move, we’ll write formulas to figure out if it’s an available move, if it’s a winning move, etc., and then we’ll search for the best move based on those properties. Our goal is to make a table like this:
The real magic, of course, is in the formulas. The formula for an available move is simple:
= Move.Value.IsBlank()
The formula for a winning move is a little more involved. Essentially, we’re looking for winning combinations that currently contain two O’s and one blank square. To make it easier to identify such combinations, let’s first add an O count column to our Win Conditions table:
With that in hand, the Winning move? formula can be written as:
= [Available?] and [Win Conditions].CountIf(In(Move, Squares) and [O count] = 2) > 0
Similarly, to write theStops win? formula, we can add an X count column to the Win Conditions table and then write the formula as
= [Available?] and [Win Conditions].CountIf(In(Move, Squares) and [X count] = 2) > 0
Finally, for the Middle and Corner columns, we don’t need a formula, since those values never change. Just check 2B for Middle, and check 1A, 1C, 3A, and 3C for Corner.
Now that we have all this information about each possible move, we can put it together to pick the best move. There are a few ways to do this, but my favorite is to assign a score to each move based on which conditions it satisfies, like so:
Now, the best move is just the available move with the highest score. We can now replace our dumb Bot move canvas formula with a much better strategy:
= [Potential moves].Filter([Available?]).Sort(false, Score).Move.First()
And there you have it! It’s not unbeatable, but with this in place, you can play a nice game of Tic Tac Toe against the bot, like so:

Making it Unbeatable

This is mostly outside the scope of this post, but for those of you who really aren’t satisfied until your bot can play the perfect game of Tic Tac Toe, I’d suggest checking out . It talks about the Minimax algorithm, which is a general strategy for optimally playing games that can be modeled by finite decision trees (like the 9 moves in a game of Tic Tac Toe). If you can implement this for Tic Tac Toe in Coda, you’ll probably be my favorite puzzler of all time.

Parting Thoughts

This was a pretty deep dive on how to make Tic Tac Toe in Coda. If you made it this far, congratulations! Hopefully you learned something along the way. Before you go, we have a little inspiration for you, and a challenge. After all this, you might be thinking that this is cool, but Tic Tac Toe is pretty much the simplest game you could imagine. Is that really all you can build in Coda? Obviously not. Consider this game of Boggle built by one of my coworkers, :
Or, consider these two games built by one of our puzzlers, :
Crazy, right? The first game is a decked out submission for this puzzle, with support for multiple players with avatars. You can read more about the second game on .
If any of this is inspiring to you, our ask is simple: make your own game, and send it our way! Who knows, maybe it could be inspiration for the next puzzle, or a shoutout in our next blog post, or just a little something to make your day and ours.
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.