简体   繁体   English

Java中的对象实例,行为异常

[英]Instances of objects in Java, strange behavior

I wrote a simple Tic Tac Toe console app in Java, using a bitboard approach (just for fun). 我使用位板方法(只是为了好玩)用Java语言编写了一个简单的Tic Tac Toe控制台应用程序。 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. 之前,对于“ Nim”(非常幼稚)的游戏,我曾经这样做过,并且相同的通用面向对象方法也起作用。 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. 但是在这种情况下,当计算机开始移动时,它会在搜索其下一步移动时破坏整个board变量。 It shouldn't do so, because the makeMove method creates a brand new Board object. 不应这样做,因为makeMove方法会创建一个全新的Board对象。 My question is, why does this strange thing happen? 我的问题是,为什么会发生这种奇怪的事情? Here is the code, loosely commented, straight from NetBeans: 下面是直接从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. 我想提到的是,我研究了Cloneable接口和clone()方法,但无济于事。 Then I figured that that shouldn't be the cause, because the way the makeMove method works. 然后我认为这不应该是原因,因为makeMove方法的工作方式。 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[] . 实际上,您不是在这里制作新板, new Board(...) 引用了旧板的int[]

by calling the statement int[] newBoard = board; 通过调用语句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[] 您正在将board的引用分配给新的整数数组,而不是实际进行复制,换句话说:现在两个board对象都指向同一个int[]

To make an actual copy, you will need to clone the array by using System.arraycopy(); 要制作实际副本,您将需要使用System.arraycopy();克隆该数组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: 尝试将其添加到您的makeMove()方法中:

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

In your code you just point newBoard reference to an existing integer array also referenced by board . 在代码中,您只需将newBoard引用指向还由board引用的现有整数数组即可。

The line above creates new integer array and copies the content of the array referneced by board across. 上面的行创建了一个新的整数数组,并复制了board的数组内容。

HTH HTH

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM