Article Title

"I'm sorry Dave, I can't allow you to do that". That chilling statement made by the machine HAL in 2001 A Space Odyssey is just one of the many examples of how humans have been fascinated by the idea of artificial intelligence. I myself am very interested in the subject and hope to go back to graduate school for a degree in the field. In the meantime I'm writing small applications like these to demonstrate what AI can do.

This is an ASP.NET application that will play a perfect game of Tic-Tac-Toe using the AI algorithm called minimax. First a little about the game Tic-tac-toe. It is a deterministic game, meaning that all actions have a direct and knowable consequence to the game state. It is what theorists like to call a "Zero Sum" game - where the compluter has complete, or perfect information about the game state, the game space, and the environment. 

As with the majority of things in AI, what the sitation boils down to is search. Here's the components we need to have in order to complete our search of the tic-tac-toe game space:

  • An initial state. For us this is an empty board
  • A successor function. A method which gives us the resulting states that are accessible by making a particular action against the state we pass in.
  • A terminal test. A method that determines if we are in a terminal state - i.e., whether the game is over or not. In our case it would be Player 1, Player 2, or draw (cat's game).
  • A utility function that gives a numeric value for terminal states.

Since tic-tac-toe is a turn-based, two player game, it lends itself to using the minimax algorithm to determine the best move for a given state. The minimax algorithm is a little bit confusing.  Here's a really simple rundown:

In a normal search situation, you use a initial state and determine a sequence of moves to get you to your desired goal state. However, in turn based games, there is not just one player MAX, but the opponent player MIN that we must worry about. MIN's moves can impact the game states that are available to us and indeed can render our desired goal state (where we, MAX win) unreachable. That of course is not satisfactory. What can we do? The MAX player, the first to make a move, must pick a move that has the highest value returned by the utility function.

However, assuming MIN plays optimally, MIN will also try to choose the best move that he can, thus minimizing MAX's outcome. Thus, MAX should make the move that minimizes the value of MIN's utility function. In other words, the computer MAX, should make the move which leaves its opponent capable of doing the least damage.

Hopefully that sheds some light on how the algorith works. For an in-depth discussion  of the details of how minimax works, see this article at the AI Depot. (Note that our algorithm makes use of Alpha-Beta pruning to reduce our search tree).

-------------

Now that we have talked about how our AI logic works, lets look at the architecture of our application. Even though this is a small application, I made use of the N-Tier approach to web programming. Since our example is simple, we will only have 2 layers: our presentation layer, and our business logic. In laymans terms, this is just the layer that displays our information and the layer that contains our logic, respectively. The reason I did this is because it helps us modularize our code for easy re-use and modification, and makes the code much cleaner to read.

I created an ASP.NET User Control to display our tic-tac-toe board, then placed it in an ASP.NET page that configures and displays it. The User Control in turn uses a custom Board object that I created that represents the tic-tac-toe board as an object. I have methods implemented in a separate class that performs the Artificial Intelligence operations on the Board object. 

Board.cs

This is the class that serves as a wrapper class around a simple integer array. The positions in the array map to positions on the board; e.g. the first three spaces on the first row map to the first 3 positions in the integer array. We also implement the ICloneable and IEnumerable interfaces. ICloneable so that we can make an exact duplicate of a board and manipulate it and not touch the original. The IEnuerable interface is optional, but it lets us use a foreach statement to traverse the board, which is handy. The class also includes helper methods like IsFull, that can tell us some information about a given board.

MinimaxAlgorithm.cs

This is where the magic happens. This is the C# implementation of the minimax algorithm with alpha-beta pruning. The class implements an IGameAlgorithm interface which is an interface I created that has on simple method in it named Solve. It take a Board object as an argument and returns the optimal move. I did this so that when calling the algorithm, we can call the interface and not the class itself. That way, if we want to swap out the algorithm for a different one (say with a Q-Learning Reinforcement Algorithm), it will be quite easy. Here's the meat of the code:

 
The Home of Jon © 2007
  1. public class MinimaxAlgorithm : IGameAlgorithm
  2.     {
  3.  
  4.         #region IGameAlgorithm Members
  5.  
  6.         public int Solve(Board board)
  7.         {
  8.             int Omove = -1;
  9.             int v = -2;
  10.             int alpha = -2;
  11.             int beta = 2;
  12.  
  13.             foreach (int i in board.GetValidMoves())
  14.             {
  15.                 Board successor = (Board)board.Clone();
  16.                 successor[i] = BoardPlayer.Player2;
  17.                 int vTemp = Min_Value(successor, alpha, beta);
  18.                 // take max of min values
  19.                 if (vTemp > v)
  20.                 {
  21.                     Omove = i;
  22.                     v = vTemp;
  23.                 }
  24.                 if (v >= beta)
  25.                     break;
  26.                 alpha = Max(alpha, v);
  27.             }
  28.  
  29.             return Omove;
  30.         }
  31.  
  32.         #endregion
  33.  
  34.         private int Max_Value(Board b, int alpha, int beta)
  35.         {
  36.             if (b.Evaluate(BoardPlayer.Player1))
  37.                 return -1; // minimize
  38.             if (b.Evaluate(BoardPlayer.Player2))
  39.                 return 1;  // maximize
  40.             if (b.IsFull())
  41.                 return 0;
  42.  
  43.             int v = -2;
  44.             foreach (int i in b.GetValidMoves())
  45.             {
  46.                 Board s = (Board)b.Clone(); // successor
  47.                 s[i] = BoardPlayer.Player2;
  48.                 v = Max(v, Min_Value(s, alpha, beta));
  49.                 if (v >= beta)
  50.                     return v;
  51.                 alpha = Max(alpha, v);
  52.             }
  53.             return v;
  54.         }
  55.  
  56.         private int Min_Value(Board b, int alpha, int beta)
  57.         {
  58.             if (b.Evaluate(BoardPlayer.Player1))
  59.                 return -1;  // minimize
  60.             if (b.Evaluate(BoardPlayer.Player2))
  61.                 return 1;   // maximize
  62.             if (b.IsFull())
  63.                 return 0;
  64.  
  65.             int v = 2;
  66.             foreach (int i in b.GetValidMoves())
  67.             {
  68.                 Board s = (Board)b.Clone(); //successor
  69.                 s[i] = BoardPlayer.Player1;
  70.                 v = Min(v, Max_Value(s, alpha, beta));
  71.                 if (v <= alpha)
  72.                     return v;
  73.                 beta = Min(beta, v);
  74.             }
  75.             return v;
  76.         }
  77.  
  78.         private int Max(int a, int b)
  79.         {
  80.             return (a > b ? a : b);
  81.         }
  82.  
  83.         private int Min(int a, int b)
  84.         {
  85.             return (a < b ? a : b);
  86.         }
  87.     }
Parsed in 0.115 seconds

As you can see the code is not very complex or long. It's just very recursive, which can be confusing in its own right.

BoardManager.cs

This class encapsulates the different actions that we want to perform on the Board. If we want to make a move on the board, or compute the next best minimax move, this class handles that through a bunch of static functions. It helps to have all of this code localized into one place. 

The Home of Jon © 2007
  1. public class BoardManager
  2.     {
  3.         private static IGameAlgorithm gameAlgorithm = new MinimaxAlgorithm();
  4.  
  5.         /// <summary>
  6.         /// Sets the algorithm used by ComputeBestMove(). By default it is
  7.         /// the Minimax Algorithm.
  8.         /// </summary>
  9.         /// <param name="algorithm">Algorith to use for computing</param>
  10.         public static void SetGameAlgorithm(IGameAlgorithm algorithm)
  11.         {
  12.             gameAlgorithm = algorithm;
  13.         }
  14.  
  15.         /// <summary>
  16.         /// Determines if the board can be played further.
  17.         /// </summary>
  18.         /// <param name="board">Board to evaluate</param>
  19.         /// <returns>True if the game is over</returns>
  20.         public static bool IsGameComplete(Board board)
  21.         {
  22.             if (board.Evaluate(BoardPlayer.Player1) ||
  23.                 board.Evaluate(BoardPlayer.Player2) ||
  24.                 board.IsFull())
  25.                 return true;
  26.             else
  27.                 return false;
  28.         }
  29.  
  30.         /// <summary>
  31.         /// Determines the player who won the current board.
  32.         /// The result can be no-player.
  33.         /// </summary>
  34.         /// <param name="board">Board to evaluate</param>
  35.         /// <returns></returns>
  36.         public static BoardPlayer GetWinner(Board board)
  37.         {
  38.             if (board.Evaluate(BoardPlayer.Player1))
  39.                 return BoardPlayer.Player1;
  40.             else if (board.Evaluate(BoardPlayer.Player2))
  41.                 return BoardPlayer.Player2;
  42.             else
  43.                 return BoardPlayer.NoPlayer;
  44.         }
  45.  
  46.         /// <summary>
  47.         /// Make a move on the given board.
  48.         /// </summary>
  49.         /// <param name="board">Board to make move on</param>
  50.         /// <param name="player">Player who is making the move</param>
  51.         /// <param name="position">Positon the player make the move to</param>
  52.         public static void MakeMoveOnBoard(Board board, BoardPlayer player, int position)
  53.         {
  54.             board[position] = player;
  55.         }
  56.  
  57.         /// <summary>
  58.         /// Computes the best move for the board using the algorithm set by
  59.         /// SetGameAlgorithm. If not set, it defaults to Minimax algorithm.
  60.         /// </summary>
  61.         /// <param name="board">Board to evaluate.</param>
  62.         /// <returns>Index of the best board move.</returns>
  63.         public static int ComputeBestMove(Board board)
  64.         {
  65.             return gameAlgorithm.Solve(board);
  66.         }
  67.  
  68.         /// <summary>
  69.         /// Returns a random move o nthe board.
  70.         /// </summary>
  71.         /// <returns>Index of the random board move.</returns>
  72.         public static int ComputeRandomMove()
  73.         {
  74.             Random r = new Random();
  75.             return r.Next(8);
  76.         }
  77.     }
Parsed in 0.086 seconds

As I mentioned, all the methods that we need to interact with a Board are handled in this class. This is our true "business logic" component of the system. It functions more like a facade design pattern than anything else.

Continue on to the next page to get the rest and the source code... 


Page Page 1 of 2    Next Previous

Trackback

Trackback URL for Entry: http://www.thehomeofjon.net/trackback/receive/45.html

Leave a Comment

Name (required)
E-mail (required, will not be shared)
Website
Visual CAPTCHA Enter security code shown (required)

Receive notifications for new comments