ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

【单机离线版】纯HTML单文件实现三三棋(侗棋)—— 从零打造传统民间对弈游戏(附完整源码与逐行解析)

【单机离线版】纯HTML单文件实现三三棋(侗棋)—— 从零打造传统民间对弈游戏(附完整源码与逐行解析) 纯HTML单文件实现三三棋侗棋—— 从零打造传统民间对弈游戏附完整源码与逐行解析文章目录纯HTML单文件实现三三棋侗棋—— 从零打造传统民间对弈游戏附完整源码与逐行解析一、引言二、游戏规则速览2.1 棋盘结构2.2 棋子与规则2.3 胜负判定三、项目架构总览四、棋盘数据结构设计4.1 Canvas 坐标系与点位生成4.2 连线系统4.3 邻接表走子阶段五、UI 设计详解5.1 Canvas 棋盘绘制5.2 棋子渲染效果5.3 信息面板与阶段标记5.4 响应式适配六、核心游戏逻辑详解6.1 游戏状态管理6.2 成三检测算法6.3 布子阶段与压子机制6.4 连不吃连规则6.5 闷棋判定6.6 走子阶段与吃子6.7 胜负判定七、悔棋系统八、技术亮点总结九、可扩展方向十、完整源码十一、写在最后一、引言三三棋又称打三棋“侗棋”是侗族传统民间对弈游戏广泛流传于湖南、贵州、广西等侗族聚居地区。与常见的象棋、围棋不同三三棋的规则设计极其精巧它分为布子和走子两个阶段核心目标是通过三子连成一线“成三”来打击对手棋子。相比围棋的围地和象棋的将军三三棋更像一种战术打击型棋类——每一步都可能意味着对方棋子的减少。本文将带你用纯 HTML Canvas JavaScript约 700 行代码从零实现一个完整的三三棋游戏。采用 Canvas 绘制棋盘支持鼠标与触屏双操作实现布子、压子、走子、吃子全流程以及悔棋、闷棋判定等完整规则。下载地址https://download.csdn.net/download/qq616491978/93089820阅读本文你将学到如何用 Canvas 绘制同心方框棋盘三层嵌套 连接线三三棋独特的布子→压子→清死子→走子→吃子多阶段状态机设计成三检测算法在离散点位集上的实现连不吃连规则的代码表达不能压/吃对方已成三的棋子闷棋判定 —— 布子阶段双方均未成三时先手直接判负悔棋系统的完整实现支持多阶段回溯完整源码已随文附上文末提供下载与使用说明。二、游戏规则速览三三棋的规则比斗兽棋更为结构化整个对弈分为两个大阶段含多重状态切换。2.1 棋盘结构三三棋的棋盘由三层正方形同心嵌套组成共24 个棋位每层正方形的 4 个角 4 条边的中点三层共 8×3 24 个点位。棋盘上的任意直线横、竖、斜若经过 3 个连续点位即为一条线——这是成三判定的基本单位。2.2 棋子与规则双方各持12 枚棋子红方先手和绿方后手轮流对弈。游戏分为两大阶段阶段规则描述关键机制布子阶段双方轮流在空棋位上落子每次一枚红方先行各 12 枚棋子压子欸子布子时若三子成线标记对方一枚棋子连不吃连不能压对方已成三的棋子清死子棋盘满后统一清除所有被标记的棋子这是进入走子阶段的前置步骤走子阶段后手先行每次沿线移动一格只能走到相邻的空位吃子走子时若三子成线立即吃掉对方一枚棋子同受连不吃连约束2.3 胜负判定条件判定对方棋子被吃光获胜对方场上棋子不足 3 枚获胜无法再成三对方无路可走获胜困毙闷棋布子阶段双方均未成三先手方直接判负闷棋是三三棋极具特色的规则——如果双方在布子阶段过于保守始终未能成三裁判权直接交给后手惩罚先手方的消极策略。三、项目架构总览本项目同样采用单文件 HTML 结构但棋盘渲染改用Canvas因为三三棋的棋盘是同心方格 连接线的组合相比方形网格更适合用绘图 API 实现。三三棋.html ├── style → CSS 样式约 100 行 ├── body HTML → 页面骨架约 50 行 └── script → 游戏逻辑约 560 行 ├── 棋盘几何数据生成 → buildPoints / buildLines / buildAdjacency ├── 游戏状态对象game → board phase mainPhase 标记集合 ├── 成三检测 → findCompletedLines / getCompletedSet ├── Canvas 绘制 → drawAll / drawStone ├── 点击交互分发 → handlePlace / handleMark / handleMove / handleEat ├── 棋盘清理与阶段切换 → checkAndCleanupBoard / switchTurn ├── 胜负判定 → checkEnd / endGame └── 悔棋系统 → undoMove支持多阶段回溯设计亮点三三棋的规则比斗兽棋多了一个维度——时间阶段。实现上用game.phase和game.mainPhase两个字段精确表达当前处于哪个子阶段所有点击事件先判断phase再分发到对应处理函数逻辑清晰且易于调试。四、棋盘数据结构设计4.1 Canvas 坐标系与点位生成三三棋使用 Canvas 渲染棋盘是一个 480×480 的画布。核心思路是按半径分层生成 24 个点位坐标constS480,P55;// S画布尺寸, P边距constCS/2;// 中心坐标 (240, 240)constDS-2*P;// 可用直径 370constR[D/2,D/3,D/6];// 三层半径185, 123, 62// 从外到内逐层生成 8 个方向点functionbuildPoints(){points[];for(letlayer0;layer3;layer){constrR[layer];points.push({x:C-r,y:C-r},{x:C,y:C-r},{x:Cr,y:C-r},{x:Cr,y:C},{x:Cr,y:Cr},{x:C,y:Cr},{x:C-r,y:Cr},{x:C-r,y:C});}}通过三个半径值R[0]185、R[1]123、R[2]62从中心点向 8 个方向45° 步进生成坐标共 24 个棋位。这种极坐标→直角坐标的转换方式让代码只需一个循环就完成了三层点位的生成。4.2 连线系统三三棋的成三建立在直线经过 3 个连续点位的基础上。需要定义所有合法的三连线functionbuildLines(){allLines[];// 每层的四条边横向纵向每边3个点for(letlayer0;layer3;layer){constblayer*8;allLines.push([b,b1,b2]);// 上边allLines.push([b2,b3,b4]);// 右边allLines.push([b4,b5,b6]);// 下边allLines.push([b6,b7,b]);// 左边}// 层间辐射线从中心向外每条经过3层allLines.push([0,8,16],[2,10,18],[4,12,20],[6,14,22]);// 对角allLines.push([1,9,17],[3,11,19],[5,13,21],[7,15,23]);// 正中}总共定义了16 条三连线4×3 层内 8 条层间辐射线。所有成三判定都基于这个连线数组进行。4.3 邻接表走子阶段走子阶段需要知道每个点可以通往哪些相邻点。邻接表由三连线推导而来functionbuildAdjacency(){adjacencyArray.from({length:24},()newSet());for(const[a,b,c]ofallLines){adjacency[a].add(b);adjacency[b].add(a);adjacency[b].add(c);adjacency[c].add(b);}for(leti0;i24;i)adjacency[i][...adjacency[i]];}同一条线上的相邻两点即为可走路径。这种从连线推导邻接的方式避免了手动指定——添加新连线时邻接表自动更新。设计心得棋盘几何数据点位、连线、邻接全部在初始化时计算并缓存后续的游戏逻辑只做查找和判断无需重复运算。这本质上是预计算 查表的时空权衡策略。五、UI 设计详解5.1 Canvas 棋盘绘制functiondrawAll(){ctx.clearRect(0,0,S,S);// ① 绘制背景暖色渐变// ② 高亮成三的线金色加粗// ③ 绘制棋盘连线深棕色线条// ④ 高亮可走位置绿色虚线圆// ⑤ 绘制棋眼深色圆点// ⑥ 绘制棋子带阴影高光的3D效果}Canvas 绘制的优势是可以轻松实现复杂的视觉效果棋子的径向渐变、3D 阴影、选中时的金色虚线圈、被标记棋子的红色叉号等这些在纯 DOM 方案中需要多层嵌套才能实现。5.2 棋子渲染效果functiondrawStone(index,color,selected,marked){// 被标记状态 → 红色叉号覆盖if(marked){/* 画 X */}// 选中状态 → 金色虚线外圈 半透明底环if(selected){/* 虚线圆 */}// 阴影层 → 偏移的暗色底片// 主体 → 径向渐变高光→固有色→暗部// 高光 → 左上角白色反光点}通过径向渐变createRadialGradient模拟石材的立体感高光点放在左上角增强光泽效果。红方#c62828→#7f0000和绿方#2e7d32→#1b5e20色系区分明显。5.3 信息面板与阶段标记右侧信息面板展示双方剩余棋子数、当前回合高亮、状态提示。走子阶段每个玩家卡片上会显示不同的阶段徽章布子→ 蓝色徽章走子→ 绿色徽章标记→ 橙色徽章状态栏在关键阶段压子选择、吃子选择会切换为闪烁警告样式引导玩家操作。5.4 响应式适配media(max-width:820px){.game-container{flex-direction:column;}#gameCanvas{width:100%;height:auto;}}手机端布局自动切换为上下排列Canvas 自适应宽度确保在 375px 宽的屏幕上也能完整显示。六、核心游戏逻辑详解6.1 游戏状态管理三三棋的状态管理比斗兽棋复杂一个数量级因为它有两个维度的状态letgame{board:newArray(24).fill(null),// 24个棋位null/red/greenmarked:newSet(),// 被压子标记的棋子索引集合currentPlayer:red,redLeft:12,greenLeft:12,// 手中剩余棋子redOnBoard:0,greenOnBoard:0,// 场上棋子数phase:place,// 子阶段place|mark|remove|gameovermainPhase:placing,// 主阶段placing|movingselected:null,validMoves:[],lineHighlight:null,// 当前高亮的成三线history:[],// 操作历史悔棋用firstPlayer:red,// 记录先手后手先行走子、闷棋判定anyoneFormedThree:false,// 布子阶段是否有人成过三};关键设计phase控制子操作类型mainPhase控制大阶段。点击事件先检查phase将请求路由到对应的处理函数handlePlace/handleMark/handleEat或走子逻辑。这种状态机路由模式让多阶段游戏的代码保持高度可读。6.2 成三检测算法functionfindCompletedLines(player,idx){constresult[];for(constlineofallLines){if(line.includes(idx)line.every(igame.board[i]player))result.push([...line]);}returnresult.length0?result:null;}核心逻辑检查新落子的位置属于哪些三连线再判断该线上三个点位是否都属于当前玩家。以当前落子点为锚点遍历相关连线而非遍历所有 16 条线大大减少了不必要的检查。// 获取某方所有已成三的棋子索引集合用于连不吃连判断functiongetCompletedSet(player){constsetnewSet();for(constlineofallLines){if(line.every(igame.board[i]player))line.forEach(iset.add(i));}returnset;}getCompletedSet返回一个 Set用于快速判断某个棋子是否处于已成三的状态中——连不吃连规则的核心依赖。6.3 布子阶段与压子机制functionhandlePlace(index){game.board[index]player;// 更新棋子计数...constlinesfindCompletedLines(player,index);if(lines)game.anyoneFormedThreetrue;// 记录有人成过三constoppCompletedgetCompletedSet(opp);constcanMarkgame.board.some((c,i)copp!oppCompleted.has(i));if(linescanMark){game.phasemark;// 进入压子子阶段updateStatus(三子成线请点击对方棋子进行压子标记);}else{// 无成三或无法压子 → 检查棋盘是否满 → 切换回合checkAndCleanupBoard();switchTurn();}}布子逻辑的关键分支落子后检查是否成三若成三检查对方是否有可压棋子连不吃连过滤可压 → 进入mark子阶段等待玩家点击对方棋子不可压对方全是已成的三或全无子→ 直接跳转6.4 连不吃连规则这是三三棋最具特色的规则防止了无限互压的僵局functionhandleMark(index){if(game.board[index]!opp)return;// 不是对方棋子constoppCompletedgetCompletedSet(opp);if(oppCompleted.has(index)){updateStatus(不能压对方已成三线的棋子连不吃连,true);return;// ← 关键已成三的棋子受到保护}game.marked.add(index);// 标记为死子// ...棋盘满→统一清理}getCompletedSet(opp)返回对方所有处于成三状态的棋子索引。如果玩家想压的棋子在集合中操作被拒绝。这个规则确保了当你费尽心思组成了三连你的棋子不会立即被对手压掉维护了攻防平衡。6.5 闷棋判定functioncheckAndCleanupBoard(){if(game.mainPhase!placing)return;constallPlaced(game.redLeft0game.greenLeft0);if(!allPlaced)return;// 闷棋布子结束双方均未成三 → 先手判负if(!game.anyoneFormedThree){endGame(oppOf(game.firstPlayer),闷棋双方布子均未成三先手直接判负);return;}// ...}anyoneFormedThree是布子阶段的一个全局标志位只要任何一方在布子时成过三就设为true。棋盘满时检查这个标志若为false则触发闷棋判定。闷棋的策略意义它迫使双方在布子阶段必须积极寻求成三不能一味防守。如果没有闷棋规则玩家可能倾向于纯粹占位而回避对抗让游戏变得沉闷。6.6 走子阶段与吃子走子阶段的核心流程选中己方棋子 → 高亮可走邻位 → 移动到空位 ↓ 检查是否成三 ↓ 是 对方是否有可吃棋子连不吃连过滤 ↓ 是 → 进入 remove 子阶段等待玩家点击对方棋子 ↓ 否 → 切换回合 ↓ 否 → 切换回合functionhandleMove(index){// 执行移动game.board[from]null;game.board[index]player;constlinesfindCompletedLines(player,index);constcanEatgame.board.some((c,i)copp!getCompletedSet(opp).has(i));if(linescanEat){game.phaseremove;// 进入吃子子阶段updateStatus(三子成线请吃掉对方一子连不吃连);}else{switchTurn();// 直接切换回合}}走子阶段的踩线三吃机制是三三棋最具攻击性的设计——走子形成三连后立刻吃子所以走子阶段每一步都要小心被对手借道成三。6.7 胜负判定functioncheckEnd(){constredTotalgame.redLeftgame.redOnBoard;constgreenTotalgame.greenLeftgame.greenOnBoard;if(redTotal0){endGame(green,红方被吃光);returntrue;}if(greenTotal0){endGame(red,绿方被吃光);returntrue;}if(redTotal3){endGame(green,红方不足3枚);returntrue;}if(greenTotal3){endGame(red,绿方不足3枚);returntrue;}// 走子阶段 → 困毙检查if(game.mainPhasemoving){letcanMoveAnyfalse;for(leti0;i24;i){if(game.board[i]playeradjacency[i].some(ngame.board[n]null)){canMoveAnytrue;break;}}if(!canMoveAny){endGame(oppOf(player),无路可走);returntrue;}}returnfalse;}四种胜负路径吃光、不足 3 枚、无路可走、闷棋在棋盘清理阶段判定。每次落子/走子/吃子后都会调用checkEnd()确保立即响应胜负。七、悔棋系统三三棋的悔棋实现比一般游戏复杂因为它需要回溯多阶段的复合操作functionundoMove(){constlastgame.history.pop();if(last.typeeat){// 回退吃子 → 恢复被吃棋子 → 回到 remove 阶段game.board[last.index]last.removed;game.phaseremove;}elseif(last.typemark){// 回退压子 → 取消标记 → 回到 mark 阶段game.marked.delete(last.index);game.phasemark;}elseif(last.typeplace){// 回退布子 → 清空棋位 → 恢复手中棋子game.board[last.index]null;game.redLeft(或 greenLeft);game.anyoneFormedThreerecalcAnyoneFormedThree();// 重新扫描}elseif(last.typemove){// 回退走子 → 恢复原位game.board[last.to]null;game.board[last.from]last.player;}}悔棋的核心挑战是anyoneFormedThree标志——回退布子后需要重新扫描整个棋盘来重建这个标志。recalcAnyoneFormedThree()遍历所有 24 个点位重新执行成三检测虽然复杂度为 O(n²)但悔棋操作低频、棋盘只有 24 个点位完全够用。八、技术亮点总结方面采用方案优势棋盘渲染HTML5 Canvas支持复杂图形效果、3D 光影坐标生成极坐标→直角坐标简洁的三层点位数学建模成三判定预计算连线 锚点过滤O(1)3 条相关线而非 O(n)16 条多阶段管理phase mainPhase 双态路由事件分发清晰各阶段逻辑解耦连不吃连getCompletedSet() Set 查询O(1) 判断棋子是否受保护闷棋判定anyoneFormedThree 全局标志一次扫描即可判定无需回溯悔棋history 栈 分类回滚覆盖所有操作类型的精确回溯响应式Canvas 自适应 Flexbox一套代码适配桌面和手机九、可扩展方向AI 对弈三三棋的搜索空间适中24 点位适合用 Minimax 算法实现 AI 对手联机对战通过 WebSocket 实现双人远程对弈房间机制棋谱记录支持导出/导入棋谱文件实现复盘分析音效落子、成三、吃子等事件添加音效反馈变种规则支持不同地区的三三棋变体如连可吃连不用闷棋等十、完整源码完整的单文件 HTML 源码已随本文附上可直接保存为.html文件双击浏览器即可运行。核心代码约 700 行所有逻辑均有详细注释。下载地址https://download.csdn.net/download/qq616491978/93089820使用方式复制全部代码保存为三三棋.html双击用浏览器打开推荐 Chrome / Edge红方先手两人轮流在同一设备上操作支持悔棋按钮和触屏操作十一、写在最后三三棋是中华传统民间智慧的一个缩影——规则简单却能衍生丰富的策略。实现过程中它提供了一个绝佳的编程练习场景多阶段状态机的设计、连不吃连这种负向规则的表达、闷棋这种全局判定条件、以及悔棋系统对操作可逆性的要求……每一个特性都在考验代码结构的清晰度。如果你觉得本文有帮助欢迎点赞、收藏、关注也欢迎在评论区交流你的想法或改进方案。把三三棋改成自己的版本才是最好的学习方式
返回列表