Pen Settings

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

You're using npm packages, so we've auto-selected Babel for you here, which we require to process imports and make it all work. If you need to use a different JavaScript preprocessor, remove the packages in the npm tab.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Use npm Packages

We can make npm packages available for you to use in your JavaScript. We use webpack to prepare them and make them available to import. We'll also process your JavaScript with Babel.

⚠️ This feature can only be used by logged in users.

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.

HTML Settings

Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

            
              <main>

    <div class="container">

      <div id="main-header" class="row text-center">
        <div class="col">
          <h1 class="display-4 text-center">Tic Tac Toe</h1>
        </div>
      </div>

      <div class="row">
        <!-- Board Begin -->
        <div id="clock" class="m-auto">
          <!-- Board Display Begin-->
          <div id="display" class="text-center unselectable">
            <button type="button" class="board-cell br bb" id="0_0"><i class="cell-i fa fa-3x"></i></button>
            <button type="button" class="board-cell bb" id="0_1"><i class="cell-i fa fa-3x"></i></button>
            <button type="button" class="board-cell bb bl" id="0_2"><i class="cell-i fa fa-3x"></i></button>
            <button type="button" class="board-cell br" id="1_0"><i class="cell-i fa fa-3x"></i></button>
            <button type="button" class="board-cell" id="1_1"><i class="cell-i fa fa-3x"></i></button>
            <button type="button" class="board-cell bl" id="1_2"><i class="cell-i fa fa-3x"></i></button>
            <button type="button" class="board-cell bt br" id="2_0"><i class="cell-i fa fa-3x"></i></button>
            <button type="button" class="board-cell bt" id="2_1"><i class="cell-i fa fa-3x"></i></button>
            <button type="button" class="board-cell bl bt" id="2_2"><i class="cell-i fa fa-3x"></i></button>
          </div> <!-- End Board Display -->

          <div id="buttons" class="container">
            <div class="row btn-row">

              <div id="playerSelect" class="btn-group col" data-toggle="buttons">
                <label class="btn btn-outline-secondary active radio">
                  <input type="radio" name="X" id="radio-x" autocomplete="off" checked><i class="fa fa-times"></i></label>
                <label class="btn btn-outline-secondary radio">
                  <input type="radio" name="O" id="radio-o" autocomplete="off"><i class="fa fa-circle-o"></i></label>
              </div>

              <div class="col">
                <label id="reset" class="btn btn-outline-secondary"><i id="reset-i" class="fa fa-rotate-left"></i></label>
              </div>

              <div id="opponentSelect" class="btn-group col" data-toggle="buttons">
                <label class="btn btn-outline-secondary active radio">
                  <input type="radio" name="human" id="human" autocomplete="off" checked><i class="fa fa-group"></i></label>
                <label class="btn btn-outline-secondary radio">
                  <input type="radio" name="computer" id="computer" autocomplete="off"><i class="fa fa-laptop"></i></label>
              </div>

            </div>
          </div> <!-- End Buttons -->

        </div> <!-- End Board -->
      </div> 

      <div id="statusdisplay" class="display-2 text-center">
        <span id="status"></span>
      </div>

    </div> <!-- End Container -->
  </main> 
            
          
!
            
              html, body {
  height: 100%;
  min-width: 320px;
}

body {
  width: 100%;
  margin-bottom: 60px;
  background-color: #5A7;
}

header,
main,
footer {
  max-width: 1024px;
  margin: auto;
}

main {
  margin-top: 56px;
  min-height: 100%;
  margin-bottom: 56px;
}

i.fa {
  margin-right: 10px;
  margin-left: 10px;
}

a:hover {
  text-decoration: none;
}

footer {
  display: inline-block;
  position: fixed !important;
  bottom: 0;
  right: 0;
  left: 0;
  padding: .5rem 1.5rem;
  z-index: 50;
  height: 56px;
}

input {
  background-color: #222;
  background-color: transparent;
}

#main-header {
  padding: 15px 0 15px 0;
  font-family: 'Permanent Marker', cursive;
}

#clock {
  width: 320px;
  border-radius: 20px;
  padding: 10px;
  background: #222;
  background: #222 -webkit-radial-gradient(20% 80%, 60% 60%, rgba(255,255,255,.12), rgba(0,0,0,.2));
  box-shadow: 0px 2px .7px 1px #777, inset 0 -7px rgba(0,0,0,.4);
}

#display {
  width: 100%;
  height: 250px;
  margin-top: 20px;
  border-radius: 10px;
  margin: auto;
  padding: 20px;
  color: #DDD;
  background: #111;
  background: linear-gradient(to top left,  #000 20%, #444 100%);
  box-shadow: inset 3px 3px 4px #111;
}

.board-cell {
  width: 80px;
  height: 70px;
  margin: 0 -4px 0 0;
  border: 0;
  color: #CCC;
  background-color: transparent;
}

.board-cell:focus {
  outline: none;
}

/* Fixes issus with FontAwesome resizing buttons */
.board-cell i.fa {
  display: inline;
}

#statusdisplay {
  font-family: 'Permanent Marker', cursive;
}

.winner {
  color: limegreen;
}

.loser {
  color: red;
}


#buttons {
  margin-top: 20px;
  margin-bottom: 20px;
  width: 100%;
  display:flex;
  justify-content:center;
}

#buttons .btn {
  padding: 0.4rem 0.4rem;
}

#buttons label.active {
  background-color: #DDD;
  color: #222;
}

#buttons label i {
  font-size: 0.8em;
}

#buttons label.active i,
#reset-i {
  font-size: 1.33em !important;
}

#buttons .btn-group {
  padding-left: 0;
  padding-right: 0;
}

#buttons .btn-row {
  padding-top: 10px;
  padding-left: 5px;
  padding-right: 5px;
}




/* Utility Styles */
.unselectable {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    cursor: default;
}

.display-5 {
  font-size: 2.5rem;
  font-weight: 300;
  line-height: 1.1;
}

.white-shadow {
  color: #DDD;
}
.white-shadow:hover,
.white-shadow:focus {
  color: #FFF;
  text-shadow: 0 0 8px #FFF;
}

.radio.disabled {
  pointer-events: none;
}

/* Borders for game board */
.bt {
  border-top: 2px solid #666 !important;
}
.br {
  border-right: 2px solid #666 !important;
}
.bb {
  border-bottom: 2px solid #666 !important;
}
.bl {
  border-left: 2px solid #666 !important;
}



/* Display size adjustments */
@media only screen and (max-width: 768px){
  footer {
    text-align: center;
  }

  .display-3 {
    font-size: 2.8rem;
  }

}
            
          
!
            
              
var board,
    player,
    currentPlayer,
    opponent,
    opponentType,
    gameStarted;

$('document').ready(function(){

  resetGame();
  
  // Bind functions to events
  $('button').click(function(){ $(this).blur(); });
  $('.board-cell').click(function(){ cellClicked($(this).attr("id").split("_").map(Number)); });
  $('#playerSelect input').on('change', function(){ setPlayer(this.getAttribute("name")); });
  $('#opponentSelect input').on('change', function(){ opponentType = this.getAttribute("name"); });
  $('#reset').click(function(){ resetGame(); });

});


// Lock board once first move has been made
function lockRadios(){
  $(".radio", "#buttons").addClass("disabled");
  $("input", "#buttons").prop("disabled", true);
}


// Action performed when a cell on the board is clicked.  pos=[row, col]
function cellClicked(pos){
  var row = pos[0], 
      col = pos[1];

  lockRadios();
  gameStarted = true;

  // Make move if cell clicked is available else do nothing
  if (board[row][col]){
    return;
  } else { 
    board[row][col] = currentPlayer;
    displayMove(currentPlayer, row, col);
  }

  // Check for a winner
  var winner = checkForWin(), i;
  if (winner.score){
    $(".board-cell").prop("disabled", true);

    if (winner.score === -10){ 
      gameOver("Winner!");
      for (i in winner.pos){
        $("#" + winner.pos[i]).addClass("winner");
      }
    }
    else if (winner.score === 10) {
      gameOver("Loser!");
      for (i in winner.pos){
        $("#" + winner.pos[i]).addClass("loser");
      }
    }
    return;
  // End game as draw if no moves left and no winner
  } else {
    if (!movesLeft()){
      gameOver("Draw!");
      return;
    }
  }

  // Switch player if there are still moves available and no winner
  if (movesLeft()){
    switchPlayer();
  }
}


// Displays message and fades out board when game is over
function gameOver(message){
  $("#status").text(message);
  $(".board-cell").prop("disabled", true);
  $("#status, .cell-i").fadeOut(2000, function(){
    resetGame();
  });
  
}


// Sets player and opponent when user toggles playerSelect radio btn-group
function setPlayer(name){
  currentPlayer = player = name;
  if (player == "X") { opponent = "O"; }
  else { opponent = "X"; }
} 


// Switches currentPlayer when player completes a move
function switchPlayer(){
  if (currentPlayer == player){
    currentPlayer = opponent;
  } else {
    currentPlayer = player;
  }

  // Computer selects move based on minimax algorithm then pseudo clicks cell
  if (gameStarted && currentPlayer == opponent && opponentType == "computer"){
    var best = bestMove();
    cellClicked([best.row, best.col]);
  } 
}


// Displays move on board
function displayMove(currPlayer, x, y){
  var $cellIcon = $("#" + x + "_" + y + " i"); 
  if (currPlayer == "X"){
    $cellIcon.addClass("fa-times");
  } else {
    $cellIcon.addClass("fa-circle-o");
  }
}


// Resets game board to initial state
function resetGame() {

  // Set initial values for player and oppenent selections and empties board
  board = [ ["","",""], ["","",""], ["","",""] ];
  currentPlayer = player = $("#playerSelect label.active input").attr('name');
  if (player == "O") { opponent = "X"; } else { opponent = "O"; }
  opponentType = $("#opponentSelect label.active input").attr('name');
  gameStarted = false;

  // Set CSS and HTML to inital state
  $(".cell-i").removeClass("fa-times");
  $(".cell-i").removeClass("fa-circle-o");
  $("label", "#buttons").removeClass("disabled");
  $("input", "#buttons").prop("disabled", false);
  $(".board-cell").prop("disabled", false);
  $(".board-cell").removeClass("winner loser");
  $("#status").text("");
  $("#status, .cell-i").show();

}


// Checks rows, cols, and diagnols for three of the same
function checkForWin(){
  var result = {pos: [], score: 0};

  // Checks if three values in row,col, or diagonal are same. Returns object
  function threeInARow(pos) {
    var r = {pos:pos, score:0};

    if (pos[0] == pos[1] && pos[1] == pos[2]){
      if (pos[0] == player){
        r.score = -10;
      } else if (pos[0] == opponent){
        r.score = 10;
      }
    }
    return r;
  }

  // Check rows and columns
  for (var i = 0; i < 3; i++){
    // Check row
    result = threeInARow([ board[i][0], board[i][1], board[i][2] ]);
    if (result.score){ 
      result.pos = [ i+"_0", i+"_1", i+"_2" ];
      return result; 
    }
    // Check col
    result = threeInARow([ board[0][i], board[1][i], board[2][i] ]);
    if (result.score){ 
      result.pos = [ "0_"+i, "1_"+i, "2_"+i ];
      return result; 
    }
  }

  // Check diagonals
  result = threeInARow([ board[0][0], board[1][1], board[2][2] ]);
  if (result.score){ 
    result.pos = [ "0_0", "1_1", "2_2"];
    return result; 
  }
  result = threeInARow([ board[0][2], board[1][1], board[2][0] ]);
  if (result.score){ 
    result.pos = [ "0_2", "1_1", "2_0" ];
    return result; 
  }

  // Return results with score of 0 if no winner
  return result;
}


// Iterates over board and checks for available moves
function movesLeft(){
  for (var row = 0; row < 3; row++){
    for (var col = 0; col < 3; col++){
      if (!board[row][col]){
        return true;
      }
    }
  }
  return false;
}


// minimax function from https://goo.gl/Se8kN4 converted to JS
function minimax(depth, isMaximizingPlayer){
  var score = checkForWin().score,
      best, row, col;

  // Return score if player or opponent has won the game
  if (score == 10) {
    return score - depth;
  }
  if (score == -10){
    return score + depth;
  }

  if (!movesLeft()){ return 0; }

  // Computer's turn
  if (isMaximizingPlayer){
    best = -1000;

    // Iterate over board
    for (row = 0; row < 3; row++){
      for (col = 0; col < 3; col++){

        // Make a move at first empty position then recursively choose the best
        if (!board[row][col]) {
          board[row][col] = opponent;
          best = Math.max( best, minimax(depth + 1, false) );

          // Undo move
          board[row][col] = "";
        }
      }
    }
    return best;
  } else {
    best = 1000;

    // Iterate over board
    for (row = 0; row < 3; row++){
      for (col = 0; col < 3; col++){

        // Make a move at first empty position then recursively choose the worst
        if (!board[row][col]){
          board[row][col] = player;
          best = Math.min( best, minimax(depth + 1, true) );
          
          // Undo move
          board[row][col] = "";
        } 
      }
    }
    return best;
  }
}


// bestMove function from https://goo.gl/Se8kN4 converted to JS
function bestMove(){
  var bestVal = -1000,
      move = {row: -1, col: -1},
      row, col;

  // Iterate over board
  for (row = 0; row < 3; row++){
    for (col = 0; col < 3; col++){

      // Check if cell is empty
      if (!board[row][col]){
        board[row][col] = opponent;

        // Compute evaluation function for this move
        var moveVal = minimax(0, false);

        // Undo the move
        board[row][col] = "";

        // If value of current move is greater than best value, update best
        if (moveVal > bestVal){
          move.row = row;
          move.col = col;
          bestVal = moveVal;
        }
      }
    }
  }

  return move;
}
            
          
!
999px
🕑 One or more of the npm packages you are using needs to be built. You're the first person to ever need it! We're building it right now and your preview will start updating again when it's ready.

Console