0

Im trying to build tic tac toe game using javascript, so basically my version of tic tac toe is without AI so computer play in the first empty spot (as you can see in bestSpot() function) then I replace it with a minimax function to make computer unbeatable (from this repo: sourcecode) when I tried to combine it to my code I get an error [see attached pic][]2. i couldn't figure whats wrong with minimax function that throws an error. any help will be much appreciable. (to disable minimax uncomment first line in bestSpot() function) NOTE: PLEASE EXPAND FULL-VIEW SNIPPET TO SKIP PAGE RESPONSIVE ERRORS.

var currentPlayer = '';
var computerPlayer = '';
var playerSelection = [];
var computerSelection = [];
var boardInputs = [1, 2, 3, 4, 5, 6, 7, 8, 9];

const winCombos = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
  [1, 4, 7],
  [2, 5, 8],
  [3, 6, 9],
  [1, 5, 9],
  [7, 5, 3]
]


$(document).ready(function() {

  $('.board').hide();
  $('.restart').hide();
  // $('.mine').hide();
  choseSign();

});

function choseSign() {
  $('.choseSq').on('click', function() {
    var element = $(this).attr('value');
    console.log('element player', element);
    if (element == 'o' || element == 'x') {
      currentPlayer += element;
      $('.mine').fadeOut(500);
      $('.board').fadeIn(3000);
      if (currentPlayer === 'x') {
        computerPlayer = 'o';
      } else {
        computerPlayer = 'x';
      }
      startGame();
    }
  });
}

function startGame() {
  document.getElementById('tictactoe').style.pointerEvents = 'auto';
  $('.square').on('click', function() {
    var element2 = $(this).attr('value');
    var myIndex = playerSelection.indexOf(parseInt(element2));
    var comIndex = computerSelection.indexOf(parseInt(element2));

    if (myIndex === -1 && comIndex === -1) {
      if (currentPlayer == 'o') {
        $(this).append("<img src='http://www.dreamincode.net/forums/uploads/monthly_10_2010/post-0-12884151170642.png'/>");
        boardInputs[parseInt(element2) - 1] = currentPlayer;
        console.log('board: ', boardInputs);
      } else {
        $(this).append("<img src='http://www.dreamincode.net/forums/uploads/post-97990-1260678617.png'/>");
        boardInputs[parseInt(element2) - 1] = currentPlayer;
        console.log('board: ', boardInputs);
      }

      playerSelection.push(parseInt(element2));
      console.log('current player: ', playerSelection);
      let gameWon = checkWin(boardInputs, currentPlayer);
      if (gameWon) {
        gameOver(gameWon, 'you won');
      } else if (playerSelection.length < 6) {
        document.getElementById('tictactoe').style.pointerEvents = 'none';
        console.log('##CUR PLY: ', currentPlayer, '$$%AI PLY: ', computerPlayer)
        setTimeout(() => {
          computerTurn(bestSpot(), computerPlayer);
        }, 1000);
      }
    } else {
      console.log('Position Taken !');
    }
  });

}


function computerTurn(squareId, player) {
  document.getElementById('tictactoe').style.pointerEvents = 'auto';
  //var random_number = Math.floor(Math.random() * (9 - 1 + 1)) + 1;
  console.log("SQR_ID: ", squareId);
  //boardInputs[squareId] = computerPlayer;
  var playerIndex = playerSelection.indexOf(squareId);
  var computerIndex = computerSelection.indexOf(squareId);
  var generatePosition = '.sq';

  if (playerIndex === -1 && computerIndex === -1) {
    computerSelection.push(squareId);
    generatePosition += squareId
    console.log("genPos: ", generatePosition);

    if (currentPlayer === 'x') {
      $(generatePosition).append("<img src='http://www.dreamincode.net/forums/uploads/monthly_10_2010/post-0-12884151170642.png'/>");
      boardInputs[squareId - 1] = computerPlayer;
    } else {
      $(generatePosition).append("<img src='http://www.dreamincode.net/forums/uploads/post-97990-1260678617.png'/>");
      boardInputs[squareId - 1] = computerPlayer;
    }
    console.log('board: ', boardInputs);
    let gameWon = checkWin(boardInputs, computerPlayer);
    if (gameWon) {
      gameOver(gameWon, 'Computer won');
    }

  } else {
    if (computerSelection.length < 4) {
      computerTurn(bestSpot(), computerPlayer);
    } else {
      console.log('it\'s a -- DRAW -- Game Finished');
      console.log('board result', boardInputs);


      $('.status').text('its a draw');
      $('.restart').show();
      $('.RestartBtn').on('click', function() {
        clearGame();
        $('.restart').hide();
      });
    }
  }
  console.log("computer board: ", computerSelection);

}

function clearGame() {
  for (i = 1; i < 10; i++) {
    var sq = '.sq' + i;
    $(sq).text('');
    $(sq).css("background-color", "rgb(248, 248, 248)");
  }

  $('.board').hide();
  $('.mine').fadeIn(1500);
  currentPlayer = '';
  computerPlayer = '';
  playerSelection = [];
  computerSelection = [];
  boardInputs = [];
  boardInputs = [1, 2, 3, 4, 5, 6, 7, 8, 9];
}

function checkWin(board, player) {
  let plays = board.reduce((a, e, i) =>
    (e === player) ? a.concat(i + 1) : a, []);
  let gameWon = null;
  for (let [index, win] of winCombos.entries()) {
    if (win.every(elem => plays.indexOf(elem) > -1)) {
      gameWon = {
        index: index,
        player: player
      };
      break;
    }
  }
  return gameWon;
}

function gameOver(gameWon, status) {
  document.getElementById('tictactoe').style.pointerEvents = 'none';
  for (let index of winCombos[gameWon.index]) {
    var square = '.sq' + index;
    $(square).css("background-color", "rgb(41, 168, 62)");
  }

  setTimeout(() => {
    $('.status').text(status);
    $('.restart').show();
    // $('.board').hide();

  }, 1000);

  $('.RestartBtn').on('click', function() {
    clearGame();
    $('.restart').hide();
  });

}

function emptySquares() {
  // console.log('emt func')    
  return boardInputs.filter(s => typeof s === 'number');
  // return boardInputs.filter((elm, i) => i === elm);
}

function bestSpot() {
  // return emptySquares()[0];  // uncomment to disable minimax algorithem
  return minimax(boardInputs, computerPlayer).index; // comment to disable minimax 
}

function minimax(newBoard, player) {
  var availSpots = emptySquares(newBoard);
  console.log('aviSpt: ', availSpots);


  if (checkWin(newBoard, currentPlayer)) {
    return {
      score: -10
    };
  } else if (checkWin(newBoard, computerPlayer)) {
    return {
      score: 10
    };
  } else if (availSpots.length === 0) {
    return {
      score: 0
    };
  }
  var moves = [];
  for (var i = 0; i < availSpots.length; i++) {
    var move = {};
    move.index = newBoard[availSpots[i]];
    newBoard[availSpots[i]] = player;

    if (player == computerPlayer) {
      var result = minimax(newBoard, currentPlayer);
      move.score = result.score;
    } else {
      var result = minimax(newBoard, computerPlayer);
      move.score = result.score;
    }

    newBoard[availSpots[i]] = move.index;

    moves.push(move);
  }

  var bestMove;
  if (player === computerPlayer) {
    var bestScore = -10000;
    for (var i = 0; i < moves.length; i++) {
      if (moves[i].score > bestScore) {
        bestScore = moves[i].score;
        bestMove = i;
      }
    }
  } else {
    var bestScore = 10000;
    for (var i = 0; i < moves.length; i++) {
      if (moves[i].score < bestScore) {
        bestScore = moves[i].score;
        bestMove = i;
      }
    }
  }

  return moves[bestMove];
}
body {
  background: rgb(248, 248, 248);
  text-transform: capitalize;
  font-family: 'Architects Daughter', cursive;
}

.game {
  width: 433px;
  height: 433px;
}

.board {
  align-items: center;
  align-content: center;
  height: 399px;
  width: 399px;
  margin: auto;
  margin-top: 20px;
}


/* .restart{
    border: 2px solid rgb(201, 23, 23);
    display: inline-flex;
    height: 149px;
    width: 299px;
    left: 38%;
    background-color: rgb(255, 255, 255);
    margin-bottom:-82px; 
    
} */


/* .extraline{
    border: 2px solid rgb(23, 201, 47);
    position: absolute;
    align-items: center;
    align-content: center;
    height: 149px;
    width: 299px;
    background-color: rgb(255, 255, 255);
    
} */

.chose {
  /* text-transform: capitalize;
    color: rgb(201, 23, 23);
    border: 8px solid rgb(41, 168, 62);
    position: absolute;
    align-items: center;
    align-content: center;
    height: 349px;
    width: 299px;
    margin: auto;
    background-color: rgb(255, 255, 255);
    text-align: center;   
    margin-left: 53px;  */
}

img {
  height: 60px;
  width: 60px;
  margin: 29px 0 0 29px;
}

.center {
  margin: auto;
  width: 40%;
  margin-top: 93px;
}

.square {
  border: 5px solid rgb(201, 23, 23);
  float: left;
  width: 120px;
  height: 120px;
  overflow: hidden;
}

.squarex {
  border: 6px solid red;
  float: left;
  position: relative;
  width: 400px;
  height: 1px;
  padding-bottom: 30%;
  /* = width for a 1:1 aspect ratio */
  background-position: center center;
  background-repeat: no-repeat;
  background-size: cover;
  /* you change this to "contain" if you don't want the images to be cropped */
  margin-top: -4px;
}

.x2 {
  border: 6px solid green;
  height: 150px;
  width: 400px;
  display: inline-block;
  margin-right: -4px;
}

#rec1 {
  border: 5px solid rgb(41, 168, 62);
  border-top-color: rgb(248, 248, 248);
  border-left-color: rgb(248, 248, 248);
  border-bottom-color: rgb(218, 172, 25);
}

#rec2 {
  border: 5px solid rgb(218, 172, 25);
  border-top-color: rgb(248, 248, 248);
  border-right-color: rgb(137, 60, 153);
  border-bottom-color: rgb(113, 216, 54);
}

#rec3 {
  border-top-color: rgb(248, 248, 248);
  border-right-color: rgb(248, 248, 248);
  border-bottom-color: rgb(153, 60, 77);
}

#rec4 {
  border-left-color: rgb(248, 248, 248);
  border-bottom-color: rgb(248, 248, 248);
  border-right-color: rgb(153, 60, 77);
  border-top-color: rgb(0, 162, 211);
}

#rec5 {
  border-bottom-color: rgb(248, 248, 248);
  border-right-color: rgb(39, 131, 173);
  border-top-color: rgb(176, 0, 211);
  border-left-color: rgb(68, 187, 161);
  text-align: center;
  color: rgb(209, 16, 16);
  font-size: 25px;
  font-weight: bold;
}

#rec6 {
  border-right-color: rgb(248, 248, 248);
  border-bottom-color: rgb(248, 248, 248);
  border-left-color: rgb(236, 202, 48);
  border-top-color: rgb(36, 128, 233);
}

.restart {
  position: absolute;
  text-align: center;
}

.choseSq {
  border-radius: 22px;
  margin: auto;
  width: 100px;
  padding-bottom: 25px;
  padding-right: 27px;
  display: inline;
}

.choseSq:hover {
  background-color: rgb(255, 223, 223);
}

.sq1 {
  border: 5px solid rgb(25, 116, 103);
  border-top-color: rgb(248, 248, 248);
  border-left-color: rgb(248, 248, 248);
}

.sq2 {
  border: 5px solid rgb(39, 131, 173);
  border-top-color: rgb(248, 248, 248);
}

.sq3 {
  border: 5px solid rgb(236, 202, 48);
  border-top-color: rgb(248, 248, 248);
  border-right-color: rgb(248, 248, 248);
}

.sq4 {
  border: 5px solid rgb(151, 153, 60);
  border-left-color: rgb(248, 248, 248);
}

.sq5 {
  border: 5px solid rgb(153, 60, 77);
}

.sq6 {
  border: 5px solid rgb(60, 69, 153);
  border-right-color: rgb(248, 248, 248);
}

.sq7 {
  border: 5px solid rgb(137, 60, 153);
  border-left-color: rgb(248, 248, 248);
  border-bottom-color: rgb(248, 248, 248);
}

.sq8 {
  border: 5px solid rgb(218, 172, 25);
  border-bottom-color: rgb(248, 248, 248);
}

.sq9 {
  border: 5px solid rgb(41, 168, 62);
  border-right-color: rgb(248, 248, 248);
  border-bottom-color: rgb(248, 248, 248);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!DOCTYPE html>
<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js">
<!--<![endif]-->

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title></title>
  <meta name="description" content="">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="style.css">
  <link href="https://fonts.googleapis.com/css?family=Architects+Daughter" rel="stylesheet">
</head>

<body>
  <!--[if lt IE 7]>
            <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="#">upgrade your browser</a> to improve your experience.</p>
        <![endif]-->
  <div class="restart">
    <h1 class="status"></h1>
    <h2>Restart the game?</h2>
    <button class='RestartBtn'>Restart</button>
  </div>
  <div class="mine">
    <div id="rec1" class="x2">
    </div>
    <div id="rec2" class="x2">

    </div>
    <div id="rec3" class="x2">
    </div>

    <div id="rec4" class="squarex">
    </div>

    <div id="rec5" class="squarex ">
      <h2>Please Chose a sign to start a game</h2>
      <div class=" choseSq" value="x">
        <img src="http://www.dreamincode.net/forums/uploads/post-97990-1260678617.png" />
      </div>
      <div class=" choseSq" value="o">
        <img src="http://www.dreamincode.net/forums/uploads/monthly_10_2010/post-0-12884151170642.png" />
      </div>
    </div>

    <div id="rec6" class="squarex">
    </div>
  </div>

  <div class="game center">
    <div class="board" id='tictactoe'>
      <div class="square sq1" value="1">
      </div>
      <div class="square sq2" value="2">
      </div>
      <div class="square sq3" value="3">
      </div>
      <div class="square sq4" value="4">
      </div>
      <div class="square sq5" value="5">
      </div>
      <div class="square sq6" value="6">
      </div>
      <div class="square sq7" value="7">
      </div>
      <div class="square sq8" value="8">
      </div>
      <div class="square sq9" value="9">
      </div>
    </div>

  </div>

  <script src="script.js" type="text/javascript"></script>
</body>

</html>
OT AMD
  • 183
  • 5
  • 19
  • 1
    Every recursive function can be refactored to use a loop instead of a function call. You'll need to do this in here. – Kijewski Oct 12 '17 at 14:13
  • 3
    Or you need to add proper break conditions in the recursion calls. – rrk Oct 12 '17 at 14:13
  • Minimax algorithm goes more broad than deep - it should not tax the callstack limits for tic tac toe (should only be 9 deep from the minimax recursions at most). Likely there is a problem with detecting an end or win condition... – arbuthnott Oct 12 '17 at 14:44
  • Your `checkWin` looks pretty complicated, I'd start debugging that. Even if it's returning correct results, it's also adding a lot to your call-stack! Old fashioned loops may be needed in place of `filter`, `reduce`, etc (but I doubt the actual callstack depth is the real problem). – arbuthnott Oct 12 '17 at 14:53
  • i start debugging it, checkWin works fine so the original version logs the player sign successfully to the console so it becomes boardInputs=[1,2,'x',4,'o',6,7,8,9] as expected. but when i implement the minimax function the computer plays out of context so boardInputs becomes [1,2,'x',4,5,6,7,8,9,NaN,'o'] fixing the issue here is to replace with player sign not to push into the array. any thoughts please ? – OT AMD Oct 13 '17 at 14:38
  • when i chnaged the array to start from 0 instead of 1 the minimax function now can work but still some bugs there thanks for all – OT AMD Oct 13 '17 at 20:02
  • [here](https://stackoverflow.com/questions/64882717/solving-tictactoe-with-minimax-algorithm-in-javascript/65417503#65417503) is a working implementation of minimax for Tic Tac Toe in JavaScript. – trincot Dec 23 '20 at 00:23

0 Answers0