Javascript code to recognize all possible combinations of winning

喜欢而已 提交于 2019-12-11 05:46:48

问题


I am working on making a tic-tac-toe game for the science fair and I need help for recognizing all possible ways of finding a win rather than writing all of them down with brute force.

The game mechanics were just started today and I just started learning javascript (about 5 days ago) so I might not be the best looking code and the most efficent one. Also take notice i'm not done yet as this code is not 100% finished but i'm just putting my code for anyone who would like to look as I only need the combinations for all the possible wins. If you would like to watch me and help me while I work I would gladly invite you to see my code on Gitlab (I know its not the most famous but its very nice).

<!DOCTYPE html>
    <html>
        <head>
            <style>
                table,tr,td{
                    border-style:solid;
                    border-length:1px;
                }
            </style>
            <script>
                    var s1="s1";
                    var s2="s2";
                    var s3="s3";
                    var s4="s4";
                    var s5="s5";
                    var s6="s6";
                    var s7="s7";
                    var s8="s8";
                    var s9="s9";
                    var turn=0;
                function gotClicked(s1,s2,s3,s4,s5,s6,s7,s8,s9) {
                    if (!(id1="s1" || id2="s2" || id3="s3" || id4="s4" || id5="s5" || id6="s6" || id7="s7" || id8="s8" || id9="s9")) {
                        switch(x1 || x2 || x3 || x4 || x5 || x6 || x7 || x8 || x9) {
                            case x1:
                                var x1=NA1;
                                turn++;
                                if(turn=3) {
                                    if(s1=NA1 && s2=NA3 )
                                }
                        }
                    }
                }
            </script>
        </head>
        <body>
            <table>
                <tr>
                    <td>
                        <p id="s1" onclick="document.getElementById('s1').innerHTML='x';var id1=x1;gotClicked();"><a href="#">N/A</a></p>
                    </td>

                    <td>
                        <p id="s2" onclick="document.getElementById('s2').innerHTML='x';var id2=x2;gotClicked();"><a href="#">N/A</a></p>
                    </td>

                    <td>
                        <p id="s3" onclick="document.getElementById('s3').innerHTML='x';var id3=x3;gotClicked();"><a href="#">N/A</a></p>
                    </td>
                <tr/>
                <tr>
                    <td>
                        <p id="s4" onclick="document.getElementById('s4').innerHTML='x';var id4=x4;gotClicked();"><a href="#">N/A</a></p>
                    </td>

                    <td>
                        <p id="s5" onclick="document.getElementById('s5').innerHTML='x';var id5=x5;gotClicked();"><a href="#">N/A</a></p>
                    </td>

                    <td>
                        <p id="s6" onclick="document.getElementById('s6').innerHTML='x';var id6=x6;gotClicked();"><a href="#">N/A</a></p>
                    </td>
                <tr/>
                <tr>
                    <td>
                        <p id="s7" onclick="document.getElementById('s7').innerHTML='x';var id7=x7;gotClicked();"><a href="#">N/A</a></p>
                    </td>

                    <td>
                        <p id="s8" onclick="document.getElementById('s8').innerHTML='x';var id8=x8;gotClicked();"><a href="#">N/A</a></p>
                    </td>

                    <td>
                        <p id="s9" onclick="document.getElementById('s9').innerHTML='x';var id9=x9;gotClicked();"><a href="#">N/A</a></p>
                    </td>
                <tr/>
            </table>
        </body>
    </html>

Thanks to one contributor for showing my mistakes!


回答1:


Some issues:

  • border-length is not valid CSS, it shoud be border-width
  • You are not doing comparisons in this if statement, but assignments:

    if (!(id1="s1" ...
    

    Use triple equality signs ( === ) to perform (strict) string comparisons.

  • Your code would greatly improve if you would use an array instead of 9 separate variables: it will result in less duplication of almost identical code. For example, use this as your representation of the board:

    var board = Array(9);
    

    ... and put a 0 or 1 in a particular array element when the corresponding square receives and 'X' or and 'O'. For instance:

    board[0] = 1 
    
  • With the array presentation you can list a winning configuration as a triplet of indices in that array. For instance, if the values at index 0, 1 and 2 are all 1, then player 1 ('O') has won. All such presentations can be listed as follows:

    var wins = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8],
        [0, 3, 6],
        [1, 4, 7],
        [2, 5, 8],
        [0, 4, 8],
        [2, 4, 6]
    ];
    

    The check for a win would be a matter of checking board entries at these indices to see they contain all 1 or all 0 (see code in snippet below).

  • It is better not to write JavaScript code in HTML onclick attributes. For one, it results again in duplication of code, but it is also harder to debug when errors happen. Overall, it is considered better to separate code (JavaScript) from layout (HTML).

    Remove the onclick attributes and use addEventListener method instead inside your script:

    Array.from(document.querySelectorAll('td a'), function (link, i) {
        link.addEventListener('click', gotClicked.bind(null, link, i));
    });
    
    function gotClicked(link, i) {
        // From the `turn` value we can know who the current player is:  0 or 1.
        // We use the modulo operator to get the remainder when dividing by 2: 
        var player = turn % 2; // alternates between 0 and 1.
        // Use this 0 or 1 as index in the XO string and display it.
        // When player is 0 this gives 'X', when 1 it gives 'O':
        link.parentNode.textContent = 'XO'[player];
        // Also put the 0 or 1 in our array
        board[i] = player;
        // Player played, so now increment `turn`.
        turn++;
    }
    

    For this to work, you should move your script to the end of your document, just before the closing </body>. Or, if you prefer it at the top, then wrap the code in:

    document.addEventListener('DOMContentLoaded', function () {
        // your code that needs access to the document elements comes here
    });
    
  • The click event handler should prevent the click from initiating a navigation. For that you can use e.preventDefault(), but you must then also get that e argument in your function.

    function gotClicked(link, i, e) { e.preventDefault(); // ... etc. }

  • Don't use innerHTML when you just want to put text. For that purpose textContent is more suited.

Here is a working snippet:

document.addEventListener('DOMContentLoaded', function () {
  // *** Initialise board with "empty" cells -- we use -1 for that:
  var board = [-1,-1,-1,-1,-1,-1,-1,-1,-1];
  var wins = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6] ];
  var turn = 0;
  Array.from(document.querySelectorAll('td a'), function (link, i) {
    link.addEventListener('click', gotClicked.bind(null, link, i));
  });

  function gotClicked(link, i, e) {
    // Prevent the click on the link to initiate a navigation
    e.preventDefault();
    // *** call function to perform move: will return true when game is over
    if (doMove(i, 'You won!')) return;
    // ***Let AI play a move.
    randomAi();
  }

  // ***Separate function to perform a particular move: can be used for 
  // both players
  function doMove(i, msg) {
    // *** Get td-element that corresponds to the given index
    var td = document.querySelectorAll('td')[i];
    // From the `turn` value we can know who the current player is:  0 or 1.
    // We use the modulo operator to get the remainder when dividing by 2: 
    var player = turn % 2;
    // Use this 0 or 1 as index in the XO string and display it.
    // When player is 0 this gives 'X', when 1 it gives 'O':
    td.textContent = 'XO'[player];
    // Also put the 0 or 1 in our array
    board[i] = player;
    // Player played, so now increment `turn`.
    turn++;
    // Now board has changed, check if there is a 3-in-a-row for this player,
    // *** and return it as the result of this function
    if (isWinFor(player)) {
        // *** Show appropriate message
        alert(msg);
        return true; // signify end of game
    }
    // *** Detect a draw
    if (turn == 9) {
        alert("It's a draw");
        return true; // signify end of game
    }
  }

  // *** Modified completely
  function randomAi() {
    // ***Pick random number among the free cells. Make sure it is integer.
    var randomPick = Math.floor(Math.random()*(9-turn));
    // ***Find out its position on the board
    for (var i in board) {
        if (board[i] !== -1) continue; // used cell -- skip it
        if (randomPick == 0) break; // found it
        randomPick--;
    }
    // ***Perform this AI move
    doMove(i, 'You lost');
  }

  function isWinFor(player) {
    // Player is 0 or 1.
    // Iterate over all the 8 potential wins
    for (var win of wins) {
      // `win` is a triplet of indices, which when they contain the player's value
      // constitute a winning position. Let's count how many of those three
      // have the player's value (0 or 1)
      var count = 0;
      for (var index of win) {
        // Is the value on the board the value that corresponds to the player?
        if (board[index] !== player) break; // don't bother looking further.
        count++;
      }
      // If we have a count of 3 here, it is a winning position.
      if (count == 3) {
        // Don't try other wins: one is enough :-)
        // ***Remove all hyperlinked cells -- no move should be played anymore
        Array.from(document.querySelectorAll('td a'), function (link, i) {
          link.parentNode.innerHTML = ''; // remove link
        });
        return true;
      }
    }
  }
});
table,tr,td{
  border-style:solid;
  border-width:1px;
  text-align: center
}
/* ***Fix the size of the board */
td { width: 30px; height: 30px }
<table>
  <tr>
    <td>
      <a href="#">N/A</a>
    </td>
    <td>
      <a href="#">N/A</a>
    </td>
    <td>
      <a href="#">N/A</a>
    </td>
  <tr/>
  <tr>
    <td>
      <a href="#">N/A</a>
    </td>
    <td>
      <a href="#">N/A</a>
    </td>
    <td>
      <a href="#">N/A</a>
    </td>
  <tr/>
  <tr>
    <td>
      <a href="#">N/A</a>
    </td>
    <td>
      <a href="#">N/A</a>
    </td>
    <td>
      <a href="#">N/A</a>
    </td>
  <tr/>
</table>

Explanation

In comments you asked about this piece of code:

Array.from(document.querySelectorAll('td a'), function (link, i) {
    link.addEventListener('click', gotClicked.bind(null, link, i));
});

It looks for all a elements that are descendants of td elements with querySelectorAll:

document.querySelectorAll('td a')

As the returned list is not an array, I convert it to one for easier looping, using Array.from:

Array.from(  )

The above returns array that I want to visit each element of. There are many ways to do this, but here I chose Array.from and using the callback function that can be provided to it: that callback is called for each element in the first argument.

At each call the next array element is passed to it as first argument, and a sequence number (the array index) is passed to it as second argument:

function (link, i) {   }

Within the function I call addEventListener on the element (an a element) to listen to the click event:

link.addEventListener('click',   );

This really does something similar as the onclick attribute. The second argument passed to it must be the function that will be called whenever there is a click on this particular a element. For that I reference your gotClicked function. But as I want to pass it some arguments I use bind:

gotClicked.bind(null, link, i)

The first argument of bind is whatever this should be inside gotClicked. As I don't care about that, I pass null. But I do care about the real arguments passed to gotClicked: link (the element clicked) and i (the index of that element). Note that bind does not call the function, it just creates a reference to the function already specifying what will be passed as arguments once it will get called.




回答2:


You can create an array of all possible winning combinations; create an array to store number at clicked element id; attach click event to each p element within for..of loop; use Array.prototype.some(), Array.prototype.every(), Array.prototype.indexOf() to check if each element in array containing clicked element digit portion of id is within an array containing a set of possible winning combinations and element .innerHTML is equal to "x"; utilize setTimeout() to allow time for last "x" to be rendered, then reset <p> elements .innerHTML to <a href='#'>N/A</a>, set array containing clicked element digit portion of id to 0 .length

<!DOCTYPE html>
<html>

<head>
  <style>
    table,
    tr,
    td {
      border: 1px solid #000;
    }
  </style>
  <script>
    window.onload = function() {
      var winners = [
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9],
        [1, 5, 9],
        [3, 5, 7],
        [1, 4, 7],
        [2, 5, 8],
        [3, 6, 9]
      ];
      var clicked = [];
      var p = document.querySelectorAll("table td p");
      for (el of p) {
        el.onclick = function() {
          if (this.innerHTML !== "x") {
            this.innerHTML = "x";
            clicked.push(+this.id.match(/\d/).pop());
          };
          var win = winners.some(function(winner) {
            return winner.every(function(n) {
              return clicked.indexOf(n) > -1 
                     && document.querySelector("[id$='" + n + "']")
                        .innerHTML === "x"
            })
          });
          setTimeout(function() {
            if (win) {
              alert("winner");
              clicked.length = 0;       
              for (el of p) {
                el.innerHTML = "<a href='#'>N/A</a>"
              }
            }
          }, 1000)
        }
      }
    }
  </script>
</head>

<body>
  <table>
    <tr>
      <td>
        <p id="s1"><a href="#">N/A</a>
        </p>
      </td>

      <td>
        <p id="s2"><a href="#">N/A</a>
        </p>
      </td>

      <td>
        <p id="s3"><a href="#">N/A</a>
        </p>
      </td>
    </tr>
    <tr>
      <td>
        <p id="s4"><a href="#">N/A</a>
        </p>
      </td>

      <td>
        <p id="s5"><a href="#">N/A</a>
        </p>
      </td>

      <td>
        <p id="s6"><a href="#">N/A</a>
        </p>
      </td>
    </tr>
    <tr>
      <td>
        <p id="s7"><a href="#">N/A</a>
        </p>
      </td>

      <td>
        <p id="s8"><a href="#">N/A</a>
        </p>
      </td>

      <td>
        <p id="s9"><a href="#">N/A</a>
        </p>
      </td>
    </tr>
  </table>
</body>

</html>


来源:https://stackoverflow.com/questions/39946626/javascript-code-to-recognize-all-possible-combinations-of-winning

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!