Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/jsp/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java中的对象实例,奇怪的行为_Java_Instance Variables_Minimax - Fatal编程技术网

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

Java中的对象实例,奇怪的行为,java,instance-variables,minimax,Java,Instance Variables,Minimax,我用Java编写了一个简单的Tic-Tac-Toe控制台应用程序,使用了位板方法(只是为了好玩)。它对两个人类玩家都很有效。我的目标是找出minimax算法并实现一个计算机播放器。我以前做过这件事,是为了“Nim”这个(非常幼稚的)游戏,同样的通用面向对象方法也奏效了。我想用同样的结构。但在这种情况下,当计算机开始移动时,它在搜索下一个移动时会破坏整个板变量。它不应该这样做,因为makeMove方法创建了一个全新的Board对象。我的问题是,为什么会发生这种奇怪的事情?下面是直接来自NetBea

我用Java编写了一个简单的Tic-Tac-Toe控制台应用程序,使用了位板方法(只是为了好玩)。它对两个人类玩家都很有效。我的目标是找出minimax算法并实现一个计算机播放器。我以前做过这件事,是为了“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;
    }
}
package-tictactoe;
导入java.util.*;
公共类Tictatcoe{
公共静态void main(字符串[]args){
游戏=新游戏();
game.start();
}
}
班级游戏{
ArrayList players=new ArrayList();//播放器的ArrayList
公共游戏(){//确定玩家是人类还是CPU
扫描仪输入=新扫描仪(System.in);
字符串回答;
System.out.printf(“您希望播放器1成为CPU吗?[是/否]”);
answer=input.nextLine();
if(answer.toLowerCase().startsWith(“y”)players.add(newcomputerplayer(0,3));
其他玩家。添加(新玩家());
System.out.printf(“您希望播放器2成为CPU吗?[是/否]”);
answer=input.nextLine();
if(answer.toLowerCase().startsWith(“y”)players.add(newcomputerplayer(1,3));
其他玩家。添加(新玩家());
}
公开作废开始(){
扫描仪输入=新扫描仪(System.in);
while(true){
清除屏幕();
线路板=新线路板();
而(!board.isGameOver()){
board=board.makeMove(players.get(board.getCurrentPlayer()).getMove(board));
}
board.display();
int winner=board.checkWinner();
如果(获胜者>=0){
players.get(winner.addWin();
System.out.printf(“玩家%d赢了。他赢了%d对%d。\n匹配?[是/否]”,赢家+1,玩家.get(赢家).getWins(),玩家.get(赢家==0?1:0).getWins());
}
否则{
System.out.printf(“比赛打成平局。\n比赛?[是/否]”);
}
字符串answer=input.nextLine();
if(answer.toLowerCase().startsWith(“n”)中断;
否则{
玩家温度=玩家。移除(0);
玩家。添加(临时);
对于(inti=0;i<2;i++){//只是为了帮助计算机播放器跟踪自己的ID
players.get(i).flipID();
}
}
}
System.out.printf(“游戏中止,谢谢您玩”);
}
公共静态无效清除屏幕(){
对于(inti=0;i<30;i++)System.out.printf(“\n”);
}   
}
类板实现了可克隆性{
private int[]board;//用于存储播放器X和的二维数组
//玩家O的动作是分开的。或者把它们放在一起
//所有的动作都完成了。
private final int[]map={0,1,2,4,8,16,32,64,128,256};//一个简单的
//将数字1->9(如numpad上的数字)映射到
//电路板的位。你只需要按位操作
//对map[n]-n为数字。
//类似numpad的映射如下所示:
//7 8 9//在存储器中,位存储如下:
// 4 5 6 //  987654321
// 1 2 3 //
私有final int[]win={7,56,73,84,146,273,292,448};//映射
//所有可能获胜的组合转换为十进制
//数字。按顺序列出: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;//轮到的玩家。0代表X,1代表O。
私有int对手;//对手。将始终是相反的。
//普通构造函数。将参数的当前状态作为参数
//棋盘,由二维整数表示,以及
//将其当前为,由0或1表示
公共棋盘(int[]棋盘,int玩家){
董事会=董事会;
当前玩家=玩家;
对手=玩家==0?1:0;
}
//如果未传递任何参数,则使用默认值构造bord,
//例如,一个空棋盘,供两名玩家和X的回合使用。
公共委员会(){
这(新的int[2],0);
}
//通常的嫌疑犯。属性的附件。
public int[]getBoard(){
返回板;
}
public int getCurrentPlayer(){
返回当前播放器;
}
公共整数{
回击对手;
}
//首先检查两名玩家的赢地图,看看他们中是否有人赢了
//一行有3个符号。如果没有,请检查电路板是否已满。
公共布尔值isGameOver(){
对于(int-player=0;player<2;player++){
for(int n:win){
如果((棋盘[player]&n)==n)返回true;
}
}
返回(单板[0]|单板[1])==511;
}
//如果没有人赢了,则返回-1;如果没有人赢了,则返回0或1
//球员们做到了。
public int checkWinner(){
对于(int i=0;i<2;i++){
for(int m:win){
如果((板[i]&m)==m)返回i;
}
}
返回-1;
}
//在棋盘上找到可能的移动,以数组形式返回
public int[]getMoves(){
public Board makeMove(int move) {
    int[] newBoard = board;
    //               ^^^^^
    newBoard[currentPlayer] |= map[move];
    return new Board(newBoard, opponent);
}
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);
}
int[] newBoard = Arrays.copyOf(board, board.length);