Connect4 in Libgdx - java

Ok so I've been following this tutorial on Connect4 in java and I've tried to modify it to fit what I already had and to fit into libgdx. After implementing it, i have a few odd problems.
Problem 1: After i make the first move, the computer fills the entire bottom row with his chips and then makes his first move.
Problem 2: The computer isn't displaying any A.I. and simply starts at the first column and first row available and places a chip there. The computer will keep following this pattern.
Problem 3: My winning checker no longer realizes if I have won the game but does realize when the computer has won. When I first designed the game I started by having the computer place chips at random (for testing) and my winning checker worked for the computer and for myself.
The article I followed was here: Connect4 in Java
Here is my code.
ConnectFour.java
package com.comp452.tme31;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
public class ConnectFour extends ApplicationAdapter implements InputProcessor {
// Create final ints for number of columns and rows in game.
protected final static int COLUMNS = 7;
protected final static int ROWS = 6;
protected final static int TILESIZE = 64;
// Create boolean to determine if player can take a turn.
protected boolean playersTurn = true;
// Create boolean for gameover.
protected boolean gameOver = false;
// Create boolean for winners.
private boolean winner = false;
// Sprite batch for texture drawing.
SpriteBatch batch;
// Create textures to represent board and player pieces.
Texture drawingTile, empty, player, computer;
// Create 2D array to hold game board.
private final static int[][] gameBoard = new int[COLUMNS][ROWS];
public static int[][] getGameBoard() {
return gameBoard;
}
// Create variables to display status message.
BitmapFont mainStatusDisplay;
public static String mainStatusString;
public static String winningString;
// Create and set max depth for tree search
private final int MAX_DEPTH = 4;
// Create win, loss and nothing for zero sum game.
private final float WIN = 1f;
private final float LOSE = -1f;
private final float TIE = 0f;
#Override
public void create () {
batch = new SpriteBatch();
empty = new Texture("empty.jpg");
player = new Texture("player.jpg");
computer = new Texture("computer.jpg");
Gdx.input.setInputProcessor(this);
// Initialize display for status messages.
mainStatusDisplay = new BitmapFont();
mainStatusString = "Player's Turn";
winningString = "";
}
#Override
public void render () {
update();
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
drawBoard();
mainStatusDisplay.setColor(Color.YELLOW);
mainStatusDisplay.draw(batch, mainStatusString, 32, 416);
batch.end();
}
public void drawBoard() {
for (int i = 0; i < COLUMNS; i ++) {
for (int j = 0; j < ROWS; j++) {
if (gameBoard[i][j] == 0) {
drawingTile = empty;
}
else if (gameBoard[i][j] == 1) {
drawingTile = player;
}
else if (gameBoard[i][j] == 2) {
drawingTile = computer;
}
batch.draw(drawingTile, i * 64, j * 64);
}
}
}
// Method update handles updates to game logic.
public void update() {
// If it's gameover, end the game.
if (gameOver) {
// Set players turn to true to prevent computer from taking another turn.
playersTurn = true;
// Set status message to winning message;
mainStatusString = winningString;
// Disable input processor to prevent player from taking another turn.
Gdx.input.setInputProcessor(null);
}
// If it's not players turn, call computersTurn.
else if (!playersTurn) {
mainStatusString = "Computer's Turn";
computersTurn();
}
if (checkForWin(1) && checkForWin(2)) {
gameOver = true;
}
}
public void computersTurn() {
double maxScore = 2. * Integer.MIN_VALUE;
int xValue = 0;
// Search the gameboard and find the best move.
for (int x = 0; x < COLUMNS; x++) {
// If x column is a value move...
if (canMove(x)) {
// Set score of move from function.
double score = moveScore(x);
// If score is greater than max score...
if (score > maxScore) {
// Set score to max score and xValue to column.
maxScore = score;
xValue = x;
// If the score is a win, break from loop.
if (score == WIN) {
break;
}
}
}
}
// Set the piece for player at column as x.
setPiece(2, xValue);
// Set players turn and string status.
playersTurn = true;
mainStatusString = "Player's Turn";
}
// Method moveScore determines the value of a move and returns it.
public double moveScore(int xValue) {
// Set the piece in place.
setPiece(2, xValue);
// Get the score and check it's value with alpha beta pruning.
double score = alphaBetaPrune(MAX_DEPTH, Integer.MIN_VALUE, Integer.MAX_VALUE, 1);
// Remove the piece.
takeAwayPiece(xValue);
return score;
}
public double alphaBetaPrune(int depth, double alpha, double beta, int whoPlayed) {
winner = checkForWin(1) || checkForWin(2);
// If we've reached the max depth of the tree or there is a winner...
if (depth == 0 || winner) {
double score;
// If there is a winner...
if (winner) {
// If player is the winner...
if (checkForWin(1)) {
// Set a losing score (Computer does not want player to win).
score = LOSE;
}
// Else this is a win for the computer...
else {
// Set score to a win.
score = WIN;
}
}
// Otherwise there is no winner...
else {
// Set score to TIE (0).
score = TIE;
}
// Return score and remove depth level.
return score / (MAX_DEPTH - depth +1);
}
// If computer is making the move...
if (whoPlayed == 2) {
// Iterate through gameboard.
for (int x = 0; x < COLUMNS; x++) {
// Check and see if next move can be made.
if (canMove(x)) {
// Make move for computer to x.
setPiece(2, x);
// Set alpha equal to return from recursion step minus one depth level.
alpha = Math.max(alpha, alphaBetaPrune(depth - 1, alpha, beta, 1));
// Remove piece.
takeAwayPiece(x);
// Check returned alpha against beta and break from loop if beta is less than alpha.
if (beta <= alpha) {
break;
}
}
}
// We're here if alpha is larger and we didn't break from loop.
return alpha;
}
// Else if player is making move...
else {
// Iterate through gameboard.
for (int x = 0; x < COLUMNS; x++) {
// Check and see if next move can be made.
if (canMove(x)) {
// Make move for player to x.
setPiece(1, x);
// Set beta equal to return from recursion step minus one depth level for beta.
beta = Math.min(beta, alphaBetaPrune(depth - 1, alpha, beta, 2));
// Remove piece.
takeAwayPiece(x);
// Check returned alpha against beta and break from loop if beta is less than alpha.
if (beta <= alpha) {
break;
}
}
}
// We're here if alpha is larger than beta and we didn't break from loop.
return beta;
}
}
// Method setPiece takes two int values as parameters and places a piece on the game board.
public void setPiece(int whoPlayed, int xValue) {
// For loop to iterate through each row.
for (int i = 0; i < ROWS; i++) {
// If row is empty...
if (gameBoard[xValue][i] == 0) {
// Place piece on the board.
gameBoard[xValue][i] = whoPlayed;
break;
}
}
}
// Method takeAwayPiece takes two int values as parameters and removes a piece from the board.
public void takeAwayPiece(int xValue) {
// For loop to iterate through each row.
for (int i = ROWS - 1; i > 0; i--) {
// If row contains a piece..
if (gameBoard[xValue][i] != 0) {
// Remove piece.
gameBoard[xValue][i] = 0;
break;
}
}
}
// Method to determine if a move is valid
public boolean canMove(int xValue) {
// If the top spot in the given column is 0, return true.
return (gameBoard[xValue][ROWS-1] == 0);
}
// Method checkForWin takes a flag and checks to see if that player has won the game.
public boolean checkForWin(int whoPlayed) {
// Create counter to check for 4 in a row.
int win = 0;
// Iterate through gameboard and count pieces in a row.
for (int y = 0; y < ROWS; y++) {
for (int x = 0; x < COLUMNS; x++) {
// If piece is player who is checking, increment counter.
if (gameBoard[x][y] == whoPlayed) {
win++;
}
// Not in a row, set counter to 0.
else {
win = 0;
}
if (win == 4) {
break;
}
}
// If win counter is 4, winner.
if (win == 4) {
winningString = "Horizontal Win for Player " + whoPlayed;
return true;
}
// Else, reset win counter and check next column.
else {
win = 0;
}
}
// Iterate through gameboard and count pieces in a column.
for (int x = 0; x < COLUMNS; x++) {
for (int y = 0; y < ROWS; y++) {
// If piece is player who is checking, increment counter.
if (gameBoard[x][y] == whoPlayed) {
win++;
}
// Not in a row, set counter to 0.
else {
win = 0;
}
if (win == 4) {
break;
}
}
// If win counter is 4, player won.
if (win == 4) {
winningString = "Vertical Win for Player " + whoPlayed;
return true;
}
// Else, reset win counter and check next column.
else {
win = 0;
}
}
// Iterate through gameboard and count pieces in a diagonal row, left to right.
for (int x = 0; x < 3; x++) {
for (int y = 0; y < 2; y++) {
// If piece is player who is checking, check next piece diagonally.
if (gameBoard[x][y] == whoPlayed) {
// Then check next diagonal piece.
if (gameBoard[x+1][y+1] == whoPlayed) {
// Then check next diagonal piece.
if (gameBoard[x+2][y+2] == whoPlayed) {
// Then check last diagonal piece.
if (gameBoard[x+3][y+3] == whoPlayed) {
// Set winning message to player won and set gameover flag.
winningString = "Diagonal Win (LR) for Player " + whoPlayed;
// Exit function.
return true;
}
}
}
}
}
}
// Iterate through gameboard and count pieces in a diagonal row, right to left.
for (int x = 3; x < COLUMNS; x++) {
for (int y = 0; y < 2; y++) {
// If piece is player who is checking, check next piece diagonally.
if (gameBoard[x][y] == whoPlayed) {
// Then check next diagonal piece.
if (gameBoard[x-1][y+1] == whoPlayed) {
// Then check next diagonal piece.
if (gameBoard[x-2][y+2] == whoPlayed) {
// Then check last diagonal piece.
if (gameBoard[x-3][y+3] == whoPlayed) {
// Set winning message to player won and set gameover flag.
winningString = "Diagonal Win (RL) for Player " + whoPlayed;
// Exit function.
return true;
}
}
}
}
}
}
// Iterate through gameboard and if no 0 slots remain, game is a tie.
for (int x = 0; x < COLUMNS; x++) {
for (int y = 0; y < ROWS; y++) {
// If a 0 slot remains, return.
if (gameBoard[x][y] == 0) {
return false;
}
}
}
// If we're here, then there was no winners and no slots left.
winningString = "Tie Game";
return false;
}
#Override
public boolean touchDown(int x, int y, int pointer, int button) {
if (playersTurn) {
if (button == Input.Buttons.LEFT) {
if (canMove(x / TILESIZE)) {
setPiece(1, x / TILESIZE);
playersTurn = false;
return true;
}
}
}
return false;
}
#Override
public boolean touchUp(int i, int i1, int i2, int i3) {
return false;
}
#Override
public boolean touchDragged(int i, int i1, int i2) {
return false;
}
#Override
public boolean mouseMoved(int i, int i1) {
return false;
}
#Override
public boolean scrolled(int i) {
return false;
}
#Override
public boolean keyDown(int i) {
return false;
}
#Override
public boolean keyUp(int i) {
return false;
}
#Override
public boolean keyTyped(char c) {
return false;
}
}
Thanks!

So after the first fix, it was fairly easy to narrow down what the problem was for the other problem. In my takeAwayPiece method, it wasn't removing anything on the bottom row so when the A.I calculated it's next move, its testpieces it was placing at the bottom didn't work.
So in the takeAwayPiece method, i changed it to the following
// Method takeAwayPiece takes two int values as parameters and removes a piece from the board.
public void takeAwayPiece(int xValue) {
// For loop to iterate through each row.
for (int i = ROWS; i > 0; i--) {
System.out.println(i);
// If row contains a piece..
if (gameBoard[xValue][i-1] != 0) {
// Remove piece.
//System.out.println("Piece at column " + xValue + " " + i);
gameBoard[xValue][i-1] = 0;
break;
}
}
}
You might notice that the difference is that i starts = to ROWS instead of ROWS -1 and then i was checking against x-1 instead of x (Previously i just tried changing the condition from i > 0 to i = 0 but that had some crazy strange results).

Related

How to make a knight randomly move in a chessboard?

I have this program called knight tour where the knight moves around a chess board. I have been trying to figure out how to make the knight move randomly, instead of following a pattern.
I would like to know how to randomly move the knight.
Here's my code:
package assignment3;
import java.util.Random;
/*
* knows its current position (row and column)
* knows the eight types of moves it can make
* can tell you it’s current row and column
* can determine whether a move of a given type is legal or not
* can move
*/
public class Knight {
private int boardSize = 8;
private int[] rowMoves = {-1, -2, -2, -1, 1, 2, 2, 1};
private int[] colMoves = {2, 1, -1, -2, -2, -1, 1, 2};
public Knight() {
//ignore this constructor
}
public void InitializeBoard() {
//initialize board
for (int i = 0; i < boardSize; i++)
Arrays.fill(chessboard2[i], Integer.MIN_VALUE); //setting array to negative value
}
/**
* calls method canMove to check if knight can move
* moves knight
*/
public boolean move(int moveNum, int x, int y, int[][] chessboard2) {
Random rand = new Random();
//if moveNum == 64 all squares have been visited
if (moveNum == 64) {
System.out.println("\ntrue board is 64\n");
return true;
}
//int nextRow = rand.nextInt(boardSize);
//int nextCol = rand.nextInt(boardSize);
//for loop to try 8 possibe moves
for (int i = 0; i < rowMoves.length; i++) {
int nextRow = x + rowMoves[i];
int nextCol = y + colMoves[i];
//check if postion is valid and not visited yet
if (canMove(nextRow, nextCol) && chessboard2[nextRow][nextCol] == Integer.MIN_VALUE) {
//if move is valid knight moves
chessboard2[nextRow][nextCol] = moveNum + 1;
//make next move
if(move(moveNum + 1, nextRow, nextCol, chessboard2))
return true;
//move(moveNum + 1, nextRow, nextCol);
//if cant find next move: backtrack
chessboard2[nextRow][nextCol] = Integer.MIN_VALUE;
}
}
return false;
}
/**
* calls method moveLegal from class Chessboard to see if move is legal
* #return true if move is legal, else return false
*/
public boolean canMove(int x, int y) {
//if statement to check if currentRow and currentCol is whithin
//boundaries
return(x >= 0 && x < boardSize && y >= 0 && y < boardSize);
}
public void print() {
for (int i = 0; i < boardSize; i++)
System.out.println(String.join(" ", chessboard2[i]));
}
public void solve() {
//setting array location [0][0] to 0
chessboard2[0][0] = 1;
//check move
if (move(1, 0, 0)) // if true, it will print chess board
print();
else //if false, there is no solution
System.out.print("no solution");
}
}
public class TesterMain {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
Knight test = new Knight();
test.solve();
}
}
Sorry if my code is a bit messy, I am still working on the program.
There is a solution but it would require some refactoring:
Create a ChessMove class that stores a row and a column move (integers)
Add a ChessMove[] to store all possible moves that your knight can possibly do
Refactor the move method:
Create an ArrayList<ChessMove> that stores all possible moves that your knight can do in its current position
Randomly select a move in this list using rand.nextInt(possibleMoves.size());
Here is the complete code:
package assignment3;
import java.lang.*;
import java.util.*;
public class Knight {
private int boardSize = 8;
private int[][] chessboard2 = new int[boardSize][boardSize];
private final ChessMove[] moves = {
new ChessMove(-1, 2),
new ChessMove(-2, 1),
new ChessMove(-2, -1),
new ChessMove(-1, -2),
new ChessMove(1, -2),
new ChessMove(2, -1),
new ChessMove(2, 1),
new ChessMove(1, 2)
};
public Knight() {
initializeBoard();
}
public void initializeBoard() {
for (int i = 0; i < boardSize; i++)
Arrays.fill(chessboard2[i], Integer.MIN_VALUE); //setting array to negative value
}
public boolean move(int moveNum, int x, int y) {
//if moveNum == 64 all squares have been visited
if (moveNum == 64) {
System.out.println("\ntrue board is 64\n");
return true;
}
ArrayList<ChessMove> possibleMoves = new ArrayList<ChessMove>();
for (ChessMove move : moves) {
int nextRow = x + move.row;
int nextCol = y + move.col;
//check if postion is valid and not visited yet
if (canMove(nextRow, nextCol) && chessboard2[nextRow][nextCol] == Integer.MIN_VALUE)
possibleMoves.add(move);
}
if (!possibleMoves.isEmpty()) {
Random rand = new Random();
// Move choice is done here
ChessMove chosenMove = possibleMoves.get(rand.nextInt(possibleMoves.size()));
int nextRow = x + chosenMove.row;
int nextCol = y + chosenMove.col;
//if move is valid knight moves
chessboard2[nextRow][nextCol] = moveNum + 1;
//make next move
move(moveNum + 1, nextRow, nextCol);
return true;
} else
return false;
}
public boolean canMove(int x, int y) {
return (x >= 0 && x < boardSize && y >= 0 && y < boardSize);
}
public void print() {
for (int i = 0; i < boardSize; i++) {
for (int cell : chessboard2[i])
if (cell == Integer.MIN_VALUE)
System.out.print("*** ");
else
System.out.print(String.format("%3d", cell) + " ");
System.out.println();
}
}
public void solve() {
chessboard2[0][0] = 1;
if (move(1, 0, 0)) // if true, it will print chess board
print();
else //if false, there is no solution
System.out.print("no solution");
}
class ChessMove {
int row = 0, col = 0;
ChessMove(int r, int c) {
this.row = r;
this.col = c;
}
}
}
public class TesterMain {
public static void main(String[] args) {
Knight test = new Knight();
test.solve();
}
}
The easiest way to randomise your move is to create a list of valid moves for a given position of the knight and then select one at random. List and Random APIs go hand in hand:
//List<Integer> moves = ...
int move = moves.get(new Random().nextInt(moves.size()));
Restructuring your move method to something like this should do the job:
public boolean move(int moveNum, int x, int y, int [][] chessboard2) {
// 1. List all valid moves
List<Integer> validMoves = new ArrayList<Integer>();
//for loop to try 8 possibe moves
for(int i = 0; i < rowMoves.length; i++) {
if (
canMove(x + rowMoves[i], y + colMoves[i])
&& chessboard2[x + rowMoves[i]][y + colMoves[i]] == Integer.MIN_VALUE
) {
validMoves.add(i);
}
}
// 2. Try to make the move if any available
if (validMoves.isEmpty()) {
return false;
}
Random rand = new Random();
int move = validMoves.get(rand.nextInt(validMoves.size()));
int nextRow = x + rowMoves[move];
int nextCol = y + colMoves[move]:
chessboard2[nextRow][nextCol] = moveNumb + 1;
return move(moveNum + 1, nextRow, nextCol, chessboard2);
}
You can use an enum, let's call it Move, to represent every single move, and then make a list of these moves using Move.values().
Then you can shuffle the list with Collections.shuffle every time you want to move and take the first legal move.

Tic Tac Toe winning condition change when scalable board is larger than 4x4

So, I have been making this Tic Tac Toe program for a while now.
It's a basic Tic Tac Toe game but the game board is scalable. The program is almost finished but one little feature is missing.
I have to make the game end if player gets five or more marks in a row when the game board is larger than 4x4.
F.E. If the game board is 9x9 the game has to end when the player or the computer gets five marks in a row.
(Mark = "O" or "X").
The game now ends when someone gets marks in a row equals to the size of the board (if 9x9 you need 9 marks in a row to win).
I have to implement a feature in the playerHasWon and I've been having a lot of trouble finding out how. I think it's a simple to implement thing but I have not found out how to do it.
Hope my explanation is easy enough to understand. Here's the code:
package tictac;
import java.util.Scanner;
import java.util.Random;
public class Tictac {
public static final int DRAW = 0; // game ends as a draw
public static final int COMPUTER = 1; // computer wins
public static final int PLAYER = 2; // player wins
public static final char PLAYER_MARK = 'X'; // The "X"
public static final char COMPUTER_MARK = 'O'; // The "O"
public static int size; // size of the board
public static String[][] board; // the board itself
public static int score = 0; // game win score
public static Scanner scan = new Scanner(System.in); // scanner
/**
* Builds the board with the integer size and user input.
*
* Displays game win message and switches play turns.
*
* #param args the command line parameters. Not used.
*/
public static void main(String[] args) {
while (true) {
System.out.println("Select board size");
System.out.print("[int]: ");
try {
size = Integer.parseInt(scan.nextLine());
} catch (Exception e) {
System.out.println("You can't do that.");
continue; // after message, give player new try
}
break;
}
int[] move = {};
board = new String[size][size];
setupBoard();
int i = 1;
loop: // creates the loop
while (true) {
if (i % 2 == 1) {
displayBoard();
move = getMove();
} else {
computerTurn();
}
switch (isGameFinished(move)) {
case PLAYER:
System.err.println("YOU WIN!");
displayBoard();
break loop;
case COMPUTER:
System.err.println("COMPUTER WINS!");
displayBoard();
break loop;
case DRAW:
System.err.println("IT'S A DRAW");
displayBoard();
break loop;
}
i++;
}
}
/**
* Checks for game finish.
*
* #param args command line parameters. Not used.
*
* #return DRAW the game ends as draw.
* #return COMPUTER the game ends as computer win.
* #return PLAYERE the game ends as player win.
*/
private static int isGameFinished(int[] move) {
if (isDraw()) {
return DRAW;
} else if (playerHasWon(board, move,
Character.toString(COMPUTER_MARK))) {
return COMPUTER;
} else if (playerHasWon(board, move,
Character.toString(PLAYER_MARK))) {
return PLAYER;
}
return -1; // can't be 0 || 1 || 2
}
/**
* Checks for win for every direction on the board.
*
* #param board the game board.
* #param move move on the board.
* #param playerMark mark on the board "X" or "O".
* #return the game is won.
*/
public static boolean playerHasWon(String[][] board, int[] move,
String playerMark) { //playermark x || o
// horizontal check
for (int i = 0; i < size; i++) {
if (board[i][0].equals(playerMark)) {
int j;
for (j = 1; j < size; j++) {
if (!board[i][j].equals(playerMark)) {
break;
}
}
if (j == size) {
return true;
}
}
}
// vertical check
for (int i = 0; i < size; i++) {
if (board[0][i].equals(playerMark)) {
int j;
for (j = 1; j < size; j++) {
if (!board[j][i].equals(playerMark)) {
break;
}
}
if (j == size) {
return true;
}
}
}
// diagonals check
int i;
for (i = 0; i < size; i++) {
if (!board[i][i].equals(playerMark)) {
break;
}
}
if (i == size) {
return true;
}
for (i = 0; i < size; i++) {
if (!board[i][(size - 1) - i].equals(playerMark)) {
break;
}
}
return i == size;
}
/**
* Checks for draws.
*
* #return if this game is a draw.
*/
public static boolean isDraw() {
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
if (board[i][j] == " ") {
return false;
}
}
}
return true;
}
/**
* Displays the board.
*
*
*/
public static void displayBoard() {
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
System.out.printf("[%s]", board[i][j]);
}
System.out.println();
}
}
/**
* Displays the board.
*
*
*/
public static void setupBoard() {
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
board[i][j] = " ";
}
}
}
/**
* Takes in user input and sends it to isValidPlay.
*
* #return null.
*/
public static int[] getMove() {
Scanner sc = new Scanner(System.in);
System.out.println("Your turn:");
while (true) {
try {
System.out.printf("ROW: [0-%d]: ", size - 1);
int x = Integer.parseInt(sc.nextLine());
System.out.printf("COL: [0-%d]: ", size - 1);
int y = Integer.parseInt(sc.nextLine());
if (isValidPlay(x, y)) {
board[x][y] = "" + PLAYER_MARK;
return new int[]{x, y};
} else { // if input is unallowed
System.out.println("You can't do that");
continue; // after message, give player new try
}
} catch (Exception e) {
System.out.println("You can't do that.");
}
return null;
}
}
/*
* Randomizes computer's turn, where it inputs the mark 'O'.
*
*
*/
public static void computerTurn() {
Random rgen = new Random(); // Random number generator
while (true) {
int x = (int) (Math.random() * size);
int y = (int) (Math.random() * size);
if (isValidPlay(x, y)) {
board[x][y] = "" + COMPUTER_MARK;
break;
}
}
}
/**
* Checks if a move is possible.
*
* #param inX x-move is out of bounds.
* #param inY y-move is out of bounds.
* #return false
*/
public static boolean isValidPlay(int inX, int inY) {
// Play is out of bounds and thus not valid.
if ((inX >= size) || (inY >= size)) {
return false;
}
// Checks if a play have already been made at the location,
// and the location is thus invalid.
return (board[inX][inY] == " ");
}
}
// End of file
Took a quick look, detected the problem and came up with a quick fix:
public static boolean checkDiagonal(String markToLook) {
// how many marks are we looking for in row?
int sizeToWin = Math.min(size, 5);
// running down and right
// don't need to iterate rows that can't be the starting point
// of a winning diagonal formation, thus can exlude some with
// row < (size - (sizeToWin - 1))
for (int row = 0; row < (size - (sizeToWin - 1)); row++) {
for (int col = 0; col < size; col++) {
int countOfMarks = 0;
// down and right
for (int i = row; i < size; i++) {
if (board[i][i] == null ? markToLook == null :
board[i][i].equals(markToLook)) {
countOfMarks++;
if (countOfMarks >= sizeToWin) {
return true;
}
}
}
countOfMarks = 0;
// down and left
for (int i = row; i < size; i++) {
if (board[i][size - 1 - i] == null ? markToLook == null :
board[i][size - 1 - i].equals(markToLook)) {
countOfMarks++;
if (countOfMarks >= sizeToWin) {
return true;
}
}
}
}
}
return false;
}
And call it from your PlayerHasWon method instead of performign the checks there. Basically we iterate each possible starting square on the board for a diagonal winning formation, and run check down+left and down+right for each of the squares.
I am in awful hurry and did not test it much, but will return in couple of hours to improve this solution. Seems to work.
Edit: My previous solution I found lacking in further tests, I've updated the above code to function as desired.
First, I think playerMark should be a char and not a String. That said, let's go for the answer. The "horizontal" case would be:
// This is the number of marks in a row required to win
// Adjust formula if necessary
final int required = size > 4 ? 5 : 3;
for (int i = 0; i < size; i++) {
int currentScore = 0;
for (j = 0; j < size; j++) {
if (board[i][j].equals(playerMark)) {
currentScore++;
if (currentScore >= required)
return true;
}
else {
currentScore = 0;
}
}
}
}
The vertical case would be analogous. The diagonal one is a bit trickier as now it would require board[i][i+k] for the main diagonal and board[i][k-i] for the secondary; and it may not be obvious which values k and i must traverse. Here's my attempt (variable required as in horizontal case):
Note: everything from here down has been completely rewritten on 2015-12-16. The previous versions didn't work and the algorithm was not explained.
After two failed attempts I decided to do my homework and actually sort things out instead of doing everything in my head thinking I can keep track of all variables. The result is this picture:
Main diagonals are painted in blue, secondary diagonals in green.
Each diagonal is identified by a value of k, with k=0 being always being the longest diagonal of each set. Values of k grow as diagonals move down, so diagonals above the longest one have negative k while diagonals below the longest one have positive k.
Things that hold for both diagonals:
Diagonal contains size-abs(k) elements. Diagonals in which size-abs(k) is less than required need not be searched. This means that, for board size size and required length required, we'll search values of k from required-size to size-required. Notice that these have the same absolute value, with the first being <=0 and the second >=0. These values are both zero only when required==size, i.e. when we need the full diagonal to claim a win, i.e. when we only need to search k=0.
For k<=0, possible values of i (row) go from 0 to size+k. Values greater than or equal to size+k cross the right edge of the board and are thus outside the board.
For k>=0, possible values of i (row) go from k to size. Values below k cross the left edge of the board and are thus outside the board.
Only for main (blue) diagonals:
Value of j (column) is k+i.
Only for secondary (green) diagonals:
Value of j (column) is size-1+k-i. In case this is not obvious, just pick the top right corner (k=0,i=0) and notice j=size-1. Then notice that adding 1 to k (keeping i constant) always moves j by 1 right (it would go out of the board if done from k=0,i=0, just think about the intersection of horizontal line i=0 with diagonal k=1), and adding 1 to i (keeping k constant) always moves j by 1 to the left.
The ressulting code would be:
// Main diagonal
for (int k = required - size; k < size - required; k++)
{
int currentScore = 0;
startI = Math.max (0, k);
endI = Math.min (size, size+k);
for (int i = startI, i < endI; i++)
{
if (board[i][k+i].equals (playerMark))
{
currentScore++;
if (currentScore >= required)
return true;
}
else
currentScore = 0;
}
}
// Secondary diagonal
for (int k = required - size; k < size - required; k++)
{
int currentScore = 0;
startI = Math.max (0, k);
endI = Math.min (size, size+k);
for (int i = startI, i < endI; i++)
{
if (board[i][size-1+k-i].equals (playerMark))
{
currentScore++;
if (currentScore >= required)
return true;
}
else
currentScore = 0;
}
}
At this point, the code is nearly identical in both cases, changing only the j index in board[i][j]. In fact, both loops could be merged, taking care only of keeping two currentScore variables, one for the main (blue) diagonal and the other for the secondary (green) diagonal.

TicTacToe minimax algorithm returns unexpected results in 4x4 games

In my method newminimax499 I have a minimax algorithm that utilizes memoization and alpha beta pruning. The method works normally for 3x3 games, however when I play 4x4 games I get strange, unexpected position choices for the computer. He still never loses, but he doesn't seem to be playing to win. To illustrate the problem here is a scenario from 2 games in 3x3 and 4x4. First here is a scenario from a 3x3 game where the player is X and makes the first move:
This isn't bad, in fact it's what one would expect the computer to do. Now take a look at a scenario from a 4x4 game. Again O is the computer and X starts:
As you can see, the computer is simply placing Os in a systematic order one after the other and only breaking that order to block X when it has a potential win. This is very defensive play, unlike what was seen in the 3x3 game. So why is the method behaving differently for 3x3 and 4x4?
Here is the code:
//This method returns a 2 element int array containing the position of the best possible
//next move and the score it yields. Utilizes memoization and alpha beta
//pruning to achieve better performance.
public int[] newminimax499(int a, int b){
//int bestScore = (turn == 'O') ? +9 : -9; //X is minimizer, O is maximizer
int bestPos=-1;
int alpha= a;
int beta= b;
int currentScore;
//boardShow();
String stateString = "";
for (int i=0; i<state.length; i++)
stateString += state[i];
int[] oldAnswer = oldAnswers.get(stateString);
if (oldAnswer != null)
return oldAnswer;
if(isGameOver()!='N'){
int[] answer = {score(), bestPos};
oldAnswers.put (stateString, answer);
return answer;
}
else{
for(int x:getAvailableMoves()){
if(turn=='X'){ //X is minimizer
setX(x);
//System.out.println(stateID++);
currentScore = newminimax499(alpha, beta)[0];
revert(x);
if(currentScore<beta){
beta=currentScore;
bestPos=x;
}
if(alpha>=beta){
break;
}
}
else { //O is maximizer
setO(x);
//System.out.println(stateID++);
currentScore = newminimax499(alpha, beta)[0];
revert(x);
if(currentScore>alpha){
alpha=currentScore;
bestPos=x;
}
if(alpha>=beta){
break;
}
}
}
}
if(turn=='X'){
int[] answer = {beta, bestPos};
oldAnswers.put (stateString, answer);
return answer;
}
else {
int[] answer = {alpha, bestPos};
oldAnswers.put (stateString, answer);
return answer;
}
}
Following are the other components and complementary methods needed for you guys to run the code.
The fields and constructor used in my class State2:
private char [] state; //Actual content of the board
private char turn; //Whose turn it is
private Map<String,int[]> oldAnswers; //Used for memoization. It saves every state along with the score it yielded which allows us to stop exploring the children of a certain node if a similar node's score has been previously calculated. The key is the board state(i.e OX------X for example), the int array is a 2 element array containing the score and position of last placed seed of the state.
private Map<Integer, int []> RowCol; //A mapping of positions from a board represented as a normal array to a board represented as a 2d array. For example: The position 0 maps to 0,0 on a 2d array board, 1 maps to 0,1 and so on.
private static int n; //Size of the board
private static int stateID; //An simple incrementer used to show number of recursive calls in the newminiax49 method.
private static int countX, countO; //Number of placed Xs and Os
private static int lastAdded; //Position of last placed seed
private char [][] DDState; //A 2d array representing the board. Contains the same values as state[]. Used for simplicity in functions that check the state of the board.
public State2(int n){
int a=0;
State2.n=n;
state=new char[n*n];
RowCol=new HashMap<Integer, int []>();
countX=0;
countO=0;
//Initializing the board with empty slots
for(int i = 0; i<state.length; i++){
state[i]='-';
}
//Mapping
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
RowCol.put(a, new int[]{i, j});
a++;
}
}
a=0;
DDState=new char[n][n];
//Initializing the 2d array with the values from state[](empty slots)
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
DDState[i][j]=state[a];
a++;
}
}
oldAnswers = new HashMap<String,int[]>();
}
Complementary methods:
getAvailableMoves, returns an array with the empty slots on the board(i.e. the possible next moves).
public int[] getAvailableMoves(){
int count=0;
int i=0;
for(int j=0; j<state.length; j++){
if(state[j]=='-')
count++;
}
int [] availableSlots = new int[count];
for(int j=0; j<state.length; j++){
if(state[j]=='-')
availableSlots[i++]=j;
}
return availableSlots;
}
isGameOver2(), simply checks the current state of the board for whether the game is over. returns a char 'X', 'O', 'D' and 'N' which stand for X won, O won, Draw, and Not gameover respectively.
public char isGameOver2(){
char turnOpp;
int count;
if(turn=='X'){
count=countO;
turnOpp='O';
}
else {
count=countX;
turnOpp='X';
}
if(count>=n){
for(int i=0; i<n; i++){
if(DDState[i][RowCol.get(lastAdded)[1]]!=turnOpp)
break;
if(i==(n-1)){
return turnOpp;
}
}
//Check row for win
for(int i=0; i<n; i++){
if(DDState[RowCol.get(lastAdded)[0]][i]!=turnOpp)
break;
if(i==(n-1)){
return turnOpp;
}
}
//Check diagonal for win
if(RowCol.get(lastAdded)[0] == RowCol.get(lastAdded)[1]){
//we're on a diagonal
for(int i = 0; i < n; i++){
if(DDState[i][i] != turnOpp)
break;
if(i == n-1){
return turnOpp;
}
}
}
//check anti diagonal
for(int i = 0; i<n; i++){
if(DDState[i][(n-1)-i] != turnOpp)
break;
if(i == n-1){
return turnOpp;
}
}
//check for draw
if((countX+countO)==(n*n))
return 'D';
}
return 'N';
}
boardShow, returns a matrix display of the current state of the board:
public void boardShow(){
if(n==3){
System.out.println(stateID);
for(int i=0; i<=6;i+=3)
System.out.println("["+state[i]+"]"+" ["+state[i+1]+"]"+" ["+state[i+2]+"]");
System.out.println("***********");
}
else {
System.out.println(stateID);
for(int i=0; i<=12;i+=4)
System.out.println("["+state[i]+"]"+" ["+state[i+1]+"]"+" ["+state[i+2]+"]"+" ["+state[i+3]+"]");
System.out.println("***********");
}
}
score, is a simple evaluation function which returns +10 for an O win, -10 for an X win and 0 for a draw:
public int score(){
if(isGameOver2()=='X')
return -10;
else if(isGameOver2()=='O')
return +10;
else
return 0;
}
The seed setters:
//Sets an X at a certain location and updates the turn, countX and lastAdded variables
public void setX(int i){
state[i]='X';
DDState[RowCol.get(i)[0]][RowCol.get(i)[1]]='X';
turn='O';
countX++;
lastAdded=i;
}
//Sets an O at a certain location and updates the turn, countO and lastAdded variables
public void setO(int i){
state[i]='O';
DDState[RowCol.get(i)[0]][RowCol.get(i)[1]]='O';
turn='X';
countO++;
lastAdded=i;
}
Revert, simply reverts a move. For example if an X has been placed in position 0 revert(0) sets a '-' in it's place and updates the variables changed by setX:
public void revert(int i){
state[i]='-';
DDState[RowCol.get(i)[0]][RowCol.get(i)[1]]='-';
if(turn=='X'){
turn = 'O';
countO--;
}
else {
turn = 'X';
countX--;
}
}
How the main method might look like:
public static void main(String[] args) {
State2 s=new State2(4);
int [] results=new int[2];
s.setX(0);
long startTime = System.currentTimeMillis();
results=s.newminimax499(Integer.MIN_VALUE,Integer.MAX_VALUE);
long endTime = System.currentTimeMillis();
System.out.println("Score: "+results[0]+" Position: "+ results[1]);
System.out.println("Run time: " + (endTime-startTime));
s.boardShow();
}
I'm not convinced that there's a bug here -- if O plays in one of the earlier positions, it gets forked, whereas if it plays in the center, it forces a draw. Presumably the 4x4 game is even harder to win/lose, so the computer indifferently chooses the first open square.
Below, 1 denotes the forced response by O, 2 denotes the forking move by X, and ? denotes a possible win location.
X|O|
-+-+-
2|X|?
-+-+-
?| |1
X| |O
-+-+-
X|2|?
-+-+-
1| |?
X|2|?
-+-+-
O|X|
-+-+-
|?|1
I gave up trying to get my head around the code - the room began to get way too noisy for my collegues.
However - I did put together my own solution - which I feel may be worth posting. It is not designed to be especially efficient - in fact I am still waiting for it to make it's first move in the 4X4 test but it does seem to do the right thing on a 3X3 (i.e. draw against itself and win against a stupid strategy).
It should be able to handle boards of unequal sizes. A diagonal is any line of squares reaching from one side of the board to the other using diagonal steps.
As soon as it finishes it's 4X4 run I'll post it's moves for comparison.
public class TicTacToe {
/**
* Strategies for players.
*/
interface Strategy {
/**
* Think up a place to move.
*
* #param board - The current board position.
* #return Where to move.
*/
Optional<Board.Square> think(Board board, Player player);
}
/**
* Stupid strategy just finds the first empty square and plays it.
*/
static final Strategy stupid = (Board board, Player player) -> {
List<Board.Square> empty = board.getEmpties();
if (empty.size() > 0) {
return Optional.of(empty.get(0));
}
return Optional.empty();
};
/**
* Minimax strategy.
*/
static final Strategy minimax = new Strategy() {
// Divide by this at each depth step.
private static final double FACTOR = 3;
// Memoize each discovered best move.
Map<Player, Map<String, Board.Place>> best = new HashMap<>();
{
// One map for each player.
for (Player p : Player.values()) {
best.put(p, new HashMap<>());
}
}
#Override
public Optional<Board.Square> think(Board board, Player player) {
Optional<Board.Square> win = Optional.empty();
// Seen this one before?
Board.Place seen = best.get(player).get(board.toString());
if (seen == null) {
double winValue = Double.NEGATIVE_INFINITY;
for (Board.Square play : board.getEmpties()) {
double value = value(board, player, play, 1);
if (value > winValue) {
win = Optional.of(play);
winValue = value;
}
}
if (win.isPresent()) {
// Record my result.
best.get(player).put(board.toString(), win.get().place);
} else {
System.out.println("No best found for " + board);
}
} else {
// Reuse it.
win = Optional.of(board.square(seen));
}
return win;
}
private double value(Board board, Player player, Board.Square move, double scale) {
// My value.
double value = 0;
// Make the move.
board.mark(player, move);
try {
// A win?
if (!board.win()) {
// Remaining empties.
List<Board.Square> empties = board.getEmpties();
if (!empties.isEmpty()) {
// Record it's value.
double[] values = new double[empties.size()];
for (int i = 0; i < values.length; i++) {
// Try each further move negating and downscaling at each level.
values[i] = value(board, player.next(), empties.get(i), (scale / FACTOR) * -1);
}
// Add them up.
value += DoubleStream.of(values).sum();
}
} else {
// Somebody already won.
value = scale;
}
} finally {
//System.out.println("Value of " + board + " to player " + player + " = " + value);
// Unroll the move.
board.unmark(player, move);
}
return value;
}
};
/**
* There are exactly two players.
*/
enum Player {
O, X;
/**
* Each player has a strategy.
*
* Defaults to stupid.
*/
private Strategy strategy = stupid;
/**
* What mark they make on the board.
*
* #return String representing the mark they make.
*/
public char mark() {
// The mark they make is the first character of their name.
return name().charAt(0);
}
/**
* Who is the next player.
*
* #return The other player.
*/
public Player next() {
return other(this);
}
/**
* Who is the other player.
*
* #return The other player.
*/
static Player other(Player it) {
return it == O ? X : O;
}
public Strategy getStrategy() {
return strategy;
}
public Player setStrategy(Strategy strategy) {
this.strategy = strategy;
return this;
}
}
/**
* The board.
*/
static class Board {
/**
* Top left corner is 0,0. Access is [y][x].
*/
final Square[][] board;
// The board as columns.
final List<List<Square>> columns;
// The dagonals.
final List<List<Square>> diagonals;
// For sense checking.
final int width;
final int height;
// Counts for each player - useful optimisation.
final int[] counts = new int[Player.values().length];
// A clear square.
private static final char Clear = ' ';
public Board(int width, int height) {
this.width = width;
this.height = height;
board = new Square[height][];
for (int y = 0; y < height; y++) {
board[y] = new Square[width];
// Fill it.
for (int x = 0; x < width; x++) {
board[y][x] = new Square(x, y);
}
}
// Build my columns lists.
columns = new ArrayList<>();
for (int x = 0; x < width; x++) {
List<Square> column = new ArrayList<>();
for (int y = 0; y < height; y++) {
column.add(board[y][x]);
}
columns.add(column);
}
// And the diagonals.
diagonals = new ArrayList<>();
for (int y = 0; y <= height - width; y++) {
List<Square> diagonal = new ArrayList<>();
// Left to right.
for (int x = 0; x < width; x++) {
diagonal.add(board[y + x][x]);
}
diagonals.add(diagonal);
// Right to left.
diagonal = new ArrayList<>();
for (int x = 0; x < width; x++) {
diagonal.add(board[y + x][width - 1 - x]);
}
diagonals.add(diagonal);
}
}
public Board(int size) {
this(size, size);
}
private Stream<List<Square>> asRows() {
// Map each row to a list.
return Arrays.stream(board).map(r -> Arrays.asList(r));
}
private Stream<List<Square>> asCols() {
// Walk the columns.
return columns.stream();
}
private Stream<List<Square>> asDiagonals() {
// Walk the diagonals.
return diagonals.stream();
}
private boolean winning(List<Square> row) {
//System.out.println("winner(" + row + ")");
if (row.isEmpty()) {
return false;
}
Optional<Player> owner = row.get(0).owner;
// First square owned.
if (!owner.isPresent()) {
return false;
}
return !row.stream()
//.peek(s -> System.out.println(s))
.anyMatch((s) -> (!s.owner.isPresent() || s.owner.get() != owner.get()));
}
public Square square(Place place) {
return board[place.y][place.x];
}
private Optional<Player> getWinner() {
Optional<Player> winner = Optional.empty();
// Must be at least enough plays by one player to reach across/down the board.
OptionalInt min = IntStream.of(counts).min();
if (!min.isPresent() || min.getAsInt() < Math.min(width, height)) {
// Nobody can posibly have won.
return winner;
}
List<List<Square>> winners = asRows()
.filter(r -> winning(r))
.collect(Collectors.toList());
if (winners.isEmpty()) {
winners = asCols()
.filter(r -> winning(r))
.collect(Collectors.toList());
}
if (winners.isEmpty()) {
winners = asDiagonals()
.filter(r -> winning(r))
.collect(Collectors.toList());
}
if (!winners.isEmpty()) {
winner = winners.get(0).get(0).owner;
}
return winner;
}
private boolean isDrawn() {
// All square occupied - counts add up to width*height.
return IntStream.of(counts).sum() == width * height;
}
private boolean win() {
return getWinner().isPresent();
}
/**
* One square of the board.
*
* Remember that a Square is ON a board - i.e. it is a sub-object of a Board. Do not attempt to memoize Squares as they will hold a reference to their parent board.
*/
class Square {
// The owner of it.
Optional<Player> owner = Optional.empty();
// where it is on the board.
final Place place;
public Square(Place place) {
this.place = place;
}
public Square(int x, int y) {
this(new Place(x, y));
}
public char getMark() {
return owner.isPresent() ? owner.get().mark() : Clear;
}
public boolean isTaken() {
return owner.isPresent();
}
#Override
public String toString() {
return place + "(" + (owner.isPresent() ? owner.get().toString() : "") + ")";
}
private void take(Player player) {
if (!isTaken()) {
owner = Optional.of(player);
// Keep track of the counts.
counts[player.ordinal()] += 1;
} else {
throw new IllegalStateException("Attempt to take taken square!"
+ " Square=" + this
+ " Player=" + player
+ " board=" + Board.this.toString()
);
}
}
private void release(Player player) {
if (isTaken()) {
if (owner.get() == player) {
owner = Optional.empty();
// Keep track of the counts.
counts[player.ordinal()] -= 1;
} else {
throw new IllegalStateException("Attempt to release other player's square!"
+ " Square=" + this
+ " Player=" + player
+ " board=" + Board.this.toString()
);
}
} else {
throw new IllegalStateException("Attempt to release untaken square!"
+ " Square=" + this
+ " Player=" + player
+ " board=" + Board.this.toString()
);
}
}
}
private List<Square> getEmpties() {
// Walk all squares.
return Arrays.stream(board)
// Unroll each row.
.flatMap(l -> Arrays.stream(l))
// All not taken.
.filter(s -> !s.isTaken())
// As a list.
.collect(Collectors.toList());
}
/**
* A place on the board.
*/
class Place {
final int x;
final int y;
public Place(int x, int y) {
// Sense check.
if (x < 0 || x >= width) {
throw new IllegalArgumentException("Off board: x = " + x);
}
if (y < 0 || y >= height) {
throw new IllegalArgumentException("Off board: x = " + x);
}
this.x = x;
this.y = y;
}
#Override
public String toString() {
return "{" + x + "," + y + '}';
}
}
/**
* Mark a place on the board.
*
* #param player
*/
public void mark(Player player, Square square) {
// Take the square.
square.take(player);
}
/**
* UnMark a place on the board.
*
* Confirms the mark first.
*/
public void unmark(Player player, Square square) {
square.release(player);
}
#Override
public String toString() {
return Arrays.stream(board)
.map(r -> Arrays.stream(r)
.map(s -> String.valueOf(s.getMark()))
.collect(Collectors.joining("", "[", "]")))
.collect(Collectors.joining(""));
}
}
class Game {
// The current board state.
final Board board;
public Game(Board board) {
this.board = board;
}
public Game(int size) {
this(new Board(size));
}
public Game(int width, int height) {
this(new Board(width, height));
}
private void play(Player firstPlayer) {
boolean gameFinished = false;
Player player = firstPlayer;
do {
Optional<Board.Square> move = player.getStrategy().think(board, player);
if (move.isPresent()) {
board.mark(player, move.get());
System.out.println("Moved: " + move.get() + " -> " + board);
} else {
System.out.println("No move!");
}
// Has anyone won?
Optional<Player> winner = board.getWinner();
if (winner.isPresent()) {
System.out.println("Player " + winner.get() + " won! " + board);
gameFinished = true;
} else {
if (board.isDrawn()) {
System.out.println("Draw! " + board);
gameFinished = true;
}
}
// Next player.
player = player.next();
} while (!gameFinished);
}
}
private void testStupid() {
// Both start off stupid.
new Game(3).play(Player.X);
}
private void testMinimax() {
// Test minimax strategy.
Board testBoard = new Board(3);
// Set it up like http://neverstopbuilding.com/minimax
testBoard.mark(Player.O, testBoard.board[0][0]);
testBoard.mark(Player.X, testBoard.board[0][2]);
testBoard.mark(Player.X, testBoard.board[1][0]);
testBoard.mark(Player.X, testBoard.board[2][0]);
testBoard.mark(Player.O, testBoard.board[2][1]);
testBoard.mark(Player.O, testBoard.board[2][2]);
// What does the strategy do?
Optional<Board.Square> play = minimax.think(testBoard, Player.X);
System.out.println("minimax(" + testBoard + ") = " + play);
}
private void test3X3Game() {
// Play a game.
// Change strategies.
new Game(3).play(Player.X);
}
private void test4X4Game() {
// Play a game.
new Game(4).play(Player.X);
}
public void test() {
testStupid();
testMinimax();
System.out.println("Test minmax vs stupid");
Player.X.setStrategy(minimax);
test3X3Game();
System.out.println("Test minmax vs minmax");
Player.O.setStrategy(minimax);
test3X3Game();
System.out.println("Test 4 X 4");
test4X4Game();
}
public static void main(String args[]) {
try {
new TicTacToe().test();
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
}
When playing minimax against itself it moves:
Moved: {1,1}(X) -> [ ][ X ][ ]
Moved: {0,0}(O) -> [O ][ X ][ ]
Moved: {2,2}(X) -> [O ][ X ][ X]
Moved: {0,2}(O) -> [O ][ X ][O X]
Moved: {0,1}(X) -> [O ][XX ][O X]
Moved: {2,1}(O) -> [O ][XXO][O X]
Moved: {1,0}(X) -> [OX ][XXO][O X]
Moved: {1,2}(O) -> [OX ][XXO][OOX]
Moved: {2,0}(X) -> [OXX][XXO][OOX]
Draw! [OXX][XXO][OOX]
Note that this os not the moves OP posted so there is clearly something wrong with OP's algorithm.

Instances of objects in Java, strange behavior

I wrote a simple Tic Tac Toe console app in Java, using a bitboard approach (just for fun). It works well for two human players. My objective was to figure out the minimax algorith and implement a computer player. I did this before, for the (very naive) game of "Nim", and the same general object oriented approach worked. I wanted to use the same structure. But in this case, when the computer goes to make a move, it defaces the whole board variable when it searches for its next move. It shouldn't do so, because the makeMove method creates a brand new Board object. My question is, why does this strange thing happen? Here is the code, loosely commented, straight from NetBeans:
Thanks in advance for anybody who has the patience to take a look. I want to mention that I looked into the Cloneable interface and the clone() method, but to no avail. Then I figured that that shouldn't be the cause, because the way the makeMove method works. So why does the computer player destroy the board?
package tictactoe;
import java.util.*;
public class TicTacToe {
public static void main(String[] args) {
Game game = new Game();
game.start();
}
}
class Game {
ArrayList<Player> players = new ArrayList(); // An ArrayList for the players
public Game() { // Determine if players are to be human or CPU
Scanner input = new Scanner(System.in);
String answer;
System.out.printf("Would you like Player 1 to be CPU? [Yes/No] ");
answer = input.nextLine();
if(answer.toLowerCase().startsWith("y")) players.add(new ComputerPlayer(0, 3));
else players.add(new Player());
System.out.printf("Would you like Player 2 to be CPU? [Yes/No] ");
answer = input.nextLine();
if(answer.toLowerCase().startsWith("y")) players.add(new ComputerPlayer(1, 3));
else players.add(new Player());
}
public void start() {
Scanner input = new Scanner(System.in);
while(true) {
clearScreen();
Board board = new Board();
while(!board.isGameOver()) {
board = board.makeMove(players.get(board.getCurrentPlayer()).getMove(board));
}
board.display();
int winner = board.checkWinner();
if(winner >= 0) {
players.get(winner).addWin();
System.out.printf("Player %d wins. He has %d wins vs %d.\nRematch? [Yes/No] ", winner + 1, players.get(winner).getWins(), players.get(winner == 0 ? 1 : 0).getWins());
}
else {
System.out.printf("The game is a tie.\nRematch? [Yes/No] ");
}
String answer = input.nextLine();
if(answer.toLowerCase().startsWith("n")) break;
else {
Player temp = players.remove(0);
players.add(temp);
for(int i = 0; i < 2; i++) { // just to help the computer player track his own ID
players.get(i).flipID();
}
}
}
System.out.printf("Game aborted. Thank you for playing.");
}
public static void clearScreen() {
for(int i = 0; i < 30; i++) System.out.printf("\n");
}
}
class Board implements Cloneable {
private int[] board; // A two dimensional array for storing player X's and
// player O's moves separately. OR them together to get
// all moves made.
private final int[] map = {0, 1, 2, 4, 8, 16, 32, 64, 128, 256}; // A simple
// way of mapping the digits 1 -> 9 (like on the numpad) to
// the bits of the board. You just do bitwise operations
// against map[n] - n being the digit.
// The numpad-like mapping looks like this:
// 7 8 9 // In memory the bits are stored thus:
// 4 5 6 // 987654321
// 1 2 3 //
private final int[] win = {7, 56, 73, 84, 146, 273, 292, 448}; // A mapping
// of all possible winning combinations translated to decimal
// numbers. Listed in order: 1,2,3; 4,5,6; 1,4,7; 3,5,7;
// 2,5,8; 1,5,9; 3,6,9; 7,8,9.
private int currentPlayer; // The player whose turn it is. 0 for X, 1 for O.
private int opponent; // The opponent. Will always be the opposite.
// The normal constructor. Takes as arguments the current state of the
// board, represented by a two dimensional integer, and the player whose
// turn it currently is, represtented by a 0 or 1
public Board(int[] theBoard, int player) {
board = theBoard;
currentPlayer = player;
opponent = player == 0 ? 1 : 0;
}
// If passed no arguments, construct the bord with default values,
// e.g. an empty board for both players and X's turn.
public Board() {
this(new int[2], 0);
}
// The usual suspects. Accesors for the attributes.
public int[] getBoard() {
return board;
}
public int getCurrentPlayer() {
return currentPlayer;
}
public int getOpponent() {
return opponent;
}
// First check against the win maps, for both players, to see if any of them
// got 3 symbols in a row. If not, check if the board is full.
public boolean isGameOver() {
for(int player = 0; player < 2; player++) {
for(int n: win) {
if((board[player] & n) == n) return true;
}
}
return (board[0] | board[1]) == 511;
}
// Returns -1 if nobody won, or returns 0 or 1 in case either of the
// players did.
public int checkWinner() {
for(int i = 0; i < 2; i++) {
for(int m: win) {
if((board[i] & m) == m) return i;
}
}
return -1;
}
// Find the possible moves on the board, returned in an array
public int[] getMoves() {
// Count the number of possible moves, prerequisite for initializing
// the array of moves that will later be returned.
int allMoves = (board[0] | board[1]);
int count = countBits(allMoves);
// Populate the array of possible moves and then return it
int[] moves = new int[9 - count];
int j = 0;
for(int i = 1; i < 10; i++) {
if((allMoves & map[i]) == 0) {
moves[j] = i;
j++;
}
}
return moves;
}
// Return the number of activated bits in an integer
// (in this case an 8 bit integer)
public static int countBits(int board) {
int count = 0;
for(int i = 1; i <= 256; i <<= 1) {
if((board & i) != 0) count++;
}
return count;
}
// The static evaluation function, used by the minmax algorithm.
// Returns 3 / -3 for victory, or the number of symbols the player
// has on any given line, if there's no opponent's symbol on it.
// Returns 0 otherwise
public int evaluate(int player) {
int allMoves = board[0] | board[1];
int ret = 0, max = 0, min = 0;
for(int p = 0; p < 2; p++) {
for(int w: win) {
int line = board[p] & w;
if(line == w) { // If victory condition found, return immediately
if(p == player) return 3;
else return -3;
}
if((line ^ allMoves) == 0) { // No moves on the line by the opp.
if(p == player) max = countBits(line) > max ? countBits(line) : max;
else min = -countBits(line) < min ? -countBits(line) : min;
}
}
}
if(Math.abs(min) != max) {
ret = Math.abs(min) > max ? min : max;
}
return ret;
}
// Now for the tricky part... this method returns a completely new
// board object. But when the minimax method calls it, it sure doesn't
// behave that way
public Board makeMove(int move) {
int[] newBoard = board;
newBoard[currentPlayer] |= map[move];
return new Board(newBoard, opponent);
}
// Tried to use something like this, at one point, but then I realized
// that it won't help me understand my problem. May use at a later time, tho
/*
public Board undoMove(int move) {
int[] newBoard = board;
newBoard[opponent] ^= map[move];
return new Board(newBoard, opponent);
}
*/
// The method to (very plainly) display the board
public void display() {
for(int i = 6; i >= 0; i -= 3) {
for(int j = 1; j <= 3; j++) {
if(((board[0] | board[1]) & map[i + j]) == 0) System.out.printf("%d", i + j);
else if((board[0] & map[i + j]) != 0) System.out.printf("X");
else System.out.printf("O");
}
System.out.printf("\n");
}
}
// Returns true/false whether a move is valid on the board
public boolean isValidMove(int move) {
if(move < 1 || move > 9) return false;
return ((board[0] | board[1]) & map[move]) == 0;
}
}
class Player {
int wins = 0; // Simple way of keeping track of the number of wins.
// Accessor for the win atr.
public int getWins() {
return wins;
}
// Add a win
public void addWin() {
wins++;
}
public void flipID() {
// To be overridden by the ComputerPlayer class
}
// Query the user for a valid move
public int getMove(Board board) {
Scanner input = new Scanner(System.in);
int move;
board.display();
do {
System.out.printf("Input a valid move: ");
move = input.nextInt();
} while(!board.isValidMove(move));
//Game.clearScreen();
return move;
}
}
class ComputerPlayer extends Player {
int self; // Keep track of his own place in the players array
int maxSearchDepth; // Seach depth setting for the minimax algorithm
public ComputerPlayer(int n, int m) { // Constructor
self = n;
maxSearchDepth = m;
}
#Override
public void flipID() {
self = self == 0 ? 1 : 0;
}
// The implementation of the minimax algorithm
#Override
public int getMove(Board board) {
int[] temp = minimax(board, 0, maxSearchDepth);
return temp[1];
}
public int[] minimax(Board mmBoard, int depth, int maxDepth) {
int[] ret = new int[2]; //ret[0] = bestScore, ret[1] = bestMove
int currentScore, bestScore, bestMove;
if(mmBoard.isGameOver() || depth == maxDepth) {
ret[0] = mmBoard.evaluate(mmBoard.getCurrentPlayer());
ret[1] = 0;
return ret;
}
bestMove = 0;
bestScore = mmBoard.getCurrentPlayer() == self ? -4 : 4;
for(int move: mmBoard.getMoves()) {
// System.out.printf("Board: %s, Depth: %d. Moves: %s. Trying: %d\n", Arrays.toString(mmBoard.getBoard()), depth, Arrays.toString(mmBoard.getMoves()), move);
Board newBoard = mmBoard.makeMove(move); // The problem call...
// System.out.printf("Original: %s New: %s", mmBoard, newBoard);
int[] temp = minimax(newBoard, depth + 1, maxDepth);
currentScore = temp[0];
if(mmBoard.getCurrentPlayer() == self) {
if(currentScore > bestScore) {
bestScore = currentScore;
bestMove = move;
}
}
else {
if(currentScore < bestScore) {
bestScore = currentScore;
bestMove = move;
}
}
}
ret[0] = bestScore;
ret[1] = bestMove;
return ret;
}
}
Note, I did not read through all the code, as there is no minimal example, but I saw an issue here:
public Board makeMove(int move) {
int[] newBoard = board;
// ^^^^^
newBoard[currentPlayer] |= map[move];
return new Board(newBoard, opponent);
}
You are in fact not making a new board here, the new Board(...) has a reference to the old board's int[].
by calling the statement int[] newBoard = board; you are assigning the reference of board to the new integer array and not actually making a copy, in other words: both board objects are now pointing to the same int[]
To make an actual copy, you will need to clone the array by using System.arraycopy();
So the new method would look like this:
public Board makeMove(int move) {
int[] newBoard = new int[board.length];
System.arraycopy(board, 0, newBoard, 0, board.length);
newBoard[currentPlayer] |= map[move];
return new Board(newBoard, opponent);
}
note that I have not read through all your code, but the assumption you made in that method is not correct
Try adding this to your makeMove() method:
int[] newBoard = Arrays.copyOf(board, board.length);
In your code you just point newBoard reference to an existing integer array also referenced by board.
The line above creates new integer array and copies the content of the array referneced by board across.
HTH

Array programming - check winner in a Tic Tac Toe game for an nxn board with n players

I am making a tic tac toe game for n number of players on a nxn board, but the winning condition is aways 3 on a row. My so far solution to the problem is: when a move is made the program will check the following square for 3 on a row.
(x-1,y+1) (x,y+1) (x+1,y+1)
(x-1,y) (x,y) (x+1,y)
(x-1,y-1) (x,y-1) (x+1,y-1)
It will check the top (x-1,y+1) (x,y+1) (x+1,y+1) bottom(x-1,y-1) (x,y-1) (x+1,y-1)
sides(x+1,y+1) (x+1,y) (x+1,y-1) , (x-1,y+1) (x-1,y) (x-1,y-1) , the diagonals and the ones going through the middle(x,y).
my code so far is:
public int checkWinning() {
for(int a = 1; a < size-1; a++){
for(int b = 1; b < size-1; b++){
if (board[a][b] == board[a+1][b] && board[a][b] == board[a-1][b]){
return board[a][b];
}else if(board[a][b] == board[a][b+1] && board[a][b] == board[a][b-1]){
return board[a][b];
}else if(board[a][b] == board[a+1][b-1] && board[a][b] == board[a-1][b+1]){
return board[a][b];
}else if(board[a][b] == board[a+1][b+1] && board[a][b] == board[a-1][b-1]){
return board[a][b];
}
}
}
for(int d = 1; d < size-1; d++){
if (board[0][d] == board[0][d-1] && board[0][d] == board[0][d+1]){
return board[0][d];
} else if (board[size-1][d] == board[size-1][d-1] && board[size-1][d] == board[size-1][d+1]){
return board[size-1][d];
}
}
for(int c = 1; c < size-1; c++){
if (board[c][0] == board[c-1][0] && board[c][0] == board[c+1][0]){
return board[c][0];
}else if(board[c][size-1] == board[c-1][size-1] && board[c][size-1] == board[c+1][size-1]){
return board[c][size-1];
}
}
return 0;
}
where the first section is where I check the ones through the middle and diagonals. the second section I check the top an bottom and the top and the thrid section checks the sides.
When it returns 0 is means that there are no winner yet.
#override
public void checkResult() {
int winner = this.board.checkWinning();
if (winner > 0) {
this.ui.showResult("Player "+winner+" wins!");
}
if (this.board.checkFull()) {
this.ui.showResult("This is a DRAW!");
}
}
Board[x][y] -> 2-dimensional array representing the board, The coordinates are counted from top-left (0,0) to bottom-right (size-1, size-1), board[x][y] == 0 signifies free at position (x,y), board[x][y] == i for i > 0 signifies that Player i made a move on (x,y), just so you know it.
my problem is that when i expands the board to a size larger than 3x3 the program somehow overwrites it self or a does not check every thing sides top and bottom every time, and I can't seem too se why.
EDIT:
played with the app for a few minutes... interesting results
java -jar tic-tac-toe.jar 5 20
It was a cats game!!
|1|1|5|5|1|3|5|3|1|5|2|5|1|1|2|
|2|3|2|3|1|5|3|5|3|2|3|1|5|2|2|
|5|4|5|4|1|5|5|4|2|1|4|5|4|2|2|
|3|2|1|5|5|5|2|4|5|3|4|1|2|4|2|
|3|4|1|2|5|4|1|1|4|5|1|3|3|4|1|
|1|5|4|4|3|2|5|1|3|5|1|3|5|3|4|
|2|5|1|4|3|3|3|5|3|1|1|4|3|4|4|
|1|4|5|1|1|5|4|5|2|4|1|1|5|4|3|
|1|3|2|1|4|2|4|3|3|4|5|2|4|3|3|
|5|1|1|3|3|4|4|4|2|2|1|4|3|2|5|
|2|2|3|1|5|5|4|1|3|5|3|2|3|3|2|
|2|4|2|4|4|1|3|1|1|3|1|2|1|2|2|
|2|5|5|1|4|3|4|5|5|4|5|3|3|5|2|
|4|5|2|1|5|3|2|1|3|2|2|2|2|4|4|
|4|1|1|4|5|4|5|4|2|2|3|3|2|2|3|
Played 100 games:
Number wins by Player1: 0
Number wins by Player2: 0
Number wins by Player3: 0
Number wins by Player4: 0
Number wins by Player5: 0
Number of ties: 100
didn't scroll through all 100 games to find the winning board, but I thought this was interesting:
java -jar tic-tac-toe.jar 2 10
Player2 won the game!
|1|1|2|1|2|2| |2|1|2|
|2|2|2|2|2|2|2|2|2|2|
|2|1|2|2|2|1|1|1|1|1|
|1|1|1|1|2|1|2|1|1|1|
|2|2| |1|2|1|1|1|1|2|
|2|2|2|1|1|1| |1|2|2|
|2|2|1|2|2|2|2|2|1|1|
| | |2|2|2|2| |1|1|1|
|1|1|2|2|2|1|1|1|1| |
| | |1|1|1|1|1|2|1| |
Played 100 games:
Number wins by Player1: 0
Number wins by Player2: 1
Number of ties: 99
This does answer your question... but I took it a bit far... decided to implement the solution.
Instead of counting matches... I just check from teh point the last player plays, if all marks in a row column and diagnal match the players, he wins.
package com.clinkworks.example;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TicTacToe {
private static final String TIE = "TIE";
private static final Map<String, Integer> gamesToWinsMap = new HashMap<String, Integer>();
/**
* accepts input in the following format:
*
* playerCount rowCount columnCount (sets the game with the n players, n columns, and n rows)
* - java -jar tic-tac-toe.jar 2 3 3
* PlayerCount squareSize (defaults to a game with rows and cols the same as squareSize and the player count given)
* - java -jar tic-tac-toe.jar 2 3
* PlayerCount (defaults to a 3 by 3 game)
* - java -jar tic-tac-toe.jar 2
* no input (defaults to a 3 by 3 game with 2 players)
* - java -jar tic-tac-toe.jar
* #param args
*/
public static void main(String[] args) {
int playerCount = 2;
int rows = 3;
int cols = 3;
if(args.length == 3){
playerCount = Integer.valueOf(args[0]);
rows = Integer.valueOf(args[1]);
cols = Integer.valueOf(args[2]);
}
if(args.length == 2){
playerCount = Integer.valueOf(args[0]);
rows = Integer.valueOf(args[1]);
cols = rows;
}
if(args.length == 1){
playerCount = Integer.valueOf(args[0]);
}
for(int i = 1; i <= playerCount; i++){
gamesToWinsMap.put("Player" + i, 0);
}
//lets play 100 games and see the wins and ties
playGames(100, playerCount, rows, cols);
for(int i = 1; i <= playerCount; i++){
System.out.println("Number wins by Player" + i + ": " + gamesToWinsMap.get("Player" + i));
}
System.out.println("Number of ties: " + gamesToWinsMap.get(TIE));
}
public static void playGames(int gamesToPlay, int playerCount, int rows, int cols) {
//play a new game each iteration, in our example, count = 100;
for (int i = 0; i < gamesToPlay; i++) {
playGame(playerCount, rows, cols);
}
}
public static void playGame(int playerCount, int rows, int cols) {
//create a new game board. this initalizes our 2d array and lets the complexity of handling that
// array be deligated to the board object.
Board board = new Board(playerCount, rows, cols);
//we are going to generate a random list of moves. Heres where we are goign to store it
List<Move> moves = new ArrayList<Move>();
//we are creating moves for each space on the board.
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
moves.add(new Move(row, col));
}
}
//randomize the move list
Collections.shuffle(moves);
//do each move
for (Move move : moves) {
board.play(move);
if(gameOver(board)){
break;
}
}
}
public static boolean gameOver(Board board){
if (board.whoWon() != null) {
System.out.println(board.whoWon() + " won the game!");
System.out.println(board);
Integer winCount = gamesToWinsMap.get(board.whoWon());
winCount = winCount == null ? 1 : winCount + 1;
gamesToWinsMap.put(board.whoWon(), winCount);
return true;
} else if (board.movesLeft() == 0) {
System.out.println("It was a cats game!!");
System.out.println(board);
Integer tieCount = gamesToWinsMap.get(TIE);
tieCount = tieCount == null ? 1 : tieCount + 1;
gamesToWinsMap.put(TIE, tieCount);
return true;
}
return false;
}
public static class Move {
private int row;
private int column;
public Move(int row, int column) {
this.row = row;
this.column = column;
}
public int getRow() {
return row;
}
public int getColumn() {
return column;
}
}
public static class Board {
private final int rowSize;
private final int columnSize;
private final Integer[][] gameBoard;
private int playerCount;
private int currentPlayer;
private String winningPlayer;
public Board() {
gameBoard = new Integer[3][3];
currentPlayer = 1;
winningPlayer = null;
this.rowSize = 3;
this.columnSize = 3;
playerCount = 2;
}
public Board(int players) {
gameBoard = new Integer[3][3];
currentPlayer = 1;
winningPlayer = null;
this.rowSize = 3;
this.columnSize = 3;
playerCount = players;
}
public Board(int rowSize, int columnSize) {
gameBoard = new Integer[rowSize][columnSize];
currentPlayer = 1;
winningPlayer = null;
playerCount = 2;
this.rowSize = rowSize;
this.columnSize = columnSize;
}
public Board(int players, int rowSize, int columnSize) {
gameBoard = new Integer[rowSize][columnSize];
currentPlayer = 1;
winningPlayer = null;
playerCount = players;
this.rowSize = rowSize;
this.columnSize = columnSize;
}
/**
*
* #return the amount of empty spaces remaining on the game board, or if theres a winning player, zero.
*/
public int movesLeft() {
if(whoWon() != null){
return 0;
}
int moveCount = 0;
for (int x = 0; x < getRowSize(); x++) {
for (int y = 0; y < getColumnSize(); y++) {
moveCount += getMoveAt(x, y) == null ? 1 : 0;
}
}
return moveCount;
}
/**
* If someone won, this will return the winning player.
*
* #return the winning player
*/
public String whoWon() {
return winningPlayer;
}
/**
* This move allows the next player to choose where to place their mark.
*
* #param row
* #param column
* #return if the game is over, play will return true, otherwise false.
*/
public boolean play(Move move) {
if (!validMove(move)) {
// always fail early
throw new IllegalStateException("Player " + getCurrentPlayer() + " cannot play at " + move.getRow() + ", " + move.getColumn() + "\n" + toString());
}
doMove(move);
boolean playerWon = isWinningMove(move);
if (playerWon) {
winningPlayer = "Player" + getCurrentPlayer();
return true;
}
shiftPlayer();
boolean outOfMoves = movesLeft() <= 0;
return outOfMoves;
}
public int getRowSize() {
return rowSize;
}
public int getColumnSize() {
return columnSize;
}
public int getCurrentPlayer() {
return currentPlayer;
}
public Integer getMoveAt(int row, int column) {
return gameBoard[row][column];
}
private void doMove(Move move) {
gameBoard[move.getRow()][move.getColumn()] = getCurrentPlayer();
}
private void shiftPlayer() {
if(getCurrentPlayer() == getPlayerCount()){
currentPlayer = 1;
}else{
currentPlayer++;
}
}
private int getPlayerCount() {
return playerCount;
}
private boolean validMove(Move move) {
boolean noMoveAtIndex = false;
boolean indexesAreOk = move.getRow() >= 0 || move.getRow() < getRowSize();
indexesAreOk = indexesAreOk && move.getColumn() >= 0 || move.getColumn() < getColumnSize();
if (indexesAreOk) {
noMoveAtIndex = getMoveAt(move.getRow(), move.getColumn()) == null;
}
return indexesAreOk && noMoveAtIndex;
}
private boolean isWinningMove(Move move) {
// since we check to see if the player won on each move
// we are safe to simply check the last move
return winsDown(move) || winsAcross(move) || winsDiagnally(move);
}
private boolean winsDown(Move move) {
boolean matchesColumn = true;
for (int i = 0; i < getColumnSize(); i++) {
Integer moveOnCol = getMoveAt(move.getRow(), i);
if (moveOnCol == null || getCurrentPlayer() != moveOnCol) {
matchesColumn = false;
break;
}
}
return matchesColumn;
}
private boolean winsAcross(Move move) {
boolean matchesRow = true;
for (int i = 0; i < getRowSize(); i++) {
Integer moveOnRow = getMoveAt(i, move.getColumn());
if (moveOnRow == null || getCurrentPlayer() != moveOnRow) {
matchesRow = false;
break;
}
}
return matchesRow;
}
private boolean winsDiagnally(Move move) {
// diagnals we only care about x and y being teh same...
// only perfect squares can have diagnals
// so we check (0,0)(1,1)(2,2) .. etc
boolean matchesDiagnal = false;
if (isOnDiagnal(move.getRow(), move.getColumn())) {
matchesDiagnal = true;
for (int i = 0; i < getRowSize(); i++) {
Integer moveOnDiagnal = getMoveAt(i, i);
if (moveOnDiagnal == null || moveOnDiagnal != getCurrentPlayer()) {
matchesDiagnal = false;
break;
}
}
}
return matchesDiagnal;
}
private boolean isOnDiagnal(int x, int y) {
if (boardIsAMagicSquare()) {
return x == y;
} else {
return false;
}
}
private boolean boardIsAMagicSquare() {
return getRowSize() == getColumnSize();
}
public String toString() {
StringBuffer stringBuffer = new StringBuffer();
for(int y = 0; y < getColumnSize(); y++) {
for(int x = 0; x < getRowSize(); x++) {
Integer move = getMoveAt(x, y);
String moveToPrint = "";
if (move == null) {
moveToPrint = " ";
} else {
moveToPrint = move.toString();
}
stringBuffer.append("|").append(moveToPrint);
}
stringBuffer.append("|\n");
}
return stringBuffer.toString();
}
}
}
I have to revise my answer. If you want to have three in a row regardless of your board size, your loop code might be sufficient, but you are always checking whether the values of the fields are the same but never make a difference between empty and non-empty fields.
So “empty” can win too, which would effectively hide a possible win of a player. In other words, your code does not work correctly, even for a field size of three. You didn’t test it enough.
If I initialize the board as
int[][] board={
{ 1, 1, 1 },
{ 0, 0, 0 },
{ 0, 0, 0 },
};
your code returns 0 as the second row contains three zeros. I assumed that 0 represents the empty field but the actual value for “empty” doesn’t matter. You have to exclude empty fields from the three-in-a-row check.
You can simplify this a fair amount by breaking the logic up a bit.
First realize that you only need to check for a win around the piece you just placed.
Now we need a way to check whether that move is a winner.
First we need a simple function to check whether a cell matches a given value, returning true if its within bounds and matches.
private boolean cellMatches(int x, int y, int val) {
if (x<0||x>boardWidth)
return false;
if (y<0||y>boardHeight)
return false;
return board[x][y]==val;
}
Now a function that you give a starting position (x and y) and a delta (dx, dy) and it checks up to two cells in that direction returning a count of how many in a row matched value. The for loop may be overkill for two checks but it would easily allow you to expand up to longer lines being used.
private int countMatches(int x, int y, int dx, int dy, int val) {
int count = 0;
for (int step=1;step<=2;step++) {
if (cellMatches(x+dx*step, y+dy*step, val) {
count++;
} else {
return count;
}
}
return count;
}
Now we can use the previous method. When we place a new piece we can just count out in each matching pair of directions. The combined count is the total number in a row. (i.e. two in a row top + 1 bot = a total run length of 4). If any of those run lengths is three then it is a winning move.
private boolean makeMove(int x, int y, int val) {
board[x][y] = val;
int runlength=countMatches(x,y,0,1,val) + countMatches(x,y,0,-1,val);
if (runLength >= 2)
return true;
int runlength=countMatches(x,y,1,0,val) + countMatches(x,y,-1,0,val);
if (runLength >= 2)
return true;
int runlength=countMatches(x,y,1,1,val) + countMatches(x,y,-1,-1,val);
if (runLength >= 2)
return true;
int runlength=countMatches(x,y,1,-1,val) + countMatches(x,y,-1,1,val);
if (runLength >= 2)
return true;
return false;
}
Note that because we need to count the center piece that we placed we just need a run length of two or more.

Categories

Resources