从零实现五子棋AI游戏:完整教程
在这篇教程中,我们将一步步实现一个具有AI对手的五子棋游戏。通过这个项目,你将学习到:
- 如何使用HTML和CSS创建游戏界面
- 如何实现五子棋的基本规则和胜负判定
- 如何开发一个简单但有效的AI对手
1. 游戏界面设计
1.1 HTML结构
我们的游戏界面包含三个主要部分:
- 标题
- 棋盘
- 状态显示和控制按钮
<div class="container">
<h1>五子棋</h1>
<div id="board"></div>
<div class="status" id="status">黑方回合</div>
<button class="reset-btn" onclick="resetGame()">重新开始</button>
</div>
1.2 CSS样式设计
我们使用CSS Grid来创建棋盘,使用渐变效果让棋子看起来更立体:
#board {
display: grid;
grid-template-columns: repeat(15, 30px);
grid-template-rows: repeat(15, 30px);
gap: 0px;
background-color: #DEB887;
padding: 10px;
border: 2px solid #8B4513;
}
.piece {
width: 26px;
height: 26px;
border-radius: 50%;
position: absolute;
}
.black {
background: radial-gradient(circle at 30% 30%, #666, #000);
}
.white {
background: radial-gradient(circle at 30% 30%, #fff, #ccc);
}
2. 游戏核心逻辑
2.1 初始化游戏
首先,我们需要创建棋盘并初始化游戏状态:
const BOARD_SIZE = 15;
let currentPlayer = 'black';
let gameBoard = Array(BOARD_SIZE).fill().map(() => Array(BOARD_SIZE).fill(''));
let gameOver = false;
function createBoard() {
const board = document.getElementById('board');
board.innerHTML = '';
for (let i = 0; i < BOARD_SIZE; i++) {
for (let j = 0; j < BOARD_SIZE; j++) {
const cell = document.createElement('div');
cell.className = 'cell';
cell.dataset.row = i;
cell.dataset.col = j;
cell.addEventListener('click', handleMove);
board.appendChild(cell);
}
}
}
2.2 落子判定
在每次落子时,我们需要:
- 验证落子位置是否合法
- 更新棋盘状态
- 检查是否获胜
- 切换玩家
function makeMove(row, col) {
gameBoard[row][col] = currentPlayer;
const cell = document.querySelector(`[data-row="${
row}"][data-col="${
col}"]`);
const piece = document.createElement('div');
piece.className = `piece ${
currentPlayer}`;
cell.appendChild(piece);
if (checkWin(row, col)) {
document.getElementById('status').textContent =
`${
currentPlayer === 'black' ? '黑方' : '电脑'}胜利!`;
gameOver = true;
return;
}
currentPlayer = currentPlayer === 'black' ? 'white' : 'black';
if (!gameOver) {
document.getElementById('status').textContent =
`${
currentPlayer === 'black' ? '黑方' : '电脑'}回合`;
}
}
2.3 胜负判定
五子棋的胜负判定需要检查四个方向的连子情况:
- 水平
- 垂直
- 主对角线
- 副对角线
function checkWin(row, col) {
const directions = [
[[0, 1], [0, -1]], // 水平
[[1, 0], [-1, 0]], // 垂直
[[1, 1], [-1, -1]], // 主对角线
[[1, -1], [-1, 1]] // 副对角线
];
return directions.some(dir => {
const count = 1 + countDirection(row, col, dir[0]) + countDirection(row, col, dir[1]);
return count >= 5;
});
}
function countDirection(row, col, [dx, dy]) {
let count = 0;
let r = row + dx;
let c = col + dy;
while (
r >= 0 && r < BOARD_SIZE &&
c >= 0 && c < BOARD_SIZE &&
gameBoard[r][c] === currentPlayer
) {
count++;
r += dx;
c += dy;
}
return count;
}
3. AI实现
3.1 位置评估
AI的核心是评估每个可能的落子位置的价值。我们考虑以下因素:
- 连子数量(五连、四连、三连等)
- 位置的中心性
- 攻防平衡
function evaluatePosition(row, col, player) {
if (gameBoard[row][col] !== '') return -1;
let score = 0;
const directions = [
[[0, 1], [0, -1]], // 水平
[[1, 0], [-1, 0]], // 垂直
[[1, 1], [-1, -1]], // 对角线
[[1, -1], [-1, 1]] // 反对角线
];
// 临时落子评估
gameBoard[row][col] = player;
directions.forEach(dir => {
const count = 1 + countDirection(row, col, dir[0]) + countDirection(row, col, dir[1]);
// 评分规则
if (count >= 5) score += 100000; // 五连(必胜)
else if (count === 4) score += 10000; // 四连(高威胁)
else if (count === 3) score += 1000; // 三连
else if (count === 2) score += 100; // 两连
});
gameBoard[row][col] = '';
// 考虑中心位置价值
const centerDist = Math.abs(row - 7) + Math.abs(col - 7);
score += (14 - centerDist) * 10;
return score;
}
3.2 AI决策
AI通过评估所有可能的位置来选择最佳落子点:
function makeAIMove() {
let bestScore = -1;
let bestMove = null;
// 评估所有位置
for (let i = 0; i < BOARD_SIZE; i++) {
for (let j = 0; j < BOARD_SIZE; j++) {
if (gameBoard[i][j] !== '') continue;
// 评估进攻和防守
const aiScore = evaluatePosition(i, j, 'white');
const playerScore = evaluatePosition(i, j, 'black');
// 选择最高分位置,略微偏重防守
const score = Math.max(aiScore, playerScore * 1.1);
if (score > bestScore) {
bestScore = score;
bestMove = [i, j];
}
}
}
if (bestMove) {
makeMove(bestMove[0], bestMove[1]);
}
}
4. 完整代码
以下是完整的游戏代码:
<!DOCTYPE html>
<html>
<head>
<title>五子棋</title>
<style>
.container {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
font-family: Arial, sans-serif;
}
#board {
display: grid;
grid-template-columns: repeat(15, 30px);
grid-template-rows: repeat(15, 30px);
gap: 0px;
background-color: #DEB887;
padding: 10px;
border: 2px solid #8B4513;
}
.cell {
width: 30px;
height: 30px;
border: 1px solid #000;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
position: relative;
cursor: pointer;
}
.piece {
width: 26px;
height: 26px;
border-radius: 50%;
position: absolute;
}
.black {
background: radial-gradient(circle at 30% 30%, #666, #000);
}
.white {
background: radial-gradient(circle at 30% 30%, #fff, #ccc);
}
.status {
margin-top: 20px;
font-size: 20px;
font-weight: bold;
}
.reset-btn {
margin-top: 20px;
padding: 10px 20px;
font-size: 16px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.reset-btn:hover {
background-color: #45a049;
}
.thinking {
color: #666;
font-style: italic;
}
</style>
</head>
<body>
<div class="container">
<h1>五子棋</h1>
<div id="board"></div>
<div class="status" id="status">黑方回合</div>
<button class="reset-btn" onclick="resetGame()">重新开始</button>
</div>
<script>
const BOARD_SIZE = 15;
let currentPlayer = 'black';
let gameBoard = Array(BOARD_SIZE).fill().map(() => Array(BOARD_SIZE).fill(''));
let gameOver = false;
function createBoard() {
const board = document.getElementById('board');
board.innerHTML = '';
for (let i = 0; i < BOARD_SIZE; i++) {
for (let j = 0; j < BOARD_SIZE; j++) {
const cell = document.createElement('div');
cell.className = 'cell';
cell.dataset.row = i;
cell.dataset.col = j;
cell.addEventListener('click', handleMove);
board.appendChild(cell);
}
}
}
function handleMove(e) {
if (gameOver || currentPlayer !== 'black') return;
const row = parseInt(e.target.dataset.row);
const col = parseInt(e.target.dataset.col);
if (gameBoard[row][col] !== '') return;
makeMove(row, col);
if (!gameOver) {
document.getElementById('status').textContent = '电脑思考中...';
document.getElementById('status').classList.add('thinking');
setTimeout(() => {
makeAIMove();
document.getElementById('status').classList.remove('thinking');
}, 500);
}
}
function makeMove(row, col) {
gameBoard[row][col] = currentPlayer;
const cell = document.querySelector(`[data-row="${
row}"][data-col="${
col}"]`);
const piece = document.createElement('div');
piece.className = `piece ${
currentPlayer}`;
cell.appendChild(piece);
if (checkWin(row, col)) {
document.getElementById('status').textContent =
`${
currentPlayer === 'black' ? '黑方' : '电脑'}胜利!`;
gameOver = true;
return;
}
currentPlayer = currentPlayer === 'black' ? 'white' : 'black';
if (!gameOver) {
document.getElementById('status').textContent =
`${
currentPlayer === 'black' ? '黑方' : '电脑'}回合`;
}
}
function checkWin(row, col) {
const directions = [
[[0, 1], [0, -1]], // 水平
[[1, 0], [-1, 0]], // 垂直
[[1, 1], [-1, -1]], // 对角线
[[1, -1], [-1, 1]] // 反对角线
];
return directions.some(dir => {
const count = 1 + countDirection(row, col, dir[0]) + countDirection(row, col, dir[1]);
return count >= 5;
});
}
function countDirection(row, col, [dx, dy]) {
let count = 0;
let r = row + dx;
let c = col + dy;
while (
r >= 0 && r < BOARD_SIZE &&
c >= 0 && c < BOARD_SIZE &&
gameBoard[r][c] === currentPlayer
) {
count++;
r += dx;
c += dy;
}
return count;
}
function evaluatePosition(row, col, player) {
if (gameBoard[row][col] !== '') return -1;
let score = 0;
const directions = [
[[0, 1], [0, -1]], // 水平
[[1, 0], [-1, 0]], // 垂直
[[1, 1], [-1, -1]], // 对角线
[[1, -1], [-1, 1]] // 反对角线
];
gameBoard[row][col] = player;
directions.forEach(dir => {
const count = 1 + countDirection(row, col, dir[0]) + countDirection(row, col, dir[1]);
if (count >= 5) score += 100000;
else if (count === 4) score += 10000;
else if (count === 3) score += 1000;
else if (count === 2) score += 100;
});
gameBoard[row][col] = '';
const centerDist = Math.abs(row - 7) + Math.abs(col - 7);
score += (14 - centerDist) * 10;
return score;
}
function makeAIMove() {
let bestScore = -1;
let bestMove = null;
for (let i = 0; i < BOARD_SIZE; i++) {
for (let j = 0; j < BOARD_SIZE; j++) {
if (gameBoard[i][j] !== '') continue;
const aiScore = evaluatePosition(i, j, 'white');
const playerScore = evaluatePosition(i, j, 'black');
const score = Math.max(aiScore, playerScore * 1.1);
if (score > bestScore) {
bestScore = score;
bestMove = [i, j];
}
}
}
if (bestMove) {
makeMove(bestMove[0], bestMove[1]);
}
}
function resetGame() {
gameBoard = Array(BOARD_SIZE).fill().map(() => Array(BOARD_SIZE).fill(''));
currentPlayer = 'black';
gameOver = false;
document.getElementById('status').textContent = '黑方回合';
document.getElementById('status').classList.remove('thinking');
createBoard();
}
createBoard();
</script>
</body>
</html>
5. AI策略深入分析
5.1 评分系统设计
AI的评分系统是整个游戏的核心,我们采用了分层的评分策略:
-
连子评分
- 五连:100000分(必胜)
- 四连:10000分(高威胁)
- 三连:1000分(中等威胁)
- 二连:100分(潜在威胁)
-
位置评分
- 基于到棋盘中心的距离
- 距离每增加1,分数减少10分
- 最大额外得分:140分(中心点)
5.2 AI决策过程
AI的决策过程可以分为以下步骤:
-
遍历所有空位
for (let i = 0; i < BOARD_SIZE; i++) { for (let j = 0; j < BOARD_SIZE; j++) { if (gameBoard[i][j] !== '') continue; // 评估该位置 } }
-
双重评估
- 评估AI落子后的得分
- 评估防守玩家可能落子的得分
const aiScore = evaluatePosition(i, j, 'white'); const playerScore = evaluatePosition(i, j, 'black');
-
权衡攻防
- 取攻防得分的最大值
- 给防守分数略微加权(1.1倍)
const score = Math.max(aiScore, playerScore * 1.1);
5.3 AI优化方向
当前AI还可以在以下方面进行优化:
-
深度搜索
- 实现极小极大算法
- 添加Alpha-Beta剪枝
- 考虑更多步数的局面
-
模式识别
- 识别特定的棋型(活三、冲四等)
- 为不同棋型分配更精确的权重
-
开局库
- 添加常用开局
- 在开局阶段使用预设策略
-
性能优化
- 实现启发式搜索
- 优化评估函数的计算效率
- 实现缓存机制
6. 用户交互优化
6.1 当前实现的交互特性
-
状态反馈
- 显示当前回合信息
- 显示游戏结果
- 显示AI思考状态
-
操作控制
- 鼠标悬停效果
- 非法落子预防
- 重新开始功能
6.2 可能的改进方向
-
游戏设置
- 难度选择
- 先手选择
- 棋盘大小调整
-
游戏功能
- 悔棋功能
- 保存/加载游戏
- 复盘功能
-
界面优化
- 音效反馈
- 落子动画
- 最后落子标记
- 胜利线标记
7. 总结
这个五子棋AI实现虽然简单,但包含了游戏开发的几个重要方面:
-
界面设计
- 清晰的视觉层次
- 响应式的用户交互
- 及时的状态反馈
-
游戏逻辑
- 状态管理
- 规则判定
- 胜负判定
-
AI实现
- 局面评估
- 决策算法
- 攻防平衡
这个实现为进一步优化提供了良好的基础。通过添加更复杂的算法和功能,可以将其发展成一个更强大的五子棋游戏。