I'm struggling with a minimax exercise, I'm just trying to make a connect four ai with it. Mine works when only exploring one node deep but I can't figure out why it messes up once it goes deeper.
private int minimax(Gameboard gameBoard, int alpha, int depth, char color) {
Gameboard gb = new Gameboard(gameBoard);
int value = 0;
int bestChoice = 0;
int bestValue = alpha;
// determine which use the value is for
if (gb.computerColor == color) {
value = 1;
} else {
value = -1;
}
if (gb.CheckForWinner(gb.lastSpacePlayed)) {
if(gb.winner == gb.computerColor){
bestValue = (1000000000 - depth);
}else{
bestValue = (-1000000000 - depth);
}
}
// get the bestValue at our maximum recrusion depth
else if (depth == maxDepth) {
int moveWeight = (threatLevel(gb, color));
if (moveWeight != 0) {
bestValue = value * (moveWeight - depth);
} else {
bestValue = moveWeight;
}
} else {
// Generate moves for each col and find the best score from each of
// the generated moves.
for (int c = 0; c < 7; c++) {
Gameboard game = new Gameboard(gb);
int selectedPlace = game.PlacePiece(c, color);
// Recursive call the generated game grid and compare the
// current value to move value
// If move is higher, make it the new current value.
if (selectedPlace != -1) {
char tempColor;
// change the user for the child node after a piece is played
if (color == 'Y') {
tempColor = 'R';
} else {
tempColor = 'Y';
}
// call the function so we can explore to maximum depth
if (depth < maxDepth) {
int v = minimax(new Gameboard(game), -1000000, depth + 1, tempColor);
if (v > bestValue) {
bestChoice = c;
bestValue = v;
}
System.out.println(v);
}
}
}
}
if (depth == 0) {
if (threatLevel(gb, gb.lastSpacePlayed.getColor()) == 0) {
return gb.spaces.get(0).get(3).getColor() == gb.playerColor ? 2
: 3;
} else {
return bestChoice;
}
} else {
return bestValue;
}
}
I'm starting it off as so return minimax(gameBoard, -1000000, 0, gameBoard.computerColor);
My understanding is just looping over all children and returning a value a maximum value if the nodes are the same as the parent and a minimum value if the nodes aren't. Any direction would be appreciated.
private int minimax(Gameboard gameBoard, int depth, char color) {
Gameboard gb = new Gameboard(gameBoard);
int bestChoice = 0;
int bestValue = 0;
//If we've won, return highest possible value. If we've lost, return lowest.
if (gb.CheckForWinner(gb.lastSpacePlayed)) {
if(gb.winner == color){
return Integer.MAX_VALUE
}else{
return Integer.MIN_VALUE
}
}
//if we hit maximum depth, resort to our heuristic method.
else if (depth == maxDepth) {
return threatLevel(gb, color);
} else {
// Generate moves for each col and find the best score from each of
// the generated moves. Keep track of the worst one.
int worstBestResponse = Integer.MAX_INT
boolean tie = true;
for (int c = 0; c < 7; c++) {
Gameboard game = new Gameboard(gb);
int selectedPlace = game.PlacePiece(c, color);
// Recursive call the generated game grid and compare the
// current value to move value
// If move is higher, make it the new current value.
if (selectedPlace != -1) {
tie = false;
char tempColor;
// change the user for the child node after a piece is played
if (color == 'Y') {
tempColor = 'R';
} else {
tempColor = 'Y';
}
// call the function so we can explore to maximum depth
if (depth < maxDepth) {
int v = minimax(new Gameboard(game), depth + 1, tempColor);
if (v < worstBestResponse) {
worstBestResponse = v;
}
}
}
}
if(tie) {
//if game is a tie, we return 0, to show no favour.
return 0;
} else {
//After determining the value of the opponents best response, we return the negative value of it. That is, what's bad for them is good for us and visa versa.
return -worstBestResponse;
}
}
}
I believe something like this is more what you're looking for. This is assuming that threatLevel is a heuristic method for determining approximately who is winning in a given game.
I've taken out any knowledge the method may have about who it's rooting for. It should only be rooting for whoever "color" is. I've also cleaned up your arbitrarily large integers to show winning and losing. MAX_VALUE and MIN_VALUE is much more elegant.
Anyway, try this out, and call it with depth of 0. Let me know if you have questions
If your approach isn't working you could try to mimic the basic structure of the pseudocode on wiki or here. In java I have:
/**
* initialize MiniMax(0, player1) where player1 is computer
* #param deg depth
* #param player either MAX or MIN
* #return int {score,column}
*/
int[] MiniMax(int deg, char player) {
// list of possible columns to place a checker
List<int[]> moves = nextMoves();
int v;
char prev;
if (player==player1) prev = player2;
else prev = player1;
// IF depth = 0 OR there is a connect 4 OR the board is full (draw) THEN
// return your static evaluation (heuristic)
// if the current position has a connect 4, return -inf or inf
// depending on if player is MIN or MAX respectively
if (checkPos(prev) || (deg == this.deg) || moves.isEmpty())
return new int[] {eval(),bestMove};
//MAX
else if (player==player1) {
v = -inf;
for (int[] move : moves) { // move = {row,column}
board[move[0]][move[1]] = player1; // make move
int score = MiniMax(deg+1,player2)[0];
board[move[0]][move[1]] = ' '; // undo move
if (score>v) {
v = score;
bestMove = move[1];
}
}
return new int[] {v,bestMove};
}
//MIN
else {
v = inf;
for (int[] move : moves) { // move = {row,column}
board[move[0]][move[1]] = player2; // make move
int score = MiniMax(deg+1,player1)[0];
board[move[0]][move[1]] = ' '; // undo move
if (v>score) {
v = score;
bestMove = move[1];
}
}
return new int[] {v,bestMove};
}
}
It could be useful to use a character array or something, rather than a class, to represent the board. The most efficient way though is to represent the board as a long as you can check if there is a connect four using 4 bit shifts. Here's some sloppy java that shows the above stuff working:
import java.util.ArrayList;
import java.util.List;
public class Connect4 {
static final int WIDTH = 7;
static final int HEIGHT = 6;
private static final int inf = 9999999;
private static final char player1 = 'X';
private static final char player2 = 'O';
char[][] board = new char[WIDTH][HEIGHT];
private int deg;
int bestMove;
static char[][] copy(char[][] aArray) {
char[][] copy = new char[aArray.length][aArray[0].length];
for (int idy = 0; idy < aArray.length; ++idy) {
for (int idx = 0; idx < aArray[0].length; ++idx) {
copy[idy][idx] = aArray[idy][idx];
}
}
return copy;
}
void prints() {
System.out.println();
for (int i = 0; i < 6; i++) {
System.out.println("=============================");
for (int j = 0; j < 7; j++) {
if (j == (WIDTH - 1)) {
if (board[i][j]==' ') {
System.out.println("| |");
}
else {
if(board[i][j]==player1 ) System.out.println("| " +"\u001B[32m"+board[i][j]+"\u001B[0m" + " |");
else System.out.println("| " +"\u001B[34m"+board[i][j]+"\u001B[0m" + " |");
}
}
else {
if (board[i][j]==' ') {
System.out.print("| ");
}
else {
if(board[i][j]==player1 ) System.out.print("| " +"\u001B[32m"+board[i][j]+"\u001B[0m" + " ");
else System.out.print("| " +"\u001B[34m"+board[i][j]+"\u001B[0m" + " ");
}
}
}
}
System.out.println("=============================");
}
//STATIC EVALUATION
int eval3(char player) {
if (checkPos(player)) {
return inf;
}
int count;
int open;
int evaluation = 0;
//evaluation = number of open 3 in rows
//go through all possible 4in rows and check
//horz
for (int i = 0; i < HEIGHT; i++) {
for (int j = 0; j < 4; j++) {
count = 0;
open = 0;
if (board[i][j]==player) count++;
else if(board[i][j]==' ') open++;
if (board[i][j + 1]==player) count++;
else if(board[i][j + 1]==' ') open++;
if (board[i][j + 2]==player) count++;
else if(board[i][j + 2]==' ') open++;
if (board[i][j + 3]==player) count++;
else if(board[i][j + 3]==' ') open++;
if ((count == 3) && (open == 1)) evaluation++;
}
}
//vert
for (int j = 0; j < WIDTH; j++) {
for (int i = 0; i < 3; i++) {
count = 0;
open = 0;
if (board[i][j]==player) count++;
else if (board[i][j]==' ') open++;
if (board[i + 1][j]==player) count++;
else if (board[i+1][j]==' ') open++;
if (board[i + 2][j]==player) count++;
else if (board[i + 2][j]==' ') open++;
if (board[i + 3][j]==player) count++;
else if (board[i + 3][j]==' ') open++;
if ((count == 3) && (open == 1)) evaluation++;
}
}
// pos slope diag
for (int j = 0; j < 4; j++) {
for (int i = 3; i < HEIGHT; i++) {
count = 0;
open = 0;
if (board[i][j]==player) count++;
else if (board[i][j]==' ') open++;
if (board[i - 1][j + 1]==player) count++;
else if (board[i - 1][j + 1]==' ') open++;
if (board[i - 2][j + 2]==player) count++;
else if (board[i - 2][j + 2]==' ') open++;
if (board[i - 3][j + 3]==player) count++;
else if (board[i - 3][j + 3]==' ') open++;
if ((count == 3) && (open == 1)) evaluation++;
}
}
// neg slope diag
for (int j = 0; j < 4; j++) {
for (int i = 0; i < (3); i++) {
count = 0;
open = 0;
if (board[i][j]==player) count++;
else if (board[i][j]==' ') open++;
if (board[i + 1][j + 1]==player) count++;
else if (board[i + 1][j + 1]==' ') open++;
if (board[i + 2][j + 2]==player) count++;
else if (board[i + 2][j + 2]==' ') open++;
if (board[i + 3][j + 3]==player) count++;
else if (board[i + 3][j + 3]==' ') open++;
if ((count == 3) && (open == 1)) evaluation++;
}
}
return evaluation;
}
int eval() {return eval3(player1) - eval3(player2);}
boolean checkPos(char cur) {
//horz
for (int i = 0; i < HEIGHT; i++) {
for (int j = 0; j < 4; j++) {
if ( board[i][j]==cur &&
board[i][j + 1]==cur &&
board[i][j + 2]==cur &&
board[i][j + 3]==cur) {
return true;
}
}
}
//vert
for (int j = 0; j < WIDTH; j++) {
for (int i = 0; i < 3; i++) {
if ( board[i][j]==cur &&
board[i + 1][j]==cur &&
board[i + 2][j]==cur &&
board[i + 3][j]==cur) {
return true;
}
}
}
// pos slope diag
for (int j = 0; j < 4; j++) {
for (int i = 3; i < HEIGHT; i++) {
if ( board[i][j]==cur &&
board[i - 1][j + 1]==cur &&
board[i - 2][j + 2]==cur &&
board[i - 3][j + 3]==cur) {
return true;
}
}
}
// neg slope diag
for (int j = 0; j < 4; j++) {
for (int i = 0; i < 3; i++) {
if ( board[i][j]==cur &&
board[i + 1][j + 1]==cur &&
board[i + 2][j + 2]==cur &&
board[i + 3][j + 3]==cur) {
return true;
}
}
}
return false;
}
List<int[]> nextMoves() {
List<int[]> result = new ArrayList<>();
for (int j = 0; j < WIDTH; j++) {
//if column j isnt full then add
if (board[0][j]==' ') result.add(new int[] {findY(j),j});
}
return result;
}
int findY(int col) {
int i = board.length - 1;
while (i > -1) {
if (board[i][col]==' ') break;
i--;
}
return i;
}
/**
* #param deg depth
* #param player either MAX or MIN
* #return int {score,column}
*/
int[] MiniMax(int deg, char player) {
// list of possible columns to place a checker
List<int[]> moves = nextMoves();
int v;
char prev;
if (player==player1) prev = player2;
else prev = player1;
// IF depth = 0 OR there is a connect 4 OR the board is full (draw) THEN
// return your static evaluation (heuristic)
// if the current position has a connect 4, return -inf or inf
// depending on if player is MIN or MAX respectively
if (checkPos(prev) || (deg == this.deg) || moves.isEmpty())
return new int[] {eval(),bestMove};
//MAX
else if (player==player1) {
v = -inf;
for (int[] move : moves) { // move = {row,column}
board[move[0]][move[1]] = player1; // make move
int score = MiniMax(deg+1,player2)[0];
board[move[0]][move[1]] = ' '; // undo move
if (score>v) {
v = score;
bestMove = move[1];
}
}
return new int[] {v,bestMove};
}
//MIN
else {
v = inf;
for (int[] move : moves) { // move = {row,column}
board[move[0]][move[1]] = player2; // make move
int score = MiniMax(deg+1,player1)[0];
board[move[0]][move[1]] = ' '; // undo move
if (v>score) {
v = score;
bestMove = move[1];
}
}
return new int[] {v,bestMove};
}
}
public static void main(String[] args) {
Connect4 c = new Connect4();
c.deg = 5;
char[][] b = {
{' ',' ',' ',' ',' ',' ',' '},
{' ',' ',' ',' ',' ',' ',' '},
{' ',' ',' ',' ',' ',' ',' '},
{' ',' ',' ',' ',' ',' ',' '},
{' ','X','X','X',' ',' ',' '},
{' ','O','X','X','X',' ',' '}
};
c.board = copy(b);
c.prints();
System.out.println("best value = " + c.MiniMax(0, player1)[1]);
}
}
Related
I am attempting to make an unbeatable Tic Tac Toe game using a simplified minimax algorithm. The code looks like this:
private static int findBestMove(String[][] board, boolean comp) {
// comp returns true if the computer is the one looking for the best move
// findBestMove is always called by the program as findBestMove(board, true)
// since the computer is the only one that uses it
// If the board in its current state is a win for the
// player, return -1 to indicate a loss
if (playerWon(board)) return -1;
// If the board in its current state is a win for the
// computer, return 1 to indicate a win
if (compWon(board)) return 1;
// If the board in its current state is a tie
// return 0 to indicate a tie
if (tie(board)) return 0;
// Set the default possible outcome as the opposite of what
// the respective player wants
int bestPossibleOutcome = comp ? -1 : 1;
// Loop through the board looking for empty spaces
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++)
// Once an empty space is found, create a copy of the board
// with that space occupied by the respective player
if (board[i][j].equals(" ")) {
String[][] newBoard = new String[3][3];
for (int a = 0; a < 3; a++) {
System.arraycopy(board[a], 0, newBoard[a], 0, 3);
}
newBoard[i][j] = comp ? "O" : "X";
// Recursively call findBestMove() on this copy
// and see what the outcome is
int outCome = findBestMove(newBoard, !comp);
// If this is the computer's turn, and the outcome
// is higher than the value currently stored as the
// best, replace it
if (comp && outCome > bestPossibleOutcome) {
bestPossibleOutcome = outCome;
// r and c are instance variables that store the row
// and column of what the computer's next move should be
r = i;
c = j;
// If this is the player's turn, and the outcome
// is lower than the value currently stored as the
// best, replace it
} else if (!comp && outCome < bestPossibleOutcome) {
bestPossibleOutcome = outCome;
}
}
}
}
// Return the ultimate value deemed to be the best
return bestPossibleOutcome;
}
The idea is that after I run this program, the instance variables r and c should contain the row and column, respectively, of the computer's best move. However, the program only successfully prevents a loss about half the time, and I can't tell if the other half is luck, or if the program is actually working.
I am aware that the computer will respond to every scenario exactly the same way each game. That is fine.
In the event anyone would like to run the program, I have included the full class below:
import java.util.Scanner;
public class TicTacToe {
private static int r;
private static int c;
private static void printBoard(String[][] board) {
System.out.println(" 0 1 2");
System.out.println("0 " + board[0][0] + " | " + board[0][1] + " | " + board[0][2] + " ");
System.out.println(" ---+---+---");
System.out.println("1 " + board[1][0] + " | " + board[1][1] + " | " + board[1][2] + " ");
System.out.println(" ---+---+---");
System.out.println("2 " + board[2][0] + " | " + board[2][1] + " | " + board[2][2] + " ");
}
private static boolean playerWon(String[][] board) {
return playerHasThreeInCol(board) || playerHasThreeInDiag(board) || playerHasThreeInRow(board);
}
private static boolean playerHasThreeInRow(String[][] board) {
for (int i = 0; i < 3; i++) {
if (board[i][0].equals(board[i][1]) && board[i][0].equals(board[i][2]) && board[i][0].equals("X")) return true;
}
return false;
}
private static boolean playerHasThreeInCol(String[][] board) {
for (int i = 0; i < 3; i++) {
if (board[0][i].equals(board[1][i]) && board[0][i].equals(board[2][i]) && board[0][i].equals("X")) return true;
}
return false;
}
private static boolean playerHasThreeInDiag(String[][] board) {
if (board[0][0].equals(board[1][1]) && board[0][0].equals(board[2][2]) && board[0][0].equals("X")) return true;
return board[0][2].equals(board[1][1]) && board[0][2].equals(board[2][0]) && board[0][2].equals("X");
}
private static boolean compWon(String[][] board) {
return compHasThreeInCol(board) || compHasThreeInDiag(board) || compHasThreeInRow(board);
}
private static boolean compHasThreeInRow(String[][] board) {
for (int i = 0; i < 3; i++) {
if (board[i][0].equals(board[i][1]) && board[i][0].equals(board[i][2]) && board[i][0].equals("O")) return true;
}
return false;
}
private static boolean compHasThreeInCol(String[][] board) {
for (int i = 0; i < 3; i++) {
if (board[0][i].equals(board[1][i]) && board[0][i].equals(board[2][i]) && board[0][i].equals("O")) return true;
}
return false;
}
private static boolean compHasThreeInDiag(String[][] board) {
if (board[0][0].equals(board[1][1]) && board[0][0].equals(board[2][2]) && board[0][0].equals("O")) return true;
return board[0][2].equals(board[1][1]) && board[0][2].equals(board[2][0]) && board[0][2].equals("O");
}
private static boolean tie(String[][] board) {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (board[i][j].equals(" ")) return false;
}
}
return true;
}
private static int findBestMove(String[][] board, boolean comp) {
if (playerWon(board)) return -1;
if (compWon(board)) return 1;
if (tie(board)) return 0;
int bestPossibleOutcome = comp ? -1 : 1;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (board[i][j].equals(" ")) {
String[][] newBoard = new String[3][3];
for (int a = 0; a < 3; a++) {
System.arraycopy(board[a], 0, newBoard[a], 0, 3);
}
newBoard[i][j] = comp ? "O" : "X";
int outCome = findBestMove(newBoard, !comp);
if (comp && outCome > bestPossibleOutcome) {
bestPossibleOutcome = outCome;
r = i;
c = j;
} else if (!comp && outCome < bestPossibleOutcome) {
bestPossibleOutcome = outCome;
}
}
}
}
return bestPossibleOutcome;
}
private static void go() {
Scanner input = new Scanner(System.in);
String[][] board = new String[3][3];
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
board[i][j] = " ";
}
}
printBoard(board);
for (int i = 0;; i++) {
if (i % 2 == 0) {
while (true) {
System.out.println("Enter position: ");
String position = input.nextLine();
int row, column;
try {
row = Integer.parseInt(position.substring(0, 1));
column = Integer.parseInt(position.substring(1, 2));
} catch (Exception e) {
System.out.println("Invalid entry. ");
continue;
}
if (row < 0 || row > 2 || column < 0 || column > 2) {
System.out.println("That position is not on the board. ");
continue;
}
if (!board[row][column].equals(" ")) {
System.out.println("That space is already taken. ");
continue;
}
board[row][column] = "X";
break;
}
} else {
System.out.println("\nMy move: ");
findBestMove(board, true);
board[r][c] = "O";
}
printBoard(board);
if (playerWon(board)) {
System.out.println("You win!");
break;
} else if (compWon(board)) {
System.out.println("I win!");
break;
} else if (tie(board)) {
System.out.println("Tie game");
break;
}
}
}
public static void main(String[] args) {
go();
}
}
I'm not asking for anyone to rewrite the whole thing for me, but if you can point out any obvious mistakes or point me in the right direction, that would be appreciated. I am also open to any suggestions or comments that you may have.
I haven't extensively tested it yet, but I believe that I resolved the issue. The new code looks like this:
private static void findBestMove(String[][] board) {
double bestMove = Double.NEGATIVE_INFINITY;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (board[i][j].equals(" ")) {
board[i][j] = "O";
double score = minimax(board, false);
board[i][j] = " ";
if (score > bestMove) {
bestMove = score;
r = i;
c = j;
}
}
}
}
}
private static double minimax(String[][] board, boolean comp) {
if (playerWon(board)) {
return -1;
}
if (compWon(board)) {
return 1;
}
if (tie(board)) return 0;
double bestScore;
if (comp) {
bestScore = Double.NEGATIVE_INFINITY;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (board[i][j].equals(" ")) {
board[i][j] = "O";
double score = minimax(board, false);
board[i][j] = " ";
bestScore = Math.max(score, bestScore);
}
}
}
} else {
bestScore = Double.POSITIVE_INFINITY;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (board[i][j].equals(" ")) {
board[i][j] = "X";
double score = minimax(board, true);
board[i][j] = " ";
bestScore = Math.min(score, bestScore);
}
}
}
}
return bestScore;
}
I abstracted the minimax algorithm away from the next move coordinate setter, which I think may have made a difference. Otherwise, it is very similar.
I need to create a method to check wether the tictactoe game is PLAYING, DRAW, XWIN or OWIN. However, I am having difficulty writing the code to check if X or O has won, given that the size of the gameboard and the size needed to win (sizeWin) are changing according to the user's input. AND I am forced to use a 1D array for the game board. I simply do not know where to go from here. My latest idea was to use nested for loops to check for a win by row, column or diagonal but I'm not sure how to implement it. If anyone has any tips on how to approach this problem or has any other solutions I would be very grateful
private void setGameState(int i) {
// Check rows
getLines();
getColumns();
getSizeWin();
for (row = 0; row == lines; row++) {
for (col = 0; col == columns; col++) {
}
}
}
public TicTacToeGame(int lines, int columns, int sizeWin) {
// linesXcolumns game, starts with X, need sizeWin in a line/column/diag to win
this.lines = lines;
this.columns = columns;
CellValue currentCellValue = CellValue.X;
this.sizeWin = sizeWin;
// Creating board according to given size
int size = lines * columns;
this.board = new CellValue[size];
// Setting up board to be empty
for (int i = 0; i < size; i++) {
board[i] = CellValue.EMPTY;
}
}
PS. If someone were to call the operator TicTacToe(3,4,3), a game board of 3 lines and 4 columns would print. And the number of X's or O's to win would be 3.
CAM$ java TicTacToe 3 4 3
| | |
---------------
| | |
---------------
| | |
It is a little more complicated than it looks but after you get it it's simple. I've made a function that works just fine:
private static String checkGameState() {
// Looking for errors.
if (rowCount <= 0 || columnCount <= 0) {
return "ERROR: Illegal board size: " + rowCount + "*" + columnCount;
}
if (boradContent.length != rowCount * columnCount) {
return "ERROR: boradContent not compatible with rowSize and columnSize.";
}
if (sizeWin > rowCount && sizeWin > columnCount) {
return "ERROR: Board is too small for this sizeWin: " + sizeWin + ".";
}
String gameState = "PLAYING";
// Checking rows
for (int i = 0; i < rowCount; i++) {
char currentChar = getField(i, 0);
int score = 1;
for (int j = 1; j < columnCount; j++) {
if (currentChar == getField(i, j)) {
score++;
if (score >= sizeWin) {
if (gameState.equals("PLAYING")) {
gameState = currentChar + "WIN";
} else if (!gameState.equals(currentChar + "WIN")) {
gameState = "DRAW";
return gameState;
}
}
} else {
if (j > columnCount - sizeWin) {
break;
}
score = 1;
currentChar = getField(i, j);
}
}
}
// Checking columns
for (int j = 0; j < columnCount; j++) {
char currentChar = getField(0, j);
int score = 1;
for (int i = 1; i < rowCount; i++) {
if (currentChar == getField(i, j)) {
score++;
if (score >= sizeWin) {
if (gameState.equals("PLAYING")) {
gameState = currentChar + "WIN";
} else if (!gameState.equals(currentChar + "WIN")) {
gameState = "DRAW";
return gameState;
}
}
} else {
if (j > rowCount - sizeWin) {
break;
}
score = 1;
currentChar = getField(i, j);
}
}
}
// Checking diagonally
// Checking diagonally - from top-left to bottom-right
for (int i = 0; i < rowCount - sizeWin + 1; i++) {
for (int j = 0; j < columnCount - sizeWin + 1; j++) {
char currentChar = getField(i, j);
int score = 1;
for (int k = 1; k < sizeWin; k++) {
if (currentChar == getField(i + k, j + k)) {
score++;
if (score >= sizeWin) {
if (gameState.equals("PLAYING")) {
gameState = currentChar + "WIN";
} else if (!gameState.equals(currentChar + "WIN")) {
gameState = "DRAW";
return gameState;
}
}
} else {
break;
}
}
}
}
// Checking diagonally - from top-right to bottom-left
for (int i = 0; i < rowCount - sizeWin + 1; i++) {
for (int j = sizeWin -1; j < columnCount; j++) {
char currentChar = getField(i, j);
int score = 1;
for (int k = 1; k < sizeWin; k++) {
if (currentChar == getField(i + k, j - k)) {
score++;
if (score >= sizeWin) {
if (gameState.equals("PLAYING")) {
gameState = currentChar + "WIN";
} else if (!gameState.equals(currentChar + "WIN")) {
gameState = "DRAW";
return gameState;
}
}
} else {
break;
}
}
}
}
return gameState;
}
It is worth to mention that the rowCount, columnCount, sizeWin and boradContent variables are class level variables and i used a getField(int X, int Y) method that is not a very complicated thing but is more useful. It just converts the given field coordinates to the place in a 1D array and returns it's content:
private static char getField(int X, int Y) {
return boradContent[X * columnCount + Y];
}
My checkWin method returns false until there's a winner in the Connect 4 game by putting 4 "checkers" in a row horizontally, vertically, or diagonally in my board array. Once there's a winner, the checkWin method returns true, the nearest if statement iterates, printing the winner, then terminating the entire loop (if I coded it all correctly). However, when I run the program, the while loop iterates only once, accepts one input for red, states red won, then does the same thing for yellow, then terminates.
What am I missing here?
Below is the relevant code.
Thank you.
public static void main(String[] args) {
char[][] board = new char[6][7];
boolean loop = true;
// loop to alternate players until there's a winner
while (loop) {
printData(board);
red(board);
if (checkWin(board) == true) {
printData(board);
System.out.print("Red wins!");
loop = false;
}
printData(board);
yellow(board);
if (checkWin(board) == true) {
printData(board);
System.out.print("Yellow wins!");
loop = false;
}
}
}
public static void printData(char[][] tbl) {
for (int r = 0; r < tbl.length; r++) {
for (int c = 0; c < tbl[r].length; c++) {
if (tbl[r][c] == 0) {
System.out.print("| ");
} else {
System.out.print("|" + tbl[r][c]);
}
} // end for col loop
System.out.println("|");
} // end for row loop
System.out.println("---------------");
} // end printData method
public static void red(char[][] f) {
System.out.println("Place a red checker at column (0-6)");
Scanner in = new Scanner(System.in);
int c = in.nextInt();
for (int i = 5; i >= 0; i--) {
if (f[i][c] == 0) {
f[i][c] = 'R';
break;
}
}
}
public static void yellow(char[][] f) {
System.out.println("Place a yellow checker at column (0-6)");
Scanner in = new Scanner(System.in);
int c = in.nextInt();
for (int i = 5; i >= 0; i--) {
if (f[i][c] == 0) {
f[i][c] = 'Y';
break;
}
}
}
// Method to check for a winner. Receives 2-D array as parameter. Returns
// boolean value.
public static boolean checkWin(char[][] b) {
// Create four boolean variables, one for each set of rows. Initialize
// all of them to false.
boolean foundRow = false;
boolean foundCol = false;
boolean foundMjrD = false;
boolean foundMinD = false;
// Check to see if four consecutive cells in a row match.
// check rows
for (int r = 0; r <= 5; r++) {
for (int c = 0; c <= 3; c++) {
if (b[r][c] == b[r][c + 1] && b[r][c] == b[r][c + 2] && b[r][c] == b[r][c + 3] && b[r][c] != ' ') {
foundRow = true;
break;
}
}
}
// Check to see if four columns in the same row match
// check columns
for (int r = 0; r <= 2; r++) {
for (int c = 0; c <= 6; c++) {
if (b[r][c] == b[r + 1][c] && b[r][c] == b[r + 2][c] && b[r][c] == b[r + 3][c] && b[r][c] != ' ') {
foundCol = true;
break;
}
}
}
// Check to see if four diagonals match (top left to bottom right)
// check major diagonal
for (int r = 0; r <= 2; r++) {
for (int c = 0; c <= 3; c++) {
if (b[r][c] == b[r + 1][c + 1] && b[r][c] == b[r + 2][c + 2] && b[r][c] == b[r + 3][c + 3]
&& b[r][c] != ' ') {
foundMjrD = true;
break;
}
}
}
// Check to see if four diagonals in the other direction match (top
// right to bottom left)
// check minor diagonal
for (int r = 0; r <= 2; r++) {
for (int c = 3; c <= 6; c++) {
if (b[r][c] == b[r + 1][c - 1] && b[r][c] == b[r + 2][c - 2] && b[r][c] == b[r + 3][c - 3]
&& b[r][c] != ' ') {
foundMinD = true;
break;
}
}
}
// If ONE of the booleans is true, we have a winner.
// checks boolean for a true
if (foundRow || foundCol || foundMjrD || foundMinD)
return true;
else
return false;
} // end checkWin method
By what I've analyzed by debugging your code , you have not set boolean variable to "true" after toggling it to false. After you are coming out of condition make that boolean variable "true" again.
May this help you. Happy Coding
You should take a closer look at this line:
if (b[r][c] == b[r][c + 1] && b[r][c] == b[r][c + 2] && b[r][c] == b[r][c + 3] && b[r][c] != ' ') {
You check for b[r][c] != ' ', but you never put a space in char[][] board, therefore the default value in board[?][?] is 0.
How do I prevent the same tic tac toe coordinate from being inputted by the user?
The user input is taken at the main method in the Game Class.
The tic tac toe cells with [x, y] coordinates ranging from (0-2) can be either:
0(_), 1 (X) or 2 (O)
Grid Class with alpha beta search tree pruning algorithm
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
class Grid {
List<Cell> availableCells;
int[][] board = new int[3][3];
Scanner scan = new Scanner(System.in);
// Set limit to search tree depth
int treeDepth = 9;
List<CellsAndScores> rootsChildrenScore = new ArrayList<>();
public int score() {
int score = 0;
// Check all columns
for (int j = 0; j < 3; ++j) {
int X = 0;
int O = 0;
for (int i = 0; i < 3; ++i) {
if (board[i][j] == 0) {
} else if (board[i][j] == 1) {
X++;
} else {
O++;
}
}
score += changeInScore(X, O);
}
// Check all rows
for (int i = 0; i < 3; ++i) {
int X = 0;
int O = 0;
for (int j = 0; j < 3; ++j) {
if (board[i][j] == 0) {
} else if (board[i][j] == 1) {
X++;
} else {
O++;
}
}
score += changeInScore(X, O);
}
int X = 0;
int O = 0;
// Check diagonal (first)
for (int i = 0, j = 0; i < 3; ++i, ++j) {
if (board[i][j] == 1) {
X++;
} else if (board[i][j] == 2) {
O++;
} else {
}
}
score += changeInScore(X, O);
X = 0;
O = 0;
// Check Diagonal (Second)
for (int i = 2, j = 0; i > -1; --i, ++j) {
if (board[i][j] == 1) {
X++;
} else if (board[i][j] == 2) {
O++;
} else {
}
}
score += changeInScore(X, O);
return score;
}
private int changeInScore(int X, int O) {
int change;
if (X == 3) {
change = 100;
} else if (X == 2 && O == 0) {
change = 10;
} else if (X == 1 && O == 0) {
change = 1;
} else if (O == 3) {
change = -100;
} else if (O == 2 && X == 0) {
change = -10;
} else if (O == 1 && X == 0) {
change = -1;
} else {
change = 0;
}
return change;
}
public int alphaBetaMinimax(int alpha, int beta, int depth, int turn) {
if (beta <= alpha) {
System.out.println("Pruning at tree depth = " + depth + " alpha: " + alpha + " beta: " + beta);
if (turn == 1)
return Integer.MAX_VALUE;
else
return Integer.MIN_VALUE;
}
if (depth == treeDepth || gameOver()) {
return score();
}
List<Cell> cellsAvailable = getAvailableStates();
if (cellsAvailable.isEmpty()) {
return 0;
}
if (depth == 0) {
rootsChildrenScore.clear();
}
int maxValue = Integer.MIN_VALUE, minValue = Integer.MAX_VALUE;
for (int i = 0; i < cellsAvailable.size(); ++i) {
Cell cell = cellsAvailable.get(i);
int currentScore = 0;
if (turn == 1) {
placeAMove(cell, 1);
currentScore = alphaBetaMinimax(alpha, beta, depth + 1, 2);
maxValue = Math.max(maxValue, currentScore);
// Set alpha
alpha = Math.max(currentScore, alpha);
if (depth == 0) {
rootsChildrenScore.add(new CellsAndScores(currentScore, cell));
}
} else if (turn == 2) {
placeAMove(cell, 2);
currentScore = alphaBetaMinimax(alpha, beta, depth + 1, 1);
minValue = Math.min(minValue, currentScore);
// Set beta
beta = Math.min(currentScore, beta);
}
// reset board
board[cell.x][cell.y] = 0;
// Do not evaluate the rest of the branches after search tree is pruned
if (currentScore == Integer.MAX_VALUE || currentScore == Integer.MIN_VALUE)
break;
}
return turn == 1 ? maxValue : minValue;
}
public boolean gameOver() {
// Game is over is someone has won, or board is full (draw)
return (hasXWon() || hasOWon() || getAvailableStates().isEmpty());
}
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("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] == 1)
|| (board[0][i] == board[1][i] && board[0][i] == board[2][i] && board[0][i] == 1))) {
// System.out.println("X Row or Column win");
return true;
}
}
return false;
}
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("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] == 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;
}
public List<Cell> getAvailableStates() {
availableCells = new ArrayList<>();
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
if (board[i][j] == 0) {
availableCells.add(new Cell(i, j));
}
}
}
return availableCells;
}
public void placeAMove(Cell Cell, int player) {
board[Cell.x][Cell.y] = player; // player = 1 for X, 2 for O
}
public Cell returnBestMove() {
int MAX = -100000;
int best = -1;
for (int i = 0; i < rootsChildrenScore.size(); ++i) {
if (MAX < rootsChildrenScore.get(i).score) {
MAX = rootsChildrenScore.get(i).score;
best = i;
}
}
return rootsChildrenScore.get(best).cell;
}
public void displayBoard() {
System.out.println();
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
if (board[i][j] == 0)
System.out.print("_" + " ");
if (board[i][j] == 1)
System.out.print("X" + " ");
if (board[i][j] == 2)
System.out.print("O" + " ");
}
System.out.println("");
}
System.out.println();
}
public void resetGrid() {
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
board[i][j] = 0;
}
}
}
}
Cell class
class Cell {
int x, y;
public Cell(int x, int y) {
this.x = x;
this.y = y;
}
public String toString() {
return "[" + x + ", " + y + "]";
}
}
class CellsAndScores {
int score;
Cell cell;
CellsAndScores(int score, Cell cell) {
this.score = score;
this.cell = cell;
}
}
Game Class with main method - takes user input
import java.util.Random;
public class Game {
public static void main(String[] args) {
Grid grid = new Grid();
Random random = new Random();
grid.displayBoard();
System.out.print("Who moves first? [1]Computer(X) [2]User(O): ");
int turn = grid.scan.nextInt();
if (turn == 1) {
Cell p = new Cell(random.nextInt(3), random.nextInt(3));
grid.placeAMove(p, 1);
grid.displayBoard();
}
while (!grid.gameOver()) {
int x = 0, y = 0;
System.out.print("Please enter an x coordinate [0-2]: ");
x = grid.scan.nextInt();
System.out.print("Please enter an y coordinate [0-2]: ");
y = grid.scan.nextInt();
Cell userMove = new Cell(y, x);
grid.placeAMove(userMove, 2); // 2 for O and O is the user
grid.displayBoard();
if (grid.gameOver())
break;
grid.alphaBetaMinimax(Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 1);
for (CellsAndScores pas : grid.rootsChildrenScore)
System.out.println("Cell: " + pas.cell + " Score: " + pas.score);
grid.placeAMove(grid.returnBestMove(), 1);
grid.displayBoard();
}
if (grid.hasXWon()) {
System.out.println("Unfortunately, you lost!");
grid.resetGrid();
} else if (grid.hasOWon()) {
System.out.println("You win!");
grid.resetGrid();
} else {
System.out.println("It's a draw!");
grid.resetGrid();
}
}
}
My answer would be to add a boolean check method into your Grid.java class and then in your main method - call this boolean check method before the placeAMove() method.
For example, in your Grid.java class, adding the following method:
/*
* Return true if space is ok to use.
*/
public boolean isMoveOK(Cell cell) {
return board[cell.x][cell.y] == 0;
}
This way, using your pre-existing 0/1/2 values that keep track of empty/X/O space values, you may provide a check to see if the space value is zero or not.
This would be one way to use it in your main method, to answer your question of, 'How do I prevent the same tic tac toe coordinate from being inputted by the user?'
Cell userMove = new Cell(y, x);
if (grid.isMoveOK(userMove)) {
grid.placeAMove(userMove, 2); // 2 for O and O is the user
} else {
System.out.println("Please try a different space/cell");
continue;
}
grid.displayBoard();
if (grid.gameOver())
break;
In this way, I am skipping the remaining loop code in your main method loop, until there's a valid open space. (When there is, then the program should proceed to check for winning values or whether to proceed playing)
Hope this answers your question! :)
Cheers
This question already has answers here:
Algorithm to generate a crossword [closed]
(13 answers)
Closed 6 years ago.
I am working on cross word algorithm to develop a word app. After doing a lot of googling or search on StackOverflow, I was able to reach this point. But yet I am not able to understand the right implementation for algorithm in Java. Below is the class I used.
public class Crosswords {
char[][] cross;
int rows;
int cols;
char[][] numberGrid;
boolean startword;
final char DEFAULT = ' ';
public Crosswords() {
rows = 50;
cols = 50;
cross = new char[rows][cols];
numberGrid = new char [rows][cols];
for (int i = 0; i < cross.length;i++){
for (int j = 0; j < cross[i].length;j++){
cross[i][j] = DEFAULT;
}
}
}
public Crosswords(int ros, int colls) {
rows = ros;
cols = colls;
cross = new char[rows][cols];
numberGrid = new char [rows][cols];
for (int i = 0;i < cross.length; i++){
for (int j = 0; j < cross[i].length; j++){
cross[i][j] = DEFAULT;
}
}
}
public String toString() {
String s = new String();
//String d = new String();
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++){
s = s + cross[i][j] + " ";
}
s = s + "\n";
}
return s;
}
public void addWordh(String s, int r, int c) {
int i = 0;
int j = 0;
boolean b = true;
boolean intersectsWord = true;
if (s.length() > cols) {
System.out.println(s + " is longer than the grid. Please try another word.");
return;
}
if (c + s.length() > cols) {
System.out.println(s + " is too long. Please try another word.");
return;
}
if ((r - 2) >= 0) {
if ((cross[r - 1][c - 1 + s.length()] == DEFAULT) || (cross[r - 1][c - 1 + s.length()] == '*')) {
intersectsWord = false;
}
else { intersectsWord = true;}
if (intersectsWord == true) {
System.out.println("The word " + s + " intersects the beginning of another word!");
return;
}
}
for (i = 0; i < s.length(); i++) {
if ((cross[r - 1][c - 1 + i] == DEFAULT) || (cross[r - 1][c - 1 + i] == s.charAt(i))) {
b = true;
}
else {
b = false;
System.out.println("Unable to add " + s + ". Please try another word.");
return;}
}
if (b == true) {
if ((s.length() <= cols) && (c + s.length() <= cols) &&
(cross[r - 1][c - 1] == s.charAt(0)) || (cross[r - 1][c - 1] == DEFAULT)) {
while (j < s.length()) {
cross[r - 1][c - 1 + j] = s.charAt(j);
if (j==0){
startword = true;
}
cross[rows - 1 - (r - 1)][cols - 1 - (c - 1 + j)] = '*';
j++;
}
}
}
}
public void addWordv(String s, int r, int c) {
int i = 0;
int j = 0;
boolean b = true;
boolean intersectsWord = true;
if (s.length() > rows) {
System.out.println(s + " is longer than the grid. Please try another word.");
}
if (r + s.length() > rows) {
System.out.println(s + " is too long. Please try another word.");
}
else {
if ((r - 2) >= 0) {
if ((cross[r - 2][c - 1] == DEFAULT) || (cross[r - 2][c - 1] == '*')) {
intersectsWord = false;
}
else { intersectsWord = true;}
if (intersectsWord == true) {
System.out.println("The word " + s + " intersects the end of another word!");
return;
}
}
if ((cross[r - 1 + s.length()][c - 1] == DEFAULT) || (cross[r - 1 + s.length()][c - 1] == '*')) {
intersectsWord = false;
}
else { intersectsWord = true;}
if (intersectsWord == true) {
System.out.println("The word " + s + " intersects the end of another word!");
return;
}
for (i = 0; i < s.length(); i++) {
if ((cross[r - 1 + i][c - 1] == DEFAULT) || (cross[r - 1 + i][c - 1] == s.charAt(i))) {
b = true;
}
else {
b = false;
System.out.println("Unable to add " + s + ". Please try another word.");
return;}
}
if (b == true) {
if ((s.length() <= rows) && (r + s.length() <= cols) &&
(cross[r - 1][c - 1] == s.charAt(0)) || (cross[r - 1][c - 1] == DEFAULT)) {
while (j < s.length()) {
cross[r - 1 + j][c - 1] = s.charAt(j);
if (j==0){
startword = true;
}
cross[rows - 1 - (r - 1 + j)][cols - 1 - (c - 1)] = '*';
j++;
}
}
}
}
}
public void setNumberGrid(){
numberGrid = new char [rows][cols];
for (int i = 0; i < cross.length; i++){
for (int j=0; j < cross[rows].length; j++){
if (cross[i][j] == DEFAULT){
numberGrid[i][j] = (char) 0;
}
else if (startword == true){
numberGrid[i][j] = (char) -2;
}
else {
numberGrid[i][j] = (char) -1;
}
}
int count = 1;
for (i=0; i < cross.length; i++){
for (int j=0; j < cross[rows].length; j++){
if (numberGrid[i][j] == -2){
numberGrid[i][j] = (char)count;
count++;
}
}
}
}
}
public String printNumberGrid() {
for (int i=0; i < cross.length; i++){
for (int j=0; j < cross[rows].length; j++){
if (numberGrid[i][j] == (char)-1){
numberGrid[i][j] = ' ';
}
else if (numberGrid[i][j] == (char)0){
numberGrid[i][j] = '#';
}
}
}
String d = new String();
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++){
d = d + numberGrid[i][j] + " ";
}
d = d + "\n";
}
return d;
}
public static void main(String[] args) {
Crosswords g = new Crosswords();
g.addWordv("rawr", 4, 5);
g.addWordh("bot", 5, 4);
g.addWordv("raw", 7, 5);
g.addWordh("cat", 4, 5);
g.addWordh("bass", 6, 10);
System.out.println(g);
Crosswords c = new Crosswords(20, 20);
c.addWordh("HELLO", 1, 1);
c.addWordv("HAPLOID", 1, 1);
c.addWordh("COMPUTER", 3, 12);
c.addWordv("CAT", 2, 11);
c.addWordv("WOAH", 2, 20);
c.addWordh("PARKING", 20, 5);
c.addWordv("ARK", 17, 6);
c.addWordh("AHOY", 6, 18);
c.addWordv("AHOY", 18, 10);
c.addWordv("ADVANTAGE", 2, 12);
c.addWordv("INTERNAL", 2, 18);
c.addWordh("BANTER", 7, 11);
c.addWordv("BEAGLE", 5, 12);
c.addWordh("BASE", 8, 3);
c.addWordv("BALL", 8, 3);
c.addWordh("LEFT", 10, 3);
c.addWordv("SAFE", 8, 5);
System.out.print(c);
}
}
As you can see in Main method that i am adding the words but also giving the row and column number to place the words like c.addWordv("Safe",8,5); where 8 and 5 is column number.
Now Question is how can i implement cross word algorithm which just take words and place them on board randomly without taking the row and column numbers.
Thanks in advance
EDIT:
I want to modify this class algo the way that i dont have to give away the rows and columns number..
//Pseudo Code
If the crossword size is maxSize and any word's length is stored in wordLength ,then you can use random method as below
int maxSize=20;
int wordLength=4;
Random random =new Random();
int r,c;
//for horizontal
r=random.nextInt(maxSize-wordLength);
c=random.nextInt(maxSize);
//for vertical
r=random.nextInt(maxSize);
c=random.nextInt(maxSize-wordLength);
You can store the row and column and generate the new one if its already present.