四连棋的极小化极大算法产生失败的移动

当深度设置为4时,算法似乎能产生正确的移动,但当我将其增加到5时,效果意外地变差了。在这个特定情况下,它建议第0列是下一个最佳移动,而我认为应该是第3列。我可能对极小化极大算法的理解还不够全面,所以我请求您的帮助来解决这个问题,因为我已经尝试了几天都没有成功。此外,任何关于提高代码可读性的建议也将不胜感激。

这是游戏的链接:http://connect4.getforge.io/ – 请原谅这个简陋的用户界面(正在开发中)。默认深度为4级,请观察当您增加AI_DEPTH时的游戏差异。

这是棋盘,轮到AI作为G(最大化玩家)进行移动。

 ''  '' ''   ''  ''  '' '' ''  '' ''   ''  ''  '' '' ''  '' ''   ''  ''  '' '' ''  '' 'G'  'R' 'G' '' 'G' 'G' '' 'R'  'R' 'R' '' 'R' 'G' '' 'R'  'R' 'G' '' 'G'

这是从我的项目中提取的代码:

const GRID_ROW_COUNT = 6;const GRID_COL_COUNT = 7;const GRID_ROW_MID = 3;const GRID_COL_MID = 3;const WIN_SCORE = 1000;const rotateGrid = grid => grid.reduce((newGrid, gridRow) => {  return newGrid.map((column, i) => column.concat(gridRow[i]));}, [...Array(grid[0].length)].map(_ => []));function* getValidMoves(grid, player) {  for(let col = 0; col < grid[0].length; col++){    for(let row = grid.length; row > 0; row--){      if(!grid[row - 1][col]){        const tempGrid = JSON.parse(JSON.stringify(grid));        tempGrid[row - 1][col] = player;        yield [tempGrid, col];        break;      }    }  }}const isDrawn = function(grid){  for(let row = GRID_ROW_COUNT; row > 0; row--){    if(grid[row - 1].filter(Boolean).length < GRID_COL_COUNT){      return false;    }  }  return true;}const countInRow = (target, row, index, count) => {  if(count == 0 || !row[index] || row[index] != target){    return index;  }  return countInRow(target, row, index - 1, count - 1);}const countInDiagonal = (target, grid, row, col, count) => {  const colModulus = Math.abs(col);  if(count == 0 || row < 0 || !grid[row][colModulus] || grid[row][colModulus] != target){    return row;  }  return countInDiagonal( target, grid, row - 1, col - 1, count - 1 );};const countInCounterDiagonal = (target, grid, row, col, count) => countInDiagonal(target, grid, row, -col, count); function scoreGridPosition(grid, player, count = 4){  let score = 0;     function checkWinOnHorizontals(grid, count){    const GRID_ROW_COUNT = grid.length;    const GRID_COL_COUNT = grid[0].length;        const GRID_COL_MID = player ? 0 : Math.floor(GRID_COL_COUNT/2);    for(let row = GRID_ROW_COUNT - 1; row >= 0; row--){      for(let col = GRID_COL_COUNT - 1; col >= GRID_COL_MID; col--){        const cell = grid[row][col];        if(!cell){ continue; }        const colIndex = countInRow(cell, grid[row], col - 1, count - 1);                if(col - colIndex == count){ return WIN_SCORE; }        if(player){          const weight = col - 1 - colIndex;          if(cell == player){            score += weight;          } else {            score -= weight * 2;                   }        }         col = colIndex + 1;      }    }    return 0;  }  const checkWinOnVerticals = (grid, count) => checkWinOnHorizontals(rotateGrid(grid), count);  function checkWinOnDiagonals(grid, count){    const _GRID_ROW_MID = player ? 0 : GRID_ROW_MID;    for(let row = GRID_ROW_COUNT - 1; row >= _GRID_ROW_MID; row--){      for(let col = GRID_COL_COUNT - 1; col >= 0; col--){        const cell = grid[row][col];        if(!cell){ continue; }                let rowIndexL = row, rowIndexR = row;                if(col >= GRID_COL_MID){          rowIndexL = countInDiagonal(cell, grid, row - 1, col - 1, count - 1);        }        if(col <= GRID_COL_MID){          rowIndexR = countInCounterDiagonal(cell, grid, row - 1, col + 1, count - 1);        }                if(row - rowIndexL == count || row - rowIndexR == count){ return WIN_SCORE; }        if(player){          const weight = (row - rowIndexL) + (row - rowIndexR);          if(cell == player){            score += weight          } else {            score -= weight;          }        }      }    }    return 0;  }      return [    checkWinOnHorizontals(grid, count) ||    checkWinOnVerticals(grid, count) ||    checkWinOnDiagonals(grid, count),    score  ];}const alphaBetaAI = (grid, depth = 5, alpha = -Infinity, beta = Infinity, isMaxPlayer = true) => {  let value = isMaxPlayer ? -Infinity : Infinity;  let move = null;  if(isDrawn(grid)){ return [0, move]; }  const player = isMaxPlayer ? 'G' : 'R';  const [terminalScore, score] = scoreGridPosition(grid, player);    if(terminalScore){                       // -1000            1000      return [isMaxPlayer ? -terminalScore : terminalScore, move]  }  if(depth == 0){ return [score, move]; }  if(isMaxPlayer){    for(let [newGrid, column] of getValidMoves(grid, player)){      let [tempVal] = alphaBetaAI(newGrid, depth - 1, alpha, beta, !isMaxPlayer);      if(tempVal > value){        value = tempVal;        move = column;      }      alpha = Math.max(value, alpha);      if(beta <= alpha){ break; }    }  } else {    for(let [newGrid, column] of getValidMoves(grid, player)){      let [tempVal] = alphaBetaAI(newGrid, depth - 1, alpha, beta, !isMaxPlayer);      if(tempVal < value){        value = tempVal;        move = column;      }      beta = Math.min(value, beta);      if(beta <= alpha){ break; }    }  }  return [value, move];}// here is the gridlet g = [  ['', '', '', '', '', '', ''],  ['', '', '', '', '', '', ''],  ['', '', '', '', '', '', ''],  ['', '', 'G', 'R', 'G', '', 'G'],  ['G', '', 'R', 'R', 'R', '', 'R'],  ['G', '', 'R', 'R', 'G', '', 'G']];console.log('Move: ', alphaBetaAI(g)[1]); // 0 - I was expecting 3


回答:

正如Ouroborus指出的那样,在深度5时,无论它采取什么移动都会输。所以现在它选择了您可能移动列表中的第一个移动,因为所有结果都返回-1000。

如果您希望它总是找到最长的输棋路线,那么当输棋时,您需要返回-1000 + depth,当赢棋时返回1000 - depth。这样,您的AI将始终选择最长的输棋路线(如果有多个赢棋方式,则选择最快的赢棋方式)。

Related Posts

L1-L2正则化的不同系数

我想对网络的权重同时应用L1和L2正则化。然而,我找不…

使用scikit-learn的无监督方法将列表分类成不同组别,有没有办法?

我有一系列实例,每个实例都有一份列表,代表它所遵循的不…

f1_score metric in lightgbm

我想使用自定义指标f1_score来训练一个lgb模型…

通过相关系数矩阵进行特征选择

我在测试不同的算法时,如逻辑回归、高斯朴素贝叶斯、随机…

可以将机器学习库用于流式输入和输出吗?

已关闭。此问题需要更加聚焦。目前不接受回答。 想要改进…

在TensorFlow中,queue.dequeue_up_to()方法的用途是什么?

我对这个方法感到非常困惑,特别是当我发现这个令人费解的…

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注