A tictactoe algorithm
A Pen by Bruno de Queiroz on CodePen.
| <div id="container"> | |
| <p id="result" class="hide"></p> | |
| <table celpadding="0" celspacing="0"> | |
| <tr> | |
| <td id="pos-0" onclick="TicTacToe.play(0)"></td> | |
| <td id="pos-1" onclick="TicTacToe.play(1)"></td> | |
| <td id="pos-2" onclick="TicTacToe.play(2)"></td> | |
| </tr> | |
| <tr> | |
| <td id="pos-3" onclick="TicTacToe.play(3)"></td> | |
| <td id="pos-4" onclick="TicTacToe.play(4)"></td> | |
| <td id="pos-5" onclick="TicTacToe.play(5)"></td> | |
| </tr> | |
| <tr> | |
| <td id="pos-6" onclick="TicTacToe.play(6)"></td> | |
| <td id="pos-7" onclick="TicTacToe.play(7)"></td> | |
| <td id="pos-8" onclick="TicTacToe.play(8)"></td> | |
| </tr> | |
| </table> | |
| <div id="victory"></div> | |
| <button onclick="TicTacToe.reset()">Reset</button> | |
| </div> |
| window.TicTacToe = (function() { | |
| var _board, _victories, _moves, _finished = false, _players, _victory, _victoryElement, _containerElement, _resultElement; | |
| _containerElement = document.getElementById( 'container' ); | |
| _victoryElement = document.getElementById( 'victory' ); | |
| _resultElement = document.getElementById('result'); | |
| _board = '000000000'.split(''); | |
| _victories = [ '048', '147', '246' , '345', '012' , '258' , '678' , '036' ]; | |
| _players = { | |
| user :[], | |
| ai: [] | |
| }; | |
| function _diff( a1, a2, log ){ | |
| var k = 0, i = 0, diff = []; | |
| for( i = 0 ; i < a2.length ; i++ ){ | |
| if( a1.indexOf( a2[ i ] ) == -1 ){ | |
| diff.push( a2[ i ] ); | |
| } | |
| } | |
| return diff; | |
| } | |
| function _isPossible( n ){ | |
| return _board[ n ] == '0'; | |
| } | |
| function _has( key ){ | |
| var i=0, j=_victories.length; | |
| for( ; i < j ; i++ ){ | |
| if ( key == _victories[ i ] ){ | |
| _victory = _victories[ i ]; | |
| return true; | |
| } | |
| } | |
| return null; | |
| } | |
| function _hasWinner( next ){ | |
| _find( _players.ai, _has , function( result ){ | |
| if ( result === true ){ | |
| return next( 'computer' ); | |
| } | |
| return _find( _players.user, _has, function( result ){ | |
| if ( result === true ){ | |
| return next( 'user' ); | |
| } | |
| return next(); | |
| }); | |
| }); | |
| } | |
| function _find( a, condition, next ){ | |
| var i=0,j,l,b,c,d,e,result,len = a.length; | |
| a.sort(); | |
| for( ; i < len; i++ ){ | |
| b = a[ i ]; | |
| if( a[ i + 3] ){ | |
| e = a[i] + '' + a[i+1] + '' + a[i+2] + '' + a[i+3]; | |
| result = condition.call(this, e ); | |
| if ( result != null ){ | |
| return next( result ); | |
| } | |
| } | |
| for( j = i+1; j < len; j++ ){ | |
| c = b + '' + a[ j ]; | |
| for( l = len -1; l > j; l-- ){ | |
| d = c + '' + a[ l ]; | |
| result = condition.call(this, d ); | |
| if ( result != null ){ | |
| return next( result ); | |
| } | |
| } | |
| result = condition.call(this, c ); | |
| if ( result != null ){ | |
| return next( result ); | |
| } | |
| } | |
| result = condition( b ); | |
| if ( result != null ){ | |
| return next( result ); | |
| } | |
| } | |
| return next(); | |
| } | |
| function _mark( n, u, log ){ | |
| if( n > 8 ) { | |
| return false; | |
| } | |
| if( !_isPossible( n ) ){ | |
| return false; | |
| } | |
| _board[ n ] = u; | |
| _players[ u == '1' ? 'user' : 'ai' ].push( n + ''); | |
| return _draw(); | |
| } | |
| function _write( msg ){ | |
| _resultElement.className = msg != '' ? '' : 'hide'; | |
| _resultElement.innerHTML = msg; | |
| } | |
| function _draw() { | |
| _hasWinner( function( winner ){ | |
| var msg = "Game ended."; | |
| if( winner ){ | |
| _write( msg + " Winner: " + winner ); | |
| _containerElement.className = winner; | |
| return _finished = true; | |
| } | |
| if( _board.join('').indexOf(0) == -1 ){ | |
| _containerElement.className = ''; | |
| _write( msg ); | |
| return _finished = true; | |
| } | |
| }); | |
| var i=0, el, klass; | |
| for( ; i< _board.length; i++ ){ | |
| switch( _board[ i ]){ | |
| case '1': | |
| klass = 'user'; | |
| break; | |
| case '2': | |
| klass = 'computer'; | |
| break; | |
| default : | |
| klass = ''; | |
| } | |
| el = document.getElementById( 'pos-' + i ); | |
| el.className = klass; | |
| } | |
| if( _victory != null ){ | |
| _victoryElement.className = 'won-' + _victory; | |
| } | |
| return true; | |
| } | |
| function _calculate( player, threshold, max, next, log ){ | |
| var i = 0, k = 0, l = 0, user = player, _d, diff = []; | |
| for( ; i < _victories.length; i++ ){ | |
| _d = _diff( user, _victories[ i ].split(''), log ); | |
| if( diff.length == max ){ | |
| break; | |
| } | |
| if( _d.length == threshold ){ | |
| for( k = 0; k < _d.length; k++ ){ | |
| if( diff.indexOf( _d[ k ] == -1 ) ){ | |
| diff.push(_d[ k ]); | |
| } | |
| } | |
| } | |
| } | |
| return next( diff ); | |
| } | |
| function _verify( moves ){ | |
| for( i = 0; i < moves.length; i++ ){ | |
| if( _isPossible( moves[ i ] ) ){ | |
| return moves[ i ]; | |
| } | |
| } | |
| return null; | |
| } | |
| function _verifyPossible( moves ){ | |
| var possibles = []; | |
| for( i = 0; i < moves.length; i++ ){ | |
| if( _isPossible( moves[ i ] ) ){ | |
| possibles.push(moves[ i ]); | |
| } | |
| } | |
| return possibles; | |
| } | |
| function _getNextRandom(){ | |
| var random = Math.floor((Math.random() * 8 )); | |
| return _isPossible( random ) ? random : _getNextRandom(); | |
| } | |
| function _simulate( moves, next ){ | |
| var tempUserA, tempUserB, move, i, j; | |
| tempUserA = _players.user.slice(0); | |
| tempUserB = _players.user.slice(0); | |
| for( i = 2; i <= moves.length; i+=2 ){ | |
| move = moves[ i - 2 ]; | |
| nmove = moves[ i - 1 ]; | |
| if( _isPossible( move ) && _isPossible( nmove )){ | |
| if( nmove ){ | |
| tempUserA.push( nmove ); | |
| return _calculate( tempUserA, 1, 3, function( m ){ | |
| m = _verifyPossible( m ); | |
| if( m.length < 2 ){ | |
| return next( move ); | |
| } else { | |
| tempUserB.push( move ); | |
| return _calculate( tempUserB, 1, 3, function( v ){ | |
| v = _verifyPossible( v ); | |
| if( v.length < 2 ){ | |
| return next( nmove ); | |
| } else { | |
| moves.splice( i - 2, 2 ); | |
| return _simulate( moves, next ); | |
| } | |
| }); | |
| } | |
| }); | |
| } else { | |
| return next() | |
| } | |
| } | |
| } | |
| return next(); | |
| } | |
| function _ai(){ | |
| var move; | |
| if( !_finished ){ | |
| // Verifica se pode ganhar | |
| _calculate( _players.ai, 1, 3, function( finish ){ | |
| if( finish != null ){ | |
| move = _verify( finish ); | |
| if( move ){ | |
| return _mark( move, '2' ); | |
| } | |
| } | |
| // Verifica se o usuario pode ganhar | |
| _calculate( _players.user, 1, 3, function( defend ) { | |
| if( defend != null ){ | |
| move = _verify( defend ); | |
| if( move ){ | |
| return _mark( move, '2' ); | |
| } | |
| } | |
| // Preve a jogada do usuario para determinar a propria jogada | |
| if( _players.user.length == 1 ){ | |
| if( _players.user[ 0 ] != 4 ) | |
| move = 4; | |
| else { | |
| var corners = [ '0', '2', '6', '8' ]; | |
| move = corners[ Math.floor( Math.random() * (corners.length-1) ) ]; | |
| } | |
| return _mark( move, '2' ); | |
| } | |
| _calculate( _players.ai, 2, 8, function( moves ){ | |
| if( moves != null ){ | |
| _simulate( moves, function( move ){ | |
| if( move != null ){ | |
| return _mark( move, '2' ); | |
| } | |
| return _mark( _getNextRandom(), '2' ); | |
| }) | |
| } | |
| }); | |
| }); | |
| }); | |
| } | |
| } | |
| function _play( n ){ | |
| if( !_finished ){ | |
| if( _mark( n , '1' ) ){ | |
| _ai(); | |
| } | |
| } | |
| } | |
| function _reset(){ | |
| _finished = false; | |
| _players = { user: [], ai: [] }; | |
| _board = '000000000'.split(''); | |
| _write(''); | |
| _draw(); | |
| _victory = null; | |
| _victoryElement.className = ''; | |
| _containerElement.className = ''; | |
| } | |
| _draw(); | |
| return { | |
| play: _play, | |
| reset: _reset | |
| } | |
| })(); |
| #container { position:absolute; top:50%; margin-top:-170px; margin-left:-150px; left:50%; } | |
| table { width:300px; height:300px; margin:0; padding:0; font-family: 'arial'; border:collapsed;} | |
| table tr td { width: 100px; height:100px; background:#efefef; text-align:center; position:relative; text-align:center; -webkit-transition:opacity .2s ease-in-out; cursor:pointer; } | |
| table tr td::before, table tr td::after { opacity: 0; } | |
| table tr td:hover { opacity:0.5; -webkit-transition:opacity .2s ease-in-out; } | |
| table tr td.user { } | |
| table tr td.user::before { content:''; border-top:6px solid rgb(79, 130, 206); width:80px; transform:rotate(45deg); left:8px; position:absolute; -webkit-transition:all .2s ease-in-out; opacity:1; } | |
| table tr td.user::after { content:''; border-top:6px solid rgb(79, 130, 206); width:80px; position:absolute; left:8px; transform:rotate(-45deg); -webkit-transition:all .2s ease-in-out; opacity:1;} | |
| table tr td.computer { text-align:center;} | |
| table tr td.computer::before { position:absolute; content: ''; display:inline-block; width:60px; height:60px; border-radius:80px; border:6px solid rgb(245, 74, 74); left:12px; top:15px; -webkit-transition:all .2s ease-in-out; opacity:1;} | |
| button { display:block; margin:10px auto; padding:5px 10px 10px 10px; border-radius:5px; cursor:pointer; border:none; box-shadow: inset 0 -5px 0 rgba(100, 184, 167, 1); background:rgb(175, 219, 204); text-shadow:0 1px 0 white; color:rgb(40, 122, 99);} | |
| button:active { padding: 10px 10px 5px 10px; box-shadow:inset 0 5px 0 white; } | |
| button:focus { outline:none; } | |
| #result.hide { display:none; } | |
| #result { position:absolute; left:0; top:-70px; right:0; text-align:center; font-family: 'Arial'; | |
| background: rgb(253, 246, 61); | |
| padding: 10px 0; | |
| color: rgb(189, 153, 41); | |
| font-weight: bold; | |
| border-top: 2px solid white; | |
| border-bottom: 2px solid white; | |
| box-shadow: 0 2px 0 rgb(253, 246, 61), 0 -2px 0 rgb(253, 246, 61); | |
| text-shadow: -1px -1px 0 rgb(241, 247, 197); | |
| } | |
| #victory { width:6px; background:black; position:absolute; left:0; height:300px; top:0; display:none; } | |
| #victory[class^="won-"] { display:block; } | |
| #victory.won-246 { transform: rotate(44deg); left:150px; } | |
| #victory.won-048 { transform: rotate(-44deg); left:143px; } | |
| #victory.won-012 { transform: rotate(90deg); left:147px; top:-97px; } | |
| #victory.won-345 { transform: rotate(90deg); left:147px; top:6px; } | |
| #victory.won-678 { transform: rotate(90deg); left:147px; top:107px; } | |
| #victory.won-036 { left:47px; top:4px; } | |
| #victory.won-147 { left:146px; top:4px; } | |
| #victory.won-258 { left:244px; top:4px; } | |
| .user #victory { background:rgb(79, 130, 206);} | |
| .computer #victory { background:rgb(245, 74, 74);} |
A tictactoe algorithm
A Pen by Bruno de Queiroz on CodePen.