目录
作者:watermelo37
CSDN全栈领域优质创作者、万粉博主、华为云云享专家、阿里云专家博主、腾讯云“创作之星”特邀作者、支付宝合作作者,全平台博客昵称watermelo37。
一个假装是giser的coder,做不只专注于业务逻辑的前端工程师,Java、Docker、Python、LLM均有涉猎。
---------------------------------------------------------------------
温柔地对待温柔的人,包容的三观就是最大的温柔。
---------------------------------------------------------------------
五子棋中的算法实现:如何判断胜负?
一、前言
五子棋是一种极其经典的棋类游戏,规则简单却蕴含丰富的博弈思想。其核心目标是在棋盘上连成五个同色棋子(横、竖、斜任意方向),因此“如何高效判断胜负”就成了五子棋程序实现的关键。最近找到一个实现五子棋的html文件,于是想琢磨琢磨其中的“奥秘”。
在一个典型的 15×15 棋盘上,可能存在成千上万种棋形,如果算法不合理,会导致运行效率低下,甚至影响用户体验。下面我们就从基础到优化,逐步分析实现思路。
优化后的代码放在文章最后,有需要摸鱼的小伙伴可以自取(全部复制,粘贴到记事本里面,然后将后缀名修改为.html,然后双击打开就可以玩咯)
二、胜负判断的核心思路
五子棋的胜负条件可以简化为一句话:某一方刚下的棋子,在横向、纵向、两条对角线方向上,是否形成连续的 5 个棋子?
也就是说,程序只需要关注最近落下的棋子,而不是全盘扫描,这就是算法优化的关键。
三、常见实现方法
1、暴力全盘扫描
每下一步就扫描整个棋盘,逐个格子检查是否有连续 5 个相同棋子。时间复杂度:O(N²),N 为棋盘大小。
2、基于落子点的方向枚举
只从“当前落子点”出发,检查四个方向:横向(左右延伸)、纵向(上下延伸)、左上-右下对角线、右上-左下对角线。每个方向都往两边扩展,统计连续棋子的数量,若 ≥ 5,则判定胜利。
时间复杂度O(1),效率极高。
3、带缓存的方向优化(进阶)
如果希望在AI五子棋(即高难度人机对战)中提升速度,可以给每个格子维护一个“方向统计表”,避免重复计算。这种方法更适合复杂对局,尤其是实现AI搜索时。
四、代码实现与优化
1、代码简介
这段五子棋代码结构比较清楚,主要分为棋盘绘制、游戏逻辑和用户交互几个部分。棋盘用 drawBoard() 画出来,包括横竖格子和星位,棋子用径向渐变填色,看起来更像真实棋子。胜利的五子会用红色边框高亮。
胜负判断用 checkWin(),枚举横、竖、主对角和副对角四个方向,向两端延伸统计连续棋子,如果连续五个或以上就判定胜利,并返回胜利五子的坐标数组。
用户交互方面,通过canvas的点击事件处理落子,同时用board数组保证落子合法性。当前轮到谁和胜利信息会显示在页面上的#status。
2、代码分享
下面是一份基于 方向枚举法 的五子棋实现代码,页面截图如下,快拿去和好盆友微机课摸鱼去吧~
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>五子棋</title>
<style>
body { text-align: center; font-family: "微软雅黑", sans-serif; background: #f5deb3; }
canvas { border: 2px solid #333; display: block; margin: 20px auto; background: linear-gradient(#f5deb3, #deb887); }
button { display: block; margin: 10px auto; padding: 8px 16px; font-size: 16px; }
#status { font-size: 18px; margin: 10px; }
</style>
</head>
<body>
<h1>五子棋</h1>
<div id="status">当前由黑方落子</div>
<canvas id="board" width="600" height="600"></canvas>
<button onclick="restart()">重新开始</button>
<script>
const SIZE = 15;
const GRID = 40;
const board = Array.from({ length: SIZE }, () => Array(SIZE).fill(0));
const canvas = document.getElementById("board");
const ctx = canvas.getContext("2d");
const status = document.getElementById("status");
let current = 1; // 1 黑棋,2 白棋
let gameOver = false;
let winCells = []; // 保存胜利五子坐标
// 画棋盘
function drawBoard() {
ctx.clearRect(0, 0, 600, 600);
// 绘制格子
for (let i = 0; i < SIZE; i++) {
ctx.beginPath();
ctx.moveTo(GRID / 2, GRID / 2 + i * GRID);
ctx.lineTo(GRID / 2 + (SIZE - 1) * GRID, GRID / 2 + i * GRID);
ctx.strokeStyle = "#333";
ctx.stroke();
ctx.beginPath();
ctx.moveTo(GRID / 2 + i * GRID, GRID / 2);
ctx.lineTo(GRID / 2 + i * GRID, GRID / 2 + (SIZE - 1) * GRID);
ctx.strokeStyle = "#333";
ctx.stroke();
}
// 绘制星位
const stars = [3, 7, 11];
for (let i of stars) {
for (let j of stars) {
ctx.beginPath();
ctx.arc(GRID / 2 + i * GRID, GRID / 2 + j * GRID, 5, 0, 2 * Math.PI);
ctx.fillStyle = "#333";
ctx.fill();
}
}
// 重绘棋子
for (let y = 0; y < SIZE; y++) {
for (let x = 0; x < SIZE; x++) {
if (board[y][x] !== 0) {
const highlight = winCells.some(cell => cell[0] === x && cell[1] === y);
drawStone(x, y, board[y][x], highlight);
}
}
}
}
// 画棋子
function drawStone(x, y, player, highlight=false) {
ctx.beginPath();
ctx.arc(GRID/2 + x*GRID, GRID/2 + y*GRID, 15, 0, 2 * Math.PI);
const grad = ctx.createRadialGradient(GRID/2 + x*GRID - 3, GRID/2 + y*GRID - 3, 2,
GRID/2 + x*GRID, GRID/2 + y*GRID, 15);
if (player === 1) {
grad.addColorStop(0, "#555");
grad.addColorStop(1, "#000");
} else {
grad.addColorStop(0, "#fff");
grad.addColorStop(1, "#aaa");
}
ctx.fillStyle = grad;
ctx.fill();
ctx.strokeStyle = highlight ? "red" : "#333";
ctx.lineWidth = highlight ? 3 : 1;
ctx.stroke();
ctx.lineWidth = 1;
}
// 检查胜负并返回胜利五子
function checkWin(x, y, player) {
const directions = [
[1, 0], [0, 1], [1, 1], [1, -1]
];
for (const [dx, dy] of directions) {
let count = 1;
let cells = [[x, y]];
for (let i = 1; i < 5; i++) {
const nx = x + dx * i, ny = y + dy * i;
if (nx < 0 || ny < 0 || nx >= SIZE || ny >= SIZE) break;
if (board[ny][nx] === player) { count++; cells.push([nx, ny]); }
else break;
}
for (let i = 1; i < 5; i++) {
const nx = x - dx * i, ny = y - dy * i;
if (nx < 0 || ny < 0 || nx >= SIZE || ny >= SIZE) break;
if (board[ny][nx] === player) { count++; cells.push([nx, ny]); }
else break;
}
if (count >= 5) return cells;
}
return null;
}
// 点击下棋
canvas.addEventListener("click", e => {
if (gameOver) return;
const x = Math.floor(e.offsetX / GRID);
const y = Math.floor(e.offsetY / GRID);
if (board[y][x] !== 0) return;
board[y][x] = current;
const win = checkWin(x, y, current);
if (win) {
winCells = win;
gameOver = true;
status.textContent = (current === 1 ? "黑棋" : "白棋") + "获胜!";
} else {
current = 3 - current;
status.textContent = "当前由" + (current === 1 ? "黑方" : "白方") + "落子";
}
drawBoard();
});
// 重新开始
function restart() {
for (let i = 0; i < SIZE; i++) board[i].fill(0);
gameOver = false;
winCells = [];
current = 1;
status.textContent = "当前由黑方落子";
drawBoard();
}
drawBoard();
</script>
</body>
</html>
五、结语
实现一个五子棋小游戏方向枚举法是最常见、最实用的写法,几乎所有五子棋实现都用它,如果做AI五子棋,还可以用缓存优化、启发式搜索来进一步提升性能。
只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
其他热门文章,请关注:
极致的灵活度满足工程美学:用Vue Flow绘制一个完美流程图
你真的会使用Vue3的onMounted钩子函数吗?Vue3中onMounted的用法详解
通过array.filter()实现数组的数据筛选、数据清洗和链式调用
测评:这B班上的值不值?在不同城市过上同等生活水平到底需要多少钱?
通过Array.sort() 实现多字段排序、排序稳定性、随机排序洗牌算法、优化排序性能
TreeSize:免费的磁盘清理与管理神器,解决C盘爆满的燃眉之急
通过MongoDB Atlas 实现语义搜索与 RAG——迈向AI的搜索机制
深入理解 JavaScript 中的 Array.find() 方法:原理、性能优势与实用案例详解
前端实战:基于Vue3与免费满血版DeepSeek实现无限滚动+懒加载+瀑布流模块及优化策略
el-table实现动态数据的实时排序,一篇文章讲清楚elementui的表格排序功能
JavaScript双问号操作符(??)详解,解决使用 || 时因类型转换带来的问题
高效工作流:用Mermaid绘制你的专属流程图;如何在Vue3中导入mermaid绘制流程图
内存泄漏——海量数据背后隐藏的项目生产环境崩溃风险!如何避免内存泄漏