Tic Tac Toe

In this example below you will see how to do a Tic Tac Toe with some HTML / CSS and Javascript

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

Technologies

  • HTML
  • CSS
  • JavaScript
    <html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>Tic Tac Toe</title>

    <meta name="description" content="A game of Tic Tac Toe">
    <meta name="author" content="Garrett Poore">

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<!--     <script src="scripts.js"></script>
    <script src="tic_tac_toe.js"></script>
    <script src="ai.js"></script> -->
    <link rel="stylesheet" href="styles.css">
    <!-- <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> -->
  </head>

  <body>
    <h1>Tic Tac Toe</h1>
    <div id="main">
      <div id="heading">
        <div id="ask_players">How many players are there?</div>
        <div id="ask_character">Choose X or O:</div>
        <div id="X_score" class="score">X: 0</div>
        <div id="O_score" class="score">O: 0</div>
        <div id="current_turn"></div>
      </div>
      <div id="player_select_container">
        <button type="button" value="1" class="player_select">One Player</button>
        <button type="button" value="2" class="player_select">Two Players</button>
      </div>
      <div id="character_select_container">
        <button type="button" value="X" class="character_select">X</button>
        <button type="button" value="O" class="character_select">O</button>
      </div>
      <div id="board">
        <table id="board_table">
          <tr>
            <td><button type="button" value="7" class="space"></button></td>
            <td><button type="button" value="8" class="space"></button></td>
            <td><button type="button" value="9" class="space"></button></td>
          </tr>
          <tr>
            <td><button type="button" value="4" class="space"></button></td>
            <td><button type="button" value="5" class="space"></button></td>
            <td><button type="button" value="6" class="space"></button></td>
          </tr>
          <tr>
            <td><button type="button" value="1" class="space"></button></td>
            <td><button type="button" value="2" class="space"></button></td>
            <td><button type="button" value="3" class="space"></button></td>
          </tr>
        </table>
      </div>
      <button type="button" id="reset">Reset Game</button>
    </div>
    <div>
      <div id="end_game"></div>
    </div>
  </body>
</html>


/*Downloaded from https://www.codeseek.co/GarrettPoore/tic-tac-toe-wqdRmJ */
    body {
  color: #c2c9d3;
  background: #364a68;
  text-align: center;
}

h1 {
  margin-top: -1px;
  margin-bottom: 5px;
}

button {
  color: #c2c9d3;
  background: #2b1f38;
}

#main {
  font-size: 48pt;
  background: #2e343d;
  width: 10em;
  border-radius: 1em;
  box-shadow: inset 5px 5px 5px #0d0e11, 2px 2px 2px #343c47, 5px 5px 5px #434c5b, 5px 5px 2px #243247;
  padding: 5px;
  margin: auto;
}

#heading {
  font-size: 24pt;
}

#player_select_container {
  font-size: 16pt;
}

#ask_character {
  display: none;
}

#character_select_container {
  display: none;
  font-size: 16pt;
}

#current_turn {
  display: none;
  width: 10em;
}

#X_score{
  margin-left: 1em;
}

.score {
  display: none;
  width: 4em;
  text-align: left;
}

#board {
  display: none;
  background: #2b1f38;
  width: 8.25em;
  height: 0em;
  border-radius: 15px;
  box-shadow: inset 3px 3px 3px #6b5384, inset 3px 3px 3px #3f304f, 3px 3px #1f1628, 3px 3px 3px #000000;
  padding: 5px;
  margin: auto;
  margin-top: 10px;
  margin-bottom: -25px;
}

#board_table {
  margin: auto;
}

#reset {
  display: none;
  background: #8c1919;
}

#end_game {
  display: none;
  position: absolute;
  color: #68998d;
  text-shadow: -1px 0 #12755e, 0 1px #12755e, 1px 0 #12755e, 0 -1px #12755e;
  font-size: 48pt;
  top: 5em;
  left: 0;
  right: 0;
  margin: auto;
}

.space {
  display: block;
  background: #11705b;
  color: #c2c9d3;
  font-size: 48pt;
  height: 2.5em;
  width: 2.5em;
  margin: 5px;
  box-shadow: inset 5px 5px #0a4437;
  text-decoration: none;
  border: none;
  outline: none;
}



/*Downloaded from https://www.codeseek.co/GarrettPoore/tic-tac-toe-wqdRmJ */
    $(document).ready(function() {
  $(".player_select").click(function(event) {
    var val = event.target.value;

    if (val == 2) {
      $("#ask_character").prepend("Player 1 - ");
      data.versusAI = false;
    } else {
      data.versusAI = true;
    }

    $("#ask_players").fadeOut(200, function(){
      $("#ask_character").fadeIn(200);
    });
    $("#player_select_container").fadeOut(200, function(){
      $("#character_select_container").fadeIn(200);
    });
  });

  $(".character_select").click(function(event) {
    var val = event.target.value;
    data.player1 = val;
    data.player2 = notCharacter(val);

    pickFirstTurn();

    $("#ask_character").fadeOut(400, function(){
      $("#current_turn").css("display", "inline-block");
      $(".score").css("display", "inline-block");
    });
    $("#character_select_container").fadeOut(200, function(){
      $("#board").show().animate({height: "8.2em"}, {
        duration: 600,
        complete: function(){
          $("#reset").fadeIn();
        }
      });
    });
  });

  $(".space").click(function(event) {
    var val = event.target.value;
    tryToPlay(val);
  });

  $(document).keypress(function(event){
    //1-9 are 49-57
    var key = event.which;
    var num = key - 48;
    if (num >= 1 && num <= 9) {
      tryToPlay(num);
    }
  });

  function tryToPlay(space) {
    if (data.pause) {return null;}
    if (data.board[space-1] === "") {
      if (isPlayerTurn()) {
        placeMove(space);
        endTurn();
        if (data.versusAI && !data.pause) {
          aiPlay();
        }
      }
    }
  }

  $("#reset").click(function(){
    resetBoard();
    data = initData();
    $("#reset").fadeOut(400, function(){
      $("#board").animate({height: "0"},{
        duration: 600,
        complete: function() {
          $("#board").hide();
          $(".score").fadeOut(400, function(){
            $("#X_score").text("X: 0");
            $("#O_score").text("O: 0");
          });
          $("#current_turn").fadeOut(400, function(){
            $("#ask_players").fadeIn();
            $("#player_select_container").fadeIn();
          });
        }
      })
    });
  });
});


//tic_tac_toe.js
//Board is as follows:
// 7 | 8 | 9
// 4 | 5 | 6
// 1 | 2 | 3
//Or as indexed in data.board
// 6 | 7 | 8
// 3 | 4 | 5
// 0 | 1 | 2
var data = initData();

function initData() {
  var d = {};
  d.board = ["","","","","","","","",""];
  d.currentTurn = ""; //X or O for current turn
  d.player1 = ""; //X or O for player
  d.player2 = ""; //Mainly so the AI can track what it is
  d.score = {
    X: 0,
    O: 0
  }
  d.pause = false;
  d.versusAI = false;
  return d;
}

function pickFirstTurn() {
    if (Math.random() > 0.5) {
      updateCurrentTurn("X");
    } else {
      updateCurrentTurn("O");
    }

  if (!isPlayerTurn()) {
    aiPlay();
  }
}

function updateCurrentTurn(player) {
  data.currentTurn = player;
  $("#current_turn").text("Current Turn: " + player);
}

function isPlayerTurn() {
  return (!data.versusAI || data.player1 == data.currentTurn);
}

function notCharacter(c) {
  if (c == "X") {
    return "O";
  } else {
    return "X";
  }
}

function endTurn() {
  if (checkForWinner(data.board, data.currentTurn)) {
    data.pause = true;
    adjustScore(data.currentTurn);
    $("#end_game").text(data.currentTurn + " wins!").fadeIn(500);
    $("#main").animate({opacity: "0.2"}, {
      duration: 500,
      queue: false,
      complete: function() {
        setTimeout(resetBoard, 3000);
      }
    });
  } else if (checkForDraw()) {
    data.pause = true;
    $("#end_game").text("Draw!").fadeIn(500);
    $("#main").animate({opacity: "0.2"}, {
      duration: 500,
      queue: false,
      complete: function() {
        setTimeout(resetBoard, 3000);
      }
    });
  }

  changeTurn();
}

function adjustScore(winner) {
  data.score[winner] += 1;
  if (winner ==  "X") {
    $("#X_score").text("X: " + data.score.X)
  } else {
    $("#O_score").text("O: " + data.score.O)
  }
}

function checkForDraw() {
  for (var i = 0; i < data.board.length; i++) {
    if (data.board[i] === "") {
      return false;
    }
  }
  return true;
}

//This method will check each combination of wins for given board and letter
//and return true or false
// 6 | 7 | 8
// 3 | 4 | 5
// 0 | 1 | 2
function checkForWinner(bo, le) {
  return (bo[0] == le && bo[1] == le && bo[2] == le) || //Bottom Row
    (bo[3] == le && bo[4] == le && bo[5] == le) || //Middle Row
    (bo[6] == le && bo[7] == le && bo[8] == le) || //Top Row
    (bo[0] == le && bo[3] == le && bo[6] == le) || //Left Column
    (bo[1] == le && bo[4] == le && bo[7] == le) || //Middle Column
    (bo[2] == le && bo[5] == le && bo[8] == le) || //Right Column
    (bo[0] == le && bo[4] == le && bo[8] == le) || //Diagonal Bottom-Left to Top-Right
    (bo[2] == le && bo[4] == le && bo[6] == le) //Diagonal Top-Left to Bottom-Right
}

function changeTurn() {
  if (data.currentTurn == "X") {
    updateCurrentTurn("O");
  } else {
    updateCurrentTurn("X");
  }
  aiPlay();
}

//Board Functions
function getSpace(spaceNum) {
  return $('.space[value="' + spaceNum + '"]');
}

function getBoardCopy(board) {
  return board.slice();
}

function getAvailableSpaces() {
  var available = [];
  data.board.forEach(function(space, i){
    if (space === "") {
      available.push(i);
    }
  });
  return available;
}

function placeMove(space) {
  getSpace(space).text(data.currentTurn);
  data.board[space-1] = data.currentTurn;
}

function placeMoveByIndex(index) {
  placeMove(index + 1);
}

function resetBoard() {
  $("#end_game").fadeOut(500, function(){
    data.pause = false;
    aiPlay();
  });
  $("#main").animate({opacity: "1"}, 500)
  $(".space").each(function(){
    $(this).text(null);
  });
  data.board = ["","","","","","","","",""];
}


//ai.js
var CORNERS = [0, 2, 6, 8]

//AI Functions
function aiPlay() {
  //Order of possible plays taken from Wikipedia:
  //https://en.wikipedia.org/wiki/Tic-tac-toe#Strategy
  if (!isPlayerTurn() && !data.pause) {
    if (aiWin()) {}
    else if (aiBlock()) {}
    else if (aiFork()) {}
    else if (aiBlockFork()) {}
    else if (aiCenter()) {}
    else if (aiOpposingCorner()) {}
    else if (aiCorner()) {}
    else {aiDefault();}
    endTurn();
  }
}

//All of the below functions will try to complete an action for the ai
//and return true if they did, or false if they did nothing

//Win the game
function aiWin() {
  var moves = getWinningMoves(data.board, data.player2);
  if (moves.length > 0) {
    var move = pickRandomSpace(moves);
    placeMoveByIndex(move);
    return true;
  }
  return false;
}

//Block the enemy from winning
function aiBlock() {
  var moves = getWinningMoves(data.board, data.player1);
  if (moves.length > 0) {
    var move = pickRandomSpace(moves);
    placeMoveByIndex(move);
    return true;
  }
  return false;
}

//Create a fork
//A fork is a situation where you can get 2 winning moves
function aiFork() {
  var forks = getForkMoves(data.board, data.player2);
  if (forks.length > 0) {
    var move = pickRandomSpace(forks);
    placeMoveByIndex(move);
    return true;
  }
  return false;
}

//Block a fork
function aiBlockFork() {
  var forks = getForkMoves(data.board, data.player1);
  if (forks.length == 1) {
    var move = forks[0];
    placeMoveByIndex(move);
    return true;
  } else if (forks.length > 1) {
    //If there are multiple forks, then a threatening winning move must be found
    //that does not let the enemy place a fork at the same time
    var spaces = getAvailableSpaces();
    spaces = spaces.filter(function(space) {
      return !forks.includes(space);
    });

    var moves = [];
    for (var i = 0; i < spaces.length; i++) {
      var copyBoard = getBoardCopy(data.board);
      var space = spaces[i];

      copyBoard[space] = data.player2;
      var wins = getWinningMoves(copyBoard, data.player2)
      if (wins.length > 0) {
        moves.push(space);
      }
    }

    var move = pickRandomSpace(moves);
    placeMoveByIndex(move);
    return true;
  }
  return false;
}

//Place in the center, if available
function aiCenter() {
  var spaces = getAvailableSpaces();
  var CENTER = 4;

  if (spaces.includes(CENTER)) {
    placeMoveByIndex(CENTER);
    return true;
  }
  return false;
}

//Place in a corner that is opposite the enemy
function aiOpposingCorner() {
  var spaces = getAvailableSpaces();
  var enemyCorners = [];
  data.board.forEach(function(move, space){
    if (CORNERS.includes(space) && move == data.player1) {
      enemyCorners.push(space);
    }
  });

  for (var i = 0; i < enemyCorners.length; i++) {
    var copyBoard = getBoardCopy(data.board);
    var enemyCorner = enemyCorners[i];
    var oppositeCorner = null;

    switch(enemyCorner) {
      case 0:
        oppositeCorner = 8;
        break;
      case 2:
        oppositeCorner = 6;
        break;
      case 6:
        oppositeCorner = 2;
        break;
      case 8:
        oppositeCorner = 0;
    }
    if (spaces.includes(oppositeCorner)) {
      placeMoveByIndex(oppositeCorner);
      return true;
    }
  }
  return false;
}

//Take any open corner
function aiCorner() {
  var corners = getAvailableSpaces().filter(function(space){
    return CORNERS.includes(space);
  });

  if (corners.length > 0) {
    var space = pickRandomSpace(corners);

    placeMoveByIndex(space);
    return true;
  }
  return false;
}

//Default to any open space
function aiDefault() {
  var space = pickRandomSpace(getAvailableSpaces());
  placeMoveByIndex(space);
  return true;
}

//Other Functions

//Get all the winning moves for a specific player and board state
function getWinningMoves(board, player) {
  var spaces = getAvailableSpaces();
  var moves = [];
  for (var i = 0; i < spaces.length; i++) {
    var copyBoard = getBoardCopy(board);
    var space = spaces[i];

    copyBoard[space] = player;
    if (checkForWinner(copyBoard, player)) {
      moves.push(space);
    }
  }
  return moves;
}

//Get all possible forks for a specific player and board state
function getForkMoves(board, player) {
  var spaces = getAvailableSpaces();
  var forks = [];
  for (var i = 0; i < spaces.length; i++) {
    var copyBoard = getBoardCopy(data.board);
    var space = spaces[i];

    copyBoard[space] = player;
    var moves = getWinningMoves(copyBoard, player);
    if (moves.length > 1) {
      forks.push(space);
    }
  }
  return forks;
}

//Returns a random spaces from an array of spaces
function pickRandomSpace(spaces) {
  return spaces[Math.floor(Math.random() * spaces.length)]
}


Comments