2016-05-09 6 views
1

Ich habe Probleme bei der Implementierung dieser TicTacToe AI, die I found here. Ich bin relativ neu in Javascript, also bin ich mir sicher, dass ich etwas falsch mit dem Variablen-Scoping mache.TicTacToe Minimax AI in Javascript

Der Code wont im Snippet aber heres my codepen

choices = { 
 
    0: '#ul', 
 
    1: '#um', 
 
    2: '#ur', 
 
    3: '#ml', 
 
    4: '#mm', 
 
    5: '#mr', 
 
    6: '#ll', 
 
    7: '#lm', 
 
    8: '#lr' 
 
} 
 

 
function getGrid() { 
 
    var divs = [] 
 
    for (var i = 0; i < 9; i++) { 
 
    divs.push($(choices[i]).html()) 
 
    } 
 
    return divs 
 
} 
 

 
function getGame() { 
 
    var divs = [] 
 
    for (var i = 0; i < 9; i++) { 
 
    divs.push([$(choices[i]).html(), i]) 
 
    } 
 
    return divs 
 
} 
 

 
function convertGameToGrid(game) { 
 
    var divs = [] 
 
    for (var i = 0; i < game.length; i++) { 
 
    divs.push(game[i][0]) 
 
    } 
 
    return divs 
 
} 
 

 
function checkGrid(divs) { 
 
    var options = [ 
 
    [divs[0], divs[1], divs[2]], 
 
    [divs[3], divs[4], divs[5]], 
 
    [divs[6], divs[7], divs[8]], 
 
    [divs[0], divs[3], divs[6]], 
 
    [divs[1], divs[4], divs[7]], 
 
    [divs[2], divs[5], divs[8]], 
 
    [divs[0], divs[4], divs[8]], 
 
    [divs[2], divs[4], divs[6]] 
 
    ] 
 

 
    for (var i = 0; i < options.length; i++) { 
 
    if (options[i][0] == 'X' && options[i][1] == 'X' && options[i][2] == 'X') { 
 
     return 'X' 
 
    } else if (options[i][0] == 'O' && options[i][1] == 'O' && options[i][2] == 'O') { 
 
     return 'O' 
 
    } 
 
    } 
 
    for (var i = 0; i < 9; i++) { 
 
    if (divs[i] == '') { 
 
     return false //still moves 
 
    } 
 
    } 
 
    return 'Tie' //no winner and no moves 
 
} 
 

 
var player = 'O' 
 
var ai = 'X' 
 

 
$(document).ready(function() { 
 

 
    function playerTurn(i) { 
 
    return function() { 
 
     var g = getGrid() 
 
     var cG = checkGrid(g) 
 
     if (!cG) { 
 
     if ($(choices[i]).html() == '') { 
 
      $(choices[i]).html(player) 
 
      var g = getGrid() 
 
      var cG = checkGrid(g) 
 
      if (cG == player) { 
 
      console.log('You win') 
 
      } else if (cG == 'Tie') { 
 
      console.log('Tie') 
 
      } else { 
 
      aiTurn() 
 
      } 
 
     } 
 
     } 
 
    } 
 
    } 
 

 
    for (var i = 0; i < 9; i++) { 
 
    $(choices[i]).on('click', playerTurn(i)); 
 
    } 
 

 
    function score(g, depth) { 
 
    var cG = checkGrid(g) 
 
    console.log(cG, g) 
 
    if (cG == ai) { 
 
     return 10 - depth 
 
    } else if (cG == player) { 
 
     return depth - 10 
 
    } else { 
 
     return 0 
 
    } 
 
    } 
 

 
    function minimax(game, depth) { 
 
    var g = convertGameToGrid(game) 
 
    if (checkGrid(g)) { 
 
     return score(g, depth) 
 
    } 
 
    depth += 1 
 
    var scores = [] 
 
    var moves = [] 
 

 
    var availMoves = getAvailMoves(game) 
 
    console.log('moves', availMoves) 
 
    for (var i = 0; i < availMoves.length; i++) { 
 
     var possibleGame = game 
 
     if (depth % 2 == 0) { 
 
     possibleGame[availMoves[i]][0] = ai 
 
     } else { 
 
     possibleGame[availMoves[i]][0] = player 
 
     } 
 
     var m = minimax(possibleGame, depth) 
 
     scores.push(m) 
 
     console.log('mm: ', depth, i, scores) 
 
     moves.push(availMoves[i]) 
 
    } 
 

 
    //even depths are ai, odd are player 
 
    if (depth % 2 == 0) { 
 
     var max_score_index = 0 
 
     var max_score = -100000000 
 
     for (var i = 0; i < scores.length; i++) { 
 
     if (scores[i] > max_score) { 
 
      max_score_index = i 
 
      max_score = scores[i] 
 
     } 
 
     } 
 
     if (depth == 0) { //we need the best move 
 
     return moves[max_score_index] 
 
     } else { //otherwise this function needs scores 
 
     return scores[max_score_index] 
 
     } 
 
    } else { 
 
     var min_score_index = 0 
 
     var min_score = 100000000 
 
     for (var i = 0; i < scores.length; i++) { 
 
     if (scores[i] < min_score) { 
 
      min_score_index = i 
 
      min_score = scores[i] 
 
     } 
 
     } 
 
     return scores[max_score_index] 
 
    } 
 
    } 
 

 
    function getAvailMoves(game) { 
 
    var moves = [] 
 
    for (var i = 0; i < game.length; i++) { 
 
     if (game[i][0] == '') { 
 
     moves.push(game[i][1]) 
 
     } 
 
    } 
 
    return moves 
 
    } 
 

 
    function aiTurn() { 
 
    //Dumb ai 
 
    // c = Math.floor(Math.random()*9) 
 
    // while ($(choices[c]).html()) { 
 
    // c = Math.floor(Math.random()*9) 
 
    // } 
 

 
    //new strategy taken from http://neverstopbuilding.com/minimax 
 
    console.log('ai') 
 
    var c; 
 
    game = getGame() 
 
    c = minimax(game, -1) 
 

 
    $(choices[c]).html('X') 
 
    var g = getGrid() 
 
    var cG = checkGrid(g) 
 
    if (cG == ai) { 
 
     console.log('You lose') 
 
    } else if (cG == 'Tie') { 
 
     console.log('Tie') 
 
    } 
 
    } 
 

 

 
})
#ttt-box { 
 
    position: relative; 
 
    height: 304px; 
 
    width: 304px; 
 
    margin: 30px auto; 
 
    background-color: #bbb; 
 
    border: solid #000 4px; 
 
    border-radius: 20%; 
 
} 
 
#l1, 
 
#l2, 
 
#l3, 
 
#l4 { 
 
    position: absolute; 
 
    background-color: #000; 
 
} 
 
#l1 { 
 
    left: 99px; 
 
    width: 3px; 
 
    height: 296px; 
 
} 
 
#l2 { 
 
    left: 199px; 
 
    width: 3px; 
 
    height: 296px; 
 
} 
 
#l3 { 
 
    top: 99px; 
 
    width: 296px; 
 
    height: 3px; 
 
} 
 
#l4 { 
 
    top: 199px; 
 
    width: 296px; 
 
    height: 3px; 
 
} 
 
#ul, 
 
#um, 
 
#ur, 
 
#ml, 
 
#mm, 
 
#mr, 
 
#ll, 
 
#lm, 
 
#lr { 
 
    cursor: pointer; 
 
    position: absolute; 
 
    width: 99px; 
 
    height: 99px; 
 
    font-size: 70px; 
 
    text-align: center; 
 
} 
 
#ul { 
 
    top: 0; 
 
    left: 0; 
 
} 
 
#um { 
 
    top: 0; 
 
    left: 101px; 
 
} 
 
#ur { 
 
    top: 0; 
 
    left: 201px; 
 
} 
 
#ml { 
 
    top: 101px; 
 
    left: 0; 
 
} 
 
#mm { 
 
    top: 101px; 
 
    left: 101px; 
 
} 
 
#mr { 
 
    top: 101px; 
 
    left: 201px; 
 
} 
 
#ll { 
 
    top: 201px; 
 
    left: 0; 
 
} 
 
#lm { 
 
    top: 201px; 
 
    left: 101px; 
 
} 
 
#lr { 
 
    top: 201px; 
 
    left: 201px; 
 
}
<body> 
 
    <div class="container"> 
 
    <div id="content"> 
 
     <div id="ttt-box"> 
 
     <div id="l1"></div> 
 
     <div id="l2"></div> 
 
     <div id="l3"></div> 
 
     <div id="l4"></div> 
 

 
     <div id="boxes"> 
 
      <div id="ul"></div> 
 
      <div id="um"></div> 
 
      <div id="ur"></div> 
 
      <div id="ml"></div> 
 
      <div id="mm"></div> 
 
      <div id="mr"></div> 
 
      <div id="ll"></div> 
 
      <div id="lm"></div> 
 
      <div id="lr"></div> 
 
     </div> 
 

 
     </div> 
 
    </div> 
 
    </div> 
 
</body>

Der Code insbesondere ausgeführt, dass ich denke, ist unter dem Abschnitt zu brechen. Nach dem Zug des ersten Spielers denke ich, dass das console.log 8 ausdrucken soll! Wegen der vielen verschiedenen Wege, die es zu nehmen gilt, druckt es nur acht Mal, als ob es einen einzigen Weg hinunter gegangen wäre.

var availMoves = getAvailMoves(game) console.log('moves',availMoves) for (var i=0;i<availMoves.length;i++) { var possibleGame = game if (depth%2==0) { possibleGame[availMoves[i]][0] = ai } else { possibleGame[availMoves[i]][0] = player } var m = minimax(possibleGame,depth) scores.push(m) console.log('mm: ', depth,i, scores) moves.push(availMoves[i]) }

Edit: Was ist ich bemerkt, dass manchmal die Minimax-Rekursion undefiniert zurückkehrt. Ich habe versucht, herauszufinden, warum das so ist (siehe mein Codepen), aber ich war erfolglos.

Edit2: Es scheint zu undefiniert zurückzukehren, weil es diese Rekursionen vollständig überspringt. Ich kann immer noch keinen Weg finden, das zu beheben.

Antwort

2

Zuerst ein Vorschlag, da Sie gerade erst anfangen: lernen, wie man den Debugger benutzt. Es wird in Fällen wie diesen von unschätzbarem Wert sein und die meisten modernen Browser haben sie eingebaut.

In Bezug auf Ihr Problem, habe ich nicht durch alle Ihre Code verfolgt, aber ich habe eine Sache, die das Problem in Ihrer minmax Funktion verursachen könnte . Nach unten hin dieser Funktion haben Sie diesen Code:

//even depths are ai, odd are player 
if (depth % 2 == 0) { 
    var max_score_index = 0 
    // snip... 
} else { 
    var min_score_index = 0 
    // snip... 
    return scores[max_score_index] 
} 

Beachten Sie, dass Sie deklarieren und max_score_index im if Block zuweisen, wobei jedoch auch im else Block (ohne es zuweisen). Dies führt dazu, dass es vom Block else undefiniert zurückkehrt.

+0

Danke! Das war definitiv ein Problem. Es wurde mein "undefiniertes" Problem gelöst, aber es funktioniert immer noch nicht richtig, ich werde versuchen, später mehr herauszufinden. Gibt es im Debugger etwas Besseres als nur die Konsole? Mein Code wirft keine Fehler, also gibt es nichts, was ich in der Konsole nachverfolgen kann, aber wenn Sie mehr Ratschläge über den besten Weg haben, JS zu debuggen, nehme ich es gerne an –

+1

Sie bekommen den Debugger sehr ähnlich die Konsole, durch die Entwickler-Tools. Wählen Sie in IE- und FireFox-Entwicklertools die Registerkarte "Debugger" aus. Wählen Sie in den Chrome-Entwicklertools die Registerkarte "Quellen" aus. Dort können Sie Haltepunkte setzen, Variablenwerte untersuchen und den Code durchgehen. Es gibt nichts Besseres als den Code in Aktion zu sehen, um Probleme zu lösen. –

+1

Danke für all deine Hilfe Jack, ich habe alles in Ordnung gebracht, mein anderes Problem war ein Fehler bei der Tiefenkopie und ich brauchte den Debugger nicht, aber ich werde ihn definitiv benutzen –