引言
在人工智能(AI)逐渐融入我们生活的今天,它不仅改变了我们的工作方式,也在娱乐领域掀起了革命。最近,突发奇想,尝试用AI编写一个五子棋游戏,模式是人机对战。最后结果令人诧异------AI程序展现出了不错的竞技水平。
初始化与角色设定
首先,我们需要创建一个GameAI
类来封装所有与AI相关的逻辑。这个类接收棋盘大小作为参数,默认值设为16x16。通过setRoles
方法,我们可以指定玩家的角色(黑子或白子),而AI则自动分配另一个角色。
js
class GameAI {
constructor(boardSize = 16) {
this.boardSize = boardSize;
this.aiRole = null;
this.humanRole = null;
this.cache = {};
}
setRoles(playerRole) {
this.aiRole = playerRole === 1 ? 2 : 1;
this.humanRole = playerRole;
}
}
棋盘评估函数
为了让AI具备决策能力,我们需要教它如何评估当前棋盘的状态。evaluateBoard
方法通过计算AI和人类玩家各自模式的价值差来量化这一点。这里使用了简单的启发式算法,比如连五、连四等不同长度的连续棋子组合会给不同的分值。
js
evaluateBoard(board) {
const aiScore = this.countPatterns(board, this.aiRole);
const humanScore = this.countPatterns(board, this.humanRole);
return aiScore - humanScore;
}
模式计数与缓存
为了提高性能,countPatterns
方法利用记忆化技术(即缓存已计算过的模式),避免重复计算相同的棋盘片段。此外,它还会考虑四个方向上的可能排列,确保每个位置都被充分评估。
js
countPatterns(board, player) {
const patterns = {
'5': 100000, // 连五
'4': 10000, // 连四
'3': 1000, // 连三
'2': 100, // 连二
'1': 10 // 连一
};
let score = 0;
const cache = {}; // 用于记忆化的缓存对象
const directions = [
{dx: 0, dy: 1}, // 水平
{dx: 1, dy: 0}, // 垂直
{dx: 1, dy: 1}, // 对角线(左上到右下)
{dx: 1, dy: -1} // 对角线(右上到左下)
];
for (let dir of directions) {
for (let i = 0; i < this.boardSize; i++) {
for (let j = 0; j < this.boardSize; j++) {
let line = [];
for (let k = 0; k < 5; k++) {
const x = i + k * dir.dx;
const y = j + k * dir.dy;
if (x < 0 || x >= this.boardSize || y < 0 || y >= this.boardSize) {
break;
}
line.push(board[x][y]);
}
if (line.length === 5) {
const positionKey = `${i}-${j}-${dir.dx}-${dir.dy}`; // 用位置和方向组成的字符串作为缓存的键
if (cache[positionKey]) {
score += cache[positionKey];
} else {
const patternScore = this.getPatternScore(line, player, patterns);
cache[positionKey] = patternScore;
score += patternScore;
}
}
}
}
}
// 添加额外的启发式评分
score += this.positionalHeuristic(board, player);
return score;
}
// 获取特定模式的分数
getPatternScore(line, player, patterns) {
let pattern = line.join('');
let opponent = player === this.aiRole ? this.humanRole : this.aiRole;
// 检查是否有对手的棋子阻断
if (pattern.includes(opponent.toString())) {
return 0;
}
// 计算连子数量
let count = pattern.split('').filter(cell => cell === player.toString()).length;
// 返回对应的分数
return patterns[count] || 0;
}
获取可用移动与位置启发式评分
getAvailableMoves
方法用于列出所有可下的位置,而positionalHeuristic
则提供了一种基于位置的重要性评分机制,鼓励AI优先选择中心附近的空位,因为这些地方往往更有战略价值。
js
getAvailableMoves(board) {
let moves = [];
for (let i = 0; i < this.boardSize; i++) {
for (let j = 0; j < this.boardSize; j++) {
if (board[i][j] === 0) {
moves.push([i, j]);
}
}
}
return moves;
}
positionalHeuristic(board, player) {
let score = 0;
const boardSize = this.boardSize;
// 动态生成权重数组(这里以简单的对称模式生成权重,可根据实际游戏策略调整权重生成逻辑)
const weights = new Array(boardSize * boardSize).fill(0);
const centerWeight = boardSize / 2;
for (let i = 0; i < boardSize; i++) {
for (let j = 0; j < boardSize; j++) {
const distanceToCenterX = Math.abs(i - centerWeight);
const distanceToCenterY = Math.abs(j - centerWeight);
const weight = Math.max(1, boardSize - (distanceToCenterX + distanceToCenterY));
weights[i * boardSize + j] = weight;
}
}
// 统计目标玩家棋子数量,仅遍历有棋子的位置获取权重累加
const playerPositions = [];
for (let i = 0; i < boardSize; i++) {
for (let j = 0; j < boardSize; j++) {
if (board[i][j] === player) {
playerPositions.push(i * boardSize + j);
}
}
}
for (const position of playerPositions) {
score += weights[position];
}
return score;
}
核心算法--(α-β剪枝算法)
为了找到最佳落子点,我们采用了α-β剪枝算法来进行深度优先搜索。这种方法可以在合理的时间内探索更多的可能性,同时减少不必要的计算。
js
minimax(board, depth, alpha, beta, maximizingPlayer) {
const boardKey = JSON.stringify(board);
if (this.cache[boardKey]) {
return this.cache[boardKey];
}
if (depth === 0) {
const evaluation = this.evaluateBoard(board);
this.cache[boardKey] = evaluation;
return evaluation;
}
let moves = this.getAvailableMoves(board);
// 简单示例:根据位置启发式分数对移动进行排序(可进一步完善这个排序逻辑)
moves.sort((a, b) => {
const posScoreA = this.positionalHeuristic(board, maximizingPlayer ? this.aiRole : this.humanRole);
const posScoreB = this.positionalHeuristic(board, maximizingPlayer ? this.humanRole : this.aiRole);
return posScoreB - posScoreA;
});
if (maximizingPlayer) {
let maxEval = -Infinity;
for (let [i, j] of moves) {
board[i][j] = this.aiRole;
let evaluate = this.minimax(board, depth - 1, alpha, beta, false);
board[i][j] = 0; // 回溯
maxEval = Math.max(maxEval, evaluate);
alpha = Math.max(alpha, evaluate);
if (beta <= alpha) {
break;
}
}
this.cache[boardKey] = maxEval;
return maxEval;
} else {
let minEval = Infinity;
for (let [i, j] of moves) {
board[i][j] = this.humanRole;
let evaluate = this.minimax(board, depth - 1, alpha, beta, true);
board[i][j] = 0; // 回溯
minEval = Math.min(minEval, evaluate);
beta = Math.min(beta, evaluate);
if (beta <= alpha) {
break;
}
}
this.cache[boardKey] = minEval;
return minEval;
}
}
查找最佳落子点
findBestMove
方法结合了动态调整搜索深度和缓存机制,确保在不同局面下都能快速找到最佳落子点。随着棋局进展,搜索深度会根据棋盘上剩余的空位数量进行调整,以平衡计算时间和准确性。
js
findBestMove(board) {
let bestMove = [-1, -1];
let bestValue = -Infinity;
const availableMoves = this.getAvailableMoves(board);
const moveCache = {}; // 用于缓存移动在不同深度下的minimax值
// 动态调整搜索深度
const availableMovesCount = availableMoves.length;
if (availableMovesCount === this.boardSize * this.boardSize){
return [6,6];
}
let depth = 1;
// if(availableMovesCount > 250){
// depth = 1;
// } else if (availableMovesCount > 200) {
// depth = 2; // 棋盘较为空时,设置较大的深度
// } else if (availableMovesCount > 100) {
// depth = 3; // 棋盘有一定棋子时,设置中等深度
// } else {
// depth = 4; // 棋盘较满时,设置较小的深度
// }
// 简单的启发式排序,可根据更多因素完善此排序逻辑
availableMoves.sort((a, b) => {
const posScoreA = this.positionalHeuristic(board, this.aiRole);
const posScoreB = this.positionalHeuristic(board, this.aiRole);
return posScoreB - posScoreA;
});
let keepSearching = true;
while (keepSearching) {
let tempBestMove = [-1, -1];
let tempBestValue = -Infinity;
for (let [i, j] of availableMoves) {
const moveKey = `${i}-${j}-${depth}`; // 以移动坐标和深度组成键,用于缓存
if (moveCache[moveKey]) {
const cachedValue = moveCache[moveKey];
if (cachedValue > tempBestValue) {
tempBestMove = [i, j];
tempBestValue = cachedValue;
}
} else {
board[i][j] = this.aiRole;
// 可以根据局面情况动态调整alpha和beta的初始值,这里简单示例
// const alpha = -Infinity;
// const beta = Infinity;
const alpha = -this.countPatterns(board, this.aiRole);
const beta = this.countPatterns(board, this.humanRole);
const moveVal = this.minimax(board, depth, alpha, beta, false);
board[i][j] = 0; // 回溯
if (moveVal > tempBestValue) {
tempBestMove = [i, j];
tempBestValue = moveVal;
}
moveCache[moveKey] = moveVal; // 缓存本次计算结果
}
}
if (tempBestValue > bestValue) {
bestMove = tempBestMove;
bestValue = tempBestValue;
depth++;
} else {
keepSearching = false;
}
}
return bestMove;
}
游戏结束检测
最后,需要检查游戏是否已经结束。这包括判断是否有玩家获胜或棋盘上没有剩余的空位。
js
isGameOver(board) {
let availableMoveNum = this.getAvailableMoves(board).length;
if(availableMoveNum > 250){
return false;
}
if (this.checkWin(board, this.aiRole) || this.checkWin(board, this.humanRole)) {
return true;
}
return availableMoveNum === 0; // 如果没有可用的移动则平局
}
// 检查某个玩家是否获胜
checkWin(board, player) {
const directions = [
{dx: 0, dy: 1}, // 水平
{dx: 1, dy: 0}, // 垂直
{dx: 1, dy: 1}, // 对角线(左上到右下)
{dx: 1, dy: -1} // 对角线(右上到左下)
];
for (let i = 0; i < this.boardSize; i++) {
for (let j = 0; j < this.boardSize; j++) {
if (board[i][j]!== player) continue;
for (let dir of directions) {
let count = 1;
for (let k = 1; k < 5; k++) {
let x = i + k * dir.dx;
let y = j + k * dir.dy;
// 严格进行边界检查,确保坐标在棋盘范围内
if (x < 0 || x >= this.boardSize || y < 0 || y >= this.boardSize || board[x][y]!== player) {
break;
}
count++;
}
if (count === 5) return true;
}
}
}
return false;
}
}
欢迎大家对代码提出宝贵的意见和建议!
下面是运行效果图
开始
这里我已经赢了
完整源码在这里(goBangGame: AI 编写的人机对战五子棋游戏)