关键时刻,第一时间送达!
最近有个很火的视频叫做“5 分钟编写贪吃蛇”。视频很不错,这种快速编程的方法也很有意思,所以我决定自己也做一个。
我小时候刚开始接触编程时学过一个游戏叫做“康威生命游戏”。它是一个简单的元胞自动机的例子,只需几条非常简单的规则,就可以演化出极其复杂的变化。其内容是,在一个格子棋盘上有许多生命,每个回合这些生命按照一定的规则繁殖或死亡:
某个格子的“相邻”格子指它周围的八个格子;
如果一个生命的相邻的格子中包含少于两个生命,则该生命下一回合死亡(人口过少孤独而死);
如果一个生命的相邻格子中包含两个或三个生命,则该生命下一回合存活;
如果一个生命的相邻格子中包含三个以上生命,则该生命下一回合死亡(过于拥挤);
如果一个空格子的相邻格子中包含正好三个生命,则该格子下一回合产生一个生命(繁殖)。
不算第一条关于“相邻”的定义,我们只有四条非常简单的规则。游戏的图像显示很也简单,只是方格的颜色变化而已,所以不需要操作 canvas,用 React就可以很容易地做出来。
如此说来这篇文章也可以算作一篇简单的 React 入门教程。让我们开始吧!
设置 React 环境
首先需要设置 React 环境。
通过 create-react-app(https://github.com/facebook/create-react-app)来创建 React 项目非常方便:
不到一分钟的时间,react-gameoflife 就创建好了。接下来只需要启动它:
这条命令将在 http://localhost:3000 上启动一个开发服务器,并且会自动启动浏览器打开该地址。
实现过程
我们需要实现的最终游戏画面如下所示:
一个简单的格子棋盘,加上一些白色的方块(生命),点击格子可以放置或移除方块。Run 按钮可以按照给定的时间间隔开始回合迭代。
看起来很简单吧?想一想在 React 中怎么做.必须明确的是,React 不是图形框架,所以这里不会使用 canvas。
如果想用canvas做,可以参考下PIXI(http://www.pixijs.com/)或Phaser(https://phaser.io/)。
整个棋盘可以做成一个组件,并渲染成一个
。格子怎么办呢?我们不能用一个个
来画格子,那样效率太低,而且由于格子是静态的,这样做也没必要。实际上可以用CSS3的linear-gradient画格子。
至于生命则可以用
来画。我们将其做成独立的组件,它接收参数x, y,以确定它在棋盘上的位置。
第一步:棋盘
首先来画棋盘。在 src 目录下创建一个文件名为 Game.js,内容如下:
还需要 Game.css 来定义样式:
更新 App.js 导入 Game.js 并将 Game 组件显示出来(代码省略,请参见我在GitHub上分享的完整代码 https://github.com/charlee/react-gameoflife)。现在就能看到一个全黑的棋盘了。
下一步是画格子。只需要一行 linear-gradient 就可以做到(加到 Game.css 中):
其实为了让格子能正确显示,我们还得定义 background-size 样式。但由于 Game.js 中定义了 CELL_SIZE 常量,我们希望能通过该常量来定义格子大小,而不是写死在 CSS 中,所以可以用行内样式来直接定义背景大小。
修改 Game.js 中的 style 行:
刷新浏览器就能看到漂亮的格子。
创建表示生命的方块
向 Game 类添加以下代码:
下一步要允许用户通过点击棋盘的方式添加或删除生命。React 可以给
指定 onClick 事件处理函数,该函数可以通过点击事件的属性来获得点击发生的坐标。但问题是这个事件的坐标是相对于整个客户端区域(即浏览器的可视区域)的,所以需要一些额外的代码将其转换成相对于棋盘的坐标。
向 render() 方法中添加以下事件处理函数。我们同时还保存了棋盘元素的引用,以便稍后获取棋盘的位置。
还需要再加几个函数。getElementOffset() 计算棋盘元素的位置。handleClick() 获取点击的位置,转换成相对坐标,再计算被点击的格子所在的行和列。然后反转相应格子的状态。
别忘了给 Cell 组件加一些样式(Game.css):
刷新浏览器,试着点一下棋盘。现在可以添加或删除生命了!
运行游戏
我们需要一些辅助的东西来运行游戏。首先添加一些控制元素。
这些代码会在页面底部添加一个时间间隔输入框,以及一个 Run 按钮。
现在点击 Run 还没有任何效果,因为我们还没有写游戏规则。下面就开始写游戏规则吧。
这个游戏中,每个回合都会更新棋盘状态。因此我们需要一个方法 runIteration(),该方法将以固定的时间间隔调用,比如每 100 毫秒调用一次。这可以通过 window.setTimeout() 实现。
点击 Run 按钮将调用 runIteration() 方法。该方法在结束之前会调用 window.setTimeout(),设置在 100ms 之后重新运行自己。这样 runIteration() 将反复执行。点击 Stop 按钮会调用 window.clearTimeout() 取消安排好的执行,这样就能打断反复执行。
刷新浏览器并点击“Run”按钮。我们可以在控制台(按 Ctrl-Shift-I 可以调出控制台)中看到“running iteration”的调试信息。
接下来需要给runIteration()方法添加代码以实现游戏规则。回想一下我们的游戏规则:
如果一个生命的相邻的格子中包含少于两个生命,则该生命下一回合死亡。
如果一个生命的相邻格子中包含两个或三个生命,则该生命下一回合存活。
如果一个生命的相邻格子中包含三个以上生命,则该生命下一回合死亡。
如果一个空格子的相邻格子中包含正好三个生命,则该格子下一回合产生一个生命。
我们可以写一个方法 calculateNeighbors() 来计算给定 (x, y) 的相邻格子中的生命数量。
这里省略了 calculateNeighbors() 的代码,源代码在这里:
https://github.com/charlee/react-gameoflife/blob/master/src/Game.js#L134
然后规则就很容易实现了:
刷新浏览器,放置一些生命,然后点击 Run 按钮,就能看到漂亮的动画了!
总结
最后的项目里我还加了个 Random 和 Clear 按钮,让操作更容易些。完整的代码可以在我的 GitHub 上找到:https://github.com/charlee/react-gameoflife。
领取专属 10元无门槛券
私享最新 技术干货