Artificial Intelligence with Java: Understanding the Min-Max Algorithm in AI Games

Learn the min-max algorithm in this tutorial by Nisheeth Joshi, an associate professor and a researcher at Banasthali University with a Ph.D. in Natural Language Processing.

In order to understand the min-max algorithm, you should get familiar with game playing and game trees. A game tree is made of a root node, which has child nodes. Each child node is then subdivided into multiple children. This forms the tree, and the terminal nodes are termed leaves, as shown in the following diagram:

In game play, the main goal is to win the game; in other words, try to find the best possible solution by looking ahead in the game tree. The most important thing to note about playing a game is that you don’t actually go down to a particular node (or down a complete tree), and you don’t play the entire game. You’ll be in the root position looking for the best option available, in order to maximize your chances of winning the game.

While performing game playing, take turns similar to a game of chess or tic-tac-toe, where you take a turn and then your opponent takes a turn. This means that all children of a particular node will be your opponent’s move.

In min nodes, select the minimum cost successor. Out of all of the successors that you have for a particular node, choose the minimum. In a max node, you should try to find out the maximum successor, because the nodes are your moves.

Now, don’t actually move to a particular point; you are only looking ahead, performing certain computations in the memory, and trying to find the best move possible. The terminal nodes are the winning or losing nodes, but it is often not feasible to search the terminal nodes; so, you apply heuristics to compare the non-terminal nodes. The following diagram illustrates your game tree:

Start at the root node, A. You have two options: either the right subtree or the left subtree. If you select either of the subtrees at random, your chances of losing the game become higher. To avoid this, apply certain heuristics so that your chances of winning the game increase.

Therefore, try to model the game. Suppose you select B; your opponent will have the option to select either D or E. If your opponent selects D, you’ll have the option to select either H or I. Similarly, if your opponent chooses H, you’ll have the option to select either 10 or 11; this is the maximum that can be performed. From this point on, apply heuristics.

In the preceding diagram, the heuristic values of all of the terminal nodes can be seen. The game has not yet ended and you are only looking ahead. The heuristic values comprise the maximum depth that you can go for a look ahead. The chances of winning the game at particular points are 10%, 11%, 9%, and so on. These are the terminal values that you have.

Now, suppose your opponent selects the H node. This is a min node, and a min node will always choose a minimum out of its successors. Therefore, the min node will always choose 10, if choosing between 10 and 11. If you move ahead, you have 9 and 11; so, your opponent will select 9. Similarly, your opponent will select the rest of the nodes.

Now, it’s your move. D, E, F, and G are the max nodes. The max nodes will always choose the maximum value out of their successors. Therefore, you’ll choose 10, 14, 2, and 20 as your nodes. Now it’s your opponent’s move again, and your opponent will always choose the minimum among his successors. This time, he will select 10 and 2. Finally, it is your turn, and you have a max node. Choose the maximum value of the successor: 10. This is illustrated in the following diagram:

So, this is how the gameplay works.

Implementing a min-max algorithm

In this section, you’ll learn to implement the min-max algorithm (a tic-tac-toe example). Start with NetBeans. You’ll have an ArrayList; apply randomization and take input. The following are the four classes that you’ll be working with:

import java.util.ArrayList;

import java.util.List;

import java.util.Random;

import java.util.Scanner;

Now, define the x and y points. In a tic-tac-toe game, there are nine tiles, and, on a one-on-one basis with the opponent, the squares are filled, as shown here:

So, define Point and the x and y points. This will give you the x and y values onto which you have to enter the values. String will return those values. PointAndScore will provide the point value and its score at each particular square, whether it is filled in or not.

The Board class will define the entire nine tiles and take input; this will give you three states. It can be that either X has won, the person who has an X has won, or the person who has a 0 has won, and the available states, if any are Empty:

If X has won, check which values are equal, such as board [0] [0] is equal to [1] [1] and [0] [0] is equal to [2] [2]. This means that the diagonals are equal, [0] [0] is equal to 1, or board 0 is equal to [1] [1]. You have all diagonals, you have any one of the horizontal lines, or you have all three squares in a vertical line. If this happens, return true; otherwise, check the other values on the board. The following part of the code will check the values and return false if they do not comply with the preceding conditions:

Next, look whether 0 has won and do the same thing for 0. Here, check whether the value is 2. Then, if nobody has won, check the available states for the users and print them. You’ll then have placeAMove, and either player 1 will move or player 2.

Next, you have takeHumanInput. Take the human input for the x and y points, and you’ll display the board using the displayBoard method. Finally, apply a min-max algorithm. Check if X has won or if 0 has won; if not, start playing the game, and print the score positions.

Finally, in the main class, start with the one who makes the first move (either the computer or the user). If your user starts a move, you have to provide the values in x and y coordinates (in an x and y plane); otherwise, the computer will start the move, and every time, you’ll have to check whether X has won. If X has won, you’ll print Unfortunately, you lost!, and if 0 has won, print You won!. If both win, then print It’s a draw!.

Run the program to get the following output:

The preceding output has been printed at the initial position of the port. Now, you have to select your turn. Suppose you enter 1; you’ll get the following output:

The computer’s went first and placed the position at [0] [0]. Now, it’s your move; so, place [0] [2]. This will enter 2 in the last position on your board, as shown in the following screenshot:

Your 2 has been placed at [0] [2]. The preceding screenshot shows your current positions. The computer has placed a mark on [1] [0]. Now, place a mark on [2] [0], as follows:

You now have a position over [2] [0] and blocked the computer. Now, the computer has entered 1 at [1] [1]. Put a mark on [1] [2] and block the computer again:

The computer has entered 1 at [2] [2] and won the game.

If you found this article interesting, you can explore Nisheeth Joshi’s Hands-On Artificial Intelligence with Java for Beginners to build, train, and deploy intelligent applications using Java libraries. This book will help you to not only have a solid grasp of AI concepts, but you’ll also build your own smart applications for multiple domains.