如何使用纯 CSS 制作四子连珠游戏

 序言:你是否想过单纯使用 CSS 也可以制作一款游戏?甚至可以双人对决!这是一篇非常有趣的文章,作者详细讲解了使用纯 CSS 制作四子连珠游戏的思路以及使用奇淫巧技解决困难问题的方法。因为案例本身比较复杂,而本人水平有限,翻译必有不恰当之处,欢迎留言评论。

原文:How the Roman Empire Made Pure CSS Connect 4 Possible

翻译:nzbin

实验是学习新技巧、思考新想法、并突破自身极限的有趣的方式。“纯 CSS”演示很早就有了,但是随着浏览器和CSS的发展,新的挑战又出现了。CSS 和 HTML 预处理器也促进了纯 CSS 演示的发展。有时候,预处理程序用于每个可能的硬编码场景,比如 :checked 的长字符串和相邻兄弟选择器。

在本文中,我将介绍使用纯 CSS 制作的四子连珠游戏的关键思想。在我的实验中,我尽量避免硬编码,并且不使用预处理器,专注于保持代码的简洁。以下是游戏的所有代码以及演示:

See the Pen Pure CSS Connect 4 by Bence Szabó (@finnhvman) on CodePen.

基本概念

我认为在“纯 CSS”类型中有一些概念是必不可少的。通常,表单元素用于管理状态和捕获用户操作。当我发现有人使用 <button type="reset"> 重置或者重新开始新游戏时,我非常兴奋。只需要将元素包裹在 <form> 标签中并添加按钮。在我看来,这是一个比刷新页面更方便的解决方案。

第一步就是创建表单元素,再在表单中创建一些用作圆孔(the slots)的 input,然后添加重置按钮。以下是使用 <button type="reset">的基本演示:

See the Pen Pure HTML Form Reset by Bence Szabó (@finnhvman) on CodePen.

为了让演示好看一些,我使用 radial-gradient(),而不是在游戏板(the board)或者圆盘(the discs)上贴一张图片。我经常使用 Lea Verou 制作的 CSS3 图案库。它是使用渐变制作的图案集,而且很容易编辑。我使用了currentcolor,非常适合圆盘的图案。我添加了头部,并且复用了自己制作的纯 CSS 波纹按钮


现在,布局和圆盘已经设计好了,只是还不能游戏

把圆盘放到游戏板上

接下来,需要让用户轮流将圆盘放到四子连珠的游戏板上。在四子连珠游戏中,玩家(一个红色,一个黄色)轮流将圆盘放置在面板的列中。游戏板有 7 列 6 行(一共有 42 个圆孔)。每一个圆孔可以为空或者被一个红色或黄色的圆盘占用。所以,一个圆孔可以有三种状态(空、红色或者黄色)。在同一列中掉落的圆盘会堆叠在一起。

首先我为每个圆孔放置了两个 checkbox 。当它们都没有被选中时,圆孔就被认为是空的,当其中一个被选中时,相应的玩家就会把他的圆盘放进去。

当其中任何一个被选中之后,应该把它隐藏起来,避免出现两者都被选中的状态。这些 checkbox 是直接的兄弟类,所以如果选中第一个之后,可以使用 :checked 伪类和相邻兄弟选择器(+)来隐藏两个元素。但是如果选中第二个呢?你可以隐藏第二个,但是怎么才能影响第一个呢?可惜没有选择前一个的兄弟选择器,这不是 CSS 选择器的工作方式。我不得不拒绝这个想法。

实际上,一个 checkbox 本身可以有三个状态,可以使用 indeterminate 状态。问题是,仅仅使用 HTML 不能将其置于不确定状态。即使可以,当再次点击复选框时,它也会转换成选中状态。强迫第二个玩家在移动圆盘时进行双击是不现实的。

我仔细阅读了 MDN 上关于 :indeterminate 的文档后发现 radio input 通用都有 indeterminate 状态。名称相同的 radio 按钮在未选中时都处于这种状态。哇,这是一个真正的初始状态!真正有用的是,选中后一个同胞元素也会对前者产生影响!于是我在游戏板上放置了 42 对 radio input。

从以往的经历来看,使用 label ,并通过合理的顺序搭配 checkbox 或 radio 可以解决问题,但我认为 label 不能使代码更简洁。

为了获得更好的用户体验,我希望交互区域可以更大一些,所以合理的做法是让玩家点击一个列来移动圆盘。通过在合适的元素上添加绝对和相对位置,我将同一列的控件相互叠加。这样,在每一列中只能选择最下面的圆孔。我仔细地设置了每一行的圆盘下降的时间,它们的时间函数近似于一个二次曲线,与现实中的自由落体相似。到目前为止,游戏的各部分都做好了,但是下图清晰地显示出只有红色的玩家才能操作。


尽管已经设置了所有的控件,但只有红色的圆盘可以落在游戏板上

我用彩色且半透明的矩形对 Radio input 的可点击区域用进行了可视化显示。黄色和红色的 input 在每列上重叠 6 次(= 6 行),将最下面一行的红色的 input 放在顶部。红色和黄色的混合形成了橙黄色,可以在游戏板上看到。每一列中可用的圆孔越少,这种橙黄色就越不强烈,因为 radio input 只有在 :indeterminate 状态时才会显示。由于在每个圆孔上,红色 input 总是盖住黄色 input,所以只有红色的玩家能够移动。

轮流游戏

我只有一个模糊的想法,就是能不能使用普通的兄弟选择器解决玩家轮流游戏的问题。这个想法就是统计选中的 input 的数量,为偶数(0、2、4等)时红色玩家移动,为奇数时黄色玩家移动。很快我就意识到一般的兄弟选择器不能(也不应该!)按照我想要的方式工作。

还有一种方式就是使用 nth 选择器。尽管我喜欢使用evenodd这样的关键词,但我还是走进了死胡同。:nth-child 选择器 “统计”父类中的子元素,包括所有类型,类、伪类等等。:nth-of-type 选择器 “统计”在父类中某类型的子类,不包括类或伪类。所以问题就在于无法通过 :checked 状态去统计。

CSS counters 也可以统计,所以为什么不试试呢?计数器的一个常见用法是在文档中对标题(甚至多个级别)进行编号。它们由 CSS 规则控制,可以在任何时候被重置,其增加(或递减!)值可以是任意整数。计数器“counter()”函数显示在 content 属性中。

所以最简单的方法就是设置计数器,然后统计四子连珠游戏中 :checked 的 input 的数量。这种方法只有两个困难。首先,你不能在一个计数器上执行算术运算来检测它是偶数还是奇数。其次,你不能基于计数器的值在元素上应用 CSS 规则。

我使用二进制解决了第一个问题。计数器的初始值设为 0 。当红色玩家选中 radio 按钮时,计数器加 1。当黄色玩家选中 radio 按钮时,计数器就减 1,以此类推。因此,计数器的值始终是 0 或 1,偶数或奇数。

解决第二个问题需要更多的创造力(read: hack)。如上所述,计数器只能显示在 

50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信