【发布时间】:2021-05-09 05:40:54
【问题描述】:
当深度设置为 4 时,该算法似乎产生了正确的移动,但当我将其增加到 5 时,它出乎意料地变得更糟。在这种特殊情况下,它建议第 0 列是下一个最佳举措,而我相信第 3 列是。我很可能不完全理解极小极大算法,所以我请求您帮助解决这个问题,因为我已经尝试了几天但没有成功。任何提高代码可读性的建议都将不胜感激。
这里是游戏的链接:http://connect4.getforge.io/ - 原谅糟糕的用户界面(wip)。默认为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 grid
let 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
【问题讨论】:
-
也许它意识到自己赢不了。 R 最多在四步中获胜。
-
我明白了,但是我不知道为什么当深度增加到5时它会变得更糟。
标签: javascript artificial-intelligence minimax alpha-beta-pruning