简体   繁体   English

Java中具有递归的堆栈溢出

[英]Stack Overflow with recursion in Java

I'm trying to write an AI that never loses at Tic Tac Toe, and I want to use the minimax algorithm to do so. 我正在尝试编写一种在井字游戏中永远不会输的AI,并且我想使用minimax算法来做到这一点。 However, when I try to run the program, a stack overflow appears and I can't seem to find what is the error. 但是,当我尝试运行该程序时,出现堆栈溢出,而且我似乎找不到任何错误。 Could you take a look and tell me what I'm doing wrong? 你能看看我告诉我我做错了吗? It doesn't go as deep in the recursion I believe, since it should only go through all the possible game outcomes, which go up to 8 moves (since the player is first to play, not the AI). 我认为递归没有那么深入,因为它只应该经历所有可能的游戏结果,最多可达8步(因为玩家是第一个玩游戏,而不是AI)。 It is probably me doing something wrong, but I can't find anything. 可能是我做错了,但找不到任何东西。

EDIT: Here's the full code, the mechanics function is the main part: EDIT2: Fixed the constructor 编辑:这是完整的代码,机械函数是主要部分:编辑2:修复了构造函数

package Packet;
import java.util.*;
import java.util.Scanner;

public class Logic {

public static class TicTacToe{

    private int[] currentBoard = new int[9];
    private int[] availableSpots = new int [9];

    private int emptySpace = 0;
    private int playerAI = 1;
    private int playerHuman = 2;


    void TicTacToe(){
        for (int i = 0; i < 9; i++){
            this.currentBoard[i] = this.emptySpace;
        }
        for (int i = 0; i < 9; i++){
            this.availableSpots[i] = i;
        }
    }

    private int movesNumber(){
        int counter = 0;
        for (int i = 0; i < 9; i++){
            if (this.currentBoard[i] == this.emptySpace){
                counter++;
            }
        }
        return counter;
    }

    private boolean win(int[] board,int player){
        if (
        (board[0] == player && board[1] == player && board[2] == player) ||
        (board[3] == player && board[4] == player && board[5] == player) ||
        (board[6] == player && board[7] == player && board[8] == player) ||
        (board[0] == player && board[3] == player && board[6] == player) ||
        (board[1] == player && board[4] == player && board[7] == player) ||
        (board[2] == player && board[5] == player && board[8] == player) ||
        (board[0] == player && board[4] == player && board[8] == player) ||
        (board[2] == player && board[4] == player && board[6] == player) ){
            return true;
        }
        else{
            return false;
        }
    }

    private int mechanics(int[] newBoard, int player){
        if (win(newBoard,this.playerHuman)){
            return -10;
        }
        else if (win(newBoard, this.playerAI)){
            return +10;
        }
        else if (this.movesNumber() == 0){
            return 0;
        }

        ArrayList<Integer> moves = new ArrayList<Integer>();
        ArrayList<Integer> scores = new ArrayList<Integer>();


        for (int i = 0; i < this.movesNumber(); i++){
            int[] possibleBoard = new int[9];
            possibleBoard = newBoard;           

            int availableSpotNumber = i;

            int j = i;

            while (this.availableSpots[j] == 9){
                availableSpotNumber++;
                j++;
            }

            possibleBoard[availableSpotNumber] = player;

            if (player == this.playerAI){
                scores.add(this.mechanics(possibleBoard, this.playerHuman));
            }
            else{
                scores.add(this.mechanics(possibleBoard, this.playerAI));
            }
            moves.add(availableSpotNumber);

            possibleBoard[availableSpotNumber] = this.emptySpace;
        }

        int bestMove = 0;

        if (player == this.playerAI){
            int bestScore = -10000;
            for (int i = 0; i < moves.size(); i++){
                if (scores.get(i) > bestScore){
                    bestScore = scores.get(i);
                    bestMove = i;
                }
            }
        }
        else {
            int bestScore = 10000;
            for (int i = 0; i < moves.size(); i++){
                if (scores.get(i) < bestScore){
                    bestScore = scores.get(i);
                    bestMove = i;
                }
            }
        }
        return moves.get(bestMove);
    }

    public void printTable(){
        System.out.println(this.currentBoard[0] + " | " + this.currentBoard[1] + " | " + this.currentBoard[2]);
        System.out.println("-   -   -");
        System.out.println(this.currentBoard[3] + " | " + this.currentBoard[4] + " | " + this.currentBoard[5]);
        System.out.println("-   -   -");
        System.out.println(this.currentBoard[6] + " | " + this.currentBoard[7] + " | " + this.currentBoard[8]);
        System.out.println();
    }

    private void fillTable(int position,int player){
        this.currentBoard[position] = player;
        this.availableSpots[position] = 9;
    }

    public void startGame(){
        while(true){
            this.printTable();
            Scanner ulaz = new Scanner(System.in);
            fillTable(ulaz.nextInt(), this.playerHuman);
            this.printTable();
            fillTable(this.mechanics(this.currentBoard, this.playerAI), this.playerAI);
            ulaz.close();
        }
    }

    public void resetGame(){
        for (int i = 0; i < 9; i++){
            this.currentBoard[i] = this.emptySpace;
        }
        for (int i = 0; i < 9; i++){
            this.availableSpots[i] = i;
        }
    }
}

public static void main(String[] args){
    TicTacToe game = new TicTacToe();
    game.startGame();
}
}

Also, here's the exact errors I get: 另外,这是我得到的确切错误:

Exception in thread "main" java.lang.StackOverflowError
    at Packet.Logic$TicTacToe.mechanics(Logic.java:54)
    at Packet.Logic$TicTacToe.mechanics(Logic.java:84)
    at Packet.Logic$TicTacToe.mechanics(Logic.java:87)
    at Packet.Logic$TicTacToe.mechanics(Logic.java:84)
    at Packet.Logic$TicTacToe.mechanics(Logic.java:87)
    at Packet.Logic$TicTacToe.mechanics(Logic.java:84)
    at Packet.Logic$TicTacToe.mechanics(Logic.java:87)

After this part, these parts appear a bunch of times (at least 50) 在这部分之后,这些部分会出现很多次(至少50次)

at Packet.Logic$TicTacToe.mechanics(Logic.java:84)
at Packet.Logic$TicTacToe.mechanics(Logic.java:87) 

Line 54: 第54行:

if (win(newBoard,this.playerHuman)){

Line 84: 第84行:

scores.add(this.mechanics(possibleBoard, this.playerHuman));

Line 87: 第87行:

scores.add(this.mechanics(possibleBoard, this.playerAI));

There's a couple of problems, but here's how you can debug this: 有几个问题,但是您可以通过以下方法调试它:

add a StringBuilder debug = new StringBuilder(); 添加一个StringBuilder debug = new StringBuilder(); field to your class, then change the main loop like this: 字段添加到您的班级,然后像这样更改主循环:

int debugLen = debug.length();
debug.append("\nSetting ").append(availableSpotNumber).append(" to ").append(player);
possibleBoard[availableSpotNumber] = player;

try {
    if (player == this.playerAI) {
        scores.add(this.mechanics(possibleBoard, this.playerHuman));
    } else {
        scores.add(this.mechanics(possibleBoard, this.playerAI));
    }
    moves.add(availableSpotNumber);
} catch (StackOverflowError error) {
     throw new StackOverflowError(debug.toString());
}

debug.setLength(debugLen);
possibleBoard[availableSpotNumber] = this.emptySpace;

Then you will see what is happening, which will give you a clue what to fix next. 然后,您将看到正在发生的事情,这将为您提供下一步解决的线索。 For example, the current version is doing this, for initial human move 1: 例如,对于最初的人类动作,当前版本正在执行此操作:

Setting 0 to 1
Setting 0 to 2
Setting 0 to 1
Setting 0 to 2
etc..

But, if you're too lazy, you can find a fixed version here . 但是,如果您太懒了,可以在此处找到固定版本。

This might or might not be a code issue, as Java is not a fully functional language with it comes to recursion, you might want to see this answer: Does Java 8 have tail call optimization? 这可能是代码问题,也可能不是代码问题,因为Java不是涉及到递归的功能齐全的语言,您可能希望看到以下答案: Java 8是否具有尾部调用优化功能?

Basically, a language that allows for unlimited-depth recursion has to have tail recursion optimization. 基本上,允许无限深度递归的语言必须进行尾递归优化。 With tail call optimization, if the return value is the result of exactly the same function with different parameters, the stack will get replaced instead of the new call being added to the stack. 通过尾部调用优化,如果返回值是具有不同参数的完全相同函数的结果,则将替换堆栈,而不是将新调用添加到堆栈中。

If a language does not have tail call optimization, then you're limited by stack size in how deep your recursion goes, even if the recursive calls have correct termination conditions (note: I haven't analyzed the code in depth, so obviously there might be a problem in the recursive logic itself). 如果语言没有尾部调用优化,那么即使递归调用具有正确的终止条件,您的递归深度也会受到堆栈大小的限制(请注意:我没有深入分析代码,因此很明显可能是递归逻辑本身的问题)。 If you want to tweak the stack size, use the -Xss Java runtime parameter. 如果要调整堆栈大小,请使用-Xss Java运行时参数。 Generally, increasing the stack size is a good heuristic (although not a fool-proof method) of checking whether the fault is with the language or with your algorithm. 通常,增加堆栈大小是一种很好的试探法(尽管不是万无一失的方法),它可以检查故障是由语言引起的还是由算法引起的。

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

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