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
Related
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.
I have made a class where a 6x10 2D array is generated to act as a board.
A random starting location is then generated in the constructor.I only want adjacent moves to be possible.
For example, if the random location has been generated as (2,3) then for example the user enters (1,2) it would be a valid move, but (6,1) would be an invalid move.
Then if the user enters say (1,2), they can then go to any adjacent cell from (1,2).
I have included the class below, and the adjacent method I tried to make to test it, but I'm a bit confused on how I am approaching this.
import java.util.Arrays;
import java.util.Random;
public class Test {
public static final int ROWS = 6;
public static final int COLUMNS = 10;
public int[][] board;
public static void main(String[] args)
{
Test t = new Test();
t.getBoard();
t.makeMove(6,1); //I want this to be an invalid move.
t.getBoard();
t.makeMove(1,2); // this should be a valid move
t.getBoard();
}
public Test()
{
board = new int[ROWS][COLUMNS];
createRandomLocation();
}
public void createRandomLocation()
{
Random rand = new Random();
int x = rand.nextInt(6);
int y = rand.nextInt(10);
board[x][y] = 1;
}
public void makeMove(int x,int y){
if (Math.abs(x-cur_x)==0 || Math.abs(y-cur_y)==0) {
board[x][y] = 1;
}
public String getBoard() {
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[i].length; j++) {
System.out.print(board[i][j] + " ");
}
System.out.println();
}
System.out.println();
return Arrays.deepToString(board);
}
}
Adjacent:
/*public boolean isMoveAllowed(int [][] array,int x, int y){
boolean adjacent = false;
int trueCount = 0;
if(array[x-1][y-1] == 0) trueCount++; //topleft
if(array[x-1][y] == 0) trueCount++; //top
if(array[x-1][y+1] == 0) trueCount++;//topright
if(array[x][y+1] == 0) trueCount++;//right
if(array[x][y-1] == 0) trueCount++;//left
if(array[x+1][y-1] == 0) trueCount++;//bottomleft
if(array[x+1][y] == 0) trueCount++;//bottom
if(array[x+1][y+1] == 0) trueCount++; //bottomright
if (trueCount == 8)
{
adjacent = true;
}
return adjacent;
}*/
Your problem description has the answer baked into it already. You want any move from (a,b) to (c,d) to be legal if the distance between a and c, and b and d, is zero or one. So if you see Math.abs(a-c)>1, that's an illegal move. So: have the current position stored in some variables, and compare them to the desired new location:
public static void main(String[] args)
{
Board b = new Board(6, 10);
try {
b.tryMove(6,1);
} catch(IllegalMoveException e) {
// do whatever you need to do to inform the user that move is illegal
}
}
With the Board class responsible for tracking coordinates:
class Board {
protected int cur_x, cur_y, rows, cols;
public Board(int rows, int cols) {
this.rows = rows;
this.cols = cols;
this.setRandomPosition();
}
public void setRandomPosition() {
cur_x = (int) Math.round(Math.random() * cols);
cur_y = (int) Math.round(Math.random() * rows);
}
public void tryMove(int x, int y) throws IllegalMoveException {
if (Math.abs(x-cur_x)>1 || Math.abs(y-cur_y)>1) {
throw new IllegalMoveException(...);
}
// bounds check omitted here, but: ensure that
// 0<=x<cols and 0<=y<rows, otherwise throw an
// IllegalMoveException as well.
cur_x = x;
cur_y = y;
}
// with getters for the current x and y, etc.
}
It would be much easier to test for a true case rather than a false case like you currently have, the isMoveAllowed method should look something like this:
public boolean isMoveAllowed(int[][] array, int x, int y) {
return ((array[x + 1][y] == 1) ||
(array[x - 1][y] == 1) ||
(array[x][y + 1] == 1) ||
(array[x][y - 1] == 1));
}
This will return true if the move is adjacent to the current player position
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.
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).
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.