javascript - 浏览器在扫雷克隆中报告 "too much recursion"

标签 javascript php html recursion

我正在制作一款扫雷游戏。浏览器显示“递归过多”。您能看一下代码并纠正我犯错误的部分吗?

<?php

/*
    Basic PHP code for generating a table 8x8 with every button a unique id 
*/

    $tableDesign = "<table>";
    $id = 1;

    for($i=0; $i<8; $i++){

        $tableDesign .= "<tr>";
        for($j=0; $j<8; $j++){

            $tableDesign .= "<td><button id='num_$id' onclick='get_button(this.id)'></button></td>";
            $id++;
        }
        $tableDesign .= "</tr>";
    }
    $tableDesign .= "</table>";
?>

<!DOCTYPE html>
<html>
<head>
    <title>Mine Sweeper Ver. 0.1</title>
    <style type="text/css">
        table {
            border-collapse: collapse;
        }
        td {
            width: 60px;
            height: 60px;
            border: 1px solid #ddd;
        }
        button {
            width: 60px;
            height: 60px;
            cursor: pointer;
        }
        img {
            width: 50px;
            height: 50px;
        }
        .table{
            float: left;
            width: 550px;
        }
        .switch_command {
            padding-top: 40px;
            font-size: 22px;
            font-family: sans-serif;
            float: left;
        }
    </style>
</head>
<body>
    <div class="table">
        <?php echo $tableDesign; ?>
    </div>
    <div class="switch_command">
        Switch to protect from Expload: <input id="check" type="checkbox">
    </div>
    <script type="text/javascript">

        var randomMinesLocation      = new Array(); // this array will contain the id number part of the mines

        var mineProtecition          = new Array(); // this array will contain the mine with flags to not be abled to open when the player clicks on it
        var emptyCells               = new Array(); // this array will contain the empty cells opened to prevent the recursion going through the same cell again 

        // this while loop is responsible for generating 10 different mines in random from 1 to 64
        while (randomMinesLocation.length < 10) {

            index = randomMinesLocation.length;
            match = false;
            position = Math.floor((Math.random() * 64) + 1);

            for (i = 0; i < randomMinesLocation.length; i++) {

                if(position == randomMinesLocation[i])
                    match = true;
            }

            if(!match) randomMinesLocation[index] = position;
        }

        // I made this check function to prevent to equal id to be contained to the openedEmptyCells array which will be used to open the cells in cooperation with another function
        function checkForEqualVal(valToTest, openedEmptyCells) {
            match = false;
            if (openedEmptyCells.length == 0) {
                openedEmptyCells.push(valToTest);
            } else {
                for (i = 0; i < openedEmptyCells.length; i++){
                    if(openedEmptyCells[i][1] == valToTest[1]) {
                        match = true;
                        break;
                    }
                }
                if (!match) {
                    openedEmptyCells.push(valToTest);
                }
            }
            return openedEmptyCells;
        }

        function logic_game(idNumericPart, randomMinesLocation, openedEmptyCells = []) {

            match = false;
            numberOfMinesNear =  0; 
            canditateCells = [];

                for (j = -1; j <= 1; j++) {
                    if (idNumericPart <= 8 && j == 1 || idNumericPart > 56 && j == -1 ) continue;
                    for(k= -1; k <= 1; k++){
                        if(((idNumericPart - 1) % 8 == 0 && k == -1) || (idNumericPart % 8 == 0 && k == 1))
                            continue;

                        idNumericPartOfNear = idNumericPart - (j * 8) + k;

                        if(j != 0 && k == 0 || j == 0 && k != 0 || j != 0 && k != 0)

                                canditateCells.push(idNumericPartOfNear);

                        for(i=0; i<10; i++){                
                            if(idNumericPart == randomMinesLocation[i]){
                                match = true; 
                                break;
                            }
                            else if(idNumericPartOfNear == randomMinesLocation[i])
                                numberOfMinesNear++;
                        }
                    }
                }

                if(match)
                    openedEmptyCells = checkForEqualVal([true, idNumericPart], openedEmptyCells);
                else if(numberOfMinesNear > 0){
                    openedEmptyCells = checkForEqualVal([numberOfMinesNear, idNumericPart], openedEmptyCells);
                }
                else{

                    openedEmptyCells = checkForEqualVal([false, idNumericPart], openedEmptyCells);
                    for(i=0; i<canditateCells.length; i++){
                        matchedBtw = false;
                        for(j=0; j<emptyCells.length; j++){
                            if(canditateCells[i] == emptyCells[j])
                                matchedBtw = true;
                                break;
                        }
                        if(!matchedBtw)
                            openedEmptyCells = logic_game(canditateCells[i], randomMinesLocation, openedEmptyCells);
                    }
                }
            return openedEmptyCells;    
        }

        function printToScreen(value, idNumericPart){

                if(typeof(value) === 'boolean' && value){
                    document.getElementById("num_" + idNumericPart).parentElement.innerHTML = '<img src="mine.png">';   
                }
                else if(typeof(value) === 'number'){
                    document.getElementById("num_" + idNumericPart).parentElement.style.backgroundColor = '#eee';
                    document.getElementById("num_" + idNumericPart).parentElement.innerHTML = value;
                }
                else{
                    document.getElementById("num_" + idNumericPart).parentElement.style.backgroundColor = '#eee';
                    document.getElementById("num_" + idNumericPart).parentElement.innerHTML = '';       
                }
        }

        function get_button(id){

            idNumericPart = id.substring(4);
            checkValue = document.getElementById("check").checked;
            matchCheckForMineProtect = false;

            if(checkValue){

                for(i=0; i<mineProtecition.length; i++){

                    if(mineProtecition[i] == idNumericPart){

                        matchCheckForMineProtect = true;
                        break;
                    }
                }

                if(matchCheckForMineProtect){
                    mineProtecition.splice(i, 1);
                    valueToBeSubstitude = '<button id="' + id + '" onclick="get_button(this.id)"></button>';
                }
                else{
                    mineProtecition.push(idNumericPart);
                    valueToBeSubstitude = '<button id="' + id + '" style="background: url(' + 'flag.png' + ') no-repeat; background-size: 40px 40px;" onclick="get_button(this.id)"></button>';
                }

                document.getElementById(id).parentElement.innerHTML = valueToBeSubstitude;
            }
            else {
                matchProtected = false;
                for(i=0; i<mineProtecition.length; i++){

                    if(mineProtecition[i] == idNumericPart){
                        matchProtected = true;
                        break;
                    }   
                }

                if(!matchProtected){

                    result = logic_game(idNumericPart, randomMinesLocation);
                    for(i=0; i<result.length; i++){
                        printToScreen(result[i][0], result[i][1]);
                    }
                }

            }

        }
    </script>
</body>
</html>

最佳答案

用java脚本替换代码中的php(请参阅下面修改后的代码)并将console.log添加到允许定位无限递归循环位置的方法调用中,在控制台中给出输出:

> logic_game(15)
minesweeper.html:116 randomMinesLocation
minesweeper.html:117 (10) [10, 56, 36, 33, 3, 14, 24, 54, 2, 13]
minesweeper.html:118 openedEmptyCells
minesweeper.html:119 [Array(2)]
minesweeper.html:115 logic_game(24)
minesweeper.html:116 randomMinesLocation
minesweeper.html:117 (10) [10, 56, 36, 33, 3, 14, 24, 54, 2, 13]
minesweeper.html:118 openedEmptyCells
minesweeper.html:119 (2) [Array(2), Array(2)]
minesweeper.html:115 logic_game(15)
minesweeper.html:116 randomMinesLocation
minesweeper.html:117 (10) [10, 56, 36, 33, 3, 14, 24, 54, 2, 13]
minesweeper.html:118 openedEmptyCells
minesweeper.html:119 (3) [Array(2), Array(2), Array(2)]
minesweeper.html:115 logic_game(24)
minesweeper.html:116 randomMinesLocation
minesweeper.html:117 (10) [10, 56, 36, 33, 3, 14, 24, 54, 2, 13]
minesweeper.html:118 openedEmptyCells
minesweeper.html:119 (3) [Array(2), Array(2), Array(2)]

我可以看到,对id 15的logic_game调用,用id 24调用了llogic_game,而llogic_game又对id 15调用了logic_game,它继续作为无限递归循环。

要回答这个问题本身,这不是一个正确的修复,修复是针对 id 堆栈:

var idStack =[];

对于对logic_game函数的每次递归调用,检查堆栈以确保尚未为该id调用该函数,以及它是否已从函数返回以结束无限递归。

function logic_game(idNumericPart, randomMinesLocation, openedEmptyCells = []) { // prevent infinite recursion loop; if (idStack.indexOf(idNumericPart)>-1) { console.log('infinite loop detected'); return; }

如果 id 尚未被调用,则将其添加到堆栈并继续该函数。

// push the id to the idStack so we can tell if this function enters an infinite recursion loop;
idStack.push(idNumericPart);

最后,在每次单击按钮时清除 idStack,以便仅跟踪每个递归循环的 id。

    function get_button(id){
        // clear the button ID stack so we can track recursive calls per button click to assure that will only be one call per id per button game;
        idStack = [];

我们现在已经阻止了问题中提出的问题,解决了过多递归的问题。但是,您会发现代码中还有其他逻辑错误需要修复:

minesweeper.html:180 Uncaught TypeError: Cannot read property 'parentElement' of null at printToScreen (minesweeper.html:180) at get_button (minesweeper.html:233) at HTMLButtonElement.onclick (minesweeper.html:1)

这是修改后的代码,将 php 替换为 javascript,现在可以在浏览器中本地运行而不会崩溃:

<!DOCTYPE html>
<html>
<head>
    <title>Mine Sweeper Ver. 0.1</title>
    <style type="text/css">
        table {
            border-collapse: collapse;
        }
        td {
            width: 60px;
            height: 60px;
            border: 1px solid #ddd;
        }
        button {
            width: 60px;
            height: 60px;
            cursor: pointer;
        }
        img {
            width: 50px;
            height: 50px;
        }
        .table{
            float: left;
            width: 550px;
        }
        .switch_command {
            padding-top: 40px;
            font-size: 22px;
            font-family: sans-serif;
            float: left;
        }
    </style>
    <script>

    function getTableDesign()
    {
        var tableDesign = "<table>";
        var id = 1;

        for(var i=0; i<8; i++){

            tableDesign += "<tr>";
            for(var j=0; j<8; j++){

               tableDesign += "<td><button id='num_" + id + "' onclick='get_button(this.id)'></button></td>";
               id++;
            }
            tableDesign += "</tr>";
        }
        tableDesign += "</table>";
        return tableDesign;
    }
    </script>
    <script
              src="https://code.jquery.com/jquery-2.2.4.min.js"
              integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44="
              crossorigin="anonymous"></script>
    <script>
        $(document).ready(function() { 
            $('#tblTableDesign').html(getTableDesign());
        });
    </script>
</head>
<body>
    <div class="table" id="tblTableDesign">

    </div>
    <div class="switch_command">
        Switch to protect from Expload: <input id="check" type="checkbox">
    </div>
    <script type="text/javascript">

        var randomMinesLocation      = new Array(); // this array will contain the id number part of the mines

        var mineProtecition          = new Array(); // this array will contain the mine with flags to not be abled to open when the player clicks on it
        var emptyCells               = new Array(); // this array will contain the empty cells opened to prevent the recursion going through the same cell again 

        var idStack =[];
        // this while loop is responsible for generating 10 different mines in random from 1 to 64
        while (randomMinesLocation.length < 10) {

            index = randomMinesLocation.length;
            match = false;
            position = Math.floor((Math.random() * 64) + 1);

            for (i = 0; i < randomMinesLocation.length; i++) {

                if(position == randomMinesLocation[i])
                    match = true;
            }

            if(!match) randomMinesLocation[index] = position;
        }

        // I made this check function to prevent to equal id to be contained to the openedEmptyCells array which will be used to open the cells in cooperation with another function
        function checkForEqualVal(valToTest, openedEmptyCells) {
            match = false;
            if (openedEmptyCells.length == 0) {
                openedEmptyCells.push(valToTest);
            } else {
                for (i = 0; i < openedEmptyCells.length; i++){
                    if(openedEmptyCells[i][1] == valToTest[1]) {
                        match = true;
                        break;
                    }
                }
                if (!match) {
                    openedEmptyCells.push(valToTest);
                }
            }
            return openedEmptyCells;
        }

        function logic_game(idNumericPart, randomMinesLocation, openedEmptyCells = []) {
            // prevent infinite recursion loop;
            if (idStack.indexOf(idNumericPart)>-1) {
                console.log('infinite loop detected');
                return;
            }
            // push the id to the idStack so we can tell if this function enters an infinite recursion loop;
            idStack.push(idNumericPart); 
            console.log('logic_game('+ idNumericPart +')');
            console.log('randomMinesLocation');
            console.log(randomMinesLocation);
            console.log('openedEmptyCells');
            console.log(openedEmptyCells);
            match = false;
            numberOfMinesNear =  0; 
            canditateCells = [];

                for (j = -1; j <= 1; j++) {
                    if (idNumericPart <= 8 && j == 1 || idNumericPart > 56 && j == -1 ) continue;
                    for(k= -1; k <= 1; k++){
                        if(((idNumericPart - 1) % 8 == 0 && k == -1) || (idNumericPart % 8 == 0 && k == 1))
                            continue;

                        idNumericPartOfNear = idNumericPart - (j * 8) + k;

                        if(j != 0 && k == 0 || j == 0 && k != 0 || j != 0 && k != 0)

                                canditateCells.push(idNumericPartOfNear);

                        for(i=0; i<10; i++){                
                            if(idNumericPart == randomMinesLocation[i]){
                                match = true; 
                                break;
                            }
                            else if(idNumericPartOfNear == randomMinesLocation[i])
                                numberOfMinesNear++;
                        }
                    }
                }

                if(match)
                    openedEmptyCells = checkForEqualVal([true, idNumericPart], openedEmptyCells);
                else if(numberOfMinesNear > 0){
                    openedEmptyCells = checkForEqualVal([numberOfMinesNear, idNumericPart], openedEmptyCells);
                }
                else{

                    openedEmptyCells = checkForEqualVal([false, idNumericPart], openedEmptyCells);
                    for(i=0; i<canditateCells.length; i++){
                        matchedBtw = false;
                        for(j=0; j<emptyCells.length; j++){
                            if(canditateCells[i] == emptyCells[j])
                                matchedBtw = true;
                                break;
                        }
                        if(!matchedBtw)
                            openedEmptyCells = logic_game(canditateCells[i], randomMinesLocation, openedEmptyCells);
                    }
                }
            return openedEmptyCells;    
        }

        function printToScreen(value, idNumericPart){

                if(typeof(value) === 'boolean' && value){
                    document.getElementById("num_" + idNumericPart).parentElement.innerHTML = '<b>Boom</b>';   
                }
                else if(typeof(value) === 'number'){
                    document.getElementById("num_" + idNumericPart).parentElement.style.backgroundColor = '#eee';
                    document.getElementById("num_" + idNumericPart).parentElement.innerHTML = value;
                }
                else{
                    document.getElementById("num_" + idNumericPart).parentElement.style.backgroundColor = '#eee';
                    document.getElementById("num_" + idNumericPart).parentElement.innerHTML = '';       
                }
        }

        function get_button(id){
            // clear the button ID stack so we can track recursive calls per button click to assure that will only be one call per id per button game;
            idStack = [];
            console.log('get_button('+ id + ')');
            idNumericPart = id.substring(4);
            checkValue = document.getElementById("check").checked;
            matchCheckForMineProtect = false;

            if(checkValue){

                for(i=0; i<mineProtecition.length; i++){

                    if(mineProtecition[i] == idNumericPart){

                        matchCheckForMineProtect = true;
                        break;
                    }
                }

                if(matchCheckForMineProtect){
                    mineProtecition.splice(i, 1);
                    valueToBeSubstitude = '<button id="' + id + '" onclick="get_button(this.id)"></button>';
                }
                else{
                    mineProtecition.push(idNumericPart);
                    valueToBeSubstitude = '<button id="' + id + '" style="background: url(' + 'flag.png' + ') no-repeat; background-size: 40px 40px;" onclick="get_button(this.id)"></button>';
                }

                document.getElementById(id).parentElement.innerHTML = valueToBeSubstitude;
            }
            else {
                matchProtected = false;
                for(i=0; i<mineProtecition.length; i++){

                    if(mineProtecition[i] == idNumericPart){
                        matchProtected = true;
                        break;
                    }   
                }

                if(!matchProtected){

                    result = logic_game(idNumericPart, randomMinesLocation);
                    for(i=0; i<result.length; i++){
                        printToScreen(result[i][0], result[i][1]);
                    }
                }

            }

        }
    </script>
</body>
</html>

关于javascript - 浏览器在扫雷克隆中报告 "too much recursion",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44738042/

相关文章:

javascript - 取消链接中 onClick 触发的 javascript 函数

javascript - Angular JS 的 IOS pin 用户界面

javascript - 浏览器允许卸载多少事件?

php - 如何在 Php 中创建帐户电子邮件确认?

javascript - 如何使用 NodeList 修改 CSS?

html - 为什么 margin :0 auto is not aligning to center

javascript - 正则表达式窗口路径验证器

php - 如何从 php 获取 wercker 环境变量?

php - MySQL存储过程导致 `Commands out of sync`

html - firefox textarea 占位符填充