拓冰建站拓冰建站
首页 / 资讯中心 / 正文

网页版五子棋AI:Alpha-Beta剪枝与性能优化实践

简介网页版五子棋人工智能项目是一份可直接运行的完整源码示例由超文本标记语言、层叠样式表与脚本语言共同构建将博弈树、极大极小值搜索与α-β剪枝等经典人工智能算法落地到五子棋人机对弈场景中适合对算法应用与前端游戏开发感兴趣的开发者细细研读与扩展。资源共包含7个文件压缩包仅73千字节体积轻巧其中超文本标记语言负责搭建页面骨架层叠样式表定义界面外观脚本语言集中封装人工智能决策与交互逻辑图片素材则提供棋盘背景等界面元素整体代码结构紧凑、无冗余依赖便于快速定位关键函数。截至目前该项目已有超过两万四千人学习下载是理解经典人工智能算法如何在浏览器中实时搜索棋局、评估落子价值的直观范例。通过源码可以清楚看到人工智能如何构建博弈树、评估局面并利用α-β剪枝有效压缩搜索空间在此基础上还可继续优化难度、加入开局库或调整评估权重既适合作为人工智能入门教学案例也可作为前端游戏增强功能的二次开发基础。1. 五子棋 AI 网页版为什么传统搜索比深度学习更先落地拿到“五子棋 AI 网页版”需求别急着上深度学习。15 路棋盘上每步可用落点约 20 个深度 6 的博弈树不剪枝会超过 6400 万个节点页面立刻无响应。Alpha-Beta 剪枝加一套棋型评分普通浏览器能在 2 秒内给出业余中段水平的落子还省去了模型加载和训练数据。这个标题下真正要做的事是选择可复现的搜索算法用原生 JavaScript 实现再解决主线程卡顿和棋力验证。适合前端或全栈开发给产品加五子棋人机对战也适合做课程设计时快速拿到一个能改能跑的 AI 基线。2. 五子棋 AI 算法选型Alpha-Beta 剪枝、评估函数与 MCTS 的边界2.1 为什么网页版本地优先用 Alpha-Beta 而不是深度网络浏览器端跑神经网络并非不可能TensorFlow.js 和 WebGPU 都已经成熟但五子棋并不适合用“端到端黑盒”解决。15x15 棋盘有 225 个交叉点每一步的可用落点通常在 20 个以上。神经网络即使能给出一个推荐落点也无法保证这个落点是搜索意义上的合法最优而 Alpha-Beta 剪枝的每一步都是带胜负语义的确定性计算。再看包体与维护成本。一个训练好的五子棋模型少则几 MB多则几十 MB加载和推理都会占用移动端用户的网络与内存。传统的“评估函数 Alpha-Beta”只要两个原生 JS 函数没有外部依赖能塞进任何页面。机器博弈代码大全里的经典实现也大多是搜索加评估的套路这在你需要快速交付在线人机对战时是更可控的。如果你想在五子棋 AI 里加入“可解释性”传统搜索同样可以做到。评估函数能告诉你为什么选这个点是活三威胁、冲四拦截还是双三成形。这一点在后端大模型方案里反而不容易做到因为神经网络给出的概率分数很难转成用户能理解的棋理。2.2 极小化极大与 Alpha-Beta 剪枝从博弈树到 JS 递归极小化极大Minimax把对弈建模为一棵博弈树。你落子后对手会选对你最不利的分支因此 AI 要往上回溯己方层取子节点最大值对手层取最小值。Alpha-Beta 剪枝在这个树上维护两个边界。alpha 表示最大化方目前已经能保证的最低分数beta 表示最小化方目前愿意接受的最高上限。当某个分支返回的分数会同时让 alpha 和 beta 交叉也就是 alpha beta 时后续兄弟分支不必再搜。递归的 JS 实现很直接。下面的alphaBeta在每次递归时先判断是否到达叶子节点如果是就调用评估函数返回当前局面的分。// depth: 剩余搜索深度 // alpha, beta: 剪枝边界 // isMaximizing: true 表示当前层轮到 AI最大化方 function alphaBeta(board, depth, alpha, beta, isMaximizing) { const legalMoves generateMoves(board); if (depth 0 || legalMoves.length 0) { return evaluate(board, AI_PLAYER); } if (isMaximizing) { let value -Infinity; for (const move of legalMoves) { board[move.row][move.col] AI_PLAYER; value Math.max(value, alphaBeta(board, depth - 1, alpha, beta, false)); board[move.row][move.col] EMPTY; alpha Math.max(alpha, value); if (alpha beta) break; // 对手不会接受更差的结果剪枝 } return value; } else { let value Infinity; for (const move of legalMoves) { board[move.row][move.col] HUMAN_PLAYER; value Math.min(value, alphaBeta(board, depth - 1, alpha, beta, true)); board[move.row][move.col] EMPTY; beta Math.min(beta, value); if (alpha beta) break; // 己方不会走进更差的分支 } return value; } }这段代码需要注意的是generateMoves的顺序直接影响剪枝效率。如果你让最差的走法先出现alpha 和 beta 迟迟不收敛循环次数会膨胀反过来先尝试优势着法很快就能触发alpha beta。所以真正生产级的代码都会把走法排序放在剪枝之前而不是像上面这样裸循环。后面 3.2 会单独讲启发式排序。2.3 评估函数设计活三、冲四、成五的五子棋 AI 算法核心评估函数决定 AI 的“棋感”。最简单的做法是扫描整盘棋把每个方向上连续的同色棋子以及两端状态编码成棋型。常见的棋型有五连、活四、冲四、活三、眠三、活二和眠二。评分越悬殊越好这样搜索才会优先避让对手的冲四而不是只看局面总子的数量。| 棋型 | 典型排列0 空1 己方2 对方 | 评分 | | 成五 | 11111 | 1000000 | | 活四 | 011110 或 011112 等包含四子且至少一端开放 | 500000 | | 冲四 | 11110 或 11112 等 | 50000 | | 活三 | 01110 或 010110 等可连续成活四 | 10000 | | 眠三 | 11120 等 | 1000 | | 活二 | 01100 等 | 500 | | 眠二 | 11000 或 11200 等 | 100 |代码上不要对整盘 225 个位置全部反复扫描而是用滑动窗口遍历整盘或只对目标点四周打分。下面是一个窗口打分函数// window 是某一段连续五格player 是当前要评估的一方 function evaluateWindow(window, player) { const me window.filter(c c player).length; const opp window.filter(c c ! player c ! EMPTY).length; if (opp 0) { if (me 4) return SCORES.FOUR; if (me 3) return SCORES.THREE; if (me 2) return 100; return 0; } if (me 5) return SCORES.WIN; if (me 4) return SCORES.OPEN_FOUR; if (me 3) return SCORES.OPEN_THREE; if (me 2) return SCORES.OPEN_TWO; return 0; }上面的opp 0表示这个窗口被对方挡住得分会从活棋降到冲棋甚至更低。实际评估函数会把整个棋盘的行、列、两条对角线拆成多个窗口逐个累计。需要注意的是不要简单地把双方分数相减因为有些棋型必须给极大权重比如四三、三三的复合价值可以用“己方最大形 第二大形”的方式处理这也是五子棋 AI 算法里最常见的优化点之一。2.4 MCTS 的适用边界宽搜索树或需要对局采样蒙特卡洛树搜索在围棋中很出名但五子棋的搜索宽度和深度都小于围棋树搜索同样有效其实是效率问题。MCTS 需要大量的随机模拟来估计胜率一个回合可能要模拟上千局才能得到一个稳定的节点 UCT 值。对于网页版的 2 秒预算来说模拟次数往往不够棋力反而不如一个 4 层 Alpha-Beta 稳定。当棋盘升级到 19 路或者加入了禁手、连珠规则时评估函数会失真MCTS 才更适合兜底。如果你后续想接强化学习可以用 MCTS 在自我对局中生成策略样本再训练一个轻量网络来辅助走法排序。这算是“AI 增强”的高级路线但基础搜索算法永远值得先做扎实。3. 原生 JavaScript 实现网页版五子棋 AI棋盘、搜索与渲染3.1 15 路棋盘、胜负判断与方向向量网页版棋盘最常见的是 15x15 或 19x19。五子棋标准是 15 路我就按 15 路来写。用二维数组表示棋盘0 空、1 黑、2 白。你只需要定义一个checkWin(board, row, col)来检查刚刚落下的棋子是否成五它只需要检查以落点为中心的四个方向。const BOARD_SIZE 15; const EMPTY 0, BLACK 1, WHITE 2; function checkWin(board, row, col) { const player board[row][col]; if (player EMPTY) return false; const directions [ [1, 0], // 纵向 [0, 1], // 横向 [1, 1], // 主对角线 [1, -1] // 副对角线 ]; for (const [dx, dy] of directions) { let count 1; for (let i 1; i 5; i) { const r row dx * i, c col dy * i; if (r 0 || r BOARD_SIZE || c 0 || c BOARD_SIZE || board[r][c] ! player) break; count; } for (let i 1; i 5; i) { const r row - dx * i, c col - dy * i; if (r 0 || r BOARD_SIZE || c 0 || c BOARD_SIZE || board[r][c] ! player) break; count; } if (count 5) return true; } return false; }这个函数的核心是directions数组。每个方向都分别向两端扩展如果连续同色棋子数达到 5 就返回 true。注意这里不需要处理长连标准五子棋只要 5 就算赢如果你要支持禁手规则再单独判断成五和长连即可。| 方向向量 | 实际含义 | 使用场景 | | [1,0] | 同行向下 | 判断纵向连子 | | [0,1] | 同列向右 | 判断横向连子 | | [1,1] | 右下 | 判断斜向连子 | | [1,-1] | 右上 | 判断另一斜向连子 |3.2 启发式走法生成压缩搜索空间Alpha-Beta 搜索的复杂度主要取决于分支数。15 路棋盘总共 225 个空位如果每层都全部扫描六层搜索可能要扫上亿次。常见做法是只生成已有棋子周边两格以内的空位并把它们按到最近棋子的距离排序。这样做既不会漏掉最佳着法又能让搜索优先找到好棋。function generateMoves(board) { const moves []; for (let r 0; r BOARD_SIZE; r) { for (let c 0; c BOARD_SIZE; c) { if (board[r][c] ! EMPTY) continue; if (!hasNeighbor(board, r, c, 2)) continue; moves.push({ row: r, col: c, score: neighborScore(board, r, c) }); } } moves.sort((a, b) b.score - a.score); return moves; } function hasNeighbor(board, row, col, radius) { for (let i -radius; i radius; i) { for (let j -radius; j radius; j) { const r row i, c col j; if (r 0 r BOARD_SIZE c 0 c BOARD_SIZE board[r][c] ! EMPTY) { return true; } } } return false; }neighborScore可以简单地计算周围棋子的密度比如累加半径内的敌方棋子权重。这里的排序不是精确打分而是为了尽快触发剪枝。你甚至可以不加neighborScore只按(r,c)从中心向外排序效果也接近。想要更强可以把第 4 章的历史启发式也加进来用之前搜索时引发剪枝的走法记录来优先尝试。3.3 把 Alpha-Beta 封装成 aiMove现在把评估、走法生成和搜索封装成一个函数。function aiMove(board, player, depth 4) { const moves generateMoves(board); let bestMove moves[0] || { row: 7, col: 7 }; let bestScore -Infinity; for (const move of moves) { board[move.row][move.col] player; const score alphaBeta(board, depth - 1, -Infinity, Infinity, false); board[move.row][move.col] EMPTY; if (score bestScore) { bestScore score; bestMove move; } } return bestMove; }bestScore初始化为-Infinity每尝试一个走法就用第二次搜索得到该分支的估值。注意这里的alphaBeta第一个参数isMaximizing是 false因为 AI 走完这一步后轮到对手。如果generateMoves返回空数组——比如棋盘满了——就默认下中心点。这是完整代码里最容易被忽略的边界。3.4 用 Canvas 把棋盘、落子和 AI 串起来渲染不是本文重点但完整网页版必须有一个可交互入口。我用 Canvas 绘制 15x15 网格监听 click 事件得到格子坐标。canvas.addEventListener(click, (e) { const cellSize canvas.width / BOARD_SIZE; const col Math.floor(e.offsetX / cellSize); const row Math.floor(e.offsetY / cellSize); if (board[row][col] ! EMPTY) return; board[row][col] HUMAN_PLAYER; drawBoard(); if (checkWin(board, row, col)) return; const move aiMove(board, AI_PLAYER, 4); board[move.row][move.col] AI_PLAYER; drawBoard(); });offsetX/offsetY是鼠标相对于画布左上角的坐标。如果你用 CSS 把 Canvas 放大了记得除以getBoundingClientRect()的缩放比例否则点击会偏移。这个同步版本的aiMove在深度 4 时可能会有几十到几百毫秒的阻塞用户会觉得“卡了一下”下一章用 Web Worker 解决。4. 搜索深度、Web Worker 与 Zobrist 哈希网页版五子棋 AI 调优4.1 深度与时间预算怎么定网页版的响应体验通常要求 AI 在 1 秒内给出落子最多不超过 2 秒。搜索深度直接决定棋力但也和走法排序强相关。下面是我常用的推荐值。| 搜索深度 | 平均耗时桌面 Chrome无 Worker | 棋力定位 | 推荐场景 | | 2 | 30ms | 新手能守住简单双三 | 移动端低端机 | | 4 | 100ms - 500ms | 业余中级能主动做四三 | 网页版默认值 | | 6 | 2s - 10s | 业余高级能应对多数陷阱 | 桌面端 Web Worker WASM 辅助 |如果你发现深度 4 时搜索时间超过 1 秒拔高深度的收益反而不如优化走法排序。以上数据是在 15 路棋盘、每步生成约 40 个候选点的条件下测出来的实际差距来自浏览器和机型。4.2 Web Worker把搜索放到后台线程JavaScript 是单线程搜索深度到 4 层时同步执行会阻塞界面。最简单的优化是创建一个 Web Worker把aiMove和alphaBeta相关函数全部塞进 worker.js。主线程只负责渲染和事件计算完成后通过postMessage把落点传回主线程。主线程代码const aiWorker new Worker(ai-worker.js); function requestAIMove() { const snapshot board.map(row row.slice()); aiWorker.postMessage({ board: snapshot, player: AI_PLAYER, depth: 4 }); } aiWorker.onmessage (e) { const { move } e.data; board[move.row][move.col] AI_PLAYER; drawBoard(); };worker.js 内部self.onmessage (e) { const { board, player, depth } e.data; const move aiMove(board, player, depth); self.postMessage({ move }); };这里有一个重要细节postMessage在传递board时是结构化克隆不是共享引用。也就是说 worker 里修改board不会影响主线程的棋盘所以主线程必须先快照一份再发过去。上面的map(row row.slice())是浅拷贝二维数组注意原始数组里保存的是数字所以浅拷贝足够。如果你的棋盘里存的是对象就必须深拷贝。4.3 Zobrist 哈希与置换表减少重复局面Alpha-Beta 搜索的节点数依然很大但五子棋中大量走法会形成转置——同一局面通过不同落子顺序到达。把这些局面的估值缓存下来能减少重复计算。这里用到 Zobrist 哈希。它用随机数表对每个位置和棋子类型生成 64 位整数棋盘哈希值就是所有非空位置的异或结果。const zobristTable []; for (let i 0; i BOARD_SIZE; i) { zobristTable.push([]); for (let j 0; j BOARD_SIZE; j) { zobristTable[i].push([0, makeRandom64(), makeRandom64()]); } } function zobristHash(board) { let hash 0n; for (let r 0; r BOARD_SIZE; r) { for (let c 0; c BOARD_SIZE; c) { const v board[r][c]; if (v ! EMPTY) hash ^ zobristTable[r][c][v]; } } return hash; }注意 Zobrist 哈希本身不参与搜索打分它只是置换表的 key。置换表里通常保存三样东西深度、估值类型精确值、下界、上界和估值。如果只存一个 number搜索时可能会把对手视角下的负分误用为正值剪枝会出错。所以实用代码里至少要记录该估值属于alpha还是beta还是精确结果再决定是否复用。提示置换表在五子棋里提升不如象棋明显因为状态空间大、重复概率有限。如果只是为了提高深度优先做走法排序和 Web Worker置换表属于锦上添花。5. 验证 AI 强度与增强路径自对弈、开局库和 WASM5.1 让 AI 自己下用自对弈确认棋力自对弈是验证 AI 是否“会玩”的最低成本方法。让同一个aiMove函数执黑执白互下统计平均搜索耗时、最大搜索耗时、胜负是否停留在局部。下面是一个简单脚本片段async function selfPlay(rounds 10, depth 4) { for (let i 0; i rounds; i) { let board newBoard(); let player BLACK; let steps 0; while (steps BOARD_SIZE * BOARD_SIZE) { const start performance.now(); const move aiMove(board, player, depth); const elapsed performance.now() - start; console.log(step${steps} cost${elapsed.toFixed(0)}ms move${move.row},${move.col}); board[move.row][move.col] player; if (checkWin(board, move.row, move.col)) break; player player BLACK ? WHITE : BLACK; steps; } console.log(round${i} steps${steps}); } }如果平均每步耗时在 500ms 以下、且自对弈能走到 20 手以上说明 AI 至少不会一上来就送四三。如果你发现同样的棋型反复出现但 AI 没有回应多半是评估函数里冲四或活三的权重太小。5.2 用 C 五子棋核心 WASM 提升搜索深度深度 6 在浏览器里接近极限。常见做法是把 Alpha-Beta 搜索函数用 C 实现再用 Emscripten 编译成 WebAssembly在 Web Worker 里调用。由于 C 可以直接操作连续内存数组访问比 JS 的二维数组快得多同样深度耗时能减少一半以上。这样既可以保留网页版的零安装优势又能把搜索深度推到 6 或 8。编译命令示意emcc ai.cpp -O3 -o ai.wasm --no-entry -s EXPORTED_FUNCTIONS[_aiMove]这只是路径示意具体导出函数名和内存接口要根据你封装的 C ABI 调整。如果你是 C 五子棋代码的持有者将搜索核心抽成纯函数再为棋盘和走法打印成扁平化数组WASM 的接入成本就不会太高。5.3 三条最短的增强路径| 增强方式 | 改动量 | 对棋力的提升 | | 开局库前 5 手固定 | 小 | 避免开局吃亏 | | 残局杀棋检测搜索到必胜型后直接落子 | 中 | 减少漏杀 | | 历史启发式或置换表 | 中 | 等效提升 1-2 层搜索深度 |我一般会把开局库做得最轻用一个预定义的{row, col}数组AI 对局前 5 手直接从数组取不进入搜索。残局检测则是在evaluate中提前判断五连或双四若搜索发现必胜分支就返回极大值并让aiMove直接选它。最后把历史启发式加进 3.2 的排序函数里搜索速度会有可感知提升。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门