Java中对象的实例,奇怪的行为

时间:2014-08-18 09:04:37

标签: java instance-variables minimax

我用Java编写了一个简单的Tic Tac Toe控制台应用程序,使用了一个位板方法(只是为了好玩)。它适用于两个人类玩家。我的目标是找出minimax算法并实现计算机播放器。我之前做过这个,对于#n;" Nim"的(非常天真的)游戏,以及相同的一般面向对象方法。我想使用相同的结构。但在这种情况下,当计算机进行移动时,它会在搜索下一步移动时破坏整个板变量。它不应该这样做,因为makeMove方法创建了一个全新的Board对象。我的问题是,为什么会发生这种奇怪的事情?以下是直接来自NetBeans的代码,松散评论:

提前感谢任何有耐心看的人。我想提一下,我查看了Cloneable接口和clone()方法,但无济于事。然后我认为这不应该是原因,因为makeMove方法的工作方式。那么为什么电脑玩家会破坏电路板?

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;
    }
}

2 个答案:

答案 0 :(得分:2)

注意,我没有阅读所有代码,因为没有最小的例子,但我在这里看到了一个问题:

public Board makeMove(int move) {
    int[] newBoard = board;
    //               ^^^^^
    newBoard[currentPlayer] |= map[move];
    return new Board(newBoard, opponent);
}

您实际上并未在此处制作新主板,new Board(...)对旧主板int[]引用

通过调用语句int[] newBoard = board;,您将board的引用分配给新的整数数组,而不是实际复制,换句话说:两个板对象现在都指向同一个{{ 1}}

要制作实际副本,您需要使用System.arraycopy();

克隆数组

所以新方法看起来像这样:

int[]

请注意,我没有读完您的所有代码,但您在该方法中所做的假设不正确

答案 1 :(得分:0)

尝试将此添加到您的makeMove()方法:

int[] newBoard = Arrays.copyOf(board, board.length);

在您的代码中,您只需将newBoard引用指向board引用的现有整数数组。

上面的行创建了新的整数数组,并复制了board引用的数组内容。

HTH