【问题标题】:Tic-Tac-Toe autoplay does not work as expected井字游戏自动播放无法按预期工作
【发布时间】:2019-01-25 14:41:17
【问题描述】:

我正在做一个井字游戏,功能非常适合用户转,但是到电脑转玩时,功能崩溃并给出意外的结果,比如玩了两次,跳过转等。

谁能告诉我我的代码有什么问题,并帮助我解决这个问题?

const start = document.getElementById('start');
const table = document.getElementById('table');
places = ["one", "two", "three", "four", "five", "six", "seven", 'eight', "nine"];
let move = 0;
start.addEventListener('click', function(){
    user();
});


function user(){
    table.addEventListener('click', function(event){
        let pos = event.target;
        let Id = event.target.id;
        if (/[1-9]/.test(pos.innerHTML)){
            pos.innerHTML = "X";
            move += 1;
            places.splice(places.indexOf(Id), 1 );
        }
        if (move > 8){
            gameOver();
        } 
        if (move <= 8){
            computer();
        }
    });
}

function gameOver(){
    console.log("Game Over");
}

function computer(){
    let index = places[Math.floor(Math.random() * places.length)];
    let pos = document.getElementById(index);
    if (/[1-9]/.test(pos.innerHTML)){
        pos.innerHTML = "O";
        move += 1;
        places.splice(places.indexOf(pos), 1 );
    }
    if (move > 8){
        gameOver();
    } 
    if (move <= 8) {
        user();
    }
}
<div class="col text-center">
    <table class="table text-center">
        <tbody id="table">
            <tr>
                <td id="one">1</td>
                <td id="two">2</td>
                <td id="three">3</td>
            </tr>
            <tr>
                <td id="four">4</td>
                <td id="five">5</td>
                <td id="six">6</td>
            </tr>
            <tr>
                <td id="seven">7</td>
                <td id="eight">8</td>
                <td id="nine">9</td>
            </tr>
        </tbody>
    </table>
    <br />
    <button class="btn btn-primary" type="button" id="start">Start Game</button>
</div>

【问题讨论】:

  • 每次轮到计算机时,您都会将click 事件添加到您的表中。你应该重新考虑你的方法。最简单的做法是在计算机轮到结束时不要调用user,并将事件声明移到user 函数之外。
  • @FedericoklezCulloca 我不明白你的意思,你能解释更多吗,如果你为我编辑代码,我将非常感激。

标签: javascript html css tic-tac-toe


【解决方案1】:

让我试着改写我原来的答案: 当轮到电脑时,你正在执行这段代码

function computer(){
    let index = places[Math.floor(Math.random() * places.length)];
    let pos = document.getElementById(index);
    if (/[1-9]/.test(pos.innerHTML)){
        pos.innerHTML = "O";
        move += 1;
        places.splice(places.indexOf(pos), 1 );
    }
    if (move > 8){
        gameOver();
    } 
    if (move <= 8) {
        user();
    }

}

如果if (/[1-9]/.test(pos.innerHTML)){ 返回 false 会发生什么? (即该字段已被占用),您的代码只是继续执行,就好像计算机已经做出了动作,将轮到用户交给了。

所以我的建议是,您需要确保计算机实际进行移动,一种方法是添加一个初始化为 false 并在实际找到并进行计算机移动时首先设置为 true 的 found 变量。

这可以通过修改你的方法来完成:

function computer(){
   let foundEmptyField=false;
   while(foundEmptyField===false) 
   {
     let index = places[Math.floor(Math.random() * places.length)];
     let pos = document.getElementById(index);
     if (/[1-9]/.test(pos.innerHTML)){
        pos.innerHTML = "O";
        move += 1;
        places.splice(places.indexOf(pos), 1 );
        foundEmptyField = true;
     }
     if(foundEmptyField===true) {
        if (move > 8){
           gameOver();
        } 
        if (move <= 8) {
           user();
        }
     }
   }
}

如果您查看您的代码,您实际上在您的 user() 代码中遇到了完全相同的问题

【讨论】:

  • 这只是一个原始建议,您需要调整代码,即重新计算 index 和 pos (我只是稍微修改了答案,但我没有测试过......)
【解决方案2】:

欢迎来到stackoverflow。每次调用用户函数时都添加事件侦听器,但该事件侦听器应该只添加一次。在计算机函数中,你拼接了 pos 的索引,它是一个 HTMLElement,而不是你拼接索引 var 的索引,比如“一”、“二”等。用户函数不应该从计算机功能,因为它是由事件侦听器触发的。计算机功能中的测试是多余的,因为计算机仅从某些地方未触及的元素中进行选择。为了清楚起见,我引入了一个 init 函数以进行进一步扩展。下面是一个正在运行的解决方案:

<!DOCTYPE html>
<html>
<head>
    <title>Tic Tac Toe</title>
    <meta charset="UTF-8">
    <script type="text/javascript">
        var places, move;
        function init() {
            places = ["one", "two", "three", "four", "five", "six", "seven", 'eight', "nine"];
            move = 0;
            table.addEventListener('click', user);
        }

        function user(event) {
            let pos = event.target;
            let Id = event.target.id;
            if (/[1-9]/.test(pos.innerHTML)) {
                pos.innerHTML = "X";
                move += 1;
                places.splice(places.indexOf(Id), 1);
                if (move > 8) {
                    gameOver();
                }
                else {
                    computer();
                }
            }
        }

        function gameOver() {
            console.log("Game Over");
        }

        function computer() {
            let index = places[Math.floor(Math.random() * places.length)];
            let pos = document.getElementById(index);
            pos.innerHTML = "O";
            move += 1;
            places.splice(places.indexOf(index), 1);
            if (move > 8) {
                gameOver();
            }
        }
    </script>
</head>
<body>
    <div class="col text-center">
        <table class="table text-center">
            <tbody id="table">
                <tr>
                    <td id="one">1</td>
                    <td id="two">2</td>
                    <td id="three">3</td>
                </tr>
                <tr>
                    <td id="four">4</td>
                    <td id="five">5</td>
                    <td id="six">6</td>
                </tr>
                <tr>
                    <td id="seven">7</td>
                    <td id="eight">8</td>
                    <td id="nine">9</td>
                </tr>
            </tbody>
        </table>
        <br />
        <button class="btn btn-primary" type="button" onclick="init()" id="start">Start Game</button>
    </div>
</body>
</html>

【讨论】:

  • @ayman tarig:在 Firefox 上测试 Ubuntu/canonical-1.0 和 Chrome 71.0.3578.98/Linux。根据您自己设计的游戏运行页面必须重新加载并按下开始游戏按钮。
【解决方案3】:

问题说明: 您创建了一个事件侦听器,该侦听器在用户每次点击时发生

    start.addEventListener('click', function(){
    user();
});

您没有做的是确定轮到谁了,因此用户可能会双击甚至单击已播放的图块,甚至单击计算机轮到,代码不会将用户拒之门外,而是接受移动随着玩家的转身。

因此我添加了用户功能

        }else{
      return;
    }

如果点击的棋子已经被玩过(用 X 表示),它将忽略所玩的回合

我还向计算机函数添加了一个重试移动的调用,如果计算机随机生成一个已播放的牌号,则它不会交还给玩家,而是再次尝试。

}else{
  computer();
}

最后我简化了你的代码,因为你有重复的代码,这些代码可以变成一个函数,并传递一个变量。

    function gameStateCheck(stTurn){
    if (move > 8){
        gameOver();
    } 
    if (move <= 8) {
        if (stTurn == "user"){
          user();
         }else{
          computer();
         }
    }
}

替代解决方案: 这个解决方案完全 - 重新编写您的代码,但它正在工作并且不会陷入函数调用循环,因为 CPU 被教导知道哪些动作是有效的。

const start = document.getElementById('start');
const table = document.getElementById('table');
places = [];
start.addEventListener('click', function(){
    places = [];    // reset the array of available tiles to play
    for (i = 0; i < 9; i++){ // re populate available tiles 0-8
      places.push (i);
    }
    
    // run a loop to clear the previous game cell markers (O or X);
    for (x = 0; x < table.rows.length; x++){
        for (i = 0; i < table.rows[x].cells.length; i++){
          table.rows[x].cells[i].innerHTML = "";
        }
    }
    // Mark the users move 
   user();
});


function user(){
    table.addEventListener('click', function(event){
        // get the ID of the played tile
        let Id = parseInt(event.target.id);   
         
         // locate the index number in the array that contains the ID
        let PlayedTile = places.indexOf(Id); 
        
        if (PlayedTile > -1){
          // if PlayerTile is NOT -1, it means it exists in the array
          // lets remove it from the available tiles to play array
          places.splice(places.indexOf(Id), 1 );
          
        }else{
          // Ok so this user has already played this tile or the CPU has
          // Therefore we exit the function and nothing changes on the screen
          
          // Recommendation: add some code to notify your user this move has been played
          return;
        }
        
        // Ok so if we havent exited the function above due to having played the tile
        // we can now update the cell with an "X"
        event.target.innerHTML = "X";
        
        // we now check to see if there are any further moves available (by checking the length
        // of the places array, if it is 0 we declare game over)
        
        if (places.length == 0){
            gameover();
            return;
        }else{
        // otherwise we pass control to the CPU
    		  computer();
        }
    });
}

function computer(){
    // we know how many moves are available by the length of the array
    // so lets choose 1 random box ID number
    let cpuMove = Math.floor(Math.random() * places.length);
    
    // now lets get the value of that array index, as this value will be the table cell ID
    let cpuPlayedTileId = places[cpuMove];
    
    // now lets grab the cell element by its ID
    let pos = document.getElementById(cpuPlayedTileId);
    
    // Update the cell with an "O" marker
    // NOTE the CPU cannot physically choose an invalid move as the CPU knows the available moves
    pos.innerHTML = "O";
    
    // As it is a valid move, lets remove it from the array for the player.
    places.splice(places.indexOf(cpuPlayedTileId), 1 );
    
    // check if the game is now over
    if (places.length == 0){
        gameover();
        return;
    }else{
      user();
    }
}

function gameover(){
	alert("gameover");
  console.log("Game Over");
}
#boardGame{
  background: grey;
  border: 1px solid black;
  height: 90px;
  width: 90px;
}
#boardGame tr td{
  border: 1px solid silver;
  width: 30px;
  height: 30px;
  text-align: center;
  font-size: small;
}
<div class="col text-center">
    <table id="boardGame" class="table text-center">
        <tbody id="table">
            <tr>
                <td id="0"></td>
                <td id="1"></td>
                <td id="2"></td>
            </tr>
            <tr>
                <td id="3"></td>
                <td id="4"></td>
                <td id="5"></td>
            </tr>
            <tr>
                <td id="6"></td>
                <td id="7"></td>
                <td id="8"></td>
            </tr>
        </tbody>
    </table>
    <br>
    <button class="btn btn-primary" type="button" id="start">Start Game</button>
</div>

您的代码库,已修复 请注意,您可能会陷入无限循环,这将导致超出调用错误。但我留下的代码与您上面的代码一样接近。

我在上面编写了一个无错误代码,就像我将要编写的代码一样

const start = document.getElementById('start');
const table = document.getElementById('table');
places = ["one", "two", "three", "four", "five", "six", "seven", 'eight', "nine"];
let move = 0;
start.addEventListener('click', function(){
    user();
});


function user(){
    table.addEventListener('click', function(event){
        let pos = event.target;
        let Id = event.target.id;
        if (/[1-9]/.test(pos.innerHTML)){
            pos.innerHTML = "X";
            move += 1;
            places.splice(places.indexOf(Id), 1 );
        }else{
          return;
        }
		gameStateCheck("CPU");
    });
}

function gameOver(){
    console.log("Game Over");
}

function computer(){
    let index = places[Math.floor(Math.random() * places.length)];
    let pos = document.getElementById(index);
    if (/[1-9]/.test(pos.innerHTML)){
        pos.innerHTML = "O";
        move += 1;
        places.splice(places.indexOf(pos), 1 );
    }else{
      computer();
    }
    gameStateCheck("user");
}

function gameStateCheck(stTurn){
	if (move > 8){
        gameOver();
    } 
    if (move <= 8) {
        if (stTurn == "user"){
          user();
         }else{
          computer();
         }
    }
}
<div class="col text-center">
    <table class="table text-center">
        <tbody id="table">
            <tr>
                <td id="one">1</td>
                <td id="two">2</td>
                <td id="three">3</td>
            </tr>
            <tr>
                <td id="four">4</td>
                <td id="five">5</td>
                <td id="six">6</td>
            </tr>
            <tr>
                <td id="seven">7</td>
                <td id="eight">8</td>
                <td id="nine">9</td>
            </tr>
        </tbody>
    </table>
    <br>
    <button class="btn btn-primary" type="button" id="start">Start Game</button>
</div>

【讨论】:

  • 它一直给我这个错误script.js:32 Uncaught RangeError: Maximum call stack size exceeded at RegExp.test (&lt;anonymous&gt;) at computer (script.js:32) at computer (script.js:37) at computer (script.js:37) at computer (script.js:37)
  • 所有这意味着你需要控制它不断调用函数 Computer() 的次数我可以重新编写你的代码以便能够更好地利用数组,以便它只能从可用的内容中生成一个随机数(因为这与我开发的 Battleships 游戏非常相似)
  • 你能给我解释一下怎么做吗?
  • 我将重新编写代码,使其仅允许从数组中已有的内容中生成随机数,仅此而已。
  • 好的,我在为你担心。
猜你喜欢
  • 2022-01-13
  • 1970-01-01
  • 2015-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多