3步搞定五子棋游戏在线玩 避坑实战项目
3步搞定五子棋游戏在线玩 避坑实战项目 版本升级后 API 全变了,以前能跑的 Canvas 绘图代码现在直接报错,这种痛谁懂?别急着翻文档,咱们直接上实战项目。今天不整虚的,用原生 JavaScript 加 Canvas,从零手写一个五子棋游戏在线玩的小程序。不用任何框架,不用 npm install,打开浏览器就能跑。这不仅是练手,更是理解前端交互逻辑的最佳切入点。很多初学者卡在“想做一个网页游戏”这一步,其实核心就三块:棋盘绘制、落子逻辑、胜负判断。搞定这三块,你的第一个五子棋游戏在线玩作品就诞生了。 1. 项目目标:为什么选五子棋 做前端实战项目,选什么题材很关键。太简单的没成就感,太复杂的劝退。五子棋是最佳平衡点。 为什么是它?规则简单:黑白交替,连五即胜。不需要复杂的 AI 算法(初期),适合入门。 交互直观:鼠标点哪里,棋子落哪里。视觉反馈即时,容易建立成就感。 技术覆盖全:涉及 DOM 操作、Canvas 绘制、事件监听、状态管理。这些是前端基本功。我们要实现的功能清单:静态棋盘:15x15 的标准棋盘,带坐标线。 动态落子:鼠标点击格子,生成黑/白棋子,交替进行。 胜负判定:实时检测横、竖、斜四个方向是否有五子连珠。 重开一局:清空棋盘,重置状态。注意:这里我们不写 AI。AI 算法(如极小极大算法)是进阶内容,会分散初学者对基础逻辑的注意力。先把基础打牢,再去挑战 AI。 2. 目录结构:极简主义 既然要在线玩,我们就用单文件策略,方便部署和调试。后续如果要扩展,再拆分模块。 gomoku-online/ ├── index.html # 主页面,包含 HTML 结构 ├── style.css # 样式文件,美化界面 └── script.js # 核心逻辑,所有 JS 代码index.html 核心结构: !DOCTYPE html html lang=zh-CN headmeta charset=UTF-8title五子棋游戏在线玩/titlelink rel=stylesheet href=style.css /head bodydiv class=containerh1五子棋游戏在线玩/h1p id=status当前玩家:黑棋/pbutton id=restartBtn重开一局/button!-- Canvas 容器,固定宽高 --canvas id=board width=600 height=600/canvas/divscript src=script.js/script /body /htmlstyle.css 关键点:居中布局,让棋盘看起来更专业。 背景色设为木色或浅灰,模拟真实棋盘质感。 按钮悬停效果,提升交互体验。3. 核心代码实现:逐行拆解 这部分是实战项目的核心。我们分三步走:画棋盘、处理点击、判断胜负。 3.1 初始化 Canvas 与棋盘绘制 Canvas 是 HTML5 的核心绘图 API。很多老项目用 getContext('2d') 时,因为浏览器版本差异或上下文丢失,导致 fillRect 等方法报错。Stack Overflow 上有个高赞回答指出,必须在 Canvas 尺寸确定后再获取 context,否则绘图坐标会偏移。 const canvas = document.getElementById('board'); const ctx = canvas.getContext('2d');// 定义常量,方便后续修改 const BOARD_SIZE = 15; // 15路棋盘 const CELL_SIZE = 40; // 每格40px const MARGIN = 20; // 边距let currentPlayer = 'black'; // 当前玩家,初始黑棋 let boardState = []; // 二维数组存储棋盘状态,0:空 1:黑 2:白// 初始化棋盘状态 function initBoard() {boardState = Array(BOARD_SIZE).fill().map(() = Array(BOARD_SIZE).fill(0));currentPlayer = 'black';document.getElementById('status').innerText = '当前玩家:黑棋';drawBoard(); }// 绘制棋盘网格 function drawBoard() {// 清空画布ctx.clearRect(0, 0, canvas.width, canvas.height);// 画背景ctx.fillStyle = '#DEB887'; // 木色背景ctx.fillRect(0, 0, canvas.width, canvas.height);ctx.strokeStyle = '#000';ctx.lineWidth = 1;// 画横线for (let i = 0; i BOARD_SIZE; i++) {ctx.beginPath();ctx.moveTo(MARGIN, MARGIN + i * CELL_SIZE);ctx.lineTo(MARGIN + (BOARD_SIZE - 1) * CELL_SIZE, MARGIN + i * CELL_SIZE);ctx.stroke();}// 画竖线for (let j = 0; j BOARD_SIZE; j++) {ctx.beginPath();ctx.moveTo(MARGIN + j * CELL_SIZE, MARGIN);ctx.lineTo(MARGIN + j * CELL_SIZE, MARGIN + (BOARD_SIZE - 1) * CELL_SIZE);ctx.stroke();}// 画星位(天元等,可选,增加专业感)drawStarPoints(); }关键点解析:clearRect:每次重绘前必须清空,否则棋子会重叠。 坐标计算:MARGIN + i * CELL_SIZE 是核心公式。行号 i 和列号 j 对应像素位置。 beginPath 和 stroke:这是 Canvas 绘制的标准流程,忘记 beginPath 会导致线条粘连。3.2 事件监听与落子逻辑 鼠标点击 Canvas 时,我们需要计算点击位置对应的是哪个格子。这里有个常见坑:Canvas 的坐标原点在左上角,但鼠标事件的 clientX/Y 是相对于视口的。必须减去 Canvas 本身的偏移量。 canvas.addEventListener('click', function(e) {// 获取点击位置相对于 Canvas 内部的坐标const rect = canvas.getBoundingClientRect();const x = e.clientX - rect.left;const y = e.clientY - rect.top;// 计算行列索引// 注意:要四舍五入到最近的格子中心,而不是直接除以const col = Math.round((x - MARGIN) / CELL_SIZE);const row = Math.round((y - MARGIN) / CELL_SIZE);// 边界检查if (col 0 || col = BOARD_SIZE || row 0 || row = BOARD_SIZE) {return; // 点击了棋盘外}// 检查该位置是否已有棋子if (boardState[row][col] !== 0) {return; // 已有棋子,忽略点击}// 落子boardState[row][col] = (currentPlayer === 'black' ? 1 : 2);// 重绘整个棋盘(包含新棋子)drawBoard();drawPieces();// 判断胜负if (checkWin(row, col, currentPlayer)) {document.getElementById('status').innerText = `${currentPlayer === 'black' ? '黑' : '白'}棋获胜!`;return;}// 切换玩家currentPlayer = currentPlayer === 'black' ? 'white' : 'black';document.getElementById('status').innerText = `当前玩家:${currentPlayer === 'black' ? '黑' : '白'}棋`; });避坑指南:Math.round 很重要。如果你用 Math.floor,点击格子边缘右侧时,可能会被算到下一格,体验极差。 不要只画新棋子。drawPieces 遍历整个 boardState 重新画所有棋子,这样逻辑最简单,性能对于 15x15 的棋盘也完全足够。3.3 棋子绘制与胜负判断 drawPieces 函数: function drawPieces() {for (let i = 0; i BOARD_SIZE; i++) {for (let j = 0; j BOARD_SIZE; j++) {if (boardState[i][j] !== 0) {const x = MARGIN + j * CELL_SIZE;const y = MARGIN + i * CELL_SIZE;ctx.beginPath();// 画圆,半径略小于格子一半ctx.arc(x, y, CELL_SIZE * 0.4, 0, Math.PI * 2);ctx.fillStyle = (boardState[i][j] === 1) ? '#000' : '#FFF';ctx.fill();// 加个边框,让白子在浅色背景上更清晰ctx.strokeStyle = '#000';ctx.stroke();}}} }checkWin 函数:核心算法 这是五子棋游戏在线玩逻辑中最难的部分。我们需要检查以 (row, col) 为中心的四个方向:横、竖、左斜、右斜。 function checkWin(row, col, player) {const directions = [[0, 1], // 横向[1, 0], // 纵向[1, 1], // 右斜[1, -1] // 左斜];for (const [dx, dy] of directions) {let count = 1; // 包含当前点// 正向延伸for (let i = 1; i 5; i++) {const nr = row + i * dx;const nc = col + i * dy;if (nr 0 || nr = BOARD_SIZE || nc 0 || nc = BOARD_SIZE) break;if (boardState[nr][nc] === player) count++;else break;}// 反向延伸for (let i = 1; i 5; i++) {const nr = row - i * dx;const nc = col - i * dy;if (nr 0 || nr = BOARD_SIZE || nc 0 || nc = BOARD_SIZE) break;if (boardState[nr][nc] === player) count++;else break;}if (count = 5) return true;}return false; }逻辑详解:我们不需要遍历整个棋盘,只需要以最新落子点为中心向四周探测。效率极高。 dx 和 dy 定义方向向量。例如 [1, 0] 表示向下走一步。 双向探测:因为五子连珠可能跨越中心点,比如 XXXOXXX 这种情况虽然不合法,但逻辑上我们需要确认连续长度。4. 运行与测试:如何验证你的代码 代码写完了,怎么知道它是对的? 本地运行:安装 Live Server 插件(VS Code 用户)。 右键 index.html,选择 Open with Live Server。 浏览器自动打开 http://127.0.0.1:5500。测试用例:正常落子:点击中心,出现黑子。再点旁边,出现白子。状态栏文字切换正确。 重复点击:点击已有棋子的位置,无反应,不报错。 边界点击:点击棋盘最外圈线条,能正确落子。点击画布空白处,无反应。 胜负测试:手动摆出横向五子,点击最后一子,应提示“黑棋获胜”。 手动摆出竖向五子,测试通过。 摆出“四子一断”,不应判定获胜。重开功能:点击“重开一局”,棋盘清空,状态变回黑棋。常见 Bug 排查:棋子位置偏移:检查 MARGIN 和 CELL_SIZE 是否一致。Canvas 的 width 属性必须与 CSS 设置的宽度一致,否则坐标系会缩放错乱。 点击无效:检查 getBoundingClientRect 是否返回了正确的偏移量。如果页面有滚动条,这个值会变化,所以每次点击都要实时获取。 胜负误判:在 checkWin 中加 console.log,打印出探测到的 count 值,手动推演一遍逻辑。5. 优化扩展:从 Demo 到产品 基础功能跑通后,你的五子棋游戏在线玩项目才算入门。想让它更专业,可以加这些功能: 1. 悔棋功能维护一个 history 数组,每次落子 push 一个 {row, col, player} 对象。 点击“悔棋”,pop 最后一个状态,修改 boardState,重绘。 注意:悔棋后,当前玩家也要切换回来。2. 倒计时使用 setInterval,每 30 秒切换一次玩家或提示超时。 需要在 checkWin 获胜或重开时 clearInterval,防止内存泄漏。3. 音效加载 place.mp3,落子时播放。 加载 win.mp3,获胜时播放。 代码:new Audio('place.mp3').play()。注意某些浏览器需要用户交互后才能自动播放声音。4. 响应式设计使用 CSS Media Query,在小屏幕上缩小 Canvas 尺寸。 或者使用 transform: scale() 缩放整个容器,保持逻辑坐标不变。5. 数据持久化使用 localStorage 保存最佳战绩。 例如:localStorage.setItem('gomoku_wins', count)。进阶挑战:简单 AI:实现一个基于“规则”的 AI,优先堵死对方四子,其次做自己的四三。这比随机落子有趣得多,也是面试常问的逻辑题。 WebSocket 联机:使用 Node.js + Socket.io,实现两个浏览器实时对战。这才是真正的“在线玩”,也是实战项目中最具含金量的部分。6. 小结:从代码到思维 这个项目虽然只有几百行代码,但它涵盖了前端开发的几个核心痛点:Canvas 坐标映射:像素与逻辑坐标的转换,是图形编程的基础。 状态管理:boardState 是单一数据源,所有渲染都基于它。这是 React/Vue 等框架思想的雏形。 算法思维:胜负判断的方向探测,是典型的滑动窗口或射线追踪思想,在图像处理、游戏开发中随处可见。很多初学者觉得做游戏难,其实是难在“不知道从哪下手”。只要你把五子棋游戏在线玩这个最小闭环跑通,你就建立了信心。接下来,你可以试着把它部署到 GitHub Pages 或 Vercel 上,分享给朋友玩。当看到别人通过你写的代码下棋时,那种成就感是无与伦比的。 编程不是背 API,而是解决问题。版本升级 API 变了,但逻辑没变。理解了原理,换什么框架、什么语言,你都能快速上手。 这个实战项目只是个开始。你打算给它加什么功能?是联机对战,还是更复杂的 AI?还有什么不懂的?评论区留言挨个回。

相关新闻

税拔保姆级教程:从语法到项目落地的选型避坑指南

税拔保姆级教程:从语法到项目落地的选型避坑指南

税拔保姆级教程:从语法到项目落地的选型避坑指南 刚啃完几本大部头,代码能跑通,脑子却一片空白?这种“学会语法却不知怎么搭项目”的断层感,是无数初学者深夜崩溃的根源。别再死磕枯燥的理论推导了,你需要一份能直接落地、从0到1带你跑通完整链路的…

2026/9/22 10:59:41 阅读更多 →
全球十大净水器排名实战项目性能优化避坑指南

全球十大净水器排名实战项目性能优化避坑指南

全球十大净水器排名实战项目性能优化避坑指南 配置环境就卡半天,代码跑不动,内存直接爆掉。 别急着怪电脑配置低,大概率是你没搞懂底层数据流转的阻塞点。 我在做 实战项目 时,常拿 全球十大净水器排名…

2026/9/22 10:59:41 阅读更多 →
页游乐园性能瓶颈拆解:3步保姆级教程搞定卡顿

页游乐园性能瓶颈拆解:3步保姆级教程搞定卡顿

页游乐园性能瓶颈拆解:3步保姆级教程搞定卡顿 版本升级后 API 全变了,你的页游乐园项目还在用旧代码硬扛?别慌。这份保姆级教程不玩虚的,直接带你从底层原理到落地代码,把“页游乐园”这种高交互、多组件场景下的性能瓶颈一次性掐灭。…

2026/9/22 10:59:41 阅读更多 →

最新新闻

方差怎么算源码深扒:实战项目避坑指南

方差怎么算源码深扒:实战项目避坑指南

方差怎么算源码深扒:实战项目避坑指南 版本升级后 API 全变了,这是每个老开发者的噩梦。上周接了个市政管网监控的实战项目,数据模块突然报错,排查半天发现是统计库版本迭代,计算方差的接口签名悄悄改了。别慌,今天咱们不背公式,直接钻进源码,看…

2026/9/22 11:52:20 阅读更多 →
男生女生一起差差很痛的APP下载安装20232026最新

男生女生一起差差很痛的APP下载安装20232026最新

2023版APP升级避坑:从入门到精通解析API变更 版本升级后 API 全变了,这是无数开发者在 2023 年接触新版应用时最真实的噩梦。你昨天还写得顺手的代码,今天一运行全是红叉,报错信息像天书一样让人抓狂。这种从入门到精通的断崖式体验…

2026/9/22 11:52:20 阅读更多 →
伏羲和女娲项目避坑,3步搞定环境配置保姆级教程

伏羲和女娲项目避坑,3步搞定环境配置保姆级教程

伏羲和女娲项目避坑,3步搞定环境配置保姆级教程 刚接手“伏羲和女娲”这种大型分布式仿真项目,你是不是也遇到过这种情况?明明照着网上的教程一步步敲命令,结果环境配置就卡半天。依赖版本冲突、网络代理设置错误、本地资源不足,每一个坑都能让你怀疑人…

2026/9/22 11:52:20 阅读更多 →
5个商标logo查询新手必避的坑与最佳实践

5个商标logo查询新手必避的坑与最佳实践

5个商标logo查询新手必避的坑与最佳实践 官方文档冗长到让人头皮发麻,核心逻辑被淹没在几十页的术语里,初学者往往抓不住重点。这种体验在 商标logo查询 领域尤为明显,导致大量开发者在集成查询功能时频频踩坑。真正的 最佳实践…

2026/9/22 11:52:20 阅读更多 →
3个实战项目揭秘:为什么手机代码总报错

3个实战项目揭秘:为什么手机代码总报错

3个实战项目揭秘:为什么手机代码总报错 复制来的代码跑不通,连报错信息都看不懂,这是很多初学者甚至中级开发者的噩梦。你在GitHub上搜到一个关于移动设备通信的实战项目,信心满满地克隆下来,结果一运行,屏幕一片红字,脑子瞬间宕机。别慌,这种…

2026/9/22 11:52:20 阅读更多 →
别装库了!3步手写实现散度定理,搞定大厂面试痛点

别装库了!3步手写实现散度定理,搞定大厂面试痛点

别装库了!3步手写实现散度定理,搞定大厂面试痛点 配置环境就卡半天,pip install 报错、依赖冲突、CUDA 版本不匹配,折腾一上午还没跑通 Demo?别被 NPM/PyPI 官方包…

2026/9/22 11:51:19 阅读更多 →

日新闻

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天

3台商务办公笔记本实测:手写实现环境配置,告别卡半天 配置环境就卡半天?别怪机器慢,多半是你没选对工具链。在Java、Go或Python的项目现场, 手写实现…

2026/9/22 0:00:41 阅读更多 →
剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑

剑帝加点速查手册:3分钟搞懂核心逻辑 面试被问原理答不上来,是不是常态?别慌。很多开发者对着 GitHub 开源仓库里的代码发呆,看似简单实则暗藏玄机。今天这份【剑帝加点】速查手册,直接带你拆解核心实现,把面试必考的原理讲透。…

2026/9/22 0:00:41 阅读更多 →
手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优

手写实现图片压缩网站核心:搞定WebP转换与质量调优 复制来的代码跑不通不知道怎么调?别慌,这种“复制粘贴地狱”在开发圈太常见了。尤其是做 图片压缩网站…

2026/9/22 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/22 4:32:41 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/22 4:38:57 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/22 8:51:04 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/21 15:36:51 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/21 15:36:51 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/22 2:43:42 阅读更多 →