震惊! AI竟然帮我写了一个五子棋游戏

引言

在人工智能(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 编写的人机对战五子棋游戏)

相关推荐
晴空万里藏片云1 小时前
elment Table多级表头固定列后,合计行错位显示问题解决
前端·javascript·vue.js
奶球不是球1 小时前
el-button按钮的loading状态设置
前端·javascript
无责任此方_修行中3 小时前
每周见闻分享:杂谈AI取代程序员
javascript·资讯
dorabighead4 小时前
JavaScript 高级程序设计 读书笔记(第三章)
开发语言·javascript·ecmascript
林的快手6 小时前
CSS列表属性
前端·javascript·css·ajax·firefox·html5·safari
bug总结7 小时前
新学一个JavaScript 的 classList API
开发语言·javascript·ecmascript
网络安全-老纪7 小时前
网络安全-js安全知识点与XSS常用payloads
javascript·安全·web安全
yqcoder7 小时前
Express + MongoDB 实现在筛选时间段中用户名的模糊查询
java·前端·javascript
十八朵郁金香7 小时前
通俗易懂的DOM1级标准介绍
开发语言·前端·javascript
GDAL8 小时前
HTML 中的 Canvas 样式设置全解
javascript