为什么我的minimax算法不会阻止我的行动?

时间:2018-12-21 13:18:55

标签: javascript algorithm minimax

这是我的井字游戏算法的Javascript代码:

function minimax(newGrid, depth, Player){

//debugger

const gameState = checkWin(newGrid,true);

if (gameState == false){
    values = [];

    for (let i = 0; i < 3; i++) {
        for (let j = 0; j < 3; j++) {
            const boardCopy = _.cloneDeep(newGrid);
            if (boardCopy[i][j] !== '') continue;
            boardCopy[i][j] = Player;
            console.log(boardCopy);
            const value = minimax(boardCopy, depth + 1, (Player === PLYR_TOKEN) ? COMP_TOKEN : PLYR_TOKEN);
            values.push({
                cost: value,
                cell: {
                    i: i,
                    j: j
                } 
            });
        }
    }

    //debugger

    if (Player === COMP_TOKEN){
        const max = _.maxBy(values, (v) => {
            return v.cost;
        });
        if( depth === 0 ){
            return max.cell;
        } else {
            return max.cost;
        }
    }else{
            const min = _.minBy(values, (v) => {
            return v.cost;
        });
        if( depth === 0 ){
            return min.cell;
        } else {
            return min.cost;
        }

    }
} else if (gameState === null){
    return 0;
} else if (gameState === PLYR_TOKEN){
    return depth - 10;
} else if (gameState === COMP_TOKEN){
    return 10 - depth;
   }
}

这种“算法”,“代码”的问题很简单:它不会阻碍我的行动。让我们想象一下这种情况:

X->播放器 O-> MM算法

X - O
- X -
- - -

通常,理想的MiniMax算法应该采用这种选择来阻止我获胜(小写字母o是新举动):

X - O
- X -
- - o

问题是我的代码使用了此命令(小写的o是新动作):

X - O
- X o
- - -

为什么?我不知道,但是我认为它必须赢了很多,无视我的举动,无视我离获胜还有一步之遥。 老实说,我不太了解这种算法的工作原理。

其他信息:主板是一个二维数组,并且minimax函数的结果是一个对象,该对象具有两个属性(i,j),它们表示主板上的坐标。

const board = [
['','',''],
['','',''],
['','','']
];

1 个答案:

答案 0 :(得分:1)

因此,如有疑问,请发表评论!我一步一步地做到了,当我被卡住时不会停下来,而是每次我了解更多东西时都会来回走动。

//newGrid : the board
//depth : keep track of the "guessing level"
//Player : keep track of who's turn it would be
function minimax(newGrid, depth, Player){

//checking if the game ended
const gameState = checkWin(newGrid,true);

//if not
if (gameState == false){
    values = [];
    //for each cell in the grid
    for (let i = 0; i < 3; i++) {
        for (let j = 0; j < 3; j++) {
            //we make a deep copy of the board
            const boardCopy = _.cloneDeep(newGrid);
            //if the cell isn't empty, we jump to the next one
            if (boardCopy[i][j] !== '') continue;
            //here we assign the Player to the cell (simulating a move from this player to this cell)
            boardCopy[i][j] = Player;
            //debugging
            console.log(boardCopy);
            //here go some recursivity, so we're putting our deepcopy with the simulated move, adding a depth level, and switching player
            const value = minimax(boardCopy, depth + 1, (Player === PLYR_TOKEN) ? COMP_TOKEN : PLYR_TOKEN);
            //since it was a recursive thing, please do imagine we get here at max depth BEFORE lesser depths, and then we'll climb back when each depth return its value to the previous one
            //so here the first "value" going in "values" will be the first cell where we did not go through "if (gameState == false){" : first cell where the game ended (with its associated cost, more on that later)
            values.push({
                cost: value,
                cell: {
                    i: i,
                    j: j
                } 
            });
        }
    }

    //when the loop ended
    //if we're simulating a computer turn
    if (Player === COMP_TOKEN){
        //getting the "value" with max cost out of "values"
        const max = _.maxBy(values, (v) => {
            return v.cost;
        });
        //if we endend our recursivity (we climbed all the way back to depth 0) == we are on the actual grid with no simulation
        if( depth === 0 ){
            return max.cell; //return the cell (computer will play this cell)
        } else {
            return max.cost; //else return the cost (to put in the "values" list)
        }
    }else{ //if we're simulating a player turn, same thing but with the min
        const min = _.minBy(values, (v) => {
            return v.cost;
        });
        if( depth === 0 ){ //may not be useful if you always call minimax at depth 0 on computer turn
            return min.cell;
        } else {
            return min.cost;
        }

    }
} else if (gameState === null){ //so, here we're simulating our endgame, a draw have a value of 0
    return 0;
} else if (gameState === PLYR_TOKEN){ //a player win have a value of "depth-10" (the quicker he win, the lesser the result)
    return depth - 10;
} else if (gameState === COMP_TOKEN){ //a computer win have a value of "10-depth" (the quicker he win, the greater the result)
    return 10 - depth;
   }
}

完成此操作后,我们对代码的工作方式以及为什么代码无法按预期的方式有更好的了解。 确实,在计算机转向时,它仅检查获胜最快的方法。我不确定我的解决方案是否100%可靠,但是您可以尝试以这种方式解决它:

    if (Player === COMP_TOKEN){
        //[...]
        //if we endend our recursivity (we climbed all the way back to depth 0) == we are on the actual grid with no simulation
        if( depth === 0 ){
            const min = _.minBy(values, (v) => {
                return v.cost;
            });
            if(min.cost>=-9)return min.cell; //if player win in the next turn, prevent it instead
            else return max.cell; //else return the best cell for computer
        }
        //[...]

这还远非完美(例如,您可以通过两步获胜让他获得胜利),并且尚未经过测试,但我希望现在对您来说更加清晰。 当您完成代码并按需工作时,请随时将其发布在codereview.stackexchange上以获得优化建议。