<html lang="en">

<head>
  <meta charset="utf-8">
 

  <link href='https://fonts.googleapis.com/css?family=Open+Sans:300,400,700' rel='stylesheet' type='text/css'>
  <link href='https://fonts.googleapis.com/css?family=Lato:100,300italic,700,900' rel='stylesheet' type='text/css'>


  <!--Import Google Icon Font-->
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <link href='https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900' rel='stylesheet' type='text/css'>

 
  





  <title>Tic-Tac-Toe</title>
</head>

<body>

  <nav>
    <div class="nav-wrapper z-depth-2">
      <a href="#" class="brand-logo center">Tic-Tac-Toe</a>
      <a href="#" data-activates="mobile-menu" class="button-collapse"><i class="material-icons">menu</i></a>
      <ul class="right hide-on-med-and-down">
        <li><a href="mailto:[email protected]"><i class="icon-color fa fa-envelope"></i></a></li>
        <li><a href="https://www.linkedin.com/in/brucewilliamyoung" target="_blank"><i class="icon-color fa fa-linkedin"></i></a></li>
        <li><a href="https://twitter.com/mutantspore" target="_blank"><i class="icon-color fa fa-twitter"></i></a></li>
        <li><a href="https://codepen.io/MutantSpore/" target="_blank"><i class="icon-color fa fa-codepen"></i></a></li>
      </ul>
            <div class="black side-nav" id="mobile-menu">
                <div class="container middle person">
                    <img class="portrait" src="http://2am.ninja/img/bruce_contact3.jpg">
                    <span>Bruce Young</span>
                </div>
                <ul class="contact">
                    <li><a href="mailto:[email protected]"><i class="fa fa-envelope"></i></a></li>
                    <li><a href="https://www.linkedin.com/in/brucewilliamyoung" target="_blank"><i class="fa fa-linkedin"></i></a></li>
                    <li><a href="https://twitter.com/mutantspore" target="_blank"><i class="fa fa-twitter"></i></a></li>
                    <li><a href="https://codepen.io/MutantSpore/" target="_blank"><i class="fa fa-codepen"></i></a></li>
                </ul>


            </div>
    </div>
  </nav>





<div class="container">

  <!-- Game Board -->
  <div id="board" class="container">
    <div class="row top valign-wrapper">
      <div class="square left col s4 valign waves-effect waves-light flow-text" id="0"></div>
      <div class="square middle col s4 valign waves-effect waves-light flow-text" id="1"></div>
      <div class="square right col s4 valign waves-effect waves-light flow-text" id="2"></div>
    </div>
    <div class="row middle valign-wrapper">
      <div class="square left col s4 valign waves-effect waves-light flow-text" id="3"></div>
      <div class="square middle col s4 valign waves-effect waves-light flow-text" id="4"></div>
      <div class="square right col s4 valign waves-effect waves-light flow-text" id="5"></div>
    </div>
    <div class="row bottom valign-wrapper">
      <div class="square left col s4 valign waves-effect waves-light flow-text" id="6"></div>
      <div class="square middle col s4 valign waves-effect waves-light flow-text" id="7"></div>
      <div class="square right col s4 valign waves-effect waves-light flow-text" id="8"></div>
    </div>
  </div>

</div>


  <!-- Score Board -->
  <div id="score-board" class="container">
    <div class="row">
      <div class="col s12">
        <div class="card-panel transparent">
          <span class="bluetext">
            <div class="row center-align">
              <div class="col s3">
                <span class="flow-text center-align">
                  <span><i class="fa fa-2x fa-user"></i></span>
                  <span id="score0" class="score">0</span>
                </span>
              </div>
              <div class="col s3">
                <span class="flow-text center-align">
                  <i class="fa fa-2x fa-ban"></i>
                  <span id="score1" class="score">0</span>
                </span>
              </div>
              <div class="col s3">
                <span class="flow-text center-align">
                  <i class="fa fa-2x fa-laptop"></i>
                  <span id="score2" class="score">0</span>
                </span>
              </div>
              <div class="col s3">
                <a id="new-game" class="btn-floating btn-large waves-effect waves-light amber darken-4"><i class="medium material-icons">replay</i></a>
              </div>
            </div>
          </span>
        </div>
      </div>
    </div>
  </div>





  <!-- Get Player Token Modal -->
  <div id="chooseModal" class="modal amber darken-4">
    <div class="modal-content">
      <div class="row">
        <div class="choose col s6 waves-effect waves-light" id="X"><i class='fa fa-times'></i></div>
        <div class="choose col s6 waves-effect waves-light" id="O"><i class='fa fa-circle-o'></i></div>
      </div>
    </div>
  </div>




  
</body>

</html>
body {
  background-color: #6795C2;
}

.nav-wrapper {
  background-color: #083968;
}

#board {
  width: 90vw;
  height: 90vw;
  min-width: 300px;
  min-height: 300px;
  max-width: 600px;
  max-height: 600px;
  background-color: rgba(64, 116, 167,1);
  box-shadow: 0 3px 6px 0 rgba(0, 0, 0, 0.32), 0 3px 12px 0 rgba(0, 0, 0, 0.24);
  text-align: center;
  margin-top: 50px;
}

#score-board {
  min-width: 300px;
  max-width: 600px;
  margin-top: 30px;
}

.score {
  font-size: 1.7em;
  color: #bbdefb;
}

.bluetext {
  color: rgba(64, 116, 167,1);
}

.icon-color{
  color: rgba(64, 116, 167,1);
}

.portrait {
  width: 200%;
  height: auto;
}

.person {
  font-size: 1.5em;
  color: white;
  text-align: center;
  width: 100%;
  line-height: 1.5em;
}

.contact {
  text-align: center;
}

.square {
  display: inline-block;
  margin: 0px;
  padding: 0px;
  border: 0px;
  width: calc(100%/3);
  height: 100%;
  background-color: rgba(0,0,0,0.0);
  border-style: none;
  cursor: pointer;
  text-align: center;
}

.who {
    color: #bbdefb;
    font-size: 5.5em;
    margin: 0px;
    border: 0px;
    padding: 0px;
}

.row.top,
.row.middle {
  border-bottom: dotted 2px #6795C2;
}

.square.left,
.square.middle {
  border-right: dotted 2px #6795C2;
}


#board > .row {
  height: calc(100%/3);
  margin: 0px;
  padding: 0px;
}


.choose {
  text-align: center;
  font-size: 8em;
  cursor: pointer;
  background-color: #eee;
  color: #555;
}

.choose:hover {
  background-color: rgba(0,0,0,0.0);
  color: #000;
}

.modal .modal-content {
    padding: 0px;
    margin: 0px;
}

.row {
  padding: 0px;
  margin: 0px;
}

#mobile-menu {
  padding: 0px;
  margin: 0px;
}
$(document).ready(function() {

    var squares = [0, 1, 2, 3, 4, 5, 6, 7, 8];
    var used = [0, 0, 0, 0, 0, 0, 0, 0, 0];
    var X_token = "<i class='fa fa-times'></i>";
    var O_token = "<i class='fa fa-circle-o'></i>";
    var humanPlayer = X_token;
    var aiPlayer = O_token;
    var empty = 0;
    var human = 1;
    var ai = 2;
    var hasMoved = false;
    var gameOver = false;
    var how = [];
    var score = [0, 0, 0];
    var delay;
    var move = {};
    var moveCounter = 0;
    var score_TIE = 1;
    var score_HUMAN = 0;
    var score_AI = 2;
    var outcome = null;


    var board = [
        [0, 0, 0],
        [0, 0, 0],
        [0, 0, 0]
    ];

    var win = [
        [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 humanSound = new Audio('audio/water_droplet_3.mp3');
    var aiSound = new Audio('audio/water_droplet.mp3');



    function setNewGame() {
        squares.forEach(function(v) {
            $('#' + v).empty();
            $('#' + v).css('background-color', 'rgba(0,0,0,0.0)');
        });

        board = [
            [0, 0, 0],
            [0, 0, 0],
            [0, 0, 0]
        ];
        hasMoved = false;
        outcome = null;
        moveCounter = 0;
        gameOver = false;
        how = [];
        move = {};
    }


    function playSound(sound) {
        sound.play();
    }


    function moveHUMAN(here) {
        if (!(board[here.x][here.y])) {
            board[here.x][here.y] = human;
            // playSound(humanSound);
            drawMove(here, humanPlayer, 100);
            hasMoved = true;
        }
    }


    function moveAI_Rnd() {
        var min = Math.min.apply(null, board.reduce(function(a, b) {
            return a.concat(b);
        }, []));

        var whereMove = {};

        if (min === 0) {
            while (!(hasMoved)) {
                whereMove.x = Math.floor(Math.random() * 3);
                whereMove.y = Math.floor(Math.random() * 3);
                if (!(board[whereMove.x][whereMove.y])) {
                    board[whereMove.x][whereMove.y] = ai;
                    drawMove(whereMove, aiPlayer, 300);
                  //   playSound(aiSound);
                    hasMoved = true;
                }
            }
        }
    }




    function moveAI_Wiki() {

        // transfer board to used
        used = [0, 0, 0, 0, 0, 0, 0, 0, 0];
        var corner = [0, 2, 6, 8];
        var side = [1, 3, 5, 7];
        var count = 0;
        var theMove = null;
        for (var row = 0; row <= 2; row++) {
            for (var col = 0; col <= 2; col++) {
                used[count] = board[row][col];
                count++;
            }
        }

        function move() {
            var whereMove = {};
            whereMove.x = parseInt(theMove / 3);
            whereMove.y = theMove % 3;
            board[whereMove.x][whereMove.y] = ai;
            drawMove(whereMove, aiPlayer, 300);
            hasMoved = true;
            moveCounter++;
        }


        // opening move
        function opening() {
            if (theMove === null && (used[1] === human || used[3] === human)) {
                theMove = 0;
                move();
            }

            if (theMove === null && (used[5] === human || used[7] === human)) {
                theMove = 8;
                move();
            }

            if (used[4] === human && theMove === null) {
                emptyCorner();
                return;
            }

            if (used[4] === empty && theMove === null && moveCounter === 0 && aiPlayer === X_token) {
                theMove = corner[Math.floor(Math.random() * 4)];
                move();
            }

            if (used[4] === empty && theMove === null) {
                playCenter();
                return;
            }
        }


        // look for a win
        function aWin() {
            win.forEach(function(solution) {
                if ((used[solution[0]] === ai && used[solution[1]] === ai && used[solution[2]] == empty) ||
                    (used[solution[0]] === ai && used[solution[1]] === empty && used[solution[2]] == ai) ||
                    (used[solution[0]] === empty && used[solution[1]] === ai && used[solution[2]] == ai)) {

                    solution.forEach(function(pos) {
                        if (used[pos] === empty && theMove === null) {
                            theMove = pos;
                        }
                    });
                    move();
                }
            });
        }


        // look for a block
        function block() {
            if (used[4] === empty && theMove === null) {
                playCenter();
                return;
            }

            win.forEach(function(solution) {
                if ((used[solution[0]] === human && used[solution[1]] === human && used[solution[2]] == empty) ||
                    (used[solution[0]] === human && used[solution[1]] === empty && used[solution[2]] == human) ||
                    (used[solution[0]] === empty && used[solution[1]] === human && used[solution[2]] == human)) {

                    solution.forEach(function(pos) {
                        if (used[pos] === empty && theMove === null) {
                            theMove = pos;
                        }
                    });
                    move();
                }
            });
        }


        // make fork
        function fork() {
            win.forEach(function(solution) {
                if ((used[solution[0]] === ai && used[solution[1]] === empty && used[solution[2]] == empty) ||
                    (used[solution[0]] === empty && used[solution[1]] === empty && used[solution[2]] == ai) ||
                    (used[solution[0]] === empty && used[solution[1]] === ai && used[solution[2]] == empty)) {

                    if (used[4] === ai &&
                        (([used[0] === human] && used[8] === human) || ([used[2] === human] && used[6] === human))) {
                        side.forEach(function(pos) {
                            if (used[pos] === empty && theMove === null) {
                                theMove = pos;
                            }
                        });
                    } else {
                        corner.forEach(function(pos) {
                            if (used[pos] === empty && theMove === null) {
                                theMove = pos;
                            }
                        });
                    }
                    move();
                }
            });
        }


        // block fork
        function blockFork() {
            if (moveCounter > 1 && used[4] !== empty && theMove === null) {
                emptySide();
                return;
            }

            win.forEach(function(solution) {
                if ((used[solution[0]] === human && used[solution[1]] === empty && used[solution[2]] == empty) ||
                    (used[solution[0]] === empty && used[solution[1]] === empty && used[solution[2]] == human) ||
                    (used[solution[0]] === empty && used[solution[1]] === human && used[solution[2]] == empty)) {

                    side.forEach(function(pos) {
                        if (used[pos] === empty && theMove === null) {
                            theMove = pos;
                        }
                    });
                    move();
                }
            });
        }


        // play center
        function playCenter() {
            if (used[4] === empty && theMove === null) {
                theMove = 4;
                move();
            }
        }


        // opposite corner
        function oppositeCorner() {
            if (used[0] === human && used[8] === empty && theMove === null) {
                theMove = 8;
                move();
            } else if (used[0] === empty && used[8] === human && theMove === null) {
                theMove = 0;
                move();
            } else if (used[2] === human && used[6] === empty && theMove === null) {
                theMove = 6;
                move();
            } else if (used[2] === empty && used[6] === human && theMove === null) {
                theMove = 2;
                move();
            }
        }


        // empty corner
        function emptyCorner() {
            corner.forEach(function(pos) {
                if (used[pos] === empty && theMove === null) {
                    theMove = pos;
                    move();
                }
            });
        }


        //empty side
        function emptySide() {
            side.forEach(function(pos) {
                if (used[pos] === empty && theMove === null) {
                    theMove = pos;
                    move();
                }
            });
        }


        //based on wikipedia solution
        // https://en.wikipedia.org/wiki/Tic-tac-toe

        //0. opening move
        if (theMove === null && moveCounter === 0) {
            opening();
        }

        //1. Win
        if (theMove === null) {
            aWin();
        }

        //2. Block
        if (theMove === null) {
            block();
        }

        //3. Fork
        if (theMove === null) {
            fork();
        }

        //4. Block Fork
        if (theMove === null) {
            blockFork();
        }

        //5. Center
        if (theMove === null) {
            playCenter();
        }

        //6. Opposite corner
        if (theMove === null) {
            oppositeCorner();
        }

        //7. Empty corner
        if (theMove === null) {
            emptyCorner();
        }

        //8. Empty side
        if (theMove === null) {
            emptySide();
        }
    }



    function drawMove(location, who, delay) {
        setTimeout(function() {
            $('#' + (location.x * 3 + location.y)).html("<span class='who valign center-align animated fadeIn'>" + who + "</span>");
        }, delay);
    }


    function checkStatus() {

        var min = Math.min.apply(null, board.reduce(function(a, b) {
            return a.concat(b);
        }, []));

        [ai, human].forEach(function(player) {
            win.forEach(function(solution) {
                var check = 0;
                solution.forEach(function(pos) {
                    if (board[parseInt(pos / 3)][pos % 3] === player) {
                        check++;
                    }
                });
                if (check === 3) {
                    if (player === ai) {
                        outcome = score_AI;
                        gameOver = true;
                        how = solution;
                        score[score_AI]++;
                    } else {
                        outcome = score_HUMAN;
                        gameOver = true;
                        how = solution;
                        score[score_HUMAN]++;
                    }
                }
            });
        });

        // if no moves left to make it's a draw
        if (min !== 0 && !gameOver) {
            hasMoved = true;
            gameOver = true;
            outcome = score_TIE;
            score[score_TIE]++;
            return;
        }
    }



    function showWin() {
        console.log(gameOver, outcome, how);
        how.forEach(function(v) {
            $('#' + v).css('background-color', 'rgba(0,0,0,0.2)');
        });
    }


    function updateScore() {
        score.forEach(function(count, i) {
            $('#score' + i).text(count);
        });

        $('#score' + outcome).addClass('animated flash');
        $('#score' + outcome).one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function() {
            $('#score' + outcome).removeClass('animated flash');
        });
    }


    function control() {

        if (aiPlayer === X_token && moveCounter === 0) {
            hasMoved = false;
            moveAI_Wiki();
            checkStatus();
        }

        $('#board').on('click', '.square', (function() {

            hasMoved = false;

            if (!hasMoved && !gameOver) {
                move.x = parseInt($(this).attr('id') / 3);
                move.y = $(this).attr('id') % 3;
                moveHUMAN(move);
                checkStatus();
            }

            if (hasMoved && !gameOver) {
                hasMoved = false;
                //moveAI_Rnd();
                moveAI_Wiki();
                checkStatus();
            }

            if (hasMoved && gameOver) {
                showWin();
                updateScore();
            }
        }));

    }



    $('#X').click(function() {
        humanPlayer = X_token;
        aiPlayer = O_token;
        $('#chooseModal').closeModal();
        control();
    });

    $('#O').click(function() {
        humanPlayer = O_token;
        aiPlayer = X_token;
        $('#chooseModal').closeModal();
        control();
    });

    $('#new-game').click(function() {
        setNewGame();
        control();
    });


    $(".button-collapse").sideNav();

    setNewGame();
    $('#chooseModal').openModal();


});
Run Pen

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.3/css/materialize.min.css
  2. //cdnjs.cloudflare.com/ajax/libs/animate.css/3.2.3/animate.min.css
  3. https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css

External JavaScript

  1. //cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.3/js/materialize.min.js