[教你做小游戏] 用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。

<svg id="svg" viewBox="-76,-76,152,152" xmlns="http://www.w3.org/2000/svg">
  <path stroke="brown" stroke-width="0.5" fill="none"
    d="m-70,-70h140v140h-140zm10,0v140m10,0v-140m10,0v140m10,0v-140m10,0v140m10,0v-140m10,0v140m10,0v-140m10,0v140m10,0v-140m10,0v140m10,0v-140m10,0v140m10,-10h-140m0,-10h140m0,-10h-140m0,-10h140m0,-10h-140m0,-10h140m0,-10h-140m0,-10h140m0,-10h-140m0,-10h140m0,-10h-140m0,-10h140m0,-10h-140" />
</svg>

此外,我们还需要适配多种分辨率,参考《2行代码,让你的UI适配移动端、PC端,快来收藏》,只需2行代码!

5个标记点

五子棋棋盘通常有5个标记点,我们再通过rect画5个黑色的矩形。因为可以复用,所以我们采用defs定义,这样以后可以通过use来复用。使用defs就好比我们封装了个可复用的标记组件。

<svg id="svg" viewBox="-76,-76,152,152" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <rect id="mark" x="-1" y="-1" width="2" height="2" />
  </defs>
  <path stroke="brown" stroke-width="0.5" fill="none"
    d="m-70,-70h140v140h-140zm10,0v140m10,0v-140m10,0v140m10,0v-140m10,0v140m10,0v-140m10,0v140m10,0v-140m10,0v140m10,0v-140m10,0v140m10,0v-140m10,0v140m10,-10h-140m0,-10h140m0,-10h-140m0,-10h140m0,-10h-140m0,-10h140m0,-10h-140m0,-10h140m0,-10h-140m0,-10h140m0,-10h-140m0,-10h140m0,-10h-140" />
  <use xlink:href="#mark" />
  <use xlink:href="#mark" x="40" y="40" />
  <use xlink:href="#mark" x="40" y="-40" />
  <use xlink:href="#mark" x="-40" y="40" />
  <use xlink:href="#mark" x="-40" y="-40" />
</svg>

目前,效果如图:

1.png

4. 提示功能

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

<div id="message"></div>
#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 = `<div>${content}</div>`;
  messageDiv.appendChild(div);
  setTimeout(() => messageDiv.removeChild(div), 3000);
};

调用Message('游戏结束,黑棋胜利!')的效果如下:

2.png

5. 绘制棋子

封装棋子组件

棋子也是组件,这里给出defs定义。

定义了2种渐变色,分别用于填充棋子。棋子就是简单的circle圆形,半径4.2,直径就是8.4。

<defs>
  <radialGradient id="black">
    <stop offset="0%" style="stop-color:#808080" />
    <stop offset="100%" style="stop-color:#111" />
  </radialGradient>
  <radialGradient id="white">
    <stop offset="0%" style="stop-color:#fff" />
    <stop offset="100%" style="stop-color:#ddd" />
  </radialGradient>
  <circle id="piece" r="4.2" />
</defs>

绘制新下棋子的标记

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变量。
<button id="restart" onclick="initializeGame()">重新开局</button>
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();

8. 码上掘金

代码片段

9. 写在最后

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340