The AI
The AI I am building for 2048 will be written in C++ and imported into the C# game. It is written in a different language for two primary reasons – firstly because using pointers and references significantly reduces the memory requirements and increases the speed of the program when searching through potentially large grids of cells, and secondly because I would personally like to get better at the language through practice.
The speed advantage is not to be ignored. It is possible that the AI could be adapted later to search at greater depths, in which case the time complexity increases rapidly, along with total time. It is also possible that the AI could be asked to perform on larger grids – the UI has the capability to play on grids up to 100 cells square, and that requires a lot of searching and a lot of memory if references are not employed.
The Principle
When playing 2048, one of the most effective strategies is to build chains of ascending power of two. This means that once the chain has been completed down to a 2 or 4, when a new cell is added to the board, it can be joined with the end of the chain to double the final cell. Each subsequent cell in turn can be doubled as it is adjacent to the most recently improved cell, up to improving the largest cell on the board having combined all of the cells in the chain.
However, before the chain can even be created, there are a few ways that the available moves can be narrowed down to make searching more efficient. This includes testing how many moves are possible, and whether the chain can even be built at all: As we will see later, the chain can only be built if there is a cell in the corner of the grid.
Checking the Grid
The first check that is done on the grid is to check which directions are valid moves. It is clearly not worth checking if a move in an impossible direction is best. A side effect of this is that if only one move is possible, it can be selected without doing any further analysis.
It is also possible for the board to become ‘dangerous’, if a particular situation arises where the newly spawned cell can leave the board in a situation where there is only one possible move, and it will move all of the cells on the board. This is likely to mess up the grid, and should therefore be avoided – a move which causes a dangerous board is ‘unsafe’. The situation arises when a move would leave all rows and columns either full of cells or completely empty, except for one which can be filled by the new cell. This is checked before doing anything more with the AI, and if it leaves only one safe move on the grid, this is selected.
Finding the Corner
The next key step in the AI is to start to build the chain. The chain starts at a corner of the board, and snakes outwards in ways that allow moves without disturbing earlier cells in the chain. The idea is to build large cells on edges or corners, to maximise the number of ways the board can move without disturbing the chain, and while keeping a large, continuous free area for manipulating the small cells. Leaving gaps in the corners means new cells can appear in them, and they are very difficult to improve with limited routes in to the cell. The small cells are kept in the middle, and large cells kept in the corners, to avoid this gridlock situation.
To build the chain requires a seed – the point where the chain starts. This is always a cell in the corner of the board, for reasons mentioned earlier, so if (always near the start of the game) a corner is unfilled, the AI will try to fill it. It chooses the corner to be filled based on the weighting of the grid – trying to keep large cells in corners. The corner with the greatest number of large cells near it is chosen, on the basis that it is best suited to building a chain. If it is unfilled, the AI will move to fill this corner with the largest possible cell. However, if it is filled, this corner cell is used as the basis of the chain.
Building the Chain
Now the chain can be built in full. This can be done recursively quite neatly, but it is inherently unstable so this AI favours a loop. The chain spreads out from the corner by searching away from the current cell. The next cell in the chain is found by looking for adjacent cells. The final aim of the chain is to collapse it all into one cell, so it is critical that earlier cells in the chain do not move when combining the later cells: this will break the chain. As a result, the AI only searches in directions opposite to those which will not disturb the chain as built so far.
When a cell is found, the value is analysed. The value found defines what happens to the remainder of the chain. If the value is higher than our current end cell, it cannot be added to the chain because this would form a blockage – it cannot be used to double the current end cell. If a cell is found with the same value as the previous cell in the chain, we can begin to collapse the chain because the penultimate cell can be doubled immediately. Otherwise, if a lower value is found, it is set as the current end cell, and is added to the chain. The search then continues.
A lower value cell can either be exactly half of the previous one, in which case it forms part of a major chain which is ready to be collapsed as soon as the end cell is doubled, or it can be lower than that, in which case it becomes the start of a minor chain. It will take more than one step to get this cell to a value where it can combine with the previous one. This does not change the logic in this AI, but adding a much lower cell to the chain could be given a lower priority than some other moves in other AIs.
Collapsing the Chain
A move within the chain can only be made if the penultimate and final cells are equal. The AI’s chain builder returns a variable specifying whether this is the case. Once it is established that the chain can accept a move, the direction in which the move needs to take place is found. The two end cells in the chain are retrieved and the row and column indices checked, which establishes the direction in which a move must be made in order to combine the two.
It is a result of the careful elimination of different directions in the chain building algorithm that this direction will never be an unsafe direction, so the move can go ahead whenever it is found. If the chain values are halved all the way through, the process will repeat until there is only one cell in the major chain, and then other methods must be used to re-build a chain that can be collapsed once again.
These methods will be described in a later entry.