I am currently trying to teach myself the Minimax algorithm and I have tried to implement it in java in tic tac toe. There is a bug in my algorithm however and I can't figure out what's causing it.
Below is the complete source code (Sorry for wall of text!):
public class TicTacToe {
private static boolean gameEnded = false;
private static boolean player = true;
private static Scanner in = new Scanner(System.in);
private static Board board = new Board();
public static void main(String[] args){
System.out.println(board);
while(!gameEnded){
Position position = null;
if(player){
position = makeMove();
board = new Board(board, position, PlayerSign.Cross);
}else{
board = findBestMove(board);
}
player = !player;
System.out.println(board);
evaluateGame();
}
}
private static Board findBestMove(Board board) {
ArrayList<Position> positions = board.getFreePositions();
Board bestChild = null;
int previous = Integer.MIN_VALUE;
for(Position p : positions){
Board child = new Board(board, p, PlayerSign.Circle);
int current = max(child);
System.out.println("Child Score: " + current);
if(current > previous){
bestChild = child;
previous = current;
}
}
return bestChild;
}
public static int max(Board board){
GameState gameState = board.getGameState();
if(gameState == GameState.CircleWin)
return 1;
else if(gameState == GameState.CrossWin)
return -1;
else if(gameState == GameState.Draw)
return 0;
ArrayList<Position> positions = board.getFreePositions();
int best = Integer.MIN_VALUE;
for(Position p : positions){
Board b = new Board(board, p, PlayerSign.Cross);
int move = min(b);
if(move > best)
best = move;
}
return best;
}
public static int min(Board board){
GameState gameState = board.getGameState();
if(gameState == GameState.CircleWin)
return 1;
else if(gameState == GameState.CrossWin)
return -1;
else if(gameState == GameState.Draw)
return 0;
ArrayList<Position> positions = board.getFreePositions();
int best = Integer.MAX_VALUE;
for(Position p : positions){
Board b = new Board(board, p, PlayerSign.Circle);
int move = max(b);
if(move < best)
best = move;
}
return best;
}
public static void evaluateGame(){
GameState gameState = board.getGameState();
gameEnded = true;
switch(gameState){
case CrossWin :
System.out.println("Game Over! Cross Won!");
break;
case CircleWin :
System.out.println("Game Over! Circle Won!");
break;
case Draw :
System.out.println("Game Over! Game Drawn!");
break;
default : gameEnded = false;
break;
}
}
public static Position makeMove(){
Position position = null;
while(true){
System.out.print("Select column(x-axis). 0, 1 or 2: ");
int column = getColOrRow();
System.out.print("Select row(y-axis). 0, 1 or 2: ");
int row = getColOrRow();
position = new Position(column, row);
if(board.isMarked(position))
System.out.println("Position already marked!");
else break;
}
return position;
}
private static int getColOrRow(){
int ret = -1;
while(true){
try{
ret = Integer.parseInt(in.nextLine());
} catch (NumberFormatException e){}
if(ret < 0 | ret > 2)
System.out.print("\nIllegal input... please re-enter: ");
else break;
}
return ret;
}
}
public enum PlayerSign{
Cross, Circle
}
public enum GameState {
Incomplete, CrossWin, CircleWin, Draw
}
public final class Position {
public final int column;
public final int row;
public Position(int column, int row){
this.column = column;
this.row = row;
}
}
public class Board {
private char[][] board; //e = empty, x = cross, o = circle.
public Board(){
board = new char[3][3];
for(int y = 0; y < 3; y++)
for(int x = 0; x < 3; x++)
board[x][y] = 'e'; //Board initially empty
}
public Board(Board from, Position position, PlayerSign sign){
board = new char[3][3];
for(int y = 0; y < 3; y++)
for(int x = 0; x < 3; x++)
board[x][y] = from.board[x][y];
board[position.column][position.row] = sign==PlayerSign.Cross ? 'x':'o';
}
public ArrayList<Position> getFreePositions(){
ArrayList<Position> retArr = new ArrayList<Position>();
for(int y = 0; y < 3; y++)
for(int x = 0; x < 3; x++)
if(board[x][y] == 'e')
retArr.add(new Position(x, y));
return retArr;
}
public GameState getGameState(){
if(hasWon('x'))
return GameState.CrossWin;
else if(hasWon('o'))
return GameState.CircleWin;
else if(getFreePositions().size() == 0)
return GameState.Draw;
else return GameState.Incomplete;
}
private boolean hasWon(char sign){ //8 ways to win.
if(board[1][1] == sign){
if(board[0][0] == sign && board[2][2] == sign)
return true;
if(board[0][2] == sign && board[2][0] == sign)
return true;
if(board[1][0] == sign && board[1][2] == sign)
return true;
if(board[0][1] == sign && board[2][1] == sign)
return true;
}
if(board[0][0] == sign){
if(board[0][1] == sign && board[0][2] == sign)
return true;
if(board[1][0] == sign && board[2][0] == sign)
return true;
}
if(board[2][2] == sign){
if(board[1][2] == sign && board[0][2] == sign)
return true;
if( board[2][1] == sign && board[2][0] == sign)
return true;
}
return false;
}
public boolean isMarked(Position position){
if(board[position.column][position.row] != 'e')
return true;
return false;
}
public String toString(){
String retString = "\n";
for(int y = 0; y < 3; y++){
for(int x = 0; x < 3; x++){
if(board[x][y] == 'x' || board[x][y] == 'o')
retString += "["+board[x][y]+"]";
else
retString += "[ ]";
}
retString += "\n";
}
return retString;
}
}
Here is the output when I run the program (Computer is circle):
[ ][ ][ ]
[ ][ ][ ]
[ ][ ][ ]
Select column(x-axis). 0, 1 or 2: 1
Select row(y-axis). 0, 1 or 2: 1
[ ][ ][ ]
[ ][x][ ]
[ ][ ][ ]
Child Score: 0
Child Score: 0
Child Score: 0
Child Score: 0
Child Score: 0
Child Score: 0
Child Score: 0
Child Score: 0
[o][ ][ ]
[ ][x][ ]
[ ][ ][ ]
Select column(x-axis). 0, 1 or 2: 0
Select row(y-axis). 0, 1 or 2: 1
[o][ ][ ]
[x][x][ ]
[ ][ ][ ]
Child Score: -1
Child Score: 0
Child Score: 0
Child Score: -1
Child Score: -1
Child Score: -1
[o][ ][o]
[x][x][ ]
[ ][ ][ ]
Select column(x-axis). 0, 1 or 2:
As you can see after the first move, the computer thinks that no matter what move it makes it can get a draw (Score = 0).
On the second move I put a cross on column 0, row 1. For some reason, the computer then thinks that there are two possible moves to reach a draw (Score = 0) and only four moves to lose (Score = -1). It then makes an incorrect move thinking it will get a draw.
I first thought that there was something wrong with the hasWon method, but I have tested all 8 ways of getting three in a row and they all return true.
I suspect that the problem exists somewhere in the findBestMove, max or min methods, but I haven't been able to figure out exactly what is causing it.
I would really appreciate it if someone could tell what is causing the bug or give any suggestions on how to better debug my recursive algorithm.
Looks to me like you mixed up parts of min and max. Currently, your max returns the value of the worst possible move (for him) the human could take, instead of the optimal move the computer could take. Likewise, min returns the value of the worst move the computer could take, instead of the optimal move for the opponent.
Fix this by switching the PlayerSigns in min and max, and findBestMove should call min, not max.
Related
I am coding a simple tic-tac-toe for a high-school mini project, but I need it to be within a strict data volume (not more than 112 lines). I thought checking for each row, column and cross would be long, so is there any alternative to do so (You should see a [[[HERE]]] comment)? (Btw, I already know it looks awful) Thanks in advance!
public class TTTGame {
//OPTIONS v
public static final String draw = "DRAW"; // <- Definitions for different states
public static final String circles = "CIRCLES"; // BOT
public static final String crosses = "CROSSES"; // PLAYER
public static final String getCrosses = "X"; //<- Symbols to display
public static final String getCircles = "O";
//OPTIONS ^
//DO NOT MODIFY UNDER THIS LINE (Just kidding, do whatever u want) v
public static int[][] board = {
{0,0,0},
{0,0,0},
{0,0,0},
};
public static final int empty = 0; // Definition of the values
public static final int cross = 1;
public static final int circle = 2;
public static int turns = 0; //Just here to count turns, nothing special
public static void main(String[]args) { //Main process
board[1][1] = circle;
display();
while (true) {
PlayerTurn();
if (checkStop()||checkWinner()!=null) {display();GStop();break;}
BotTurn();
if (checkStop()||checkWinner()!=null) {display();GStop();break;}
display();
turns += 1;
}
}
private static void GStop() { //Force stop the match function
System.out.println("Winner : " + checkWinner());
System.exit(1);
}
private static boolean checkStop() { //Check if match is already full / completed (Draw)
for (int x = 0; x < 3; x++)
for (int y = 0; y < 3; y++)
if (board[x][y]==empty) return false;
return true;
}
#Nullable
private static String checkWinner() { //Check Winner
// [[[ HERE ]]] ---------------
return null;
}
private static void PlayerTurn() { //Player turn
int x; Scanner c = new Scanner(System.in);
while (true) {
x = c.nextInt();
x = x-1;
if ((x>=0)&&(x < 9)) {
if (board[x / 3][x % 3] == empty) {
board[x / 3][x % 3] = cross;
break;
} else System.out.println("Already chosen");
} else System.out.println("Invalid");
}
}
private static void BotTurn() { //Bot turn -> (Modify these to change the AI behaviour, here's a very simple one);
boolean choose = true;
for (int y = 0; y < 3 ; y++)
for (int x = 0; x < 3; x++)
if (board[y][x] == empty&&choose) {
board[y][x] = circle;
choose = false;
}
}
private static void display() { //Display the board
int nn = 1;
String a = "z";
for (int y = 0; y < 3 ; y++) {
for (int x = 0; x < 3; x++) {
if (board[y][x] == 0) a = "*";
if (board[y][x] == 1) a = getCrosses;
if (board[y][x] == 2) a = getCircles;
System.out.print(a + " ");
}
System.out.print(" "); //Indications
for (int xn = 0; xn < 3; xn++) {
System.out.print(nn);
nn+=1;
System.out.print(" ");
}
System.out.println(" ");
}
}
}
How about this idea: (neither the only nor the best nor the most performant solution... just an idea)
You can use the sum of each row, diagonal and column to determine if the either player one (all 1s) or player two (all 2s) wins. Therefore you only need to set the empty field to be higher than 6.
For example let's say your board looks like this:
7 1 1 -> 7+1+1 = 9 // no one wins
2 2 2 -> 2+2+2 = 6 // player two wins, he has 3 * 2 in a row
1 7 2 -> 1+7+2 =10 // no win here
if all three numbers where 1s (sum == 3) your player one wins.
It is "cumbersome" to implement, but as I said it is just an idea:
// first we check every column
for( int x=0; x<board[y].length; x++){
int sum = 0;
for( int y=0; y<board.length; y++){
sum += board[y][x];
}
if(sum == 3 || sum == 6){
return true;
}
}
// then every row
for( int y=0; y<board.length; y++){
int sum = 0;
for( int x=0; x<board[y].length; x++){
sum += board[y][x];
}
if(sum == 3 || sum == 6){
return true;
}
}
// and finally the diagonals (if we ever reach that part)
int sum= board[0][0] + board[1][1] + board[2][2];
if(sum == 3 || sum == 6){
return true;
}
sum= board[0][2] + board[1][1] + board[2][0];
if(sum == 3 || sum == 6){
return true;
}
you could also return 1 when the sum == 3 and the first player wins or 2 when player two wins.
I am programming a connect 4 game using Java for an assignment. However, whenever player 2 makes a move about 5 moves in, the player 2 loop will infinitely loop. There is some sort of logic error that I cannot find, and it is frustrating. What is the logic error, and what is a good way to avoid future mistakes of the same vain?
I have tried changing the variables for the do > while loop where player 1 and player two attempt their moves. However that has no affect on it.
import java.util.Arrays;
public class Lab6Shell {
public static void main(String args[]) {
// variables
Scanner input = new Scanner(System.in);
char[][] board = new char[7][8];
boolean finished = false;
boolean gameOver = false;
int width = 7;
int height = 8;
char currentPlayer = 'X';
int numMoves = 0;
int bottom_row = width - 1;
// loop until user wants to stop
for (int row = 0; row < board.length; row++) {
java.util.Arrays.fill(board[row], 0, board[row].length, '*');
}
do {
// display the board
DisplayBoard(board);
// loop until this game is over
do {
// get the next move for the current player
int columnChosen = 0;
do {
if (currentPlayer == 'X') {
int counter = 1;
System.out.println("Player 1 turn");
System.out.println("Enter the column you want to place your piece.");
columnChosen = input.nextInt();
input.nextLine();
while (true) {
if (columnChosen > width) {
System.out.println("That's not a valid column");
break;
}
if ((board[bottom_row][columnChosen] == '*')) {
board[bottom_row][columnChosen] = 'X';
break;
} else if ((board[bottom_row][columnChosen] == 'X')
|| (board[bottom_row][columnChosen] == 'O')) {
if (board[bottom_row - counter][columnChosen] == '*') { // puts X if blank
board[bottom_row - counter][columnChosen] = 'X';
break;
}
counter += 1;
if (counter == width) {
System.out.println("That column is full");
break;
}
}
}
}
if (currentPlayer == 'O') {
int counter = 1;
System.out.println("Player 2's turn");
System.out.println("Enter the column you want to place your piece.");
columnChosen = input.nextInt();
input.nextLine();
while (true) {
if (columnChosen > width) {
System.out.println("That's not a valid column");
break;
}
if ((board[bottom_row][columnChosen] == '*')) {
board[bottom_row][columnChosen] = 'O';
break;
} else if ((board[bottom_row][columnChosen] == 'X')
|| (board[bottom_row][columnChosen] == 'O')) {
if (board[bottom_row - counter][columnChosen] == '*') { // puts O
board[bottom_row - counter][columnChosen] = 'O';
break;
}
counter += 1;
if (counter == width) {
System.out.println("That column is full");
break;
}
}
}
}
} while (columnChosen < 0 || columnChosen > 8 || board[1][columnChosen] != '*');
// place piece
// increment number of moves
numMoves++;
// display the board
DisplayBoard(board);
// check for win
if (checkWin(board)) {
// if winner, display congratulations and set gameOver true
System.out.println("Congratulations! You won!");
gameOver = true;
} else if (numMoves == 42) {
// if tie, display result and set gameOver true
DisplayBoard(board);
System.out.println("Tie Game! Game over");
gameOver = true;
} else if (checkWin(board) == false) {
if (currentPlayer == ('X')) {
currentPlayer = ('O');
} else {
currentPlayer = ('X');
}
}
} while (!gameOver);
// ask if user wants to play again, set finished accordingly
System.out.println("Would you like to play again?");
input.nextLine();
String decision = input.nextLine();
if (decision.toLowerCase().equals("yes")) {
finished = false;
}
else if (decision.toLowerCase().equals("no")) {
finished = true;
}
} while (finished == false);
}
// this method displays the board passed in
public static void DisplayBoard(char[][] board) {
for (int i = 0; i < board.length; i++) {
System.out.print("|");
for (int j = 0; j < board[i].length; j++) {
System.out.print(" " + board[i][j] + "|");
}
System.out.println("");
}
}
public static boolean checkWin(char[][] board) {
final int HEIGHT = board.length;
final int WIDTH = board[0].length;
final int EMPTY_SLOT = '*';
for (int r = 0; r < HEIGHT; r++) { // iterate rows, bottom to top
for (int c = 0; c < WIDTH; c++) { // iterate columns, left to right
char player = board[r][c];
if (player == EMPTY_SLOT)
continue; // don't check empty slots
if (c + 3 < WIDTH && player == board[r][c + 1] && // look right
player == board[r][c + 2] && player == board[r][c + 3])
return true;
if (r + 3 < HEIGHT) {
if (player == board[r + 1][c] && // look up
player == board[r + 2][c] && player == board[r + 3][c])
return true;
if (c + 3 < WIDTH && player == board[r + 1][c + 1] && // look up & right
player == board[r + 2][c + 2] && player == board[r + 3][c + 3])
return true;
if (c - 3 >= 0 && player == board[r + 1][c - 1] && // look up & left
player == board[r + 2][c - 2] && player == board[r + 3][c - 3])
return true;
}
}
}
return false; // no winner found
}
}
The expected result is that each player will play a piece until four of the same piece are in a row. Then the first to reach four in a row is declared the winner, and the game ends. However, once the game gets in about 5 loops, the player 2 loop infinitely loops until a column is full, and does not print out the board.
Your infinite loop is caused by checking the condition board[1][columnChosen] != '*' in your do ... while loop. The program will continue to ask the current user for a new move as long as the second to top row of the selected column is occupied.
Replace:
do
{
...
} while (columnChosen < 0 || columnChosen > 8 || board[1][columnChosen] != '*');
With:
do
{
...
} while (columnChosen < 0 || columnChosen > 8)
This should get you to a point where you can tackle the remaining issues.
I'm working in artificial intelligence project to develop a TicTacToe 4X4 using Minimax algorithm
I have this existing program that run Minimax algorithm in 3x3 TicTacToe board.
I want to extend it to 4x4 TicTacToe
but I couldn't any idea how I can do it ??
import java.util.*;
//defines the point where to place 1 or 2
class Point {
int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
#Override
public String toString() {
return "[" + x + ", " + y + "]";
}
}
//defines the score per point -1,0,1
class PointsAndScores {
int score;
Point point;
PointsAndScores(int score, Point point) {
this.score = score;
this.point = point;
}
}
//defince the game board
class Board {
List<Point> availablePoints;
Scanner scan = new Scanner(System.in);
int[][] board = new int[3][3];
public Board() {
}
public boolean isGameOver() {
//Game is over is someone has won, or board is full (draw)
return (hasXWon() || hasOWon() || getAvailableStates().isEmpty());
}
//check if X have won represented by 1
public boolean hasXWon() {
if ((board[0][0] == board[1][1] && board[0][0] == board[2][2] && board[0][0] == 1) || (board[0][2] == board[1][1] && board[0][2] == board[2][0] && board[0][2] == 1)) {
//System.out.println("O Diagonal Win");
return true;
}
for (int i = 0; i < 3; ++i) {
if (((board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][0] == 1)
|| (board[0][i] == board[1][i] && board[0][i] == board[2][i] && board[0][i] == 1))) {
// System.out.println("O Row or Column win");
return true;
}
}
return false;
}
//check if O has won represented by 2
public boolean hasOWon() {
if ((board[0][0] == board[1][1] && board[0][0] == board[2][2] && board[0][0] == 2) || (board[0][2] == board[1][1] && board[0][2] == board[2][0] && board[0][2] == 2)) {
// System.out.println("X Diagonal Win");
return true;
}
for (int i = 0; i < 3; ++i) {
if ((board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][0] == 2)
|| (board[0][i] == board[1][i] && board[0][i] == board[2][i] && board[0][i] == 2)) {
// System.out.println("X Row or Column win");
return true;
}
}
return false;
}
//check available states in the board
public List<Point> getAvailableStates() {
availablePoints = new ArrayList<>();
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
if (board[i][j] == 0) {
availablePoints.add(new Point(i, j));
}
}
}
return availablePoints;
}
//put player move in the board
public void placeAMove(Point point, int player) {
board[point.x][point.y] = player; //player = 1 for O, 2 for X..
}
//get best movement according to the board state
public Point returnBestMove() {
int MAX = -100000;
int best = -1;
for (int i = 0; i < rootsChildrenScores.size(); ++i) {
if (MAX < rootsChildrenScores.get(i).score) {
MAX = rootsChildrenScores.get(i).score;
best = i;
}
}
return rootsChildrenScores.get(best).point;
}
//accepts input from user
void takeHumanInput() {
System.out.println("Your move: ");
int x = scan.nextInt();
int y = scan.nextInt();
Point point = new Point(x, y);
placeAMove(point, 2);
}
//display current board
public void displayBoard() {
System.out.println();
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
System.out.print(board[i][j] + " ");
}
System.out.println();
}
}
//get min value
public int returnMin(List<Integer> list) {
int min = Integer.MAX_VALUE;
int index = -1;
for (int i = 0; i < list.size(); ++i) {
if (list.get(i) < min) {
min = list.get(i);
index = i;
}
}
return list.get(index);
}
//get max value
public int returnMax(List<Integer> list) {
int max = Integer.MIN_VALUE;
int index = -1;
for (int i = 0; i < list.size(); ++i) {
if (list.get(i) > max) {
max = list.get(i);
index = i;
}
}
return list.get(index);
}
//declares a list for scores
List<PointsAndScores> rootsChildrenScores;
//excutes minimax algorithm
public void callMinimax(int depth, int turn){
rootsChildrenScores = new ArrayList<>();
minimax(depth, turn);
}
//minimax algorithm
public int minimax(int depth, int turn) {
if (hasXWon()) return +1;
if (hasOWon()) return -1;
//get available states from the board
List<Point> pointsAvailable = getAvailableStates();
if (pointsAvailable.isEmpty()) return 0;
//stores scores
List<Integer> scores = new ArrayList<>();
for (int i = 0; i < pointsAvailable.size(); ++i) {
Point point = pointsAvailable.get(i);
if (turn == 1) { //O's turn select the highest from below minimax() call
placeAMove(point, 1);
int currentScore = minimax(depth + 1, 2);
scores.add(currentScore);//add scores to the list
if (depth == 0)
rootsChildrenScores.add(new PointsAndScores(currentScore, point));
} else if (turn == 2) {//X's turn select the lowest from below minimax() call
placeAMove(point, 2);
scores.add(minimax(depth + 1, 1));
}
board[point.x][point.y] = 0; //Reset this point
}
return turn == 1 ? returnMax(scores) : returnMin(scores);
}
}
//main class
public class TicTacToe {
public static void main(String[] args) {
Board b = new Board();//instantiate board
Random rand = new Random();//instantiate random value
b.displayBoard();//display board
System.out.println("Who's gonna move first? (1)Computer (2)User: ");
int choice = b.scan.nextInt();
if(choice == 1){
Point p = new Point(rand.nextInt(3), rand.nextInt(3));
b.placeAMove(p, 1);
b.displayBoard();
}
while (!b.isGameOver()) {
System.out.println("Your move: ");
Point userMove = new Point(b.scan.nextInt(), b.scan.nextInt());
b.placeAMove(userMove, 2); //2 for X and X is the user
b.displayBoard();
if (b.isGameOver()) {
break;
}
b.callMinimax(0, 1);
for (PointsAndScores pas : b.rootsChildrenScores) {
System.out.println("Point: " + pas.point + " Score: " + pas.score);
}
b.placeAMove(b.returnBestMove(), 1);
b.displayBoard();
}
if (b.hasXWon()) {
System.out.println("Unfortunately, you lost!");
} else if (b.hasOWon()) {
System.out.println("You win! This is not going to get printed.");
} else {
System.out.println("It's a draw!");
}
}
}
You need to introduce an integer variable, let's say n, which will contain the size of the board(i.e. # of cells = n*n). Before a game starts, the player will be asked for the preferred board size, 3 or 4, and the corresponding board will be created. In order for your program to work with 4x4 like it did with 3x3, we will need to place the variable n wherever we have a method or loop that needs to traverse through the board. In other words, instead of 3 we will place n. This will "generalize" our program. For example, within the display method we have 2 for loops that run as long as i and j are smaller than 3. To "generalize" our program to a semi-arbitrary board size(3 or 4), we place an n in place of the 3 so that the loop will run as long as i and j are smaller than n(3 or 4). This change from 3 to n will apply wherever we have a 3 that indicates the size of the board. Another thing we will need to change are the checking methods hasXWon() and hasOWon(). Here, we will need to add an extra section for when the board size is 4 and not 3. This will look more or less the same as it's counterpart with the only difference being that it will check the extra row, column and longer diagonals that exist in a 4x4 board. After inserting these changes, the program now looks like this:
import java.util.*;
//defines the point where to place 1 or 2
class Point {
int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
#Override
public String toString() {
return "[" + x + ", " + y + "]";
}
}
//defines the score per point -1,0,1
class PointsAndScores {
int score;
Point point;
PointsAndScores(int score, Point point) {
this.score = score;
this.point = point;
}
}
//defince the game board
class board {
List<Point> availablePoints;
Scanner scan = new Scanner(System.in);
public int n;
int[][] board;
public board(int n) {
this.n = n;
board = new int[n][n];
}
public boolean isGameOver() {
//Game is over is someone has won, or board is full (draw)
return (hasXWon() || hasOWon() || getAvailableStates().isEmpty());
}
//check if X have won represented by 1
public boolean hasXWon() {
if(n==3){
if ((board[0][0] == board[1][1] && board[0][0] == board[2][2] && board[0][0] == 1) || (board[0][2] == board[1][1] && board[0][2] == board[2][0] && board[0][2] == 1)) {
//System.out.println("O Diagonal Win");
return true;
}
for (int i = 0; i < n; ++i) {
if (((board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][0] == 1)
|| (board[0][i] == board[1][i] && board[0][i] == board[2][i] && board[0][i] == 1))) {
// System.out.println("O Row or Column win");
return true;
}
}
return false;
}
else {
if ((board[0][0] == board[1][1] && board[0][0] == board[2][2] && board[0][0] == board[3][3]&& board[0][0] == 1) || (board[0][3] == board[1][2] && board[0][3] == board[2][1] && board[0][3] == board[3][0] && board[0][3] == 1)) {
//System.out.println("O Diagonal Win");
return true;
}
for (int i = 0; i < n; ++i) {
if (((board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][0] == 1)
|| (board[0][i] == board[1][i] && board[0][i] == board[2][i] && board[0][i] == 1))) {
// System.out.println("O Row or Column win");
return true;
}
}
return false;
}
}
//check if O has won represented by 2
public boolean hasOWon() {
if(n==3){
if ((board[0][0] == board[1][1] && board[0][0] == board[2][2] && board[0][0] == 2) || (board[0][2] == board[1][1] && board[0][2] == board[2][0] && board[0][2] == 2)) {
//System.out.println("O Diagonal Win");
return true;
}
for (int i = 0; i < n; ++i) {
if (((board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][0] == 2)
|| (board[0][i] == board[1][i] && board[0][i] == board[2][i] && board[0][i] == 2))) {
// System.out.println("O Row or Column win");
return true;
}
}
return false;
}
else {
if ((board[0][0] == board[1][1] && board[0][0] == board[2][2] && board[0][0] == board[3][3]&& board[0][0] == 2) || (board[0][3] == board[1][2] && board[0][3] == board[2][1] && board[0][3] == board[3][0] && board[0][3] == 2)) {
//System.out.println("O Diagonal Win");
return true;
}
for (int i = 0; i < n; ++i) {
if (((board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][0] == 2)
|| (board[0][i] == board[1][i] && board[0][i] == board[2][i] && board[0][i] == 2))) {
// System.out.println("O Row or Column win");
return true;
}
}
return false;
}
}
//check available states in the board
public List<Point> getAvailableStates() {
availablePoints = new ArrayList<>();
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (board[i][j] == 0) {
availablePoints.add(new Point(i, j));
}
}
}
return availablePoints;
}
//put player move in the board
public void placeAMove(Point point, int player) {
board[point.x][point.y] = player; //player = 1 for O, 2 for X..
}
//get best movement according to the board state
public Point returnBestMove() {
int MAX = -100000;
int best = -1;
for (int i = 0; i < rootsChildrenScores.size(); ++i) {
if (MAX < rootsChildrenScores.get(i).score) {
MAX = rootsChildrenScores.get(i).score;
best = i;
}
}
return rootsChildrenScores.get(best).point;
}
//accepts input from user
void takeHumanInput() {
System.out.println("Your move: ");
int x = scan.nextInt();
int y = scan.nextInt();
Point point = new Point(x, y);
placeAMove(point, 2);
}
//display current board
public void displayboard() {
System.out.println();
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
System.out.print(board[i][j] + " ");
}
System.out.println();
}
}
//get min value
public int returnMin(List<Integer> list) {
int min = Integer.MAX_VALUE;
int index = -1;
for (int i = 0; i < list.size(); ++i) {
if (list.get(i) < min) {
min = list.get(i);
index = i;
}
}
return list.get(index);
}
//get max value
public int returnMax(List<Integer> list) {
int max = Integer.MIN_VALUE;
int index = -1;
for (int i = 0; i < list.size(); ++i) {
if (list.get(i) > max) {
max = list.get(i);
index = i;
}
}
return list.get(index);
}
//declares a list for scores
List<PointsAndScores> rootsChildrenScores;
//excutes minimax algorithm
public void callMinimax(int depth, int turn){
rootsChildrenScores = new ArrayList<>();
minimax(depth, turn);
}
//minimax algorithm
public int minimax(int depth, int turn) {
if (hasXWon()) return +1;
if (hasOWon()) return -1;
//get available states from the board
List<Point> pointsAvailable = getAvailableStates();
if (pointsAvailable.isEmpty()) return 0;
//stores scores
List<Integer> scores = new ArrayList<>();
for (int i = 0; i < pointsAvailable.size(); ++i) {
Point point = pointsAvailable.get(i);
if (turn == 1) { //O's turn select the highest from below minimax() call
placeAMove(point, 1);
int currentScore = minimax(depth + 1, 2);
scores.add(currentScore);//add scores to the list
if (depth == 0)
rootsChildrenScores.add(new PointsAndScores(currentScore, point));
} else if (turn == 2) {//X's turn select the lowest from below minimax() call
placeAMove(point, 2);
scores.add(minimax(depth + 1, 1));
}
board[point.x][point.y] = 0; //Reset this point
}
return turn == 1 ? returnMax(scores) : returnMin(scores);
}
}
//main class
public class TicTacToe {
public static void main(String[] args) {
//board b = new board();//instantiate board
Random rand = new Random();//instantiate random value
Point p;
Scanner s = new Scanner(System.in);
System.out.println("Choose board size: 3 or 4?");
int n=s.nextInt();
board b = new board(n); //Instantiating board after value of n has been read. This is important because the value of n is required for the instantiation to take place.
System.out.println(b.n);
b.displayboard();//display board
System.out.println("Who's gonna move first? (1)Computer (2)User: ");
int choice = b.scan.nextInt();
if(choice == 1){
if(b.n==3)
p = new Point(rand.nextInt(3), rand.nextInt(3));
else
p = new Point(rand.nextInt(4), rand.nextInt(4));
b.placeAMove(p, 1);
b.displayboard();
}
while (!b.isGameOver()) {
System.out.println("Your move: ");
Point userMove = new Point(b.scan.nextInt(), b.scan.nextInt());
b.placeAMove(userMove, 2); //2 for X and X is the user
b.displayboard();
if (b.isGameOver()) {
break;
}
b.callMinimax(0, 1);
for (PointsAndScores pas : b.rootsChildrenScores) {
System.out.println("Point: " + pas.point + " Score: " + pas.score);
}
b.placeAMove(b.returnBestMove(), 1);
b.displayboard();
}
if (b.hasXWon()) {
System.out.println("Unfortunately, you lost!");
} else if (b.hasOWon()) {
System.out.println("You win! This is not going to get printed.");
} else {
System.out.println("It's a draw!");
}
}
}
Your program is now able to create 4x4 games. There are however two things that you need to do. First, your placeAMove(...) method doesn't check if a cell is occupied before filling it. This may lead to cells being overwritten, so you must change that by checking if a cell is occupied before trying to fill it. The second and more important thing is that although the program is now able to create 4x4 games, it will not be able to carryout a proper game. The reason for this is that the number of nodes(states) created by minimax to find the next move, although manageable in 3x3 games, rises dramatically for 4x4 games. This means that it would take your computer A LOT of time, and by a lot I mean hours, to calculate the computer's second move. Even when I rigged the game(i.e. manually inserted random 1s and 2s in the cells before allowing minimax to be called to choose the computer's move) it still took the computer over 30 seconds to calculate it's move. This renders your game, of course, unplayable in 4x4 mode. There are, of course, ways, such as Memoization or Alpha-Beta Pruning or both together, to overcome this and speed up your algorithm. Here is a previously asked question to get you started on these topics. The chess programming wiki is also a good source of information on such topics, if a bit more complex in it's structure. Here's their entry on Memoization(Here called transposition tables).
I've been trying to learn the minimax algorithm and I've stumbled upon a bug which I cannot figure out how to solve.
Code:
private List<Integer> generatemoves(int[] evalFields) {
List<Integer> nextMoves = new ArrayList<Integer>();
for (int i = 0; i < evalFields.length; i++) {
if (evalFields[i] == 0) {
nextMoves.add(i);
}
}
return nextMoves;
}
private int evaluateLine(int p1, int p2, int p3, int[] evalFields) {
int score = 0;
if (evalFields[p1] == 1) {
score = 1;
} else if (evalFields[p1] == 10) {
score = -1;
}
if (evalFields[p2] == 1) {
if (score == 1) {
score = 10;
} else if (score == -1) {
return 0;
} else {
score = 1;
}
} else if (evalFields[p2] == 10) {
if (score == -1) {
score = -10;
} else if (score == 1) {
return 0;
} else {
score = -1;
}
}
if (evalFields[p3] == 1) {
if (score > 0) {
score *= 10;
} else if (score < 0) {
return 0;
} else {
score = 1;
}
} else if (evalFields[p3] == 10) {
if (score < 0) {
score *= 10;
} else if (score > 1) {
return 0;
} else {
score = -1;
}
}
return score;
}
private int evaluateBoard(int [] evalFields) {
int score = 0;
score += evaluateLine(0, 1, 2, evalFields);
score += evaluateLine(3, 4, 5, evalFields);
score += evaluateLine(6, 7, 8, evalFields);
score += evaluateLine(0, 3, 6, evalFields);
score += evaluateLine(1, 4, 7, evalFields);
score += evaluateLine(2, 5, 8, evalFields);
score += evaluateLine(0, 4, 8, evalFields);
score += evaluateLine(2, 4, 6, evalFields);
return score;
}
private int bestMove(int currentTurn, int[] board) {
int move;
int bestScore;
if (currentTurn == 1) {
bestScore = Integer.MIN_VALUE;
} else {
bestScore = Integer.MAX_VALUE;
}
List<Integer> nextMoves = generatemoves(board);
List<Integer> bestScores = new ArrayList<Integer>();
for (int i = 0; i < nextMoves.size(); i++) {
int[] newBoards = new int[9];
for (int j = 0; j < board.length; j++) {
newBoards[j] = board[j];
}
newBoards[nextMoves.get(i)] = turn;
bestScores.add(evaluateBoard(newBoards));
}
for (int scores : bestScores) {
if (currentTurn == 1) {
if (scores > bestScore) bestScore = scores;
} else {
if (scores < bestScore) bestScore = scores;
}
}
move = nextMoves.get(bestScores.indexOf(bestScore));
return move;
}
This is the most relevant part of the code. What it does or what I think it does is that it generates every possible move from the board which is called fields. Then it calculates a score for each move. It then proceeds to make the move which results in the highest or lowest score, x(1) is trying to get the highest and O(10) the lowest. The bug that occurs is that when the player starts and takes the field in the middle, then the ai acts normally but after the players second turn the ai starts to act strange:
[ ][ ][ ] [O][ ][ ] [O][ ][O]
[ ][x][ ] => [ ][x][ ] => [x][x][ ]
[ ][ ][ ] [ ][ ][ ] [ ][ ][ ]
If the player chooses this:
[O][ ][ ] [O][ ][ ]
[ ][x][x] => [O][x][x]
[ ][ ][ ] [ ][ ][ ]
Then the ai acts nomally.
I don't know what is wrong or even if I've understood the minimax algorithm correctly.
****edit****
Added this code still have the same problem
private int[] evaluateMove(int [] board, int currentTurn) {
int bestScore;
int currentScore;
int bestMove = -1;
if (currentTurn == 1) {
bestScore = Integer.MIN_VALUE;
} else {
bestScore = Integer.MAX_VALUE;
}
List<Integer> nextMoves = generatemoves(board);
if (nextMoves.isEmpty()) {
bestScore = evaluateTheBoard(board);
} else {
for (int move : nextMoves) {
int[] nextBoard = new int[9];
for (int i = 0; i < nextBoard.length; i ++) {
nextBoard[i] = board[i];
}
nextBoard[move] = currentTurn;
currentScore = evaluateMove(nextBoard, nextTurn())[0];
if (currentTurn == 1) {
if (currentScore > bestScore) {
bestScore = currentScore;
bestMove = move;
}
} else {
if (currentScore < bestScore) {
bestScore = currentScore;
bestMove = move;
}
}
}
}
return new int[] {bestScore, bestMove};
}
I think you are misunderstanding how to look ahead in a game like this. Do not 'total' the values returned by evaluateLine.
Here is pseudocode for the minimax score of a tic-tac-toe board (what evaluateBoard should return). Note that evaluateBoard will need to have a notion of currentTurn.
function evaluateBoard(board, currentTurn)
// check if the game has already ended:
if WhiteHasWon then return -10
if BlackHasWon then return +10
// WhiteHasWon returns true if there exists one or more winning 3-in-a-row line for white.
// (You will have to scan for all 8 possible 3-in-a-row lines of white pieces)
// BlackHasWon returns true if there exists one or more winning 3-in-a-row line for black
if no legal moves, return 0 // draw
// The game isn't over yet, so look ahead:
bestMove = notset
resultScore = notset
for each legal move i for currentTurn,
nextBoard = board
Apply move i to nextBoard
score = evaluateBoard(nextBoard, NOT currentTurn).score
if score is <better for currentTurn> than resultScore, then
resultScore = score
bestMove = move i
return (resultScore, bestMove)
One very key difference between this and your version and my version is that my version is recursive. Yours only goes one level deep. Mine calls evaluateBoard from inside evaluateBoard, which would be an infinite loop if we aren't careful (once the board fills up, it can't go any deeper, so it's not actually infinite)
Another difference is that yours totals stuff when it shouldn't. The resulting score from tic-tac-toe is -10,0, or 10 only once you've looked to the end of the game. You should be picking the best possible move available to that player at that time, and ignoring all other possibilities completely because you only care about the "best" line of play. The game score is equal to the result of optimal play.
Expanding <better for currentTurn> is messy in minimax, which is why negamax is cleaner. White prefers low scores and black prefers high scores, so you need some if statements to make it choose the appropriate preferred score. You have this part already (at the end of your best move code), but it needs to be evaluated inside the recursion instead of just at the end.
im creating a code for Minesweeper and trying to implement a GUI. But the problem is that when i run the code and play the game, the position i click on the board reveals the y,x coordinate of that location on the answer board rather than the x, y coordinate. Ive been trying to fix this but i cant see to figure it out. i think it maybe is the way i create the board, but i tried everything i could think of.
class Board{
public MsGUI gui;
private static char[][] userBoard;
private static char[][] solutionBoard;
private static int boundSize = 5;
public Board(){
userBoard = new char[][] {{'-','-','-','-','-'},
{'-','-','-','-','-'},
{'-','-','-','-','-'},
{'-','-','-','-','-'},
{'-','-','-','-','-'}};
solutionBoard = new char[][] {{'0','2','B','2','0'},
{'0','3','B','3','0'},
{'1','3','B','3','1'},
{'B','1','3','B','2'},
{'1','1','2','B','2'}};
return;
}
private static void printBoard(char[][] board){
for (int x = 0; x < boundSize; x++){
for(int y = 0; y < boundSize; y++){
System.out.print(" " + Character.toString(board[x][y]));
}
System.out.println("");
}
System.out.println("");
}
public void flagCell(int xCoordinate, int yCoordinate){
userBoard[xCoordinate][yCoordinate] = 'F';
}
public boolean isFlagged(int xCoordinate,int yCoordinate){
if(userBoard[xCoordinate][yCoordinate] == 'F'){
return true;
}
else{
return false;
}
}
public int getHeight() {
return userBoard.length;
}
public int getWidth(){
return userBoard[0].length;
}
public char getValue(int xCoordinate, int yCoordinate) {
return userBoard[xCoordinate][yCoordinate];
}
private static boolean checkIfAlreadyMarked(int xCoordinate, int yCoordinate)
{
boolean marked = false;
if (Character.toString(userBoard[xCoordinate][yCoordinate]).equals("-") == false)
{
marked = true;
}
return marked;
}
public void revealCell(int xCoordinate, int yCoordinate){
int count = 0;
for(int i = 0;i < userBoard.length;i++){
for(int J = 0;J < userBoard[i].length;J++){
if(userBoard[i][J] != '-'){
count = count + 1;
}
}
if(count == 19){
gui.win("you won");
return;
}
}
if(solutionBoard[xCoordinate][yCoordinate] == 'B'){
userBoard[xCoordinate][yCoordinate] = solutionBoard[xCoordinate][yCoordinate];
gui.lose("You lost. Better luck next time!");
return;
}
if(solutionBoard[xCoordinate][yCoordinate] != '0'){
userBoard[xCoordinate][yCoordinate] = solutionBoard[xCoordinate][yCoordinate];
}else{
userBoard[xCoordinate][yCoordinate] = solutionBoard[xCoordinate][yCoordinate];
for(int i = 1; i > -2; i--){
if(xCoordinate-i >= solutionBoard.length || xCoordinate-i < 0)
continue;
for(int z = 1; z > -2; z--){
if(yCoordinate-z >= solutionBoard[xCoordinate].length || yCoordinate-z < 0)
continue;
else if(userBoard[xCoordinate-i][yCoordinate-z] == 'F' || userBoard[xCoordinate-i][yCoordinate-z] != '-')
continue;
else{
revealCell(xCoordinate-i, yCoordinate-z);
}
}
}
}
}
public void unflagCell(int xCoordinate, int yCoordinate){
userBoard[xCoordinate][yCoordinate]='-';
}
public static void main(String[] args){
Board b = new Board();
b.gui = new MsGUI(b);
b.gui.setVisible(true);
}
}
The way you are initializing the solutionBoard is not what you expect it to be.
If you get solutionBoard[0], you're not accessing the first column(which would be consistent with what I think is your understanding), but the first row(first item of the two-dimensional array): {'0','2','B','2','0'}
So if you want to have x for a row index and y for a column index and still keep this "readable" initialization, you'll have to swap the indices whenever you access the array.
But this will only help you with one problem - human readable array assignment in the beginning, but I think you'll regret this decision in the future.
EDIT:
You can have the array initialized as you want and still use readable format like this:
String readableBoard =
"0 2 B 2 0;" +
"0 3 B 3 0;" +
"1 3 B 3 1;" +
"B 1 B B 2;" +
"1 1 2 B 2";
char[][] board = initBoard(readableBoard);
....
private char[][] initBoard(String readableBoard){
char[][] board = new char[5][5];
String[] rows = readableBoard.split(";");
String[] fields = null;
for (int y = 0; y<rows.length;y++){
fields = rows[y].split(" ");
for (int x = 0; x<fields.length; x++){
board[x][y]=fields[x].charAt(0);
}
}
return board;
}
Now when you call
board[2][0]
You'll get 'B'
If you look at your nested for loops, you're printing off the columns instead of what I assume to be the desired rows. Try switching your for loops to iterate over y, then x.
for (int y = 0; y < boundSize; y++){
for(int x = 0; x < boundSize; x++){
System.out.print(" " + Character.toString(board[x][y]));
}
System.out.println("");
}
System.out.println("");