Implementation Details
In this section, we delve into the implementation details of the Tablut AI, known as \tLut. The AI is built upon the Tablut game, a strategic board game with a rich history, and aims to dominate the game using the Minimax algorithm with Alpha-Beta pruning.
Game Representation
The game is represented by the Tablut
class, which inherits from the AIMA (aima.games.Game
) framework. The board itself is represented by the Board
class, and various utilities and heuristics are provided by additional classes.
Board Initialization
The Tablut
class initializes the game board (Board
object) with a default size of 9x9. The initial state includes the board configuration, the current player to move, and the utility value.
self.initial = Board(height=height, width=width, to_move='WHITE', utility=0)
self.width = width
self.height = height
Game Mechanics
Updating Game State
The update_state
method is responsible for updating the state of the board based on the current positions of the pieces and the current turn. In order to reduce the search space, the list of possible moves is computed based on the current player. For example, if it's WHITE's turn, the list of possible moves is computed based on the positions of the WHITE pieces. This approach significantly reduces the number of possible moves, as the BLACK pieces are not considered. Also the list comprehension is used to generate the list of possible moves, which is a more elegant approach than using nested loops.
Making Moves
The move
method is responsible for making a move on the board, given a specific move tuple. It handles the updating of the board state, checks for captures, and changes the turn. In our code, moves are represented as tuples of the form (from_pos, to_pos)
, where from_pos
and to_pos
are tuples of the form (x, y)
representing the coordinates of the piece. The move
method extracts the starting and ending positions from the move tuple and updates the board accordingly. The check_attacks
method is then called to check for captures. Finally, the turn is changed to the opposite player.
Terminal State and Victory Conditions
The check_win
method checks whether the game has reached a terminal state. This includes conditions such as capturing the king (BLACK wins), the king escaping (WHITE wins), a player being unable to move any checker (that player loses), or reaching the same state twice (draw, which is handled by the Java server).
Caching for Improved Performance
In order to enhance the efficiency of certain functions within this implementation, a caching mechanism has been implemented using the cache
decorator. This decorator is designed to store and retrieve the results of a function based on its arguments. When a function is decorated with cache
, the decorator creates an internal cache dictionary. Before executing the original function, the decorator calculates a unique key derived from the function's arguments, specifically focusing on the pieces
data (which are represented as a NumPy array). The key is calculated by converting the pieces
data to a byte string and storing it in the cache dictionary. The key is then used to check whether the function has been previously executed with the same arguments. This caching strategy significantly optimizes performance by avoiding redundant calculations, especially in scenarios where the function is called with identical input multiple times.
For example, the max_value
and min_value
functions are decorated with @cache
to avoid redundant calculations of the utility of the board.