五子棋作为经典策略游戏,在微信小程序上实现其实比你想象的简单。我先带大家用最基础的方式实现双人对战版本,后续再逐步升级到智能AI对战。这个过程中你会发现,很多看似复杂的功能拆解后都是基础逻辑的组合。
棋盘绘制是小程序五子棋的第一步。我推荐使用flex布局配合背景图实现,这样既能保证视觉效果,又便于后续落子定位。具体实现时,用15x15的透明view矩阵覆盖在棋盘背景上,每个view的点击事件对应一个落子位置。实测下来,这种方案在各类机型上显示效果最稳定。
javascript复制// WXML部分
<view class="chessboard">
<view
wx:for="{{grids}}"
wx:key="index"
class="grid"
bindtap="onTap"
data-index="{{index}}"
/>
</view>
// JS初始化
Page({
data: {
grids: Array(225).fill('empty'), // 15x15棋盘
currentPlayer: 'black' // 当前执子方
}
})
落子逻辑的核心是三步:检查目标位置是否为空、更新棋盘状态、切换执子方。这里有个细节要注意,小程序setData的性能优化很重要。我遇到过棋盘卡顿的情况,后来发现是频繁触发渲染导致的。解决方案是合并数据更新:
javascript复制onTap(e) {
const index = e.currentTarget.dataset.index
if (this.data.grids[index] !== 'empty') return
// 使用临时变量减少setData调用
const newGrids = [...this.data.grids]
newGrids[index] = this.data.currentPlayer
this.setData({
grids: newGrids,
currentPlayer: this.data.currentPlayer === 'black' ? 'white' : 'black'
})
this.checkWin(index)
}
胜负判断是五子棋最易出bug的环节。常见错误包括边界判断遗漏、对角线检测错误等。我的经验是采用方向向量法:预先定义四个检测方向(水平、垂直、两对角线),每个方向正反扫描连续同色棋子。这样代码更简洁且不易出错:
javascript复制// 方向向量定义
const DIRECTIONS = [
[1, 0], // 水平
[0, 1], // 垂直
[1, 1], // 主对角线
[1, -1] // 副对角线
]
checkWin(index) {
const x = index % 15
const y = Math.floor(index / 15)
const color = this.data.grids[index]
for (const [dx, dy] of DIRECTIONS) {
let count = 1 // 当前落子已计1
// 正向检测
for (let i = 1; i < 5; i++) {
const nx = x + dx * i
const ny = y + dy * i
if (nx >= 15 || ny >= 15 || nx < 0 || ny < 0) break
if (this.data.grids[ny * 15 + nx] !== color) break
count++
}
// 反向检测
for (let i = 1; i < 5; i++) {
const nx = x - dx * i
const ny = y - dy * i
if (nx >= 15 || ny >= 15 || nx < 0 || ny < 0) break
if (this.data.grids[ny * 15 + nx] !== color) break
count++
}
if (count >= 5) {
this.showResult(color + '方胜利!')
return
}
}
}
完成核心对战功能后,我们需要优化用户体验。首先是悔棋功能,这个看似简单,但实际开发时要注意状态管理的完整性。我采用栈结构记录落子历史,每次悔棋时弹出最近两步(黑白各一步):
javascript复制// 初始化历史栈
data: {
moveHistory: []
}
// 落子时记录
onTap(e) {
// ...原有逻辑...
this.data.moveHistory.push(index)
}
// 悔棋实现
undo() {
if (this.data.moveHistory.length < 2) return
const newGrids = [...this.data.grids]
// 弹出最后两步
const step1 = this.data.moveHistory.pop()
const step2 = this.data.moveHistory.pop()
newGrids[step1] = 'empty'
newGrids[step2] = 'empty'
this.setData({
grids: newGrids,
currentPlayer: 'black' // 默认回退到黑方回合
})
}
另一个实用功能是落子动画。小程序支持CSS动画,我们可以给棋子添加缩放效果增强交互感。这里有个技巧:通过动态class实现不同颜色棋子的动画效果:
css复制/* WXSS样式 */
.chess-piece {
transform: scale(0);
transition: transform 0.3s ease;
}
.chess-piece.black {
background: #000;
transform: scale(1);
}
.chess-piece.white {
background: #fff;
transform: scale(1);
}
游戏状态管理也需要特别注意。当一局结束后,要重置棋盘但保留设置选项。我建议将游戏状态分为三种:未开始、进行中、已结束。这样逻辑更清晰:
javascript复制data: {
gameStatus: 'idle', // 'idle'|'playing'|'finished'
// ...其他数据...
}
startGame() {
this.setData({
grids: Array(225).fill('empty'),
gameStatus: 'playing',
currentPlayer: 'black'
})
}
// 胜负判断后
showResult(message) {
this.setData({ gameStatus: 'finished' })
wx.showModal({ title: '游戏结束', content: message })
}
现在进入重头戏——如何让电脑成为你的对手。五子棋AI有多种实现方式,从最简单的随机落子到复杂的蒙特卡洛树搜索。考虑到小程序性能限制,我选择权值表算法,它在效果和性能间取得了很好平衡。
权值表算法的核心思想是:为每种局部棋型赋予分数,电脑选择全局得分最高的位置落子。比如"活四"(即无阻挡的四子连线)应该给最高分,因为下一步就能赢。下面是我的实战权值表:
| 棋型模式 | 分数 | 说明 |
|---|---|---|
| 2222 | 10000 | 白棋连五 |
| 222 | 1000 | 白棋活四 |
| 22_2 | 800 | 白棋冲四 |
| 22 | 100 | 白棋活三 |
| 2_2 | 50 | 白棋眠三 |
| 1111 | 5000 | 黑棋连五(需拦截) |
| 111 | 800 | 黑棋活四(需拦截) |
实现时,我们需要遍历每个空位,计算它在八个方向(横竖斜)上的得分总和。这里有个优化技巧:当检测到必胜或必防棋型时立即返回,不必继续计算:
javascript复制// AI核心逻辑
calculateAIMove() {
let bestScore = -1
let bestMove = null
for (let i = 0; i < 225; i++) {
if (this.data.grids[i] !== 'empty') continue
let score = 0
// 八个方向评估
const directions = [
[0, 1], [1, 0], [1, 1], [1, -1],
[0, -1], [-1, 0], [-1, -1], [-1, 1]
]
for (const [dx, dy] of directions) {
const pattern = this.scanDirection(i, dx, dy)
score += this.evaluatePattern(pattern)
// 紧急情况立即处理
if (score >= 10000) {
return i
}
}
if (score > bestScore) {
bestScore = score
bestMove = i
}
}
return bestMove
}
方向扫描函数需要识别连续同色棋子,并处理边界情况。这是我优化后的版本,加入了空位识别:
javascript复制scanDirection(index, dx, dy) {
const x = index % 15
const y = Math.floor(index / 15)
let pattern = ''
for (let step = 1; step <= 4; step++) {
const nx = x + dx * step
const ny = y + dy * step
if (nx < 0 || nx >= 15 || ny < 0 || ny >= 15) break
const pos = ny * 15 + nx
if (this.data.grids[pos] === 'white') {
pattern += '2'
} else if (this.data.grids[pos] === 'black') {
pattern += '1'
} else {
pattern += '_'
break
}
}
return pattern
}
基础AI实现后,你会发现电脑有时会犯低级错误。这是因为简单权值法存在局限性。通过以下几个策略,我们可以显著提升AI水平:
双向延伸评估是第一个改进点。原始方案只评估单方向,无法识别"双活三"等复合棋型。改进后同时计算相反方向的棋型组合:
javascript复制evaluateDoublePattern(p1, p2) {
// 处理如"22_22"形式的双活三
if ((p1 === '22' && p2 === '22') ||
(p1 === '2_2' && p2 === '22')) {
return 1500 // 高于普通活三
}
// 其他组合判断...
}
动态权值调整是第二个优化方向。根据游戏阶段调整防守权重:前期更注重进攻布局,后期加强防守。我的做法是引入游戏阶段系数:
javascript复制getGamePhase() {
const moveCount = this.data.moveHistory.length
if (moveCount < 10) return 'opening'
if (moveCount < 40) return 'midgame'
return 'endgame'
}
evaluatePattern(pattern) {
const phase = this.getGamePhase()
let baseScore = PATTERN_SCORES[pattern] || 0
// 中后期加强防守
if (pattern.includes('1') && phase !== 'opening') {
baseScore *= 1.5
}
return baseScore
}
性能优化同样重要。在真机上测试时,AI思考时间过长会影响体验。我通过以下手段优化:
javascript复制// 预生成模式匹配表
const PATTERN_SCORES = {
'2222': 10000,
'222': 1000,
// ...其他模式...
}
// 优化后的评估函数
evaluatePattern(pattern) {
// 直接查表避免复杂计算
return PATTERN_SCORES[pattern] || 0
}
一个容易被忽视但很重要的细节是落子顺序优化。通过从中心向外搜索、优先考察已有棋子周围位置,可以大幅减少无效计算:
javascript复制getCandidatePositions() {
const candidates = []
// 中心优先
const center = 7 * 15 + 7
if (this.data.grids[center] === 'empty') {
candidates.push(center)
}
// 已有棋子周围3格范围内
for (let i = 0; i < 225; i++) {
if (this.data.grids[i] === 'empty') {
const x = i % 15
const y = Math.floor(i / 15)
// 检查周围是否有棋子
for (let dy = -3; dy <= 3; dy++) {
for (let dx = -3; dx <= 3; dx++) {
const nx = x + dx
const ny = y + dy
if (nx >= 0 && nx < 15 && ny >= 0 && ny < 15) {
const pos = ny * 15 + nx
if (this.data.grids[pos] !== 'empty') {
candidates.push(i)
break
}
}
}
}
}
}
return [...new Set(candidates)] // 去重
}
完成基础AI后,可以考虑添加这些增强功能:
难度分级是提升用户体验的好方法。通过调整权值表和搜索深度实现不同难度:
javascript复制// 难度配置
const DIFFICULTY_SETTINGS = {
easy: {
searchDepth: 1,
defenseWeight: 1.0
},
medium: {
searchDepth: 2,
defenseWeight: 1.3
},
hard: {
searchDepth: 3,
defenseWeight: 1.8
}
}
setDifficulty(level) {
this.difficulty = DIFFICULTY_SETTINGS[level] || DIFFICULTY_SETTINGS.medium
}
开局库能让AI在前期更有策略性。收集常见开局模式,AI优先采用这些走法:
javascript复制const OPENING_BOOK = [
{ moves: [112, 128], response: 113 }, // 斜指开局
{ moves: [112, 98], response: 97 } // 直指开局
]
checkOpeningBook() {
const history = this.data.moveHistory
for (const opening of OPENING_BOOK) {
if (opening.moves.every((move, i) => history[i] === move)) {
return opening.response
}
}
return null
}
Zobrist哈希是优化重复局面判断的高级技巧。通过为每个位置生成随机数,可以快速计算棋盘哈希值:
javascript复制// 初始化哈希表
initZobrist() {
this.zobristTable = {
black: Array(225).fill(0).map(() => Math.random()),
white: Array(225).fill(0).map(() => Math.random())
}
this.currentHash = 0
}
// 更新哈希
updateHash(index, player) {
this.currentHash ^= this.zobristTable[player][index]
}
实际开发中我遇到一个典型问题:AI在优势时不会"终结"比赛。解决方案是添加必胜局检测,当检测到可以连续进攻取胜时,优先执行获胜路线:
javascript复制findWinningSequence(color) {
// 检查所有可能的五连珠路线
for (let i = 0; i < 225; i++) {
if (this.data.grids[i] !== 'empty') continue
// 模拟落子
this.data.grids[i] = color
if (this.checkWin(i)) {
// 恢复棋盘
this.data.grids[i] = 'empty'
return i
}
this.data.grids[i] = 'empty'
}
return null
}
开发过程中难免遇到各种问题,分享几个我踩过的坑和解决方案:
胜负判断不准确是最常见问题。建议编写测试用例覆盖各种赢法:
javascript复制testWinConditions() {
const testCases = [
{ desc: '水平五连', moves: [0,1,2,3,4], expected: true },
{ desc: '垂直五连', moves: [0,15,30,45,60], expected: true },
// 更多测试用例...
]
testCases.forEach(({desc, moves, expected}) => {
this.resetBoard()
moves.forEach(i => this.makeMove(i))
const actual = this.checkWin(moves[moves.length-1])
console.assert(actual === expected, `${desc}测试失败`)
})
}
AI反应慢的问题可以通过性能分析定位瓶颈。小程序开发者工具自带Performance面板,我的经验是:
javascript复制// 使用memoization的例子
const patternCache = new Map()
evaluatePattern(pattern) {
if (patternCache.has(pattern)) {
return patternCache.get(pattern)
}
const score = //...计算过程...
patternCache.set(pattern, score)
return score
}
跨设备兼容性问题也不容忽视。特别是不同机型的分辨率适配:
css复制/* 使用rpx确保在不同设备上大小一致 */
.chessboard {
width: 750rpx;
height: 750rpx;
}
.grid {
width: 50rpx;
height: 50rpx;
}
最后分享一个实用调试技巧:在开发阶段添加AI思考过程可视化,可以直观理解AI决策逻辑:
javascript复制// 在WXML中添加调试面板
<view class="debug-panel" wx:if="{{debugMode}}">
<text>当前最佳落子: {{bestMove}}</text>
<text>得分: {{bestScore}}</text>
<text>思考时间: {{thinkTime}}ms</text>
</view>
// 在AI计算时记录信息
calculateAIMove() {
const startTime = Date.now()
// ...原有计算逻辑...
this.setData({
bestMove: `${Math.floor(bestMove/15)+1}-${bestMove%15+1}`,
bestScore,
thinkTime: Date.now() - startTime
})
}
完成开发后,这些优化措施能让你的小程序更专业:
代码分包是必做优化。将AI逻辑、资源文件等拆分为子包,显著提升首屏加载速度:
json复制// app.json配置
{
"subpackages": [
{
"root": "ai-module",
"pages": ["pages/ai-logic"]
}
]
}
内存管理在小游戏中尤为重要。五子棋虽然不复杂,但长时间运行可能导致内存增长。建议:
javascript复制// 对象池示例
const piecePool = {
free: [],
used: [],
acquire() {
return this.free.pop() || { type: null }
},
release(obj) {
obj.type = null
this.free.push(obj)
}
}
数据分析能帮助你了解用户行为。添加简单的数据统计:
javascript复制// 记录游戏数据
recordGameData(result) {
wx.request({
url: '你的统计接口',
data: {
duration: Date.now() - this.startTime,
moveCount: this.data.moveHistory.length,
result
}
})
}
提交审核前注意:
javascript复制// 生产环境移除调试功能
if (!isDev) {
wx.setEnableDebug({ enableDebug: false })
}
如果你想进一步提升五子棋AI水平,这些方向值得探索:
**蒙特卡洛树搜索(MCTS)**是更强大的AI算法。虽然实现复杂,但效果显著优于权值法。核心思想是通过随机模拟评估走法价值:
javascript复制class MCTSNode {
constructor(move, parent) {
this.move = move
this.parent = parent
this.wins = 0
this.visits = 0
this.children = []
}
selectBestChild() {
// UCB1算法选择最优子节点
return this.children.reduce((best, child) => {
const score = child.wins / child.visits +
Math.sqrt(2 * Math.log(this.visits) / child.visits)
return score > best.score ? { node: child, score } : best
}, { score: -1 }).node
}
}
神经网络是当前最前沿的解决方案。你可以训练一个CNN网络评估棋盘状态:
python复制# Python示例代码
model = Sequential([
Conv2D(64, (3,3), activation='relu', input_shape=(15,15,3)),
Flatten(),
Dense(128, activation='relu'),
Dense(1, activation='sigmoid')
])
model.compile(optimizer='adam', loss='mse')
性能优化永无止境。WebAssembly是小游戏性能提升的黑科技,将核心算法用C++编写后编译为wasm:
cpp复制// wasm示例
extern "C" {
int calculateBestMove(int* board) {
// C++实现的核心算法
}
}
推荐学习资源:
我在实际项目中发现,结合传统博弈树和现代机器学习的方法往往能取得最佳效果。比如用权值法生成候选走法,再用轻量级神经网络评估这些走法,既保证了强度又控制了计算量。