体验地址 : https://emetofo.qzz.io/
马年五子棋
丙午马年 · 一码当先 · 码到成功 ![]()
一个精美的春节主题五子棋游戏,支持双人对战和人机对战,采用中国传统红金配色,充满节日氛围。
开发信息
本项目由 iFlow CLI 的 glm-4.7 模型全程开发,从需求分析、代码实现到功能测试,全程由 AI 独立完成。
项目简介
马年五子棋是一个基于 Web 的五子棋游戏,采用纯前端技术实现,无需后端服务器。游戏界面设计精美,融合了中国传统文化元素(灯笼、鞭炮、马年主题等),为玩家提供沉浸式的游戏体验。
核心功能
游戏模式
- 双人对战模式:两名玩家轮流在同一设备上对弈
- 人机对战模式:玩家与 AI 对弈,AI 具有三种难度级别
AI 难度级别
简单:随机落子,适合新手练习
中等:基于位置评估的策略性落子
困难:使用 Minimax 算法 + Alpha-Beta 剪枝,具有较高博弈水平
游戏操作
- 落子:点击棋盘空白处即可落子
- 悔棋:双人对战悔一步,人机对战悔两步
- 重新开始:重置棋盘,保留胜负记录
胜负判定
- 获胜条件:任意方向(横、竖、斜、反斜)连续五子连线
- 获胜提示:显示获胜弹窗,绘制金光连线,计分板更新
- 平局判定:棋盘填满且无玩家获胜时判定为平局
视觉效果
- 动态灯笼:页面顶部两侧有摇曳的灯笼动画
- 落子动画:棋子下落时有缩放动画效果
- 获胜连线:获胜时显示金光闪闪的连线
- 悬停效果:鼠标悬停在棋盘格子上有高亮提示
- 马年图标:标题处有跳跃的马匹图标动画
技术栈
前端技术
- HTML5:页面结构和语义化标签
- CSS3:
- 使用 Tailwind CSS 进行快速样式开发
- 自定义 CSS 动画(灯笼摇摆、棋子下落、马匹跳跃等)
- 响应式布局(适配桌面端和移动端)
- 渐变背景和阴影效果
- JavaScript (ES6+):
- 模块化代码结构
- 游戏逻辑实现
- AI 算法实现
开发工具
- VS Code:代码编辑器
- Chrome DevTools:调试工具
- Playwright:自动化测试工具
项目结构
horse-gomoku/
├── index.html # 主页面文件
├── game.js # 游戏逻辑脚本
└── README.md # 项目说明文档
快速开始
方法一:直接打开
- 下载项目文件到本地
- 使用浏览器直接打开
index.html文件即可运行
方法二:使用本地服务器(推荐)
# 使用 Python 3 启动简单的 HTTP 服务器
cd horse-gomoku
python3 -m http.server 8000
# 然后在浏览器中访问
# http://localhost:8000
# 使用 Node.js 的 http-server
cd horse-gomoku
npx http-server -p 8000
# 然后在浏览器中访问
# http://localhost:8000
实现细节
游戏核心逻辑
1. 数据结构
// 棋盘状态:15x15 二维数组
// 0 = 空, 1 = 黑棋, 2 = 白棋
board = Array(15).fill(null).map(() => Array(15).fill(0))
// 移动历史:记录每一步落子位置
moveHistory = [
{ row: 7, col: 7, player: 1 },
{ row: 7, col: 8, player: 2 },
// ...
]
2. 落子逻辑
function makeMove(row, col) {
// 1. 在棋盘对应位置放置棋子
board[row][col] = currentPlayer;
// 2. 记录移动历史
moveHistory.push({ row, col, player: currentPlayer });
// 3. 检查是否获胜
if (checkWin(row, col)) {
gameOver = true;
showWinModal(currentPlayer);
return;
}
// 4. 检查是否平局
if (moveHistory.length === 225) {
gameOver = true;
showDrawModal();
return;
}
// 5. 切换玩家
currentPlayer = currentPlayer === BLACK ? WHITE : BLACK;
}
3. 获胜判定算法
function checkWin(row, col) {
// 检查四个方向:水平、垂直、对角线、反对角线
const directions = [
[0, 1], // 水平
[1, 0], // 垂直
[1, 1], // 对角线
[1, -1] // 反对角线
];
for (const [dx, dy] of directions) {
// 向两个方向延伸计数
let count = 1; // 当前棋子算1个
// 正方向计数
for (let i = 1; i < 5; i++) {
if (isValidPosition(row + dx*i, col + dy*i) &&
board[row + dx*i][col + dy*i] === currentPlayer) {
count++;
} else {
break;
}
}
// 反方向计数
for (let i = 1; i < 5; i++) {
if (isValidPosition(row - dx*i, col - dy*i) &&
board[row - dx*i][col - dy*i] === currentPlayer) {
count++;
} else {
break;
}
}
// 如果计数≥5,判定获胜
if (count >= 5) {
drawWinningLine(row, col, dx, dy);
return true;
}
}
return false;
}
AI 算法实现
1. 简单 AI(随机策略)
function getRandomMove() {
// 获取所有空白位置
const availableMoves = [];
for (let row = 0; row < 15; row++) {
for (let col = 0; col < 15; col++) {
if (board[row][col] === EMPTY) {
availableMoves.push({ row, col });
}
}
}
// 随机选择一个位置
return availableMoves[Math.floor(Math.random() * availableMoves.length)];
}
2. 中等 AI(评估策略)
function getMediumMove() {
// 1. 优先检查是否能直接获胜
const winningMove = findWinningMove(WHITE);
if (winningMove) return winningMove;
// 2. 阻止对方获胜
const blockingMove = findWinningMove(BLACK);
if (blockingMove) return blockingMove;
// 3. 评估中心区域,选择最优位置
const centerMoves = [];
for (let row = 5; row < 10; row++) {
for (let col = 5; col < 10; col++) {
if (board[row][col] === EMPTY && hasNeighbor(row, col)) {
centerMoves.push({
row, col,
score: evaluatePosition(row, col, WHITE)
});
}
}
}
if (centerMoves.length > 0) {
centerMoves.sort((a, b) => b.score - a.score);
return centerMoves[0];
}
return getRandomMove();
}
3. 困难 AI(Minimax 算法)
function getHardMove() {
// 1. 优先检查是否能直接获胜
const winningMove = findWinningMove(WHITE);
if (winningMove) return winningMove;
// 2. 阻止对方获胜
const blockingMove = findWinningMove(BLACK);
if (blockingMove) return blockingMove;
// 3. 使用 Minimax 算法选择最优移动
let bestScore = -Infinity;
let bestMove = null;
// 只考虑有邻居的候选位置(优化性能)
const candidates = getCandidateMoves();
for (const move of candidates) {
board[move.row][move.col] = WHITE; // 尝试落子
const score = minimax(3, -Infinity, Infinity, false);
board[move.row][move.col] = EMPTY; // 撤销落子
if (score > bestScore) {
bestScore = score;
bestMove = move;
}
}
return bestMove || getRandomMove();
}
// Minimax 算法(带 Alpha-Beta 剪枝)
function minimax(depth, alpha, beta, isMaximizing) {
if (depth === 0) {
return evaluateBoard();
}
const candidates = getCandidateMoves();
if (candidates.length === 0) return 0;
if (isMaximizing) {
// AI 回合,选择最大得分
let maxScore = -Infinity;
for (const move of candidates) {
board[move.row][move.col] = WHITE;
const score = minimax(depth - 1, alpha, beta, false);
board[move.row][move.col] = EMPTY;
maxScore = Math.max(maxScore, score);
alpha = Math.max(alpha, score);
if (beta <= alpha) break; // 剪枝
}
return maxScore;
} else {
// 对手回合,选择最小得分
let minScore = Infinity;
for (const move of candidates) {
board[move.row][move.col] = BLACK;
const score = minimax(depth - 1, alpha, beta, true);
board[move.row][move.col] = EMPTY;
minScore = Math.min(minScore, score);
beta = Math.min(beta, score);
if (beta <= alpha) break; // 剪枝
}
return minScore;
}
}
4. 局面评估函数
function evaluatePosition(row, col, player) {
let score = 0;
const directions = [[0, 1], [1, 0], [1, 1], [1, -1]];
for (const [dx, dy] of directions) {
let count = 1;
let openEnds = 0;
// 正方向延伸
for (let i = 1; i < 5; i++) {
const newRow = row + dx * i;
const newCol = col + dy * i;
if (!isValidPosition(newRow, newCol)) break;
if (board[newRow][newCol] === player) {
count++;
} else if (board[newRow][newCol] === EMPTY) {
openEnds++;
break;
} else {
break;
}
}
// 反方向延伸
for (let i = 1; i < 5; i++) {
const newRow = row - dx * i;
const newCol = col - dy * i;
if (!isValidPosition(newRow, newCol)) break;
if (board[newRow][newCol] === player) {
count++;
} else if (board[newRow][newCol] === EMPTY) {
openEnds++;
break;
} else {
break;
}
}
// 根据连子数和开放端评分
if (count >= 5) score += 100000; // 五连
else if (count === 4 && openEnds === 2) score += 10000; // 活四
else if (count === 4 && openEnds === 1) score += 1000; // 冲四
else if (count === 3 && openEnds === 2) score += 500; // 活三
else if (count === 3 && openEnds === 1) score += 100; // 冲三
else if (count === 2 && openEnds === 2) score += 50; // 活二
else if (count === 2 && openEnds === 1) score += 10; // 冲二
}
return score;
}
界面设计
1. 配色方案
- 背景:红金渐变(中国红 → 火红 → 金黄)
- 棋盘:木纹纹理(棕色渐变)
- 装饰:金色边框和文字
- 棋子:黑色和白色(经典配色)
2. 动画效果
/* 灯笼摇摆动画 */
@keyframes swing {
0%, 100% { transform: rotate(-5deg); }
50% { transform: rotate(5deg); }
}
/* 棋子下落动画 */
@keyframes dropPiece {
0% { transform: scale(0); opacity: 0; }
50% { transform: scale(1.2); }
100% { transform: scale(1); opacity: 1; }
}
/* 马匹跳跃动画 */
@keyframes gallop {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
/* 获胜连线闪光动画 */
@keyframes shine {
0%, 100% { opacity: 0.8; }
50% { opacity: 1; }
}
3. 响应式布局
- 桌面端(≥1024px):左右布局,棋盘和控制面板并排显示
- 平板端(768px-1023px):垂直布局,控制面板在棋盘下方
- 移动端(<768px):紧凑布局,棋盘格子缩小为 32px
测试
项目使用 Playwright 进行自动化测试,覆盖以下测试场景:
游戏加载和显示
棋盘渲染(15×15网格)
玩家落子功能
游戏模式切换
AI功能
难度切换
悔棋功能
重新开始
获胜检测
获胜弹窗
计分板更新
响应式布局
浏览器兼容性
Chrome/Edge (推荐)
Firefox
Safari
Opera
移动端浏览器
游戏规则
- 黑棋先行,双方轮流落子
- 落子后不能移动或移动位置
- 任意方向(横、竖、斜、反斜)连续五子连线即获胜
- 棋盘填满且无玩家获胜时判定为平局
- 人机模式下,玩家执黑棋先行
祝您马年大吉 · 骏码奔腾 · 人强码壮 ![]()

