I read a tutorial about minimax, and tried to make a tac tac toe AI.
But the code doesn't work optimally for some reason, which I cannot find. The ai can place pieces, but it's not a smart ai. I expected it to be unbeatable. The higher the depth is, the dumber the ai becomes.
The 'game' is my an other class, where the actual game is.
private Game game;
private Piece[][] board;
private Piece ai = Piece.CIRCLE;
private Piece player = Piece.CROSS;
public AI(Game game) {
this.game = game;
this.board = game.getBoard();
}
public int[] move() {
int[] result = minimax(1, ai);
return new int[] {result[1], result[2]};
}
private int[] minimax(int depth, Piece piece) {
List<int[]> possibleMoves = generateMoves();
int bestScore = (piece == ai) ? Integer.MIN_VALUE : Integer.MAX_VALUE;
int currentScore;
int bestRow = -1;
int bestCol = -1;
if (possibleMoves.isEmpty() || depth == 0) {
// Game over or depth reached
bestScore = evaluate();
}
else {
for (int[] move : possibleMoves) {
// Try this move for the player
board[move[0]][move[1]] = player;
if (piece == ai) { // ai is maximizing player
currentScore = minimax(depth - 1, player)[0];
if (currentScore > bestScore) {
bestScore = currentScore;
bestRow = move[0];
bestCol = move[1];
}
}
else { // player is minimizing player
currentScore = minimax(depth - 1, ai)[0];
if (currentScore < bestScore) {
bestScore = currentScore;
bestRow = move[0];
bestCol = move[1];
}
}
// Undo move
board[move[0]][move[1]] = null;
}
}
return new int[] {bestScore, bestRow, bestCol};
}
private List<int[]> generateMoves() {
List<int[]> possibleMoves = new ArrayList<int[]>();
// If game over
if (game.getWinner() != null) {
return possibleMoves; // return empty list
}
// Add possible moves to list
for (int x = 0; x < 3; x++) {
for (int y = 0; y < 3; y++) {
if (game.getBoard()[x][y] == null) {
possibleMoves.add(new int[] {x, y});
}
}
}
return possibleMoves;
}
private int evaluate() {
int score = 0;
// Evaluate
score += evaluateLine(0, 0, 0, 1, 0, 2); // row 0
score += evaluateLine(1, 0, 1, 1, 1, 2); // row 1
score += evaluateLine(2, 0, 2, 1, 2, 2); // row 2
score += evaluateLine(0, 0, 1, 0, 2, 0); // col 0
score += evaluateLine(0, 1, 1, 1, 2, 1); // col 0
score += evaluateLine(0, 2, 1, 2, 2, 2); // col 0
score += evaluateLine(0, 0, 1, 1, 2, 2); // diag 1
score += evaluateLine(0, 2, 1, 1, 2, 0); // diag 2
return score;
}
// Return +100, +10, +1 for 3-, 2-, 1-in-a-line for ai
// Return -100, -10, -1 for 3-, 2-, 1-in a line for player
// Else return 0
private int evaluateLine(int row1, int col1, int row2, int col2, int row3, int col3) {
int score = 0;
// First cell
if (board[row1][col1] == ai) {
score = 1;
}
else if (board[row1][col1] == player) {
score = -1;
}
// Second cell
if (board[row2][col2] == ai) {
if (score == 1) { // board1 is ai
score = 10;
}
else if (score == -1) { // board1 is player
return 0;
}
else { // board1 is empty
score = 1;
}
}
else if (board[row2][col2] == player) {
if (score == -1) { // board1 is player
score = -10;
}
else if (score == 1) { // board1 is ai
return 0;
}
else { // board1 is empty
score = -1;
}
}
// Third cell
if (board[row3][col3] == ai) {
if (score > 0) { // board1 and/or board2 is ai
score *= 10;
}
else if (score < 0) { // board1 and/or board2 is player
return 0;
}
else { // board1 and/or board2 is empty
score = 1;
}
}
else if (board[row3][col3] == player) {
if (score < 0) { // board1 and/or board2 is player
score *= 10;
}
else if (score > 1) { // board1 and/or board2 is ai
return 0;
}
else { // board1 and/or board2 is empty
score = -1;
}
}
return score;
}
A couple of things I noticed:
The first line in the loop going through possible moves says board[move[0]][move[1]] = player;. That should be piece instead of player, now your AI thinks that only pieces of the human player ever end up on the board.
Minimax should be very easily capable of searching the complete game tree in less than a second. Therefore, I'd recommend allowing to to search as deep as it likes, instead of limiting to a search depth of 1. This would also eliminate the need for creating that heuristic evaluation function; you'd only give a large score for winning, 0 for tie, and a very negative score for losing. The main reason I'm recommending this is that I suspect there may be something wrong with the evaluation function too, though I'm not sure since I did not check it in detail. If you really do insist on terminating the search early and using a heuristic evaluation function, you need to make sure that the function is ''symmetrical''. With that, I mean that evaluating the board from the perspective of one player should always result in exactly -1 times the score of the same board were evaluated from the perspective of the opponent.
minimax is returning a move in terms of a row/column pair, not a score. So
currentScore = minimax(depth - 1, player)[0];
makes no sense. It probably causes any move to row 3 to look better than any move to row 1 or row 2.
minmax needs to hand
back a score in addition to the best move.
Related
The method is given NxN matrix always powers of 2 and a number,it will return true if the num is found example for 4x4 size:
this is what i wrote:
public class Search {
public static boolean Search (int [][] matrix, int num)
{
int value = matrix.length / 2;
int first_quarter_pivot = matrix[value-1][0]; // represents highest number in first quarter
int second_quarter_pivot = matrix[value-1][value]; // represents highest number in second quarter
int third_quarter_pivot = matrix[matrix.length-1][value]; // represents highest number in third quarter
int fourth_quarter_pivot = matrix[matrix.length-1][0]; // represents highest number in fourth quarter
boolean isBoolean = false;
int i=0;
int j;
// if the num is not in the range of biggest smallest number it means he can`t be there.
if(!(num >= first_quarter_pivot) && (num <= fourth_quarter_pivot)) {
return false;
}
// if num is one of the pivots return true;
if((num == first_quarter_pivot || (num ==second_quarter_pivot))
|| (num == third_quarter_pivot) || (num == fourth_quarter_pivot ))
return true;
// if num is smaller than first pivot it means num is the first quarter,we limit the search to first quarter.
// if not smaller move to the next quarter pivot
if(num < first_quarter_pivot){{
j =0;
do
if(matrix[i][j] == num) {
isBoolean = true;
break;
}
else if((j == value)) {
j = 0;
i++;
}
else if(matrix[i][j] != num){
j++;
}
while(isBoolean != true) ;
}
return isBoolean;
}
// if num is smaller than second pivot it means num is the second quarter,we limit the search to second quarter.
// if not smaller move to the next quarter pivot
if(num < second_quarter_pivot){{
j = value;// start (0,value) j++ till j=value
do
if(matrix[i][j] == num) {
isBoolean = true;
break;
}
else if((j == matrix.length-1)) {
j = value;
i++;
}
else if(matrix[i][j] != num){
j++;
}
while(isBoolean != true) ;
}
return isBoolean;
}
// if num is smaller than third pivot it means num is the third quarter,we limit the search to third quarter.
// if not smaller move to the next quarter pivot
if(num < third_quarter_pivot){{
i = value;
j = value;// start (0,value) j++ till j=value
do
if(matrix[i][j] == num) {
isBoolean = true;
break;
}
else if((j == matrix.length-1)) {
j = value;
i++;
}
else if(matrix[i][j] != num){
j++;
}
while(isBoolean != true) ;
}
return isBoolean;
}
// if num is smaller than fourth pivot it means num is the fourth quarter,we limit the search to fourth quarter.
// number must be here because we verfied his existence in the start.
if(num < fourth_quarter_pivot){
i = value;
j = 0;// start (0,value) j++ till j=value
do
if(matrix[i][j] == num) {
isBoolean = true;
break;
}
else if((j == value)) {
j = 0;
i++;
}
else if(matrix[i][j] != num){
j++;
}
while(isBoolean != true) ;
}
return isBoolean;
}
}
What i tried to do:
find in which quarter the wanted number is in,after that check
the same quarter by moving j++ until it hits the limit,than i++
until found
with the limits changing for each quarter,i cant understand if run time complexity is O(n^2) or lower? and will it be better do create one dimensional array and and move on the quarter this way: move right until limit,one down,move left until limit and il have a sorted array and just binear search
If you can map an array to a matrix, you can use a normal binary search.
You can define the translation table to achieve that like this:
X = [0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 3, 3, 2, 2, 3, 3, ...]
Y = [0, 1, 1, 0, 2, 3, 3, 2, 2, 3, 3, 2, 0, 1, 1, 0, ...]
The final program looks like this.
static final int MAX_N = 64;
static final int MAX_NN = MAX_N * MAX_N;
static final int[] DX = {0, 0, 1, 1};
static final int[] DY = {0, 1, 1, 0};
static final int[] X = new int[MAX_NN];
static final int[] Y = new int[MAX_NN];
static { // initialize X and Y
for (int i = 0; i < MAX_NN; ++i) {
int x = 0, y = 0;
for (int t = i, f = 0; t > 0; ++f) {
int mod = t & 3;
x += DX[mod] << f; y += DY[mod] << f;
t >>= 2;
}
X[i] = x; Y[i] = y;
}
}
public static boolean Search(int [][] matrix, int num) {
int n = matrix.length, nn = n * n;
int lower = 0;
int upper = nn - 1;
while (lower <= upper) {
int mid = (lower + upper) / 2;
int value = matrix[X[mid]][Y[mid]];
if (value == num)
return true;
else if (value < num)
lower = mid + 1;
else
upper = mid - 1;
}
return false;
}
and
public static void main(String[] args) {
int[][] matrix = {
{1, 3, 7, 9},
{6, 4, 15, 11},
{36, 50, 21, 22},
{60, 55, 30, 26},
};
// case: exists
System.out.println(Search(matrix, 1));
System.out.println(Search(matrix, 60));
System.out.println(Search(matrix, 11));
// case: not exists
System.out.println(Search(matrix, 0));
System.out.println(Search(matrix, 70));
System.out.println(Search(matrix, 20));
}
output:
true
true
true
false
false
false
I am doing a basic Snake Game in Java Processing. It is supposed to turn clockwise whne the user presses L or D, and when the user presses the A or J key the snake should turn counter-clockwise. The keys should not directly control the snake's direction. They should control how the snake turns.
DEMO
Look at DEMO Question 2
QUESTION
image
My Code
final int ROWS=20, COLS=15; //The number of rows and columns of squares
final int SQ_SIZE=40; //The size of each square in pixels
final int[] X_DIRECTIONS = {0, -1, 0, 1}; //X change for down, left, up, right
final int[] Y_DIRECTIONS = {1, 0, -1, 0}; //Y change for down, left, up, right
int startingLength = 5, currentLength = 0, snakeSpeed = 30, currentDirection = 0;
int [] x = new int[ROWS*COLS];
int [] y = new int[ROWS*COLS];
void setup(){
background(#ff9900);
size(600,800); //MUST be COLS*SQ_SIZE, ROWS*SQ_SIZE
resetSnake();
}
void draw(){
if(frameCount % snakeSpeed == 0){
background(#ff9900);
drawCircles(x, y, currentLength, #32CD32);
moveSnake(X_DIRECTIONS[currentDirection], Y_DIRECTIONS[currentDirection]);
//moveSnake(X_DIRECTIONS[currentDirection], 0);
}
}
void resetSnake(){
currentLength = startingLength;
fillArray(y, currentLength, ROWS/2, -1);
fillArray(x, currentLength, COLS/2, 0);
drawCircles(x, y, currentLength, #32CD32);
}
void moveSnake(int addX, int addY){
if(addX != 0){
fillArray(x, currentLength, x[0] + addX, addX);
}
fillArray(y, currentLength, y[0] + addY, -addY);
}
void keyPressed(){
int newDir = keyCode == DOWN ? 0:(keyCode == UP ? 2:(keyCode == RIGHT ? 3:(keyCode == LEFT ? 1:-1)));
if(newDir != -1) currentDirection = newDir;
}
void drawCircles(int[]x, int[]y, int n, int colour){
//Draw circles here
for(int i = 0; i < n; i++){
fill(colour);
circle(x[i]*SQ_SIZE, y[i]*SQ_SIZE, SQ_SIZE);
}
}
void fillArray(int[] a, int n, int start, int delta){
int index = 0;
if(delta > 0){
for(int i = start; i < start + n; i+=delta){
a[index++] = i;
}
}else if(delta < 0){
for(int i = start; i > start-n; i+=delta){
a[index++] = i;
}
}else{
for(int i = 0; i < n; i++){
a[index++] = start;
}
}
}
Since you have the directions in an array, you can "rotate" through them like this:
void keyPressed(){
if (keyCode == 76 //the code for 'l'
|| keyCode == 68) { //the code for 'd'
currentDirection++;
if(currentDirection > 3) { //make sure currentDirection is not bigger than the array length
currentDirection = 0;
}
}
if (keyCode == 65 //the code for 'a'
|| keyCode == 74) { //the code for 'j'
currentDirection--;
if (currentDirection < 0) { //make sure currentDirection is not smaller than 0
currentDirection = 3;
}
}
}
I'm developing a minimax alogorithm for use in a modified checkers game. In my evaluation function every score is multiplied by 10 and then a random number between 1 and 10 is added/subtracted from it (max or min node depending). However, when running the program it always executes the same sequence of moves. I've checked the evaluation function and it definitely returns randomised values for nodes that would be of equal value so I can only assume the problem lies in the minimax function itself, any ideas? The other functions, generateMoves and simulateMove also work correctly.
private int minimax(State state, int depth, int min, int max) {
ArrayList<Move> moves = generateMoves(state.board, state.colour);
char opponent = (state.colour == DraughtBoard.WHITE) ? DraughtBoard.BLACK : DraughtBoard.WHITE;
if (moves.size() == 1)
nextMove = moves.get(0);
int bestScore;
Move bestMove = new Move();
int score = 0;
if (depth == 0 || moves.size() == 0) {
return evaluateBoard(state);
}
if (colour == DraughtBoard.WHITE) {
bestScore = min;
for (Move move : moves) {
char[][] temp = state.board.clone();
boolean scored = simulateMove(move, temp);
State nextState = new State(temp, opponent, state.whiteScore, state.blackScore);
if (scored) state.whiteScore++;
score = minimax(state, depth-1, bestScore, max);
if (score > bestScore) {
bestScore = score;
bestMove = move;
}
if (bestScore > max) return max;
}
nextMove = bestMove;
return bestScore;
} else {
bestScore = max;
for (Move move : moves) {
char[][] temp = state.board.clone();
boolean scored = simulateMove(move, temp);
State nextState = new State(temp, opponent, state.whiteScore, state.blackScore);
if (scored) state.blackScore++;
score = minimax(state, depth-1, min, bestScore);
if (score < bestScore) {
bestScore = score;
bestMove = move;
}
if (bestScore < min) return min;
}
nextMove = bestMove;
return bestScore;
}
}
char[][] temp = state.board.clone(); will only do a swallow copy (except you wrote your own clone() method)
Which means temp has the same references as board therefor you will change board while calling siumlateMove.
This may causes your problem.
deep copy of a 2d array
I've been trying to learn the minimax algorithm and I've stumbled upon a bug which I cannot figure out how to solve.
Code:
private List<Integer> generatemoves(int[] evalFields) {
List<Integer> nextMoves = new ArrayList<Integer>();
for (int i = 0; i < evalFields.length; i++) {
if (evalFields[i] == 0) {
nextMoves.add(i);
}
}
return nextMoves;
}
private int evaluateLine(int p1, int p2, int p3, int[] evalFields) {
int score = 0;
if (evalFields[p1] == 1) {
score = 1;
} else if (evalFields[p1] == 10) {
score = -1;
}
if (evalFields[p2] == 1) {
if (score == 1) {
score = 10;
} else if (score == -1) {
return 0;
} else {
score = 1;
}
} else if (evalFields[p2] == 10) {
if (score == -1) {
score = -10;
} else if (score == 1) {
return 0;
} else {
score = -1;
}
}
if (evalFields[p3] == 1) {
if (score > 0) {
score *= 10;
} else if (score < 0) {
return 0;
} else {
score = 1;
}
} else if (evalFields[p3] == 10) {
if (score < 0) {
score *= 10;
} else if (score > 1) {
return 0;
} else {
score = -1;
}
}
return score;
}
private int evaluateBoard(int [] evalFields) {
int score = 0;
score += evaluateLine(0, 1, 2, evalFields);
score += evaluateLine(3, 4, 5, evalFields);
score += evaluateLine(6, 7, 8, evalFields);
score += evaluateLine(0, 3, 6, evalFields);
score += evaluateLine(1, 4, 7, evalFields);
score += evaluateLine(2, 5, 8, evalFields);
score += evaluateLine(0, 4, 8, evalFields);
score += evaluateLine(2, 4, 6, evalFields);
return score;
}
private int bestMove(int currentTurn, int[] board) {
int move;
int bestScore;
if (currentTurn == 1) {
bestScore = Integer.MIN_VALUE;
} else {
bestScore = Integer.MAX_VALUE;
}
List<Integer> nextMoves = generatemoves(board);
List<Integer> bestScores = new ArrayList<Integer>();
for (int i = 0; i < nextMoves.size(); i++) {
int[] newBoards = new int[9];
for (int j = 0; j < board.length; j++) {
newBoards[j] = board[j];
}
newBoards[nextMoves.get(i)] = turn;
bestScores.add(evaluateBoard(newBoards));
}
for (int scores : bestScores) {
if (currentTurn == 1) {
if (scores > bestScore) bestScore = scores;
} else {
if (scores < bestScore) bestScore = scores;
}
}
move = nextMoves.get(bestScores.indexOf(bestScore));
return move;
}
This is the most relevant part of the code. What it does or what I think it does is that it generates every possible move from the board which is called fields. Then it calculates a score for each move. It then proceeds to make the move which results in the highest or lowest score, x(1) is trying to get the highest and O(10) the lowest. The bug that occurs is that when the player starts and takes the field in the middle, then the ai acts normally but after the players second turn the ai starts to act strange:
[ ][ ][ ] [O][ ][ ] [O][ ][O]
[ ][x][ ] => [ ][x][ ] => [x][x][ ]
[ ][ ][ ] [ ][ ][ ] [ ][ ][ ]
If the player chooses this:
[O][ ][ ] [O][ ][ ]
[ ][x][x] => [O][x][x]
[ ][ ][ ] [ ][ ][ ]
Then the ai acts nomally.
I don't know what is wrong or even if I've understood the minimax algorithm correctly.
****edit****
Added this code still have the same problem
private int[] evaluateMove(int [] board, int currentTurn) {
int bestScore;
int currentScore;
int bestMove = -1;
if (currentTurn == 1) {
bestScore = Integer.MIN_VALUE;
} else {
bestScore = Integer.MAX_VALUE;
}
List<Integer> nextMoves = generatemoves(board);
if (nextMoves.isEmpty()) {
bestScore = evaluateTheBoard(board);
} else {
for (int move : nextMoves) {
int[] nextBoard = new int[9];
for (int i = 0; i < nextBoard.length; i ++) {
nextBoard[i] = board[i];
}
nextBoard[move] = currentTurn;
currentScore = evaluateMove(nextBoard, nextTurn())[0];
if (currentTurn == 1) {
if (currentScore > bestScore) {
bestScore = currentScore;
bestMove = move;
}
} else {
if (currentScore < bestScore) {
bestScore = currentScore;
bestMove = move;
}
}
}
}
return new int[] {bestScore, bestMove};
}
I think you are misunderstanding how to look ahead in a game like this. Do not 'total' the values returned by evaluateLine.
Here is pseudocode for the minimax score of a tic-tac-toe board (what evaluateBoard should return). Note that evaluateBoard will need to have a notion of currentTurn.
function evaluateBoard(board, currentTurn)
// check if the game has already ended:
if WhiteHasWon then return -10
if BlackHasWon then return +10
// WhiteHasWon returns true if there exists one or more winning 3-in-a-row line for white.
// (You will have to scan for all 8 possible 3-in-a-row lines of white pieces)
// BlackHasWon returns true if there exists one or more winning 3-in-a-row line for black
if no legal moves, return 0 // draw
// The game isn't over yet, so look ahead:
bestMove = notset
resultScore = notset
for each legal move i for currentTurn,
nextBoard = board
Apply move i to nextBoard
score = evaluateBoard(nextBoard, NOT currentTurn).score
if score is <better for currentTurn> than resultScore, then
resultScore = score
bestMove = move i
return (resultScore, bestMove)
One very key difference between this and your version and my version is that my version is recursive. Yours only goes one level deep. Mine calls evaluateBoard from inside evaluateBoard, which would be an infinite loop if we aren't careful (once the board fills up, it can't go any deeper, so it's not actually infinite)
Another difference is that yours totals stuff when it shouldn't. The resulting score from tic-tac-toe is -10,0, or 10 only once you've looked to the end of the game. You should be picking the best possible move available to that player at that time, and ignoring all other possibilities completely because you only care about the "best" line of play. The game score is equal to the result of optimal play.
Expanding <better for currentTurn> is messy in minimax, which is why negamax is cleaner. White prefers low scores and black prefers high scores, so you need some if statements to make it choose the appropriate preferred score. You have this part already (at the end of your best move code), but it needs to be evaluated inside the recursion instead of just at the end.
As a self-learning experience I have built a 3x3 TicTacToe game. Now I want to expand that game to a N x N size board. This presents me a problem when determining the winning condition.
The original game used a array to look for a winning condition:
private final int[][] win = new int[][] {
{0, 1, 2}, {3, 4, 5}, {6, 7, 8}, //horizontal
{0, 3, 6}, {1, 4, 7}, {2, 5, 8}, //vertical
{0, 4, 8}, {2, 4, 6} //diagonal
};
And in the ActionListener:
// Check the win array for 3-in-a-line condition.
for(int i = 0; i<=7; i++){
if( b[win[i][0]].getText().equals( b[win[i][1]].getText() ) && // A == B
b[win[i][1]].getText().equals( b[win[i][2]].getText() ) && // B == C
!b[win[i][0]].getText().equals("")){ // Not empty
b[win[i][0]].setBackground(Color.GREEN);
b[win[i][1]].setBackground(Color.GREEN);
b[win[i][2]].setBackground(Color.GREEN);
gameOver = true;
System.out.println("WIN WIN WIN");
With the game expanding to N x N size, I can't have a fixed array for determining the winning conditions.
I will need some procedure to determine if there 3 (or more) in a line. So how would you approach this? Is there a smarter way to do this rather than check all the squares closest to the placed in? (North+South, East+West, N+N, E+E, S+S, W+W, NE+SW, NW+SE, NE+NE, NW+NW, SE+SE, SW+SW) and try and filter out all the PointerExceptions?
Check the entire board each time and control the indexes of the for-loops not to go out-of-bounds?
Either solution feels like nightmare to code. Anyone have a smarter approach to this problem?
Adding the entire program for reference:
package heniv181;
import javax.swing.JFrame;
import java.awt.GridLayout;
import java.awt.event.*;
import javax.swing.JButton;
import javax.swing.JOptionPane;
/**
* #author Henrik
* Also code by John (john#codecall.net) http://forum.codecall.net/topic/36472-javatutorial-tic-tac-toe/
*
*/
public class TicTacToeBig extends JFrame
implements ActionListener {
private int size = 5;
private JButton[] b = new JButton[size*size];
private int turn = 0;
private final int[][] win = new int[][] {
{0, 1, 2}, {3, 4, 5}, {6, 7, 8}, //horizontal
{0, 3, 6}, {1, 4, 7}, {2, 5, 8}, //virticle
{0, 4, 8}, {2, 4, 6} //diagonal
};
// Constructor
public TicTacToeBig(){
setTitle("Tic-Tac-Toe");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(300, 300);
setLocation(200, 200);
setVisible(true);
setLayout(new GridLayout(size,size));
for(int i=0; i < size*size; i++){
b[i] = new JButton();
b[i].setText("");
b[i].addActionListener(this);
b[i].setActionCommand( Integer.toString(i));
add(b[i]);
}
}
public static void main(String args[]){
TicTacToeBig t = new TicTacToeBig();
}
#Override
public void actionPerformed(ActionEvent ae) {
String sign;
boolean gameOver = false;
// Whos turn is it? X's or O's?
turn++;
if(turn % 2 == 0)
sign="X";
else
sign="O";
// Set X or O on the button pressed.
JButton press = (JButton)ae.getSource();
press.setText(sign);
press.setEnabled(false);
gameOver = checkWin(press);
/* Check the win array for 3-in-a-line condition.
for(int i = 0; i<=7; i++){
if( b[win[i][0]].getText().equals( b[win[i][1]].getText() ) && // A == B
b[win[i][1]].getText().equals( b[win[i][2]].getText() ) && // B == C
!b[win[i][0]].getText().equals("")){ // Not empty
b[win[i][0]].setBackground(Color.GREEN);
b[win[i][1]].setBackground(Color.GREEN);
b[win[i][2]].setBackground(Color.GREEN);
gameOver = true;
System.out.println("WIN WIN WIN");
}
}*/
//End game if winning conditon is true or no more turns.
if(gameOver){
JOptionPane.showMessageDialog(null, "Congratulation!\n" + sign + " have won!");
System.exit(0);
}
else if(turn>=(size*size) ){
JOptionPane.showMessageDialog(null, "To bad!\n No winners. ");
System.exit(0);
}
}
public boolean checkWin(JButton j){
//HHmmmm..........
int index = Integer.valueOf( j.getActionCommand() );
System.out.println(index);
if((index+1) % size == 0 || (index+1) % size == 1)
System.out.println("R or L Edge.");
if(index-size < 0 || index+size > b.length-1)
System.out.println("U or D Edge");
//check right and left
//check if point is on right or left edge
//compare index-1 L
//compare index+1 R
//check up and down
//check if point is on top or bottom edge
//compare index - size D
//compare index + size U
//check diagonals
//check if point is on edge
//compare index - size -1 UL
//compare index - size +1 UR
//compare index + size -1 DL
//compare index + size +1 DR
return false;
}
}
The best way to solve this would be at the time you are adding the new mark to the game board.
Then, you just need to test the row, column, and diagonals that include the current cell rather than testing the entire board.
You have to do the calculations for dimensionality yourself. Here's a start. It creates a one-dimensional array for the board but provides access to pick a cell in that array from an n-dimensional coordinate.
I have put no work into range checking or tracking rows and columns.
I have chosen int for the array with the aim of using 0 = Empty, 1 = O and -1 = X. You can then add up the values in each row and see if it comes out to +/- s to see if someone has won.
public class TicTacToe {
// Each piece.
static final int Empty = 0;
static final int X = 1;
static final int O = -1;
// A Board is a number of cells.
static class Board {
// Dimensions.
final int d;
// Size.
final int s;
// The board is just an array of ints.
final int[] board;
// Create board of the specified size.
public Board(int d, int s) {
this.d = d;
this.s = s;
/* E.G.
* 3 * 3 = 9 cells in a 2-D board.
* 3 * 3 * 3 = 27 rows in a 3-D board.
*/
board = new int[(int) Math.pow(d, s)];
}
void setPiece(int[] coords, int value) {
board[getLoc(coords)] = value;
}
boolean won() {
boolean won = false;
// For each piece.
for (int p = 0; p < board.length; p++) {
// Where is this piece.
int[] coords = getCoords(p);
// No point in checking empty squares.
int piece = getPiece(coords);
if (piece != Empty) {
// First check non-diagonals.
int [] check;
// Vary each dimension from 0 to 3.
for (int i = 0; i < coords.length; i++) {
// Back to there.
check = Arrays.copyOf(coords, coords.length);
// The sum across this dimension.
int sum = 0;
// By the size of the board.
for (int j = 0; j < s; j++) {
check[i] = j;
sum += getPiece(check);
}
if (sum == piece * s) {
// A line adds up!
return true;
}
}
}
}
return won;
}
int getPiece(int[] coords) {
/*
* Say [1,1] is the center of a 3x3 board so it is at 4 in the array.
*
* i.e. the array is:
*
* 0 - [0,0]
* 1 - [0,1]
* 2 - [0,2]
* 3 - [1,0]
* 4 - [1,1] - *
* 5 - [1,2]
* 6 - [2,0]
* 7 - [2,1]
* 8 - [2,2]
*
* So (1 * 3) + 1 = 4
*
* But [1,1,1], being the center of a 3x3x3 board must be at 13!
*
* So ((1 * 3) + 1) * 3) + 1 = 13
*/
return board[getLoc(coords)];
}
// Returns the location in the array where the cell at this coordinate is.
private int getLoc(int[] coords) {
// Where this piece is in the array.
int loc = coords[0];
for (int i = 1; i < coords.length; i++) {
// Add in each dimension of coordinate.
loc = loc * s + coords[i];
}
return loc;
}
// Reverse the getLoc by taking a loc and rolling it into a coordinates.
private int[] getCoords(int loc) {
// It must be that wide.
int[] coords = new int[d];
// Work backwards from the end.
for (int i = coords.length - 1; i >= 0; i--) {
// Take remainder.
coords[i] = loc % s;
// Divide.
loc /= s;
}
return coords;
}
}
private void test() {
System.out.println("Board(2,3) - piece[1,1] # " + new Board(2, 3).getLoc(new int[]{1, 1}));
System.out.println("Board(3,3) - piece[1,1,1] # " + new Board(3, 3).getLoc(new int[]{1, 1, 1}));
System.out.println("Board(2,3) - loc[8] # " + Arrays.toString(new Board(2, 3).getCoords(8)));
System.out.println("Board(2,3) - loc[0] # " + Arrays.toString(new Board(2, 3).getCoords(0)));
System.out.println("Board(3,3) - loc[13] # " + Arrays.toString(new Board(3, 3).getCoords(13)));
Board board = new Board(3,3);
boolean won = board.won();
System.out.println("Won: " + won);
// Set a row.
board.setPiece(new int[]{0, 1, 1}, X);
board.setPiece(new int[]{1, 1, 1}, X);
board.setPiece(new int[]{2, 1, 1}, X);
// Should have a win.
won = board.won();
System.out.println("Won: " + won);
}
public static void main(String args[]) {
try {
new TicTacToe().test();
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
}
Correctly prints:
Board(2,3) - piece[1,1] # 4
Board(3,3) - piece[1,1,1] # 13
Board(2,3) - loc[8] # [2, 2]
Board(2,3) - loc[0] # [0, 0]
Board(3,3) - loc[13] # [1, 1, 1]
Won: false
Won: true
Note that this does not as yet check diagonals - you will have to do that yourself.
Okay Henrik,
The problem that you are having with hitting the edges is not inherent to testing from the last played position. It is possible to do this if you are careful and program iteratively.
Here is a partial solution for your game. It is not optimal, and does not test for diagonals that slope upward from left to right. But, it seems to work - cleanup and understanding is up to you.
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
/**
* #author JayDM
* Loosely based on code provide by Henrik
*
*/
public class TicTacToeBig extends JFrame implements ActionListener {
private static final long serialVersionUID = 1L;
private int size = 5;
private JButton[][] b;
private int turn = 0;
// Constructor
public TicTacToeBig() {
setTitle("Tic-Tac-Toe");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(300, 300);
setLocation(200, 200);
setVisible(true);
b = new JButton[size][size];
setLayout(new GridLayout(size, size));
for (int row = 0; row < size; row++) {
for (int col = 0; col < size; col++) {
System.out.println("Adding button for position: " + row + ", " + col);
b[row][col] = new JButton();
b[row][col].setText("_");
b[row][col].addActionListener(this);
b[row][col].setActionCommand(row + "," + col);
add(b[row][col]);
}
}
invalidate();
validate();
}
#Override
public void actionPerformed(ActionEvent ae) {
String sign;
// Whos turn is it? X's or O's?
turn++;
if (turn % 2 == 0) {
sign = "O";
} else {
sign = "X";
}
// Set X or O on the button pressed.
JButton press = (JButton) ae.getSource();
press.setText(sign);
press.setEnabled(false);
// End game if winning conditon is true or no more turns.
if (checkWin(press)) {
JOptionPane.showMessageDialog(null, "Congratulations!\n" + sign + " has won!");
System.exit(0);
} else if (turn >= (size * size)) {
JOptionPane.showMessageDialog(null, "To bad!\n No winners. ");
System.exit(0);
}
}
public boolean checkWin(JButton j) {
String position[] = j.getActionCommand().split(",");
int row = Integer.parseInt(position[0]);
int col = Integer.parseInt(position[1]);
System.out.println(b[row][col].getText() + " played # " + row + ", " + col);
String winner = b[row][col].getText() + b[row][col].getText() + b[row][col].getText();
String field;
// row
field = "";
for (int testCol = Math.max(0, col - 2); testCol < Math.min(size, col + 3); testCol++) {
field += b[row][testCol].getText();
}
System.out.println("Testing row field: " + field);
if (field.contains(winner)) {
System.out.println("Row winner!");
return true;
}
// col
field = "";
for (int testRow = Math.max(0, row - 2); testRow < Math.min(size, row + 3); testRow++) {
field += b[testRow][col].getText();
}
System.out.println("Testing column field: " + field);
if (field.contains(winner)) {
System.out.println("Column winner!");
return true;
}
// diagonals
int lowerBound = 0;
int upperBound = 0;
// diagonal down
field = "";
// top left
lowerBound = - Math.min(2, Math.min(col, row));
// bottom right
upperBound = Math.min(3, size - Math.max(row, col));
System.out.println("Bounds: " + lowerBound + ", " + upperBound);
for (int offset = lowerBound; offset < upperBound; offset++) {
field += b[row + offset][col + offset].getText();
}
System.out.println("Testing diagonal down field: " + field);
if (field.contains(winner)) {
System.out.println("Diagonal down winner!");
return true;
}
// diagonal up
field = "";
// bottom left
// lowerBound = ?????????????;
lowerBound = 0;
// top right
// upperBound = ?????????????;
upperBound = 0;
System.out.println("Bounds: " + lowerBound + ", " + upperBound);
for (int offset = lowerBound; offset < upperBound; offset++) {
// field += b[row +/- offset][col +/- offset].getText();
}
System.out.println("Testing diagonal up field: " + field);
if (field.contains(winner)) {
System.out.println("Diagonal up winner!");
return true;
}
return false;
}
public static void main(String args[]) {
TicTacToeBig t = new TicTacToeBig();
}
}