Tic Tac Toe winning condition change when scalable

2019-09-11 00:18发布

问题:

So, I have been making this Tic Tac Toe program for a while now. It's a basic Tic Tac Toe game but the game board is scalable. The program is almost finished but one little feature is missing.

I have to make the game end if player gets five or more marks in a row when the game board is larger than 4x4.

F.E. If the game board is 9x9 the game has to end when the player or the computer gets five marks in a row.

(Mark = "O" or "X").

The game now ends when someone gets marks in a row equals to the size of the board (if 9x9 you need 9 marks in a row to win).

I have to implement a feature in the playerHasWon and I've been having a lot of trouble finding out how. I think it's a simple to implement thing but I have not found out how to do it.

Hope my explanation is easy enough to understand. Here's the code:

package tictac; 

import java.util.Scanner;
import java.util.Random;

public class Tictac {

public static final int DRAW = 0; // game ends as a draw
public static final int COMPUTER = 1; // computer wins
public static final int PLAYER = 2; // player wins
public static final char PLAYER_MARK = 'X'; // The "X"
public static final char COMPUTER_MARK = 'O'; // The "O"

public static int size; // size of the board
public static String[][] board; // the board itself
public static int score = 0; // game win score
public static Scanner scan = new Scanner(System.in); // scanner

/**
 * Builds the board with the integer size and user input.
 * 
 * Displays game win message and switches play turns.
 * 
 * @param args the command line parameters. Not used.
 */
public static void main(String[] args) {

    while (true) {
        System.out.println("Select board size");
        System.out.print("[int]: ");
        try {
            size = Integer.parseInt(scan.nextLine());
        } catch (Exception e) {
            System.out.println("You can't do that.");
            continue; // after message, give player new try
        }

        break;
    }

    int[] move = {};
    board = new String[size][size];
    setupBoard();

    int i = 1;

    loop: // creates the loop

    while (true) {
        if (i % 2 == 1) {
            displayBoard();
            move = getMove();
        } else {
            computerTurn();
        }

        switch (isGameFinished(move)) {
            case PLAYER:
                System.err.println("YOU WIN!");
                displayBoard();
                break loop;
            case COMPUTER:
                System.err.println("COMPUTER WINS!");
                displayBoard();
                break loop;
            case DRAW:
                System.err.println("IT'S A DRAW");
                displayBoard();
                break loop;
        }

        i++;
    }
}

/**
 * Checks for game finish.
 *
 * @param args command line parameters. Not used.
 * 
 * @return DRAW the game ends as draw.
 * @return COMPUTER the game ends as computer win.
 * @return PLAYERE the game ends as player win.
 */
private static int isGameFinished(int[] move) {
    if (isDraw()) {
        return DRAW;
    } else if (playerHasWon(board, move,
            Character.toString(COMPUTER_MARK))) {
        return COMPUTER;
    } else if (playerHasWon(board, move,
            Character.toString(PLAYER_MARK))) {
        return PLAYER;
    }

    return -1; // can't be 0 || 1 || 2
}

/**
 * Checks for win for every direction on the board.
 *
 * @param board the game board.
 * @param move move on the board.
 * @param playerMark mark on the board "X" or "O".
 * @return the game is won.
 */
public static boolean playerHasWon(String[][] board, int[] move,
        String playerMark) { //playermark x || o

    // horizontal check
    for (int i = 0; i < size; i++) {
        if (board[i][0].equals(playerMark)) {
            int j;

            for (j = 1; j < size; j++) {
                if (!board[i][j].equals(playerMark)) {
                    break;
                }
            }

            if (j == size) {
                return true;
            }
        }
    }

    // vertical check
    for (int i = 0; i < size; i++) {
        if (board[0][i].equals(playerMark)) {
            int j;

            for (j = 1; j < size; j++) {
                if (!board[j][i].equals(playerMark)) {
                    break;
                }
            }

            if (j == size) {
                return true;
            }
        }
    }

    // diagonals check
    int i;

    for (i = 0; i < size; i++) {
        if (!board[i][i].equals(playerMark)) {
            break;
        }
    }

    if (i == size) {
        return true;
    }

    for (i = 0; i < size; i++) {
        if (!board[i][(size - 1) - i].equals(playerMark)) {
            break;
        }
    }

    return i == size;
}

/**
 * Checks for draws.
 *
 * @return if this game is a draw.
 */
public static boolean isDraw() {
    for (int i = 0; i < size; i++) {
        for (int j = 0; j < size; j++) {
            if (board[i][j] == " ") {
                return false;
            }
        }
    }

    return true;
}

/**
 * Displays the board.
 *
 *
 */
public static void displayBoard() {
    for (int i = 0; i < size; i++) {
        for (int j = 0; j < size; j++) {
            System.out.printf("[%s]", board[i][j]);
        }

        System.out.println();
    }
}

/**
 * Displays the board.
 *
 *
 */
public static void setupBoard() {
    for (int i = 0; i < size; i++) {
        for (int j = 0; j < size; j++) {
            board[i][j] = " ";
        }
    }
}

/**
 * Takes in user input and sends it to isValidPlay. 
 *
 * @return null.
 */
public static int[] getMove() {

    Scanner sc = new Scanner(System.in);
    System.out.println("Your turn:");

    while (true) {
        try {
            System.out.printf("ROW: [0-%d]: ", size - 1);
            int x = Integer.parseInt(sc.nextLine());
            System.out.printf("COL: [0-%d]: ", size - 1);
            int y = Integer.parseInt(sc.nextLine());

            if (isValidPlay(x, y)) {
                board[x][y] = "" + PLAYER_MARK;
                return new int[]{x, y};
            } else { // if input is unallowed
                System.out.println("You can't do that");
                continue; // after message, give player new try
            }
        } catch (Exception e) {
            System.out.println("You can't do that.");
        }

        return null;
    }
}

/*
 * Randomizes computer's turn, where it inputs the mark 'O'.
 *
 *
 */
public static void computerTurn() {
    Random rgen = new Random();  // Random number generator   

    while (true) {
        int x = (int) (Math.random() * size);
        int y = (int) (Math.random() * size);

        if (isValidPlay(x, y)) {
            board[x][y] = "" + COMPUTER_MARK;
            break;
        }
    }
}

/**
 * Checks if a move is possible.
 *
 * @param inX x-move is out of bounds.
 * @param inY y-move is out of bounds.
 * @return false
 */
public static boolean isValidPlay(int inX, int inY) {

    // Play is out of bounds and thus not valid.
    if ((inX >= size) || (inY >= size)) {
        return false;
    }

    // Checks if a play have already been made at the location,
    // and the location is thus invalid. 
    return (board[inX][inY] == " ");
    }
}

// End of file

回答1:

Took a quick look, detected the problem and came up with a quick fix:

public static boolean checkDiagonal(String markToLook) {
// how many marks are we looking for in row?
int sizeToWin = Math.min(size, 5);

// running down and right
// don't need to iterate rows that can't be the starting point
// of a winning diagonal formation, thus can exlude some with
// row < (size - (sizeToWin - 1))
for (int row = 0; row < (size - (sizeToWin - 1)); row++) {
    for (int col = 0; col < size; col++) {
        int countOfMarks = 0;

        // down and right
        for (int i = row; i < size; i++) {
            if (board[i][i] == null ? markToLook == null :
                    board[i][i].equals(markToLook)) {
                countOfMarks++;

                if (countOfMarks >= sizeToWin) {
                    return true;
                }
            }
        }

        countOfMarks = 0;

        // down and left
        for (int i = row; i < size; i++) {
            if (board[i][size - 1 - i] == null ? markToLook == null :
                    board[i][size - 1 - i].equals(markToLook)) {
                countOfMarks++;

                if (countOfMarks >= sizeToWin) {
                    return true;
                }
            }
        }
    }
}

return false;
}

And call it from your PlayerHasWon method instead of performign the checks there. Basically we iterate each possible starting square on the board for a diagonal winning formation, and run check down+left and down+right for each of the squares.

I am in awful hurry and did not test it much, but will return in couple of hours to improve this solution. Seems to work.

Edit: My previous solution I found lacking in further tests, I've updated the above code to function as desired.



回答2:

First, I think playerMark should be a char and not a String. That said, let's go for the answer. The "horizontal" case would be:

// This is the number of marks in a row required to win
// Adjust formula if necessary
final int required = size > 4 ? 5 : 3;

for (int i = 0; i < size; i++) {
        int currentScore = 0;

        for (j = 0; j < size; j++) {
            if (board[i][j].equals(playerMark)) {
                currentScore++;

                if (currentScore >= required)
                    return true;
            }
            else {
                currentScore = 0;
            }
        }
    }
}

The vertical case would be analogous. The diagonal one is a bit trickier as now it would require board[i][i+k] for the main diagonal and board[i][k-i] for the secondary; and it may not be obvious which values k and i must traverse. Here's my attempt (variable required as in horizontal case):

Note: everything from here down has been completely rewritten on 2015-12-16. The previous versions didn't work and the algorithm was not explained.

After two failed attempts I decided to do my homework and actually sort things out instead of doing everything in my head thinking I can keep track of all variables. The result is this picture:

Main diagonals are painted in blue, secondary diagonals in green. Each diagonal is identified by a value of k, with k=0 being always being the longest diagonal of each set. Values of k grow as diagonals move down, so diagonals above the longest one have negative k while diagonals below the longest one have positive k.

Things that hold for both diagonals:

  • Diagonal contains size-abs(k) elements. Diagonals in which size-abs(k) is less than required need not be searched. This means that, for board size size and required length required, we'll search values of k from required-size to size-required. Notice that these have the same absolute value, with the first being <=0 and the second >=0. These values are both zero only when required==size, i.e. when we need the full diagonal to claim a win, i.e. when we only need to search k=0.
  • For k<=0, possible values of i (row) go from 0 to size+k. Values greater than or equal to size+k cross the right edge of the board and are thus outside the board.
  • For k>=0, possible values of i (row) go from k to size. Values below k cross the left edge of the board and are thus outside the board.

Only for main (blue) diagonals:

  • Value of j (column) is k+i.

Only for secondary (green) diagonals:

  • Value of j (column) is size-1+k-i. In case this is not obvious, just pick the top right corner (k=0,i=0) and notice j=size-1. Then notice that adding 1 to k (keeping i constant) always moves j by 1 right (it would go out of the board if done from k=0,i=0, just think about the intersection of horizontal line i=0 with diagonal k=1), and adding 1 to i (keeping k constant) always moves j by 1 to the left.

The ressulting code would be: // Main diagonal

for (int k = required - size; k < size - required; k++)
{
    int currentScore = 0;

    startI = Math.max (0, k);
    endI = Math.min (size, size+k);

    for (int i = startI, i < endI; i++)
    {
        if (board[i][k+i].equals (playerMark))
        {
            currentScore++;

            if (currentScore >= required)
                return true;
        }
        else
            currentScore = 0;
    }
}

// Secondary diagonal
for (int k = required - size; k < size - required; k++)
{
    int currentScore = 0;

    startI = Math.max (0, k);
    endI = Math.min (size, size+k);

    for (int i = startI, i < endI; i++)
    {
        if (board[i][size-1+k-i].equals (playerMark))
        {
            currentScore++;

            if (currentScore >= required)
                return true;
        }
        else
            currentScore = 0;
    }
}

At this point, the code is nearly identical in both cases, changing only the j index in board[i][j]. In fact, both loops could be merged, taking care only of keeping two currentScore variables, one for the main (blue) diagonal and the other for the secondary (green) diagonal.