Solving Probabilistic Tic-Tac-Toe
louisabraham.github.io
louisabraham.github.io
Like in chess, playing against stockfish isn't very fun, but having it as a way to get better is fun.
I could see probabilistic cheese working as each grid square having an odds that the move will "work." Those odds stay the same the whole game. If you try to move from that square, the odds dictate whether you do your move or just lose it and skip your turn.
This would mean that certain squares become very valuable. A queen on a 95% square is much more powerful than a queen on a 40% square. But those squares may not may not be in useful positions. And the battles may be over who gets to put good pieces on those squares.
Sticking your king on a 20% square would be a great way to probabilistically buy yourself time to bring other pieces to its defense, at the cost that the king can't easily try to escape.
That has a nice symmetry to it, where rather than making 95% always "good" and 40% always "bad", they're both valuable for different reasons.
Agreed that 6 sided dice would not detract from the amount of strategy in the game. I think it could be cool to try a physical version of the game where you shuffle premade tiles and place them in the grid - maybe a fun game to teach kids probability :)
Based on a suggestion from a sister comment, I have also added a "Tutor" mode. When hovering over a square in tutor mode, it shows the probability of winning the game given that you a) select that square and b) continue to play optimally for the rest of the game. Both of these options are in the settings menu in the top right of the game.
Finally, I've added a little writeup to the bottom comparing the strengths of the two AI implementations. Enjoy!
https://www.csun.io/2024/06/08/probabilistic-tic-tac-toe.htm...
It was such a power trip and blissful experience to discover the game on HN, think about a strategy, implement a solution, write a blog post (thanks again to orlp for his comment here that pointed out a mistake), email igpay (the author of the game), fork the repo, implement the algorithm in C# for Unity (it was my first time using those technologies but ChatGPT was quite helpful), get it merged and finally make it to the front page of HN where all of this started.
Thank you guys, this is why the Internet exists!
Dynamic programming is a recursive algorithm that caches partial results (from the bottom up).
Apples and oranges
BTW, instead of max(min()) when I wrote mine I used this equality: V'(s) = 1 - V(swap_players(s)), where swap_players(s) switches X and O pieces. And I only need to do max(). Don't know if that would make your program simpler.
Deterministic tic-tac-toe is a very "degenerate" game, for lack of a better word, where the game readily falls into a state where one player is guaranteed to win absent a major blunder. There just isn't much space for actual decision making because most choices are just either obviously necessary or futile.
By making every square have different probabilities like you have, you've created a kind of "terrain" to the board that needs to be considered when making moves, which adds a whole new dimension. Because the layout is randomized, the utility of each square might get re-evaluated from game to game, which adds a whole new dimension to gameplay.
V(s) = max i (min j V(s, i, j))
V(s, i, j) = (probability move i or move j changes the state) * V(new state) + (probability state doesn't change) * V(s, i, j)
You can solve the second equation for all i and j and then use that to solve the first equation.I'm not sure about the first part
V(s) = max i (min j V(s, i, j))
It looks like you suppose that j "precommits" to some move.
But I'm not fully awake yet so you could be right.When the dice rolls ":|" and you pass your turn, the state does change. Namely, whose turn it is changes. V(s, i, j) fails to capture this crucial detail.
In fact I'm not sure that given a board there exists a best solution. I.e. I suspect that for most boards and for most fixed strategies you can create a counter-strategy that outperforms the given strategy (but perhaps then loses to some other strategy).
My code looks completely different, but I suspect you can uncover alternate solutions if you mess with the binary search. For example replace `x = (a + b) / 2` with `x = (a + b) / 3`, or even better pick a random point between a and b.
V(s)=max c sc×V′(s+c) + fc×V′(s−c) + nc×V′(s)
V′(s)=min c sc×V(s−c) + fc×V(s+c) + nc×V(s)
You have to solve for all states simultaneously.
One way to solve them is by using fixed point iteration. aka successive approximation.
Pick some small alpha and update the value function in a gradient ascent kind of way.
Vi+1(s) = (1-alpha)Vi(s)+alpha ( max c sc×V′i(s+c)+ fc×V′i(s−c) + nc×V′i(s) )
V′i+1(s) = (1-alpha)V'i(s)+ alpha ( min c sc×Vi(s−c) + fc×Vi(s+c) + nc×Vi(s) )
Proof:
Suppose that it is possible that different strategies exist that outperform each other. Lets consider the case that there are three "optimal" strategies that counter each other, call them Rock Paper and Scissors. Since these are the three "optimal" strategies, both players know the exact move produced by these strategies. The first important part to note is that there is no cost to changing strategy during the game. If an opponent has one fixed strategy, say Rock, the "optimal" strategy is to change your strategy to Paper. Since you have full information on the state of the board, you do not have to guess the strategy of the opponent, because there is no hidden information. Now you could consider the opponent strategy to be hidden information, but this doesn't hold for the following reason: If knowing the opponent strategy would alter your choice of move (by for example changing to Rock that specifically counters his hidden Scissors), the opponent can freely change to Paper after you made your move, because he can see that you changed to Rock before making his next move. If it was impossible for the opponent to tell that you changed your strategy, that implies that each strategy produces the same board state, and that implies the strategies are identical.
What if it's not just rock/paper/scissors, and there are more than 3 strategies?
If there are more than 3 strategies, the same argument stands. IF there are multiple "optimal" strategies, AND these strategies create different "optimal" moves on the same board state, the opponent can just play the optimal counter-strategy after you make your move, because your move is enough information to give away your strategy. If your opponent cannot determine your strategy after you make your move, that implies the different strategies have the same "optimal" move.
I don't have the time to rigorously prove there's always an optimal solution, but it seems to be the case.
It seems like even a naive search of the game tree in the browser produces a fairly strong computer opponent to a human player. It would be interesting to see if this optimization produces a better computer player to standard expectiminimax as I implemented here: https://github.com/keshavsaharia/pt3/blob/master/src/minimax...
Considering a simple score that utilizes the odds of you getting it vs. your opponent, the paths to or immediate victories/losses opened up is nearly guaranteed to give you the optimal strategy every time.
If a slot has negative odds, meaning it's more likely to help your opponent, you should NEVER play that slot unless you have no dissimilar options.
Many slots, based on position, assume a value of 0 and should be avoided while there's positively valued options available.
If a slot has a > 50% chance of handing you the win, you should always play it. (you could argue the model proves this rigidly at the 57% level)
9 calculations are all you need
To make the question more clear, let's analyze the grid in the top of the blog post. I think it's clear for everyone [1] that the first player must play one of the cells on the top row. Which one is the best move left, middel or right? How is this explained by your strategy?
[1] citation needed
5 of the 9 squares have negative odds. If you play those your expected value is guaranteed to be negative. One of them has even odds, so barring any positional nuance to evaluating it sooner (of which there is none up front), there is an expected value of 0. So off the bat we have eliminated 66% of the search space by doing just 6 comparisons of odds. In fact, we haven't just solved the first turn. The optimal strategy is invariant to the outcome of the first draws. You will ALWAYS want to play the top row until it's complete, regardless of who gets which slot.
Top left has an odds spread of +35 for the player. Top right has an odds spread of +40. So the right is guaranteed to be a superior choice. We haven't even begun to do any sort of nuanced analysis here and we're down to just two options.
Top middle has a superior odds spread of +50. You would need to run some sort of model to derive the exact positional score of the corner vs. the upper middle for a first guess, but I'll tell you based on strong gut instinct, the middle slot with 10 points better odds is going to be the better choice. You can validate this on the new tutor mode if you want.
Here's the algo I would use:
* Calculate all net odds * If there's positive options, ignore all neutral and negative options. If there's no positives but there are neutrals, ignore all negatives
* If there's any slot that offers you a > 50% chance of winning, play the one that does this with the best odds
* Otherwise add one point per unlocked path, (e.g. 3 on a corner at games start, 2 at the side, or 4 for the center) or stopped path for your opponents
* Pick the highest score (net odds + positional points). Break ties with higher probability or otherwise pick randomly.
A few nuances here: * It'd probably be better to use odds ratio instead of net odds but that's trickier to spout off the top of my head
* Probably underweighting the center by a bit for first choice but simplicity is good
Feel free to this against tutor mode and find a counter example.
The logic typically just gets easier and easier as the game goes on too.
In particular, in this example it'better to play top right than top middle.
On average for boards where both positions don't appear in the same board over a few samples, the corner was rated an average of 1 p.p. higher but it's not apples to apples when its across different boards as the scores bake in the weight of first movers advantage. I can't be arsed to test this particular board in the example with manual code.
There's no such thing as an unbeatable strategy. My point is not that the greedy solution is superior at playing the game. My point is that it will perform with very nearly perfect play with more than 1000 times few calculations. With tuning it could very likely achieve provably perfect play.
You could similarly use the logic of this greedy strategy to simply remove obviously incorrect spaces of the dynamic equation to get globally optimal answer with probably > 95% fewer calculations on average.
This kind of greedy vs. slow but globally optimal solutions are super common in real world problems.
I do see a lot of people using AI to mean specifically and only neural networks lately. I think this new use of the term is causing some confusion.
Also note that in "make a really strong AI for this" AI is not a tool, it is the end-result. It is the agent that plays the game. I never said "solve with AI".
What is discussed in the article is AI. If you want to contrast it with the modern neural network / machine learning paradigms this flavour is often labeled GOFAI (good old fashioned artificial intelligence). https://en.wikipedia.org/wiki/GOFAI