I am writing a program for the game of Chomp. You can read the description of the game on Wikipedia, however I’ll describe it briefly anyway.
We play on a chocolate bar of dimension n x m, i.e. the bar is divided in n x m squares. At each turn the current player chooses a square and eats everything below and right of the chosen square. So, for example, the following is a valid first move:

The objective is to force your opponent to eat the last piece of chocolate (it is poisoned).
Concerning the AI part, I used a minimax algorithm with depth-truncation. However I can’t come up with a suitable position evaluation function. The result is that, with my evaluation function, it is quite easy for a human player to win against my program.
Can anyone:
- suggest a good position evaluation function or
- provide some useful reference or
- suggest an alternative algorithm?
How big are your boards?
If your boards are fairly small then you can solve the game exactly with dynamic programming. In Python:
The function wins(board) computes whether the board is a winning position. The board representation is a set of tuples (x,y) indicating whether piece (x,y) is still on the board. The function moves computes list of boards reachable in one move.
The logic behind the wins function works like this. If the board is empty on our move the other player must have eaten the last piece, so we won. If the board is not empty then we can win if there is
anymove we can do such that the resulting position is a losing position (i.e. not winning i.e.not wins(move)), because then we got the other player into a losing position.You’ll also need the memoize helper function which caches the results:
By caching we only compute who is the winner for a given position once, even if this position is reachable in multiple ways. For example the position of a single row of chocolate can be obtained if the first player eats all remaining rows in his first move, but it can also be obtained through many other series of moves. It would be wasteful to compute who wins on the single row board again and again, so we cache the result. This improves the asymptotic performance from something like
O((n*m)^(n+m))toO((n+m)!/(n!m!)), a vast improvement though still slow for large boards.And here is a debugging printing function for convenience:
This code is still fairly slow because the code isn’t optimized in any way (and this is Python…). If you write it in C or Java efficiently you can probably improve performance over 100 fold. You should easily be able to handle 10×10 boards, and you can probably handle up to 15×15 boards. You should also use a different board representation, for example a bit board. Perhaps you would even be able to speed it up 1000x if you make use of multiple processors.
Here is a derivation from minimax
We’ll start with minimax:
We can remove the depth checking to do a full search:
Because the game ended, heuristic will return either -1 or 1, depending on which player won. If we represent -1 as false and 1 as true, then
max(a,b)becomesa or b, and-abecomesnot a:You can see this is equivalent to:
If we had instead started with minimax with alpha-beta pruning:
The search starts out with alpha = -1 and beta = 1. As soon as alpha becomes 1, the loop breaks. So we can assume that alpha stays -1 and beta stays 1 in the recursive calls. So the code is equivalent to this:
So we can simply remove the parameters, as they are always passed in as the same values:
We can again do the switch from -1 and 1 to booleans:
So you can see this is equivalent to using any with a generator which stops the iteration as soon as it has found a True value instead of always computing the whole list of children:
Note that here we have
any(not alphabeta(move) for move in moves(board))instead ofany([not minimax(move) for move in moves(board)]). This speeds up the search by about a factor of 10 for reasonably sized boards. Not because the first form is faster, but because it allows us to skip entire rest of the loop including the recursive calls as soon as we have found a value that’s True.So there you have it, the wins function was just alphabeta search in disguise. The next trick we used for wins is to memoize it. In game programming this would be called using “transposition tables”. So the wins function is doing alphabeta search with transposition tables. Of course it’s simpler to write down this algorithm directly instead of going through this derivation 😉