react 代码编写原则_如何在一个小时内用React编写“生命游戏”的代码

react 代码编写原则

by Charlee Li

通过李李

如何在一个小时内用React编写“生命游戏”的代码 (How to code the “Game of Life” with React in under an hour)

Recently I watched the famous video that creates a snake game in less than 5 minutes (on Youtube). It looked pretty interesting to do this type of quick coding, so I decided to do one by myself.

最近,我在YouTube上观看了不到5分钟的著名视频,该视频创建了蛇游戏。 进行这种类型的快速编码看起来很有趣,所以我决定自己做一个。

When I started to learn programming as a child, I learned a game called “Game of Life.” It is a great example of cellular automation and how simple rules can result in complex patterns. Imagine some kind of life form living in a world. At each turn, they follow some simple rules to decide whether a life is alive or dead.

当我从小开始学习编程时,我学习了一个名为“ 生活游戏”的游戏 。 这是蜂窝自动化以及简单规则如何导致复杂模式的一个很好的例子。 想象一下生活在世界中的某种生活形式。 他们在每一步都遵循一些简单的规则来决定生命是活着还是死了。

Conway's Game of Life - WikipediaEver since its publication, Conway's Game of Life has attracted much interest, because of the surprising ways in which…en.wikipedia.org

康威的人生游戏-维基百科 自出版以来,康威的人生游戏就引起了人们的极大兴趣,因为…… en.wikipedia.org

So I decided to code this game. Since it does not involve too many graphics — just a grid and some blocks — I decided React would be a good choice, and it can be used as a quick tutorial for React. Let’s start!

所以我决定编写这个游戏。 由于它不涉及太多图形(仅是一个网格和一些块),因此我认为React将是一个不错的选择,它可以用作React的快速教程。 开始吧!

React设置 (React setup)

First we need to setup React. The amazing create-react-app tool is very handy for starting a new React project:

首先,我们需要设置React。 神奇的create-react-app工具非常适合启动新的React项目:

$ npm install -g create-react-app$ create-react-app react-gameoflife

After less than one minute, react-gameoflife will be ready. Now all we need to do is start it:

不到一分钟后,将准备好react-gameoflife 。 现在,我们需要做的就是启动它:

$ cd react-gameoflife$ npm start

This will start a dev server at http://localhost:3000, and a browser window will be opened at this address.

这将在http:// localhost:3000处启动开发服务器,并在该地址打开浏览器窗口。

设计选择 (Design choices)

The final screen we want to make looks like this:

我们要制作的最终屏幕如下所示:

It is simply a board with a grid, and some white tiles (“cells”) which can be placed or removed by clicking the grid. The “Run” button will start the iterations at a given interval.

它只是一个带有网格的板,还有一些白色的瓷砖(“单元”),可通过单击网格来放置或移除。 “运行”按钮将以给定的间隔开始迭代。

Looks pretty simple, huh? Let’s think about how to do this in React. First of all, React is not a graphic framework, so we won’t think about using canvas. (You can take a look at PIXI or Phaser if you are interested in using canvas.)

看起来很简单,对吧? 让我们考虑一下如何在React中做到这一点。 首先,React 不是图形框架,因此我们不会考虑使用画布。 (如果您对使用画布感兴趣,可以看一下PIXIPhaser 。)

The board can be a component and can be rendered with a single <div>. How about the grid? It’s not feasible to draw the grids with <div> s, and since the grid is static, it is also unnecessary. Indeed we can use CSS3 linear-gradient for the grid.

该板可以是一个组件,并且可以使用单个<d iv>进行渲染。 网格呢? with <div>绘制网格是不可行的,并且由于网格是静态的,因此也没有必要。 确实,我们为网格an use CSS3 lin耳朵渐变。

In regard to the cells, we can use <div> to draw each cell. We will make it a separate component. This component accepts x, y as inputs so that the board can specify its position.

关于单元格,我们可以使用<d iv>绘制每个单元格。 我们将其作为一个单独的组件。 该组件交流c EP t SX,Y作为输入,使得板可以指定其位置。

第一步:董事会 (First step: the board)

Let’s create the board first. Create a file called Game.js under the src directory and type in the following code:

首先创建板。 在src目录下创建一个名为Game.js的文件,然后输入以下代码:

import React from 'react';import './Game.css';
const CELL_SIZE = 20;const WIDTH = 800;const HEIGHT = 600;
class Game extends React.Component {  render() {    return (      <div>        <div className="Board"          style={{ width: WIDTH, height: HEIGHT }}>        </div>      </div>    );  }}
export default Game;

We also need the Game.css file to define the styles:

我们还需要Game.css文件来定义样式:

.Board {  position: relative;  margin: 0 auto;  background-color: #000;}

Update App.js to import our Game.js and place the Game component on the screen. Now we can see a completely black game board.

更新App.js以导入我们的Game.js并将Game组件放置在屏幕上。 现在我们可以看到一个完全黑色的游戏板。

Our next step is to create the grid. The grid can be created with only one line of linear-gradient (add this to Game.css):

我们的下一步是创建网格。 可以仅使用一行linear-gradient来创建网格(将其添加到Game.css ):

background-image:    linear-gradient(#333 1px, transparent 1px),    linear-gradient(90deg, #333 1px, transparent 1px);

In fact, we need to specify background-size style as well to make it work. But since the CELL_SIZE constant is defined in Game.js, we will specify background size with inline style directly. Change the style line in Game.js:

实际上,我们还需要指定background-size样式以使其起作用。 但是由于CELL_SIZE常量是在Game.js定义的, Game.js我们将直接使用内联样式指定背景大小。 更改Game.jsstyle行:

<div className="Board"  style={{ width: WIDTH, height: HEIGHT,    backgroundSize: `${CELL_SIZE}px ${CELL_SIZE}px`}}></div>

Refresh the browser and you will see a nice grid.

刷新浏览器,您将看到一个漂亮的网格。

创建单元 (Create the cells)

The next step is to allow the user to interact with the board to create the cells. We will use a 2D array this.board to keep the board state, and a cell list this.state.cells to keep the position of the cells. Once the board state is updated, a method this.makeCells() will be called to generate the cell list from the board state.

下一步是允许用户与板交互以创建单元。 我们将使用二维数组this.board来保持板状态,并使用一个单元格列表this.state.cells来保持单元的位置。 一旦板状态更新,将调用this.makeCells()方法从板状态生成单元列表。

Add these methods to the Game class:

将这些方法添加到Game类中:

class Game extends React.Component {  constructor() {    super();    this.rows = HEIGHT / CELL_SIZE;    this.cols = WIDTH / CELL_SIZE;    this.board = this.makeEmptyBoard();  }
state = {    cells: [],  }
// Create an empty board  makeEmptyBoard() {    let board = [];    for (let y = 0; y < this.rows; y++) {      board[y] = [];      for (let x = 0; x < this.cols; x++) {        board[y][x] = false;      }    }    return board;  }
// Create cells from this.board  makeCells() {    let cells = [];    for (let y = 0; y < this.rows; y++) {      for (let x = 0; x < this.cols; x++) {        if (this.board[y][x]) {          cells.push({ x, y });        }      }    }    return cells;  }  ...}

Next, we will allow the user to click the board to place or remove a cell. In React, <div> can be attached with an onClick event handler, which could retrieve the click coordinate through the click event. However the coordinate is relative to the client area (the visible area of the browser), so we need some extra code to convert it to a coordinate that is relative to the board.

接下来,我们将允许用户单击面板以放置或移除单元格。 在React, <d IV>可附接无线th an o n点击事件处理程序,它可以检索的点击通过点击事件坐标。 但是,坐标是相对于客户区域(浏览器的可见区域)的,因此我们需要一些额外的代码将其转换为相对于木板的坐标。

Add the event handler to the render() method. Here we also save the reference of the board element in order to retrieve the board location later.

将事件处理程序添加到render()方法。 在这里,我们还保存了木板元素的引用,以便以后检索木板位置。

render() {  return (    <div>      <div className="Board"        style={{ width: WIDTH, height: HEIGHT,          backgroundSize: `${CELL_SIZE}px ${CELL_SIZE}px`}}        onClick={this.handleClick}        ref={(n) => { this.boardRef = n; }}>      </div>    </div>  );}

And here are some more methods. Here getElementOffset() will calculate the position of the board element. handleClick() will retrieve the click position, then convert it to relative position, and calculate the cols and rows of the cell being clicked. Then the cell state is reverted.

这里还有一些其他方法。 在这里, getElementOffset()将计算板元素的位置。 handleClick()将检索单击位​​置,然后将其转换为相对位置,并计算要单击的单元格的行和行。 然后,单元状态恢复。

class Game extends React.Component {  ...  getElementOffset() {    const rect = this.boardRef.getBoundingClientRect();    const doc = document.documentElement;
return {      x: (rect.left + window.pageXOffset) - doc.clientLeft,      y: (rect.top + window.pageYOffset) - doc.clientTop,    };  }
handleClick = (event) => {    const elemOffset = this.getElementOffset();    const offsetX = event.clientX - elemOffset.x;    const offsetY = event.clientY - elemOffset.y;        const x = Math.floor(offsetX / CELL_SIZE);    const y = Math.floor(offsetY / CELL_SIZE);
if (x >= 0 && x <= this.cols && y >= 0 && y <= this.rows) {      this.board[y][x] = !this.board[y][x];    }
this.setState({ cells: this.makeCells() });  }  ...}

As the last step, we will render the cells this.state.cells to the board:

最后一步,我们将把this.state.cells单元this.state.cells到板上:

class Cell extends React.Component {  render() {    const { x, y } = this.props;    return (      <div className="Cell" style={{        left: `${CELL_SIZE * x + 1}px`,        top: `${CELL_SIZE * y + 1}px`,        width: `${CELL_SIZE - 1}px`,        height: `${CELL_SIZE - 1}px`,      }} />    );  }}
class Game extends React.Component {  ...  render() {    const { cells } = this.state;    return (      <div>        <div className="Board"          style={{ width: WIDTH, height: HEIGHT,            backgroundSize: `${CELL_SIZE}px ${CELL_SIZE}px`}}          onClick={this.handleClick}          ref={(n) => { this.boardRef = n; }}>          {cells.map(cell => (            <Cell x={cell.x} y={cell.y}                key={`${cell.x},${cell.y}`}/>          ))}        </div>              </div>    );  }  ...}

And don’t forget to add styles for the Cell component (in Game.css):

并且不要忘记为Cell组件添加样式(在Game.css ):

.Cell {  background: #ccc;  position: absolute;}

Refresh the browser and try to click the board. The cells can be placed or removed now!

刷新浏览器,然后尝试单击面板。 现在可以放置或取出电池!

运行游戏 (Run the Game)

Now we need some helpers to run the game. First let’s add some controllers.

现在我们需要一些帮助程序来运行游戏。 首先让我们添加一些控制器。

class Game extends React.Component {  state = {    cells: [],    interval: 100,    isRunning: false,  }  ...
runGame = () => {    this.setState({ isRunning: true });  }
stopGame = () => {    this.setState({ isRunning: false });  }
handleIntervalChange = (event) => {    this.setState({ interval: event.target.value });  }
render() {    return (      ...        <div className="controls">          Update every <input value={this.state.interval}              onChange={this.handleIntervalChange} /> msec          {isRunning ?            <button className="button"              onClick={this.stopGame}>Stop</button> :            <button className="button"              onClick={this.runGame}>Run</button>          }        </div>      ...    );  }}

This code will add one interval input and one button to the bottom of the screen.

此代码将在屏幕底部添加一个间隔输入和一个按钮。

Note that clicking Run has no effect, because we haven’t written anything to run the game. So let’s do that now.

请注意,单击“运行”无效,因为我们尚未编写任何内容来运行游戏。 所以现在就开始做。

In this game, the board state is updated every iteration. Thus we need a method runIteration() to be called every iteration, say, 100ms. This can be achieved by using window.setTimeout().

在这个游戏中,棋盘状态在每次迭代时都会更新。 因此,我们需要在每次迭代(例如100ms runIteration()中调用一次runIteration()方法。 这可以通过使用window.setTimeout()来实现。

When the Run button is clicked, runIteration() will be called. Before it ends, it will call window.setTimeout() to arrange another iteration after 100msec. In this way, runIteration() will be called repeatedly. When the Stop button is clicked, we will cancel the arranged timeout by calling window.clearTimeout() so that the iterations can be stopped.

单击“运行”按钮时,将调用runIteration() 。 在结束之前,它将调用window.setTimeout()在100毫秒后安排另一个迭代。 这样,将反复调用runIteration() 。 单击“停止”按钮时,我们将通过调用window.clearTimeout()取消安排的超时,以便可以停止迭代。

class Game extends React.Component {  ...  runGame = () => {    this.setState({ isRunning: true });    this.runIteration();  }
stopGame = () => {    this.setState({ isRunning: false });    if (this.timeoutHandler) {      window.clearTimeout(this.timeoutHandler);      this.timeoutHandler = null;    }  }
runIteration() {    console.log('running iteration');    let newBoard = this.makeEmptyBoard();
// TODO: Add logic for each iteration here.
this.board = newBoard;    this.setState({ cells: this.makeCells() });
this.timeoutHandler = window.setTimeout(() => {      this.runIteration();    }, this.state.interval);  }  ...}

Reload the browser and click the “Run” button. We will see the log message “running iteration” in the console. (If you don’t know how to show the console, try pressing Ctrl-Shift-I.)

重新加载浏览器,然后单击“运行”按钮。 我们将在控制台中看到日志消息“运行迭代”。 (如果您不知道如何显示控制台,请尝试按Ctrl-Shift-I。)

Now we need to add the game rules to runIteration() method. According to Wikipedia, the Game of Life has four rules:

现在,我们需要将游戏规则添加到runIteration()方法中。 根据维基百科,生命游戏有四个规则:

1. Any live cell with fewer than two live neighbors dies, as if caused by under population.
1.少于两个活邻居的活细胞死亡,好像是由于人口不足所致。
2. Any live cell with two or three live neighbors lives on to the next generation.
2.任何有两个或三个活邻居的活细胞都可以存活到下一代。
3. Any live cell with more than three live neighbors dies, as if by overpopulation.
3.任何具有三个以上活邻居的活细胞都会死亡,好像是由于人口过剩。
4. Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.
4.任何具有恰好三个活邻居的死细胞都变成活细胞,就像通过繁殖一样。

We can add a method calculateNeighbors() to compute the number of neighbors of given (x, y). (The source code of calcualteNeighbors() will be omitted in this post, but you can find it here.) Then we can implement the rules in a straightforward way:

我们可以添加一个方法calculateNeighbors()来计算给定(x, y)的邻居数。 (本文中将省略calcualteNeighbors()的源代码,但您可以在此处找到。)然后,我们可以通过简单的方式实现规则:

for (let y = 0; y < this.rows; y++) {  for (let x = 0; x < this.cols; x++) {    let neighbors = this.calculateNeighbors(this.board, x, y);    if (this.board[y][x]) {      if (neighbors === 2 || neighbors === 3) {        newBoard[y][x] = true;      } else {        newBoard[y][x] = false;      }    } else {      if (!this.board[y][x] && neighbors === 3) {        newBoard[y][x] = true;      }    }  }}

Reload the browser, place some initial cells, then hit Run button. You may see some amazing animations!

重新加载浏览器,放置一些初始单元格,然后单击“运行”按钮。 您可能会看到一些惊人的动画!

结论 (Conclusion)

In order to make the game more fun, I also added a Random button and a Clear button to help with placing the cells. The full source code can be found on my GitHub.

为了使游戏更有趣,我还添加了“随机”按钮和“清除”按钮以帮助放置单元。 完整的源代码可以在我的GitHub上找到

Thanks for your reading! If you find this post interesting, please share it with more people by recommending it.

感谢您的阅读! 如果您发现此帖子有趣,请通过推荐与更多人分享。

翻译自: https://www.freecodecamp.org/news/create-gameoflife-with-react-in-one-hour-8e686a410174/

react 代码编写原则

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值