cssAudio - ActiveCSS - ActiveGeneric - ActiveHTML - ActiveImage - ActiveJS - ActiveSVG - ActiveText - Activefile-genericVideo - Activehtmlicon-personicon-teamoctocatspinnerstartv

Pen Settings

CSS Base

Vendor Prefixing

Add External CSS

These stylesheets will be added in this order and before the code you write in the CSS editor. You can also add another Pen here, and it will pull the CSS from it. Try typing "font" or "ribbon" below.

Quick-add: + add another resource

Add External JavaScript

These scripts will run in this order and before the code in the JavaScript editor. You can also link to another Pen here, and it will run the JavaScript from it. Also try typing the name of any popular library.

Quick-add: + add another resource

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

            
              <div id="modal" class="modal">
  <h1>Let's play Tic-Tac-Toe</h1>
  <p>Pick one:</p>
  <button class="choose-player player" type="button" name="X">X</button><span> or </span><button class="choose-player player" type="button" name="O">O</button>
</div>
<div id="winmodal" class="modal" style="display: none;">
  <p id="endingdialog"></p>
  <p>Would you like to play again?</p>
  <button id="yesplayagain">Yes</button><button id="noplayagain">No</button>
</div>

<div id="board">
  <button class="pick-a-button"></button>
  <button class="pick-a-button"></button>
  <button class="pick-a-button"></button>
  <button class="pick-a-button"></button>
  <button class="pick-a-button"></button>
  <button class="pick-a-button"></button>
  <button class="pick-a-button"></button>
  <button class="pick-a-button"></button>
  <button class="pick-a-button"></button>
</div>
            
          
!
            
              body, html {
  background: #435943;
  color: #9DFADB;
  font-size: 24px;
}
h1 { margin: 0; }
button {
  background: transparent;
  border: 2px solid #9DFADB;
  color: #9DFADB;
}

.modal {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
  z-index: 100;
  background: #527554;
  border-radius: 1em;
  padding: 1em;
}

#board {
  display: flex;
  flex-wrap: wrap;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  justify-content: space-around;
  align-content: space-around;
}

.pick-a-button {
  width: 31%;
  height: 30%;
}

.pick-a-button:nth-child(-n+3) {
  border-top: 0;
}

.pick-a-button:nth-last-child(-n+3) {
  border-bottom: 0;
}

.pick-a-button:nth-child(3n + 1) {
  border-left: 0;
}
.pick-a-button:nth-child(3n) {
  border-right: 0;
}

.choose-player {
  width: 2em;
  height: 2em;
}

.modal, .cpu {
  font-family: 'VT323', monospace;
  color: #9DFADB;
}

.cpu {
  font-size: 80px;
  position: relative;
}

.player {
  font-family: 'Permanent Marker', cursive;
  font-size: 72px;
  color: #f44;
}


            
          
!
            
              (function() {
  var winning = /^(...)*(\w)\2\2|^..(\w).\3.\3|(\w)..(\4|.\4.)..\4/i,
    isDraw = /^(\w){9}$/,
    boardState = "         ",
    swapPlayer = {
      X: "O",
      O: "X"
    },
    moveCache = {},
    pickButtons = document.querySelectorAll(".pick-a-button"),
    choiceButtons = document.querySelectorAll(".choose-player"),
    playerSymbol, cpuSymbol;

  /**
   * an HTML DOM element.
   * @external Element
   * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement}
   */

  /**
   * Based on a playeyer, game board, and move, returns the updated board.
   * @function updateBoard
   * @param {string} player - whether the player is an X or an O
   * @param {string} game - the current board state
   * @param {number} position - the numeric index of position on the board where the player will move.
   */
  function updateBoard(player, game, position) {
    if (position < 0 || position >= game.length) {
      throw "Invalid Position: position submitted is outside the board";
    }
    if (game[position] !== " ") {
      throw "Invaild Position: position not empty";
    }

    return game.substr(0, position) + player + game.substr(position + 1);
  }

  /**
   * Creates the current moveState of the 
   * @constructor MoveState
   * @property {number} position - position of the move on the board
   * @property {string} outcome - what happens when the player moves in that position
   * @property {string} player - movement of the player on the board.
   * @property {string} previousState - previous state of the game.
   */
  function MoveState(game, player, position) {
    this.player = player;
    this.position = position;
    this.previousState = game;
    this.outcome = updateBoard(player, game, position);
  }

  /** 
   * Calculate the score of the current game
   * @function calculateScore
   * @param {string} playeer - Whether the plaeyr is an X or an O.
   * @param {string} game - string representation of the game
   * @param {number} depth - how many moves down the pipe we are
   * @returns {number} - the score of the current move.
   */
  function calculateScore(player, game, depth) {
    if (winning.test(game)) {
      if (player === cpuSymbol) {
        return 12 - depth;
		  //return 10;
      }
      //return -10;\
	    return depth - 12;
    }
    return 0;
  }

  /**
   * Gets a list of moves from the current game, based on the current player
   * @function getMoves
   * @param {string} player - whether the player is an X or an O
   * @param {string} game - current string representation of the game
   * @returns {MoveState[]} - a list of moves based on the current move.
   */
  function getMoves(player, game) {
    var gameSteps = game.split("");

    var answers = gameSteps.map(function(letter, index) {
      if (letter === " ") {

        return new MoveState(game, player, index);
      }
      return null;
    }).filter(function(x) {
      return !!x;
    });

    return answers;
  }

  /**
   * Get the best move for the player
   * @function minimax
   * @param {string} player - whether the player playing is an X or an O
   * @param {string} game - current state of the game
   * @param {number} depth - How far ahead the cpu is looking
   * @returns {number} - score of the current game.
   */
  function minimax(player, game, depth) {
    var scores = [],
      myAnswer;
    if (isDraw.test(game)) {
      return 0;
    }

    if (winning.test(game)) {
      return calculateScore(player, game, depth);
    }

    if (moveCache.hasOwnProperty(game)) {
      return moveCache[game];
    }

    scores = getMoves(player, game).map(function(move) {

      var yourNextMove = minimax(swapPlayer[player], move.outcome, depth + 1);
      if (isNaN(yourNextMove)) {
        console.log("Move: ", move, "depth: ", depth);
        throw new Error("minimax should not give NaN answer")
      }
      moveCache[move.outcome] = yourNextMove;

      return moveCache[move.outcome];
    });

    if (scores.length === 0) {
      return 0;
    }

    if (player === playerSymbol) {
      myAnswer = Math.max.apply(null, scores);
    } else {
      myAnswer = Math.min.apply(null, scores);
    }

    return myAnswer;
  }

  /**
   * Calculates the best move based on minimax
   * @function getBestMove
   * @param {string} player - whether the current player is X or O
   * @param {string} game - current state of the game
   * @returns {number} - best position on the board
   */
  function getBestMove(player, game) {
    var moves = getMoves(player, game);

    var scores = moves.map(function(move) {
      return minimax(swapPlayer[player], move.outcome, 0);
    });

    var bestScore, bestMoveIndex;

    if (player === cpuSymbol) {
      bestScore = Math.min.apply(null, scores);
    } else {
      bestScore = Math.max.apply(null, scores);
    }
    bestMoveIndex = scores.indexOf(bestScore);

    return moves[bestMoveIndex].position;
  }

  /**
   * Resets the buttons so you can select them again.
   * @function resetButtonState
   * @param {external:Element} button - HTML button
   */
  function resetButtonState(button) {
    button.innerHTML = "";
    button.className = "pick-a-button";
    button.removeAttribute("disabled");
  }

  /**
   * Sets the button state a specific way, based on if the player or computer goes
   * @function setButtonState
   * @param {external:Element} button - HTML button
   * @param {string} symbol - whether the button gets an X or an O
   */
  function setButtonState(button, symbol) {
    button.innerHTML = symbol;
    button.className += symbol === cpuSymbol ? " cpu" : " player";
    button.setAttribute("disabled", "disabled");
  }

  /**
   * CPU makes its move
   * @function cpuMove
   * 
   */
  function cpuMove() {
    var position;
    
    position = getBestMove(cpuSymbol, boardState);

    setButtonState(pickButtons[position], cpuSymbol);

    boardState = updateBoard(cpuSymbol, boardState, position);

    if (winning.test(boardState)) {
      playAgain("I win! Better luck next time.");
    } else if (isDraw.test(boardState)) {
      playAgain("A strange game. The only winning move is not to play.");
    }

  }


  /**
   * Show dialog when computer asks whether player wants to play again.
   * @function playAgain
   * @param {string} comment - something that the computer says.
   */
  function playAgain(comment) {
    document.getElementById("endingdialog").innerHTML = comment
    document.getElementById("winmodal").style.display = "";
  }

  /**
   * Here is where we handle the UI
   */

  Array.prototype.forEach.call(choiceButtons, function(button) {
    button.addEventListener("click", function() {
      // initialize the variables
      playerSymbol = button.name;
      cpuSymbol = swapPlayer[button.name];
      boardState = "         ";
      // hide the dialog.
      document.getElementById("modal").style.display = "none";
      Array.prototype.forEach.call(pickButtons, resetButtonState);

      if (cpuSymbol === "X") {
        // todo, make X go first
        cpuMove();
      }
    });
  });

  Array.prototype.forEach.call(pickButtons, function(button, index) {
    button.addEventListener("click", function() {
      setButtonState(button, playerSymbol);
      boardState = updateBoard(playerSymbol, boardState, index);

      if (winning.test(boardState)) {
        playAgain("You have won! Does not compute...");
      } else if (isDraw.test(boardState)) {
        playAgain("A strange game. The only winning move is not to play.");
      } else {
        cpuMove();
      }

    });
  });

  var yesButton = document.getElementById("yesplayagain");
  yesButton.addEventListener("click", function () {
    document.getElementById("winmodal").style.display = "none";
    document.getElementById("modal").style.display = "";
  });

  var noButton = document.getElementById("noplayagain");
  noButton.addEventListener("click", function () {
    document.getElementById("winmodal").style.display = "none";
  });


}());
            
          
!
999px
Close

Asset uploading is a PRO feature.

As a PRO member, you can drag-and-drop upload files here to use as resources. Images, Libraries, JSON data... anything you want. You can even edit them anytime, like any other code on CodePen.

Go PRO

Loading ..................

Console