/SBU-AI-Othello

othello game with machine learning / java and javafx AI course project

Primary LanguageJava

othello game with Artificial Intelligence

This game was implemented with java and javafx. The MVC architecture is used.

game preview

player vs. player:

video5843941955607728422.mp4

AI vs. player:

Othello-AI.mp4

The project was developed in 3 main phases:

phase 1:

This class GameController implements the game logic for playing two human players. Generally, an enum named status is taken into account to define each cell on the game board, which has three values: black, white, and empty. Likewise, a class coordinate is defined which has two parameters i and j for the coordinates of each cell on the board. In the GameController class, we have the logic of turn taking newTurn. This function implements functions such as changeStatusBoard, calPlaces, updateBoard, newTurn for the players. The calPlaces function calculates the cells that the player can choose to place their mark based on the rules of Othello, before the mark is placed by each player. The changeStatusBoard function also changes the status of the board cells according to the mark placed by each player, and the marks that must be surrounded and their status changed to the color of the player’s marks. Finally, the updateBoard function graphically updates the board based on changes made in the changeStatusBoard function.

phase 2:

The GameWithAIController class implements a logic for playing a human player and an AI agent (Artificial Intelligence). The general logic of this controller is similar to the logic controller for two human players and only differs in few parts. To create an AI agent, we need to form a minimax tree. Therefore, we define a class MiniMaxNode and a class MiniMaxTree. Each node of the MiniMaxNode class represents a state of the game board. Every minimax node has parameters like a node, parent, an array of children, alpha and beta, a Boolean that indicates whether it is of type max or min, and an individual of type Individual that keeps the parameters of the heuristic function (we will discuss more about individual in the machine learning section). It also has functions such as pruning which is responsible for pruning the minimax tree and also a getHeuristic function. The getHeuristic function calculates the suitability of each game state based on a set of strategies and techniques. The features and techniques we have considered are as follows: For each cell on the game board, we take a certain value based on the cell's heuristic value and store it in a two-dimensional array called value. Then we have a parameter d that keeps the sum of the cells' values of the player. So we add the value of each cell filled by the player's markers to d. If it is filled by the opponent's marker, we subtract it from d. Another parameter we consider are the cells that are surrounded by empty spots and not surrounded. The more of these markers the player has, the worse it is, since there is a possibility of changing the color of these markers and being surrounded by the opponent. So we define two variables called ai_front and person_front and calculate the number of front markers for each and store them inside these variables. Then we calculate the suitability of each node based on these two variables and store them in a variable called f. The next parameter we consider is parity, that is, the number of markers each player has on the board. So we define a variable p and calculate the suitability of each node based on the parity and store it in p. The next parameter that we consider is the corners of each page, because from a strategic point of view, the houses are very important (because they are not vulnerable to rivals' traps). So based on this parameter, we calculate the suitability of each state and store it in a variable called c. Other houses that are important to us are the houses adjacent to the corner houses. Unlike corner houses, which are usually filled by players and are rewarded positively, filling these houses is usually negative and has a negative effect if the corner house is empty. So the suitability of each node is also calculated according to this parameter and stored in a variable called l. The next parameter to consider is mobility, which means that the more houses a player can fill in the next move, the better. So the suitability of each node is calculated according to this topic and stored in a variable called m. Finally, the final score is calculated based on these 6 variables, namely l, c, f, p, d, and m, and for each of these 6 variables, a specific weight is taken into account, which in fact are the weights that should be learned in the machine learning section and improved.

As mentioned, there is another class called MiniMaxTree, each instance of which is a tree with nodes of type MiniMaxNode. We have set the depth of this tree to 10. Also, to simplify the logic and reduce the time and memory required for building and processing each tree, at each stage the children of each node are sorted according to their heuristic value, and they are ranked. Then, using the random selection weighted by rank method, only three children of each node are selected and the rest are removed and the tree is expanded.

phase 3:

We have other classes, such as the MachineLearning class and the AIwithAIcontroller class, which have been added for Phase Three, which is Machine Learning. In the MachineLearning class, evolutionary algorithms are implemented that have a sample of a main and a generationCount function, and a number of Population species. The Population class in reality is the same population of our chromosomes. This class has an array of Individuals. The Individual class defined in reality is the chromosomes containing our data, which have an array of 6 genes, since machine learning is for the weights of the heuristic function, which are six numbers. Also, the Population class includes an initialize function that randomly takes a value from zero to a thousand for the genes in the Individual array. Likewise, each Individual has a fitness feature that keeps track of how suitable each chromosome is after calculating it. To calculate the fitness of each Individual, a calculateFitness function is defined in the Population class. In this way, for every two Individuals in the population, it constructs a new sample of the AIwithAIcontroller class and passes it to the AIwithAIcontroller class. In reality, the AIwithAIcontroller class plays two intelligent agents with different weights in the heuristic function of them. Its logic is similar to the GameWithAIController class. The difference is that the other two players are of the AI species, and if the first agent wins, one unit is added to the fitness of the first chromosome or Individual, and if the second agent wins, one unit is added to the fitness of the second chromosome or Individual. Now, in the main function of the MachineLearning class, we create an instance of the MachineLearning class and name it demo. We take the number of population to be 14 and initialize the demo population with this number. Now we have a population of chromosomes whose genes have been randomly taken from zero to a thousand. Now we take a while that runs until the value of generationCount is less than 6. In other words, the termination of the evolutionary algorithm is based on the number of generations produced, which we have taken to be 14 here. So each time the while is executed, one is added to the value of the generationCount variable. Also, in each execution of it, the fitness of each chromosome in the population is calculated by the calculateFitness function in the Population class which was explained. Then the chromosomes are sorted in ascending order according to their fitness. Now we have to select the parents from among the existing chromosomes. We take the number of parents to be two. Now we take the two chromosomes with the highest fitness in the population for selecting the parents. Now for producing two offspring from these two parents, we use the one point cross over approach. We combine the values of the two parents at a randomly selected point. Also, to apply mutation on each offspring, with a probability of four sixths (or two thirds), we randomly select a gene of each chromosome and change its value to a new random value from zero to a thousand. Finally, to select the survivors, we replace these two offspring with the two chromosomes with the lowest fitness in the current population. Then the survivors of this generation become the new population to the next phase in the while loop. After the while loop is complete, the existing population is sorted according to fitness and the genes of the chromosome with the highest fitness are taken as the final answer and the weights of the heuristic function.