I added these 3 variable to make operation of finding the winner in O(1) time.
private final int[] rowSum, colSum;
private int diagSum;
private int revDiagSum;
rowSum array contains sum of each row in n * n board, similary colSum contains column sums and diagSum and revDiagSum contains sum in diagonal and reverse diagonal.
Min-Max logic :
// returns an array containing { x, y, score }
// n is board.size here.
private int[] minMax(int player, final int n) {
// At max level --> human's turn | At min level --> Computer's turn
// MIN == -1 | MAX == +1
int best[] = null;
if(player == MIN) {
best = new int[] {-1, -1, Integer.MAX_VALUE};
}
else if(player == MAX) {
best = new int[] {-1, -1, Integer.MIN_VALUE};
}
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
if(board.get(i, j) == 0) {
int win = tryMove(i, j, player);
int []score = null;
if((win != 0) || board.filled()) {
// if this is the last move we can play in game, score is calculated.
score = new int[] {i, j, win};
}
else {
score = minMax(-1 * player, n);
}
undoMove(i, j, player);
if(player == MIN) {
if(score[2] < best[2]) {
best[0] = i;
best[1] = j;
best[2] = score[2];
}
}
else if(player == MAX) {
if(score[2] > best[2]) {
best[0] = i;
best[1] = j;
best[2] = score[2];
}
}
}
}
}
// return the optimal move(with score) player(=computer/human) can play by making a move.
return best;
}
private int tryMove(int i, int j, int player) {
int n = board.getSize();
board.set(i, j, player);
rowSum[i] += player;
colSum[j] += player;
if(i == j)
diagSum += player;
if(i + j == n - 1)
revDiagSum += player;
// if any of the sum == (+n / -n) means player won.
if(rowSum[i] == Math.abs(n) || colSum[j] == Math.abs(n) || diagSum == Math.abs(n) || revDiagSum == Math.abs(n)) {
return player;
}
return 0;
}
private int undoMove(int i, int j, int player) {
int n = board.getSize();
board.set(i, j, 0);
rowSum[i] -= player ;
colSum[j] -= player;
if(i == j)
diagSum -= player;
if(i + j == n - 1)
revDiagSum -= player;
return 0;
}
I call the above Min-Max func() always like this :
int[] move = minMax(-1, n); // always consider computer as the min-player and human as max player.
Now the issue I am facing is, whenever I am running this Game, I
("Human") am able to beat the computer which should not happen in the
ideal case as PC plays optimal moves everytime against us.
Tried debugging, but couldn't figure what's the issue here.
As per the logic, I am not able to suspect the issue in code.
Complete Code: https://github.com/tusharRawat821/Tic-Tac-Toe
Bug Fixed:
Below piece of code should be updated:
if(rowSum[i] == Math.abs(n) || colSum[j] == Math.abs(n) || diagSum == Math.abs(n) || revDiagSum == Math.abs(n)) {
....}
Corrected if condition :
if(Math.abs(rowSum[i]) == n || Math.abs(colSum[j]) == n || Math.abs(diagSum) == n || Math.abs(revDiagSum) == n) {
....}
Related
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
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]);
}
}
I've been working on a Game of Life assignment and am nearing the stage of completion, but am struggling to figure out what I've messed up on such that the GOL rules (and Fredkin rules that the assignment requires us to implement as well) are not generating the proper result.
As I have little experience working with graphics I decided to output everything in the interactions window (using Dr.Java). (It's used to set up menu options like the scale, coordinates (you manually enter), generations, and output the final generation of whichever rule you choose to run (GOL or Fredkin).
The program nextGeneration takes a Boolean array map from the main method (where people input coordinates), and should change it to correspond to the next generation of the Game of Life. This happens by creating an entirely new 2D array, map2, which gets values loaded into it based on the number of neighbors which are turned on for each point. At the end of the program, map gets loaded into map2.(Note: this isn't original, this is required by the assignment)
The program living merely checks if a point in the map array is on or off. countNeighbors takes the 8 neighbors of a particular square, passes them each through the living method, and returns the number of neighbors which are currently on. Since countNeighbors sometimes demands either a negative number, or a number greater than the scale of the map, we implemented conditions in living to create that wraparound universe.
I think the problem(s) most likely arise in nextGeneration. I am somewhat tense about using the operand "or" (written as || ), and I think this may be where I screwed up. If you could just look through the code, and see if what I have said is true is written as true, that would be absolutely wonderful.
Below is the code for the program. It also utilizes a Keyboard.class file which I'm happy to post (however one would do that) if that helps (it's required to compile).
public class GameOfLife {
public static void main(String[] args) {
int r = 0; //rules set. Either 0 or 1, 0 for life game, 1 for Fredkin game
int i = 0; // looping variable
int j = 0; // looping variable
int b = 0; // used to read integer inputs from keyboard
int x = 0; // used during the stage where the player manually changes the board. Represents x coordinate.
int y = 0; // used during the stage where the player manually changes the board. Represents x coordinate.
int gen = 0; //number of generations to be skipped before printing out new map
int scale = 0;
boolean[][] map = new boolean[0][0];
System.out.println("Start game? y/n");
String a = Keyboard.readString();
if (a.equals("n")) {
return;
} else {
System.out.println("Do you wish to know the rules? y/n");
a = Keyboard.readString();
if (a.equals("y")) {
System.out.println("Each coordinate in the printed graph is represented by a 0 or a .");
System.out.println("0 represents a live cell, . represents a dead one.");
System.out.println("Each cell has 8 neighboring cells.");
System.out.println("There are two ways in which the game can be played.");
System.out.println("In the Life model, if a cell has 3 neighbors, if dead, it turns on.");
System.out.println("If it has 2 neighbors, it keeps its current condition.");
System.out.println("Else, it dies. Brutal.");
System.out.println("In the Fredkin Model, only non-diagnol neighbors count.");
System.out.println("If a cell has 1 or 3 neighbors, it is alive.");
System.out.println("If it has 0, 2 or 4, it dies. WAY more Brutal.");
}
System.out.println("Do you want to play by Fredkin or Life Rules? 0 for life, 1 for Fredkin");
while (i == 0) {
b = Keyboard.readInt();
if (b == 1) {
r = 1;
i = 1;
}
if (b == 0) {
r = 0;
i = 1;
}
}
while (j == 0) {
System.out.println("What scale would you like to use? Please enter an integer larger than 4");
b = Keyboard.readInt();
if (b >= 5) {
map = new boolean[b][b];
scale = b;
j = 1;
} else {
System.out.println("Come on, buddy, read the rules");
}
}
j = 0;
while (j == 0) {
System.out.println("Do you want to enter coordinates? y to continue entering coordinates, n to go to next option");
a = Keyboard.readString();
if (a.equals("y")) {
i = 0;
while (i == 0) {
System.out.println("Please enter a value for an X coordinate from 0 to " + (scale - 1));
b = Keyboard.readInt();
if (b >= 0) {
if (b < scale) {
i = 1;
x = b;
}
}
}
i = 0;
while (i == 0) {
System.out.println("Please enter a value for a Y coordinate from 0 to " + (scale - 1));
b = Keyboard.readInt();
if (b >= 0) {
if (b < scale) {
i = 1;
y = b;
}
}
}
map[y][x] = true;
printgame(map);
} else {
if (a.equals("n")) {
j = 1;
}
}
}
i = 0;
while (i == 0) {
System.out.println("How many generations would you like to skip ahead? Please enter a value greater than 0");
b = Keyboard.readInt();
if (b > 0) {
gen = b;
i = 1;
}
}
i = 0;
if (r == 0) {
for (i = 0; i <= gen; i++) {
nextGeneration(map);
}
printgame(map);
} else {
if (r == 1) {
for (i = 0; i <= gen; i++) {
FredGen(map);
}
printgame(map);
}
}
}
}
public static void printgame(boolean[][] map) {
int x = map[0].length;
int y = map[0].length;
int i = 0;
int j = 0;
char c;
String Printer = "";
for (j = 0; j < y; j++) {
for (i = 0; i < x; i++) {
if (map[j][i]) {
c = '0';
} else {
c = '.';
}
Printer = (Printer + " " + c);
}
System.out.println(Printer);
Printer = new String("");
}
}
private static void nextGeneration(boolean[][] map) {
int x = map[0].length;
int y = map[0].length;
int[][] neighborCount = new int[y][x];
boolean[][] map2 = new boolean[y][x];
for (int j = 0; j < y; j++)
for (int i = 0; i < x; i++)
neighborCount[j][i] = countNeighbors(j, i, map);
//this makes a new generation array
for (int j = 0; j < y; j++) {
for (int i = 0; i < x; i++) {
if (map[j][i] = true) { //assumes initial value of array is true (AKA "ALIVE")
if (neighborCount[j][i] == 3) { //check if alive AND meeting condition for life
map2[j][i] = true; //sets character array coordinate to ALIVE: "0"
} else if ((neighborCount[j][i] <= 2) || (neighborCount[j][i] > 3)) { //check if dead from isolation or overcrowding
map2[j][i] = false; //sets character array coordinate to DEAD: "."
}
}
}
}
map = map2;
}
private static int countNeighbors(int j, int i, boolean[][] map) { //counts all 8 elements living/dea of 3x3 space surrounding and including living/dead central coordinate)
return living(j - 1, j - 1, map) + living(j - 1, i, map) +
living(j - 1, i + 1, map) + living(j, i - 1, map) + living(j, i + 1, map) +
living(j + 1, i - 1, map) + living(j + 1, i, map) + living(j + 1, i + 1, map);
}
private static int living(int j, int i, boolean[][] map) {
int x = map[0].length - 1;
if (i < 0) {
i = i + x;
} else {
i = i % x;
}
if (j < 0) {
j = j + x;
} else {
j = j % x;
}
if (map[j][i] == true) {
return 1;
} else {
return 0;
}
}
private static void FredGen(boolean[][] map) {
int x = map[0].length;
int y = map[0].length;
int[][] neighborCount = new int[y][x];
for (int j = 0; j < y; j++)
for (int i = 0; i < x; i++)
neighborCount[j][i] = Freddysdeady(j, i, map);
//this makes a new generation array
for (int j = 0; j < y; j++)
for (int i = 0; i < x; i++)
if (map[j][i] = true) { //assumes initial value of array is true (AKA "ALIVE")
if ((neighborCount[j][i] < 1) || (neighborCount[j][i] == 2) || (neighborCount[j][i] > 3)) { //check if dead from isolation or overcrowding
map[j][i] = false; //sets chracter array coordinate to DEAD: "."
} else if ((neighborCount[j][i] == 1) || (neighborCount[j][i] == 3)) { //check if alive AND meeting condition for life
map[j][i] = true; //sets character array coordinate to ALIVE: "0"
}
}
}
private static int Freddysdeady(int j, int i, boolean[][] map) {
return living(j - 1, i, map) + living(j, i - 1, map) + living(j, i + 1, map) + living(j + 1, i, map);
}
}
There might be other problems, here are a few that I could spot by eye:
In the nextGeneration method, you handle cases where a cell should stay alive or die, but you do not have anything for when cells should be born. You should have something like this:
if(map[x][y]) {
//should this cell stay alive? if yes = live, else die
} else {
//should a cell be born in this slot? if yes = born, else nothing
}
This is a minor issue, still in nextGeneration, in if(count==3)live; else if(count <=2 || count > 3) die; is redundant, you only need if(count == 3) live; else die;
let us know, if you still have problems
I'm working on a program that picks a random location in a grid, checks its neighbors, and assigns the location the value of 1 if any of the neighbors are occupied. If none are occupied, the program picks a random neighbor and moves to it (switch case in diffuse()).
public class Frost {
int width;
int height;
int density;
int seed;
int[][] grid = new int[this.width][this.height];
Random randoma = new Random();
[Later...]
boolean diffuse(){
int steps = 0;
int diffusionSteps = this.height*this.width*2;
int indexA = (int) randoma.nextInt(width);
int indexB = (int) randoma.nextInt(height);
while (steps <= diffusionSteps)
{
if (grid[indexA][indexB] == 0) { //if empty
if (this.checkNorth(indexA, indexB) || this.checkSouth(indexA,indexB) || this.checkEast(indexA,indexB) || this.checkWest(indexA,indexB))
{
grid[indexA][indexB] = 1;
return true;
}
else {
steps++; //increase counter before moving and looping again
int ofFour = this.randoma.nextInt(4);
int tempA = indexA;
int tempB = indexB;
switch(ofFour)
{
case 1:
if (tempA+1 >= this.width)
indexA = (tempA+1)%this.width;
else indexA=tempA+1;
case 2:
if(tempB+1 >= this.height)
indexB = (tempB+1)%this.height;
else indexB = tempB+1;
case 3:
if ((tempB-1) <= 0)
indexB = (indexB-1)%this.height;
else indexB = tempB-1;
case 4:
if ((tempA-1) <= 0)
indexA = (tempA-1)%this.width;
else indexA = tempA-1;
}
}
}
}
if (steps>=diffusionSteps){
grid[indexA][indexB]=1;
return true;
}
return false;
}
I'm getting ArrayIndexOutofBounds: -1 errors for getSouth and getWest methods. I thought the problem might be the decrement operators, but I fiddled with them and nothing changed. Any insights would be appreciated.
boolean checkSouth(int indexA, int indexB)
{
--indexB;
if (indexB > 0)
{return grid[indexA][indexB-1] == 1;}
else
return grid[indexA][((indexB-1)%this.height)] == 1;
}
boolean checkWest(int indexA, int indexB)
{
--indexA;
if (indexA > 0)
return grid[indexA][indexB-1] == 1;
else
return grid[(indexA)%this.width][indexB] == 1;
}
The problem seems to be here
boolean checkSouth(int indexA, int indexB) {
System.out.println(indexA+" "+indexB);
--indexB;
if (indexB > 0) {
return grid[indexA][indexB - 1] == 1;
} else
return grid[indexA][((indexB - 1) % this.height)] == 1;
}
When it's executed with indexB equal to 1. In this case it is first decremented
--indexB; //now it's equal to 0
and then here
(indexB - 1) % this.height
the result is -1, and being it used as index for an array... well, out of bounds!
The problem is probably in the way you do modulo: if index is -1, then index % height will still be -1.
You need to do (index + height) % height to deal with the negative number (provided you want the grid to be cyclic).
To clarify I only wanted one or two for loops to help me on my way, preferably in the same style as I had used in the vertical :)
I'm making a game using a 2D array, and I need a check that tests if at the current position (indicated by a green square) the character there is part of a diagonal sequence of "l" more of the character.
public static boolean diagonals(char[][] b, int row, int col, int l) {
int counter = 1; // because we start from the current position
char charAtPosition = b[row][col];
int numRows = b.length;
int numCols = b[0].length;
int topleft = 0;
int topright = 0;
int bottomleft = 0;
int bottomright = 0;
for (int i=row-1,j=col-1;i>=0 && j>=0;i--,j--) {
if (b[i][j]==charAtPosition) {
topleft++;
} else {
break;
}
}
for (int i=row-1,j=col+1;i>=0 && j<=numCols;i--,j++) {
if (b[i][j]==charAtPosition) {
topright++;
} else {
break;
}
}
for (int i=row+1,j=col-1;i<=numRows && j>=0;i++,j--) {
if (b[i][j]==charAtPosition) {
bottomleft++;
} else {
break;
}
}
for (int i=row+1,j=col+1;i<=numRows && j<=numCols;i++,j++) {
if (b[i][j]==charAtPosition) {
bottomright++;
} else {
break;
}
}
return topleft + bottomright + 1 >= l || topright + bottomleft + 1 >= l; //in this case l is 5
}
The idea is that we walk in four directions and count the steps. This may not be the most efficient implementation, but at least it looks neat and easy to understand.
Here's the function, it works. The explanation is in the comments in the code, but if you find anything confusing let me know and I'll explain it.
public static boolean diagonals(char[][] b, int row, int col, int l) {
int forwardCounter = 1; // this counts top right to bottom left
int backCounter = 1; // this counts top left to bottom right
int distance = 1;
// 0 = topleft, 1 = topright, 2 = bottomleft, 3 = bottomright
boolean[] checks = new boolean[]{true, true, true, true};
char charAtPosition = b[row][col];
while(checks[0] || checks[1] || checks[2] || checks[3]) {
for(int i = 0; i < 4; i++) {
if(checks[i]) {
// This looks confusing but it's simply just converting i into
// The four different directions
checks[i] = checkSquare(b, row + (i < 2 ? -distance : distance),
col + (i % 2 == 0 ? -distance : distance), charAtPosition);
if(checks[i]) {
// If top left or bottom right
if(i % 3 == 0) {
backCounter++;
} else {
forwardCounter++;
}
}
}
}
if (forwardCounter >= l || backCounter >= l) return true;
distance++;
}
return false;
}
private static boolean checkSquare(char[][] b, int row, int col, char check) {
if(row < 0 || row >= b.length) return false;
if(col < 0 || col >= b[0].length) return false;
return check == b[row][col];
}