TIC-TAC-TOE (Noughts and Crosses)

In this example below you will see how to do a TIC-TAC-TOE (Noughts and Crosses) with some HTML / CSS and Javascript

FCC Front end challenge. I use box-shadow in the CSS to make the glow effect on the grid to give it an old style monitor.

Thumbnail
This awesome code was written by JohnnyBizzel, you can see more from this user in the personal repository.
You can find the original code on Codepen.io
Copyright JohnnyBizzel ©

Technologies

  • HTML
  • CSS
  • JavaScript
<!DOCTYPE html>
<html lang="en" >

<head>
  <meta charset="UTF-8">
  <title>TIC-TAC-TOE (Noughts and Crosses)</title>
  
  
  <link rel='stylesheet prefetch' href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css'>

      <link rel="stylesheet" href="css/style.css">

  
</head>

<body>

  <div class="title">*** Noughts and Crosses --- TIC TAC TOE ***<br/><br/><div class="normalWeight">YOU ARE <span id="youX" class="glyphicon glyphicon-remove"></span><span id="youO" style="display:none; font-weight:bold">O</span>, CPU IS <span id="compO" style="font-weight:bold">O</span><span id="compX" class="glyphicon glyphicon-remove" style="display:none"></span> <a href="#" style="text-decoration:none;" id="swapSides">(swap)</a></div></div>

<div class="score">USER: <span class="playerScore">0</span> CPU: <span class="computerScore">0</span></div>

<div id="mainPageArea">
<div id="message">
 > <span class="message-text"></span><br/>
  > <label id="resetBtn" class="normalWeight">[RESET GAME]</label> 
</div>
<div class="tictactoe-board">
  <div class="board-row">
    <div class="board-cell board-cell-l f6" id="cell-0-0"><span id="token0">&emsp;</span></div><div class="board-cell f6" id="cell-0-1"><span id="token1">&emsp;</span></div><div class="board-cell board-cell-r f6" id="cell-0-2"><span id="token2">&emsp;</span></div>
<div class="board-cell board-cell-l f6" id="cell-1-0"><span id="token3">&emsp;</span></div><div class="board-cell f6" id="cell-1-1"><span id="token4">&emsp;</span></div><div class="board-cell board-cell-r f6" id="cell-1-2"><span id="token5">&emsp;</span></div><div class="board-cell board-cell-l f6" id="cell-2-0"><span id="token6">&emsp;</span></div><div class="board-cell f6" id="cell-2-1"><span id="token7">&emsp;</span></div><div class="board-cell no-b f6" id="cell-2-2"><span id="token8">&emsp;</span></div>
  </div>
</div>
  </div>
  <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js'></script>

  

    <script  src="js/index.js"></script>




</body>

</html>

/*Downloaded from https://www.codeseek.co/JohnnyBizzel/tic-tac-toe-noughts-and-crosses-aBBdJp */
@import url(https://fonts.googleapis.com/css?family=Share+Tech+Mono|Hammersmith+One|Comfortaa);
body {
  font-family: 'Share Tech Mono';
  background-color: #111719;
  color: #8AA3CB;
  font-size: 1.5em;
  text-shadow: -1px 1px 2px #5881AF, 1px -1px 2px #5881AF;
}

a {
  text-shadow: 0 0 0;
  text-decoration: none;
}

a:hover {
  text-shadow: 0 0 0;
  color: #8AA3CB;
  text-shadow: 1px -1px 1px #5881AF, 1px -1px 1px #5881AF;
}

.text-center {
  text-align: center;
}

.title {
  font-size: 2rem;
  float: left;
  padding-bottom: 30px;
  font-weight: bold;
}

.normalWeight {
  font-weight: normal;
}

.score {
  float: right;
  font-size: 2rem;
  padding-bottom: 20px;
  font-weight: bold;
}

.tictactoe-board {
  width: 440px;
  margin: 0px auto;
  padding: 20px;
  /*  border: 1px solid #111724;*/
}

.board-row {
  clear: both;
  width: 390px;
  margin: 0px auto;
  border: 0px solid silver;
  border-bottom: 0px solid #8AA3CB;
}

.f6 {
  font-size: 6em;
  text-align: center;
  vertical-align: middle;
}

.board-cell {
  display: inline-block;
  width: 120px;
  height: 135px;
  /* font-family:'Didact Gothic';*/
  font-family: 'Comfortaa';
  padding: 0px 5px 0px 3px;
  cursor: pointer;
  text-shadow: -1px 1px 8px #5881AF, 1px -1px 8px #5881AF;
}

.board-cell {
  border: 1px solid #8AA3CB;
  box-shadow: inset 0 0 10px #9ecaed;
}

/* @include box-shadow(inset 0 7px 9px -12px  #9ecaed, inset 7px 7px 11px 12px #9ecaed);
}*/
/* middle cells */
#cell-0-1 {
  border-top: 0;
  -moz-box-shadow: inset -8px -7px 12px -8px #9ecaed, inset 8px -7px 12px -8px #9ecaed;
  -webkit-box-shadow: inset -8px -7px 12px -8px #9ecaed, inset 8px -7px 12px -8px #9ecaed;
  box-shadow: inset -8px -7px 12px -8px #9ecaed, inset 8px -7px 12px -8px #9ecaed;
}

#cell-1-2 {
  border-right: 0;
  -moz-box-shadow: inset 8px 7px 12px -8px #9ecaed, inset 8px -7px 12px -8px #9ecaed;
  -webkit-box-shadow: inset 8px 7px 12px -8px #9ecaed, inset 8px -7px 12px -8px #9ecaed;
  box-shadow: inset 8px 7px 12px -8px #9ecaed, inset 8px -7px 12px -8px #9ecaed;
}

#cell-2-1 {
  border-bottom: 0;
  -moz-box-shadow: inset -8px 7px 12px -8px #9ecaed, inset 8px 7px 12px -8px #9ecaed;
  -webkit-box-shadow: inset -8px 7px 12px -8px #9ecaed, inset 8px 7px 12px -8px #9ecaed;
  box-shadow: inset -8px 7px 12px -8px #9ecaed, inset 8px 7px 12px -8px #9ecaed;
}

#cell-1-0 {
  border-left: 0;
  -moz-box-shadow: inset -8px -8px 12px -8px #9ecaed, inset -8px 7px 12px -8px #9ecaed;
  -webkit-box-shadow: inset -8px -8px 12px -8px #9ecaed, inset -8px 7px 12px -8px #9ecaed;
  box-shadow: inset -8px -8px 12px -8px #9ecaed, inset -8px 7px 12px -8px #9ecaed;
}

/* corners */
#cell-0-2 {
  border-right: 0;
  border-top: 0;
  box-shadow: inset 5px -5px 8px -5px #9ecaed;
}

#cell-0-0 {
  border-left: 0;
  border-top: 0;
  box-shadow: inset -5px -5px 9px -4px #9ecaed;
}

#cell-2-0 {
  border-left: 0;
  border-bottom: 0;
  box-shadow: inset -5px 5px 9px -4px #9ecaed;
}

#cell-2-2 {
  border-right: 0;
  border-bottom: 0;
  box-shadow: inset 5px 5px 9px -4px #9ecaed;
}

#message {
  /* float: left;
    border: 3px solid silver;*/
  font-size: 2rem;
  clear: both;
  padding-top: 5px;
  position: absolute;
  top: 120px;
  left: 0;
  width: 200px;
  height: 500px;
}

#youO,
#compO {
  font-family: 'Hammersmith One';
}

#resetBtn {
  cursor: pointer;
}

#mainPageArea {
  width: 100%;
}


/*Downloaded from https://www.codeseek.co/JohnnyBizzel/tic-tac-toe-noughts-and-crosses-aBBdJp */
// TODO stop and score increase on click after User wins

"use strict";
var level = 2;
var playerChoice = "×";
var computerChoice = "o";
var playerScore = 0;
var computerScore = 0;
var playersTurn = false;
var gameStarted = false;
var gameFinished = false;
var computerSpot = '#cell-' + getRandomInt(0, 2) + '-' + getRandomInt(0, 2);
var interval;
var winningCombinations = [
  [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 boardArray = ['', '', '', '', '', '', '', '', ''];
// Place an item.
$(document).ready(function() {

  $("#swapSides").click(function() {
    // todo: allow if game not started!
    console.log('gameStarted=' + gameStarted);
    if (!gameStarted) {
      if (playerChoice == "o") {
        playerChoice = "×";
        computerChoice = "o";
        level = 2;
      } else {
        playerChoice = "o";
        computerChoice = "×";
        level = 0;
      }
      $("#youO").toggle();
      $("#youX").toggle();
      $("#compO").toggle();
      $("#compX").toggle();
    }
  });
  
   $('#resetBtn').click(function() {
   console.log("resetting");
   resetBoard();
  });
    

  $(".board-cell").click(function() {
    if (gameFinished) 
          return;
    //figure out loop logic for if a spot is taken.    

    if (!gameStarted) playersTurn = true;
    if (playersTurn) {

      var okMove = updateBoardArray("#" + this.id, playerChoice);
      if (!okMove) return;
      console.log('board=' + boardArray);
      gameStarted = true;
      playersTurn = false;
    } else { // needed???
      playersTurn = true;
    }
    // check if the player won with the current board
    var checkForWin = checkWin(playerChoice, boardArray);
    if (checkForWin.length === 3) {
      console.log("User wins");
      $('.message-text').text('USER WINS');
      indicateWinner(checkForWin);      
      playerScore += 1;
      playersTurn = false;
      $('.playerScore').html(playerScore);
      // TODO Ask user for another game...
      return;
      interval = setTimeout(function() {
        resetBoard()
      }, 5000);
   
    } else if (checkTie()) {
      console.log("Tie");
      gameFinished = true;
      $('.message-text').text('Stalemate');
      // TODO Ask user for another game...
      return;
      interval = setTimeout(function() {
        resetBoard()
      }, 5000);
    } else {
      // computer's go
      gameStarted = true;
      // todo - set level, for now pass zero
      computerMove(level);
    }
  });
});

// ################################################
/* ################## Parm's code ############## */
// ################################################

function Minimax(boardTested, depth, maximizingPlayer) {

    if (checkWin(computerChoice, boardTested).length > 0) {
      return [10 - depth, boardTested];
    } else if (checkWin(playerChoice, boardTested).length > 0) {
      return [depth - 10, boardTested];
    } else if (checkTie()) {
      return [0, boardTested]
    } else {

      var val = null;
      var index = 0;
      // loop through each child of node
      for (var i = 0; i < 9; i++) {
        if (boardTested[i] == "") {
        	 if(maximizingPlayer) {
        		boardTested[i] = computerChoice;
        	 } else {
        		boardTested[i] = playerChoice;
        	 }
          
          var moveval = Minimax(boardTested, depth + 1, !maximizingPlayer)[0];
          if ((maximizingPlayer && (val == null || moveval > val)) 
          		|| (!maximizingPlayer && (val == null || moveval < val))) {
            index = i;
            val = moveval;
          }
          boardTested[i] = "";
        }
      }
      return [val, index];
    }
  }
  
// ########## Thanks Parminder! ######################

// ########### Computers move

function computerMove(level) {
  // the computer randomly chooses a cell
  var bestGo = -1;
  if (level > 0) { // Using A.I.
    var miniMaxReturn = Minimax(boardArray, 0, true); 
    bestGo = miniMaxReturn[1];  
  } else {
    // using random
    while ($(computerSpot).children().html() == playerChoice ||
      $(computerSpot).children().html() == computerChoice) {
      computerSpot = '#cell-' + getRandomInt(0, 2) + '-' + getRandomInt(0, 2);
    }
  }

 // place token
  setTimeout(function() {
    // $(computerSpot).text(computerChoice);
    if (level > 0) { // A.I. passes a number
      updateBoardArray(bestGo, computerChoice);
    } else {  // random passes a cell.
      updateBoardArray(computerSpot, computerChoice);  
    }   
    console.log('board=' + boardArray);
    playersTurn = true;
    //check if the computer wins - returns a winning line array.
    var checkForWin = checkWin(computerChoice, boardArray);
    if (checkForWin.length === 3) {
      console.log("Computer wins");
      $('.message-text').text('CPU WINS');
      indicateWinner(checkForWin);
      
      computerScore += 1;
      playersTurn = true;
      $('.computerScore').html(computerScore);


    } else if (checkTie()) {
      // This never gets reached
      console.log("Tied");
      gameFinished = true;
      $('.message-text').text('Strange game. The only way to win is not to play.');
    };
  }, 500);
};

// x will be whoevers hit spots you are checking
function checkWin(x, board) {
  // New check using arrays
  // if not more than 2 attempts win is impossible
  var count = 0;
  var theWinningLine = []; // empty array to store the winning line
  // check number of counters placed so far
  for (var i = 0; i < board.length; ++i) {
    if (board[i] == x)
      count++;
  }
  if (count < 3) {
    return theWinningLine;  // empty array - no win found
  } else { // more than 3 guesses so check for the win
    // search for winning position
    var w;
    var winArLen = winningCombinations.length;    
    for (w = 0; w < winArLen; w++) {
      var foundCounter = 0; // counter in a winning combination
      winningCombinations[w].forEach(function(cbn) {
        if (board[cbn] == x) {
          foundCounter++;
        }
      });
      if (foundCounter === 3) {
        theWinningLine = winningCombinations[w];        
      }
    }

  };

  return theWinningLine;
}

// Shows the winning line by flashing the tokens.
// Waits for reset game to be clicked.
function indicateWinner(winningLine) {
    gameFinished = true;
    var interval1, interval2, interval0;
  
    interval0 = setInterval(function() {
      $(findHtmlCell(winningLine[0])).fadeToggle("fast", "swing");
    }, 500);
    interval1 = setInterval(function() {
      $(findHtmlCell(winningLine[1])).fadeToggle("fast", "linear");
    }, 500);
    interval2 = setInterval(function() {
      $(findHtmlCell(winningLine[2])).fadeToggle("fast", "linear");
    }, 500);

    $('#resetBtn').click(function() {
      console.log("resetting");
      clearInterval(interval0);
      clearInterval(interval1);
      clearInterval(interval2);
      resetBoard();
    });
}

//clear the board (should be modal?)
function resetBoard() {
  clearTimeout(interval);
  gameStarted = false;

  boardArray = ['', '', '', '', '', '', '', '', ''];
  $('.board-cell').children().html('&emsp;');
  // TODO Not sure if this works or is needed?::
  $('.board-cell').children().show();
  $('.message-text').text('');
  gameFinished = false;
}

function checkTie() {
  return (!boardArray.includes(''));
}

// computer's random choice
function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
// Todo: computer's AI choice
function removeDisplayNone(element) {
  $(element).css("display", "block");
}

function updateBoardArray(cell, playerCh) {
  switch (cell) {
    case 0:
    case "#cell-0-0":
      if (boardArray[0] != '') return false; // space occupied, invalid move
      boardArray[0] = playerCh;
      $("#token0").html(playerCh);
      removeDisplayNone("#token0");
      break;
    case 1:
    case "#cell-0-1":
      if (boardArray[1] != '') return false;
      boardArray[1] = playerCh;
      $("#token1").html(playerCh);
      removeDisplayNone("#token1");
      break;
    case 2:
    case "#cell-0-2":
      if (boardArray[2] != '') return false;
      boardArray[2] = playerCh;
      $("#token2").html(playerCh);
      removeDisplayNone("#token2");
      break;
    case 3:
    case "#cell-1-0":
      if (boardArray[3] != '') return false;
      boardArray[3] = playerCh;
      $("#token3").html(playerCh);
      removeDisplayNone("#token3");
      break;
    case 4:
    case "#cell-1-1":
      if (boardArray[4] != '') return false;
      boardArray[4] = playerCh;
      $("#token4").html(playerCh);
      removeDisplayNone("#token4");
      break;
    case 5:
    case "#cell-1-2":
      if (boardArray[5] != '') return false;
      boardArray[5] = playerCh;
      $("#token5").html(playerCh);
      removeDisplayNone("#token5");
      break;
    case 6:
    case "#cell-2-0":
      if (boardArray[6] != '') return false;
      boardArray[6] = playerCh;
      $("#token6").html(playerCh);
      removeDisplayNone("#token6");
      break;
    case 7:
    case "#cell-2-1":
      if (boardArray[7] != '') return false;
      boardArray[7] = playerCh;
      $("#token7").html(playerCh);
      removeDisplayNone("#token7");
      break;
    case 8:
    case "#cell-2-2":
      if (boardArray[8] != '') return false;
      boardArray[8] = playerCh;
      $("#token8").html(playerCh);
      removeDisplayNone("#token8");
      break;
  }
  return true; // valid move made
}

function findHtmlCell(pos) {
  switch (pos) {
    case 0:
      return "#token0";
      break;
    case 1:
      return "#token1";
      break;
    case 2:
      return "#token2";
      break;
    case 3:
      return "#token3";
      break;
    case 4:
      return "#token4";
      break;
    case 5:
      return "#token5";
      break;
    case 6:
      return "#token6";
      break;
    case 7:
      return "#token7";
      break;
    case 8:
      return "#token8";
      break;

  }

}

Comments