[教你做小游戏]|[教你做小游戏] 用177行代码写个体验超好的五子棋

我是HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,发送加微信,交个朋友),转发本文前需获得作者HullQin授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费没广告。还开发了《Dice Crush》参加Game Jam 2022。喜欢可以关注我 HullQin 噢~我有空了会分享做游戏的相关技术。
1. 需求描述
  • 支持本地双人对战的五子棋游戏。
  • 对于刚下的一步棋,要有标记。
  • 要有提示:五联珠后提示谁赢了。
  • 支持重新开局。
  • 适配多种分辨率的屏幕。
面对这样一个五子棋游戏的需求,你会怎么做呢?
2. 技术选型 参考掘金文章《H5小游戏技术选型分析,低代码?小游戏框架?canvas或SVG?还能用React?》,我们利用文章的决策树进行技术选型:
  1. 我们不需要借助现有的游戏模板。
  2. 我们不需要素材管理、不涉及物理引擎。
  3. 我们不需要动画、不需要每帧渲染。
结论:手撸五子棋。
此外,因为要适配不同的分辨率,所以我们采用SVG绘制棋盘和棋子,不用canvas。
3. 绘制棋盘 背景
棋盘背景选个木头的棕色。
body { height: 100vh; margin: 0; background: bisque; }

15*15的线条
我们先通过path绘制15条横线、15条竖线,每个格子设置为10的宽度,那么就是从-70绘制到70。

此外,我们还需要适配多种分辨率,参考《2行代码,让你的UI适配移动端、PC端,快来收藏》,只需2行代码!
5个标记点
五子棋棋盘通常有5个标记点,我们再通过rect画5个黑色的矩形。因为可以复用,所以我们采用defs定义,这样以后可以通过use来复用。使用defs就好比我们封装了个可复用的标记组件。

目前,效果如图:
[教你做小游戏]|[教你做小游戏] 用177行代码写个体验超好的五子棋
文章图片

4. 提示功能 为了实现提示,我们参考antd的Message,可以写出这个功能:

#message { -webkit-box-sizing: border-box; box-sizing: border-box; margin: 0; padding: 0; color: rgba(0,0,0,.85); font-size: 14px; font-variant: tabular-nums; line-height: 1.5715; list-style: none; -webkit-font-feature-settings: "tnum"; font-feature-settings: "tnum"; position: fixed; top: 8px; left: 0; z-index: 1010; width: 100%; pointer-events: none; text-align: center; } #message > div { padding: 6px; } #message > div > div { display: inline-block; padding: 12px 18px; background: #fff; border-radius: 2px; -webkit-box-shadow: 0 3px 6px -4px rgba(0,0,0,.12), 0 6px 16px 0 rgba(0,0,0,.08), 0 9px 28px 8px rgba(0,0,0,.05); box-shadow: 0 3px 6px -4px rgba(0,0,0,.12), 0 6px 16px 0 rgba(0,0,0,.08), 0 9px 28px 8px rgba(0,0,0,.05); pointer-events: all; }

const messageDiv = document.getElementById('message'); const Message = (content) => { const div = document.createElement('div'); div.innerHTML = `${content}`; messageDiv.appendChild(div); setTimeout(() => messageDiv.removeChild(div), 3000); };

调用Message('游戏结束,黑棋胜利!')的效果如下:
[教你做小游戏]|[教你做小游戏] 用177行代码写个体验超好的五子棋
文章图片

5. 绘制棋子 封装棋子组件
棋子也是组件,这里给出defs定义。
定义了2种渐变色,分别用于填充棋子。棋子就是简单的circle圆形,半径4.2,直径就是8.4。

绘制新下棋子的标记
const mark = document.createElementNS("http://www.w3.org/2000/svg", 'rect'); mark.setAttribute('fill', 'red'); mark.setAttribute('width', '2'); mark.setAttribute('height', '2'); mark.setAttribute('opacity', '0'); svg.appendChild(mark);

渲染所有棋子
未下的棋子,设置为透明。一次性把15*15个棋子都提前渲染好。
顺便添加了hover事件:
  • 鼠标进入时,如果位置当前可以下棋,变成半透明。
  • 鼠标离开时,如果位置当前可以下棋,变成全透明。
顺便添加了点击事件:
  • 如果位置当前可以下棋,那么就把这个棋子变成黑色或白色。
  • 下棋后,如果游戏没结束,当前就该另一方下棋。否则,提示游戏结束。
这里需要给出game定义:
const game = { black: true, // 该黑下棋了吗? winner: null, // 游戏有胜利者了吗?是'black'或'white'或null };

const svg = document.getElementById('svg'); const pieces = []; for (let x = 0; x < 15; x++) { pieces.push([]); for (let y = 0; y < 15; y++) { const piece = document.createElementNS('http://www.w3.org/2000/svg', 'use'); pieces[x].push(piece); piece.setAttribute('x', (x * 10 - 70).toString()); piece.setAttribute('y', (y * 10 - 70).toString()); piece.setAttribute('fill-opacity', '0'); piece.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', '#piece'); piece.addEventListener('mouseenter', () => { if (game.winner || piece.getAttribute('fill-opacity') === '1') return; piece.setAttribute('fill', game.black ? 'url(#black)' : 'url(#white)'); piece.setAttribute('fill-opacity', '0.5'); }); piece.addEventListener('mouseleave', () => { if (game.winner || piece.getAttribute('fill-opacity') === '1') return; piece.setAttribute('fill-opacity', '0'); }); piece.addEventListener('click', () => { if (game.winner || piece.getAttribute('fill-opacity') === '1') return; piece.setAttribute('fill', game.black ? 'url(#black)' : 'url(#white)'); piece.setAttribute('fill-opacity', '1'); dropPiece(x, y); game.black = !game.black; }); svg.appendChild(piece); } }

下棋函数
设置了最新下棋点的标记;并把对应位置的棋子设置为不透明。
const dropPiece = (x, y) => { mark.setAttribute('x', (x * 10 - 71).toString()); mark.setAttribute('y', (y * 10 - 71).toString()); mark.setAttribute('opacity', '0.7'); game.winner = checkWinner(x, y); if (game.winner) { Message(`游戏结束,${game.winner === 'black' ? '黑' : '白'}棋胜利!`); } };

6. 判断是否胜利 只需要判断最新下的棋子,是否有五联珠,就知道是否胜利了。
参考文章《《五子棋》怎么判断输赢?你能5分钟交出代码吗?》,这里不罗列代码啦。
7. 重新开局功能 弄一个按钮,点它后重置游戏数据即可:
  • 所有棋子变透明。
  • 隐藏最新棋子的标记。
  • 重置game变量。

const game = { black: true, winner: null, }; window.initializeGame = () => { game.black = true; game.winner = null; mark.setAttribute('opacity', '0'); for (let x = 0; x < 15; x++) { for (let y = 0; y < 15; y++) { pieces[x][y].setAttribute('fill-opacity', '0'); } } }; window.initializeGame();

9. 写在最后 【[教你做小游戏]|[教你做小游戏] 用177行代码写个体验超好的五子棋】我是HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,发送加微信,交个朋友),转发本文前需获得作者HullQin授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费没广告。还开发了《Dice Crush》参加Game Jam 2022。喜欢可以关注我 HullQin 噢~我有空了会分享做游戏的相关技术。

    推荐阅读