Jako projekt praktyki wykonałem grę Tic-Tac-Toe na JSFiddle (ponieważ nie ma wystarczająco dużo, prawda?) I postępowałem w dodaniu bezkonkurencyjnego AI. W większości działa, ale istnieją jakieś kombinacje (np. Ustawianie X na polach 5, 9, 3 lub w polach 3, 7, 9), które prowadzą do komputera nie obliczającego prawidłowo optymalnego ruchu.

Projekt na JSFiddle: https://jsfiddle.net/jd8x0vjz/

Oraz odpowiednia funkcja rozpoczynająca się w linii 63:

function evaluateMove(move, player, depth) {
var gameStatus = evaluateGameStatus(move); //get status of current board
if (gameStatus < 2 && player)
    return -1; //if human won, return -1
if (gameStatus < 2 && !player)
    return 1; //if human lost, return 1

var returnValue = 0 //value to be returned later

for (var z = 0; z < 3; z++) { //loop for row
    for (var s = 0; s < 3; s++) { //loop for column
        if (move[z][s]) //if current slot has an x or o,
            continue; //skip it     
        var nextMove = cloneGameStatus(move); //create temporary array with base of current grid
        nextMove[z][s] = !player ? "x" : "o"; //assign first free field  the appropriate symbol
        var value = evaluateMove(nextMove, !player, depth+1);   //recursion but with switched player, to add the correct icon afterwards
        if ((value > returnValue) && player)
            returnValue = value;            
        if ((value < returnValue) && !player)
            returnValue = value;                
    }
}
return returnValue; //return value of current simulation
}

Myślę, że dwa ostatnie klauzule powodują te problemy, ponieważ komputer oblicza odpowiednie wartości (tak obserwowalne w debuggerze), ale czasami są nadpisane, ale nie jestem pewien, czy to jest źródło problemu . Wszelkie pomocy lub wskazówki byłyby doceniane!

Edytuj: problem rozwiązany! Poszukaj mojej odpowiedzi poniżej na wypadek, gdyby nie jest to pierwszy.

3
c-shark 26 wrzesień 2017, 16:19

2 odpowiedzi

Najlepsza odpowiedź

Idea domyślnej wartości zwroty zwiększowej, która jest błędna, zdecydowanie wysłała mnie w dół prawej ścieżki; Nie sprawił, że wszystko jest magicznie pracować (byłoby zbyt miłe, gdyby to zrobił), ale dała mi prawo pępek. Ponieważ nie chcemy zwracać żadnej wartości, jeśli nic nie jest obliczone, dostosowałem funkcję Evaluatemove jako następujące:

function evaluateMove(move, player, depth) {
var gameStatus = evaluateGameStatus(move); //get status of current board
if (gameStatus != 2)
    return gameStatus; //if the game is not running anymore, return result

var returnValue; //value to be returned later

for (var z = 0; z < 3; z++) { //loop for row
    for (var s = 0; s < 3; s++) { //loop for column
        if (move[z][s]) //if current slot has an x or o,
            continue; //skip it     
        var nextMove = cloneGameStatus(move); //create temporary array with base of current grid
        nextMove[z][s] = !player ? "x" : "o"; //assign first free field  the appropriate symbol
        var value = evaluateMove(nextMove, !player, depth+1);   //recursion but with switched player, to add the correct icon afterwards
        if ((value > returnValue || returnValue == null) && player)
            returnValue = value;            
        if ((value < returnValue || returnValue == null) && !player)
            returnValue = value;                
    }
}
return returnValue; //return value of current simulation
}

Teraz domyślnie jest wartość null i jako taki nie powinien wyrzucać obliczeń. Odrzucił jednak pierwszy blok czeków, więc dostosowałem go, aby po prostu zwrócić bieżący status, jeśli gra się skończyła, zamiast robić wszelkie opracowane czeki. Jednak , że rzucił wyniki, ponieważ używam odwróconych wartości domyślnych w dwóch metodach, więc musiałem też dostosować evaluategamestatus. Teraz, jeśli człowiek wygrał, zwraca -1 zamiast 1, a jeśli komputer wygrał, zwraca 1 zamiast -1:

function evaluateGameStatus(gameStatus) { //a clusterfuck of winning combinations
if(
X Checks
)
return -1; //there's a successful combination of x's

else if(
O Checks
)
return 1; //there's a successful combination of o's

else {
for (var z = 0; z < 3; z++) {
    for (var s = 0; s < 3; s++) {
        if (!gameStatus[z][s])
            return 2; //if there is an empty field neither has won, continue playing
        }
    }

return 0; //there's no successful combination and max moves have been reached. it's a draw
}
}

Musiałem wykonać to samo dostosowanie funkcji checkGameend, oczywiście.
Zauważysz, że zmieniłem też czek na rysunki. To dlatego, że z jakiegoś powodu stary czek dla liczenia == maxmoves już nie działał, więc zmieniłem się na pętlę, która po prostu sprawdza, czy w ogóle istnieje jakieś puste pole i zwrócić 2, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli jest i 0, jeśli istnieje Nie ma (powraca 0 tutaj, ponieważ w tym momencie przejechał przez wszystkie czeki: X nie wygrał, o nie wygrał, a nie ma otwartych slotów, więc gra musi być remisem).

Pracujący projekt można znaleźć teraz tutaj:
https://jsfiddle.net/h5ZWZKM7/

1
c-shark 28 wrzesień 2017, 08:06

Nie mogę powiedzieć na pewno, że jest to źródło problemu, ale jest to zdecydowanie błąd w swoim kodzie, który wytworzy dziwne wyniki. Linia:

var returnValue = 0 //value to be returned later

Jest nieprawidłowe. Oprócz tego, że brakuje ci pół-dwukropku, właściwy kod powinien być:

var returnValue = -1;
if(!player){
  returnValue = 1;
}

Chcesz, aby wartość domyślna dla maksymalnego odtwarzacza będzie negatywna, aby zabiera najlepszy ruch, a dla minimalizacji gracza pozytywnie, więc zabiera najgorszy ruch. Sposób, w jaki to zrobiłeś, jeśli gracz maksymalizujący był napotkany tylko z opcjami, które cenione -1, ponieważ -1 jest mniejsza niż 0, a zwrócona została zainicjowana do 0, 0 byłaby zwrócona, chociaż prawidłowa wartość została zwrócona -1.

1
chessprogrammer 28 wrzesień 2017, 03:51