de Charlee Li

Cum să codați „Jocul vieții” cu React în mai puțin de o oră

Cum sa codati „Jocul vietii cu React in mai putin

Recent am urmărit celebrul videoclip care creează un joc de șarpe în mai puțin de 5 minute (pe Youtube). Părea destul de interesant să fac acest tip de codare rapidă, așa că am decis să fac una singură.

Când am început să învăț programarea în copilărie, am învățat un joc numit „Jocul vieții. ” Este un exemplu excelent de automatizare celulară și modul în care regulile simple pot duce la modele complexe. Imaginați-vă un fel de formă de viață care trăiește într-o lume. La fiecare rând, respectă câteva reguli simple pentru a decide dacă o viață este vie sau moartă.

Jocul vieții lui Conway – Wikipedia
Încă de la publicare, Conway’s Game of Life a atras mult interes, din cauza modurilor surprinzătoare în care …en.wikipedia.org

Așa că am decis să codez acest joc. Deoarece nu implică prea multe elemente grafice – doar o grilă și câteva blocuri – am decis că React ar fi o alegere bună și poate fi folosit ca un tutorial rapid pentru React. Să începem!

Reacționează configurarea

Mai întâi trebuie să configurăm React. Uimitorul create-react-app instrumentul este foarte util pentru a începe un nou proiect React:

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

După mai puțin de un minut, react-gameoflife va fi gata. Acum tot ce trebuie să facem este să-l pornim:

$ cd react-gameoflife$ npm start

Acesta va porni un server dev la http: // localhost: 3000, și o fereastră de browser va fi deschisă la această adresă.

Alegeri de proiectare

Ecranul final pe care vrem să-l realizăm arată astfel:

1611473826 357 Cum sa codati „Jocul vietii cu React in mai putin
Jocul vieții lui Conway

Este pur și simplu o placă cu o grilă și câteva plăci albe („celule”) care pot fi plasate sau eliminate prin clic pe grilă. Butonul „Run” va începe iterațiile la un anumit interval.

Pare destul de simplu, nu? Să ne gândim cum să facem acest lucru în React. În primul rând, React este nu un cadru grafic, deci nu ne vom gândi la utilizarea pânzei. (Puteți arunca o privire la PIXI sau Phaser dacă sunteți interesat de utilizarea pânzei.)

Placa poate fi o componentă și poate fi redată cu o singură <div>. Ce zici de grilă? Nu este fezabil să desenați grilele with

s și, din moment ce grila este statică, este de asemenea inutilă. Într-adevăr, noi can use CSS3 lingradientul urechii pentru grilă.

În ceea ce privește celulele, putem folosi <div> pentru a desena fiecare celulă. Vom face din aceasta o componentă separată. Această componentă acceptsx, y ca intrări, astfel încât placa să își poată specifica poziția.

Primul pas: tabloul

Să creăm mai întâi tabla. Creați un fișier numit Game.js sub src director și introduceți următorul cod:

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;

De asemenea, avem nevoie de Game.css fișier pentru a defini stilurile:

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

Actualizați App.js să ne importăm Game.js și așezați Game componentă de pe ecran. Acum putem vedea o masă de joc complet neagră.

Următorul nostru pas este să creăm grila. Grila poate fi creată cu o singură linie de linear-gradient (adăugați acest lucru la Game.css):

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

De fapt, trebuie să specificăm background-size stil, de asemenea, pentru a face să funcționeze. Dar din moment ce CELL_SIZE constanta este definită în Game.js, vom specifica direct dimensiunea fundalului cu stilul inline. Schimba style linie în Game.js:

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

Reîmprospătați browserul și veți vedea o grilă frumoasă.

1611473826 636 Cum sa codati „Jocul vietii cu React in mai putin
Grilă, realizată cu gradient CSS3.

Creați celulele

Următorul pas este de a permite utilizatorului să interacționeze cu placa pentru a crea celulele. Vom folosi o matrice 2D this.board pentru a păstra starea plăcii și o listă de celule this.state.cells pentru a păstra poziția celulelor. Odată ce starea plăcii este actualizată, o metodă this.makeCells() va fi apelat pentru a genera lista de celule din starea de bord.

Adăugați aceste metode la Game clasă:

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;  }  ...}

Apoi, vom permite utilizatorului să facă clic pe tablă pentru a plasa sau elimina o celulă. În React, <div> poate fi atașat with an onClick handler de evenimente, care ar putea prelua coordonatele de clic prin evenimentul de clic. Cu toate acestea, coordonata este relativă la zona clientului (zona vizibilă a browserului), deci avem nevoie de un cod suplimentar pentru ao converti într-o coordonată care este relativă la bord.

Adăugați gestionarul de evenimente la render() metodă. Aici salvăm și referința elementului de bord pentru a prelua ulterior locația de bord.

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>  );}

Și iată câteva metode. Aici getElementOffset() va calcula poziția elementului de bord. handleClick() va prelua poziția de clic, apoi o va converti în poziția relativă și va calcula colurile și rândurile celulei pe care se face clic. Apoi starea celulei este revenită.

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() });  }  ...}

Ca ultim pas, vom reda celulele this.state.cells la bord:

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>    );  }  ...}

Și nu uitați să adăugați stiluri pentru Cell componentă (în Game.css):

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

Reîmprospătați browserul și încercați să faceți clic pe tablă. Celulele pot fi plasate sau eliminate acum!

1611473827 113 Cum sa codati „Jocul vietii cu React in mai putin
Celulele pot fi plasate sau eliminate, făcând clic pe tablă.

Rulați jocul

Acum avem nevoie de niște ajutoare pentru a rula jocul. Mai întâi să adăugăm câteva controlere.

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>      ...    );  }}

Acest cod va adăuga o intrare de interval și un buton în partea de jos a ecranului.

1611473827 620 Cum sa codati „Jocul vietii cu React in mai putin
Controlere

Rețineți că făcând clic pe Executare nu are efect, deoarece nu am scris nimic pentru a rula jocul. Deci, să facem asta acum.

În acest joc, starea plăcii este actualizată la fiecare iterație. Astfel avem nevoie de o metodă runIteration() să fie numit fiecare iterație, să zicem, 100ms. Acest lucru poate fi realizat prin utilizarea window.setTimeout().

Când se face clic pe butonul Executare, runIteration() va fi chemat. Înainte de a se termina, va suna window.setTimeout() pentru a aranja o altă iterație după 100msec. În acest fel, runIteration() va fi apelat în mod repetat. Când se face clic pe butonul Stop, vom anula expirarea aranjată prin apel window.clearTimeout() astfel încât iterațiile să poată fi oprite.

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);  }  ...}

Reîncărcați browserul și faceți clic pe butonul „Run”. Vom vedea mesajul jurnal „rularea iterației” în consolă. (Dacă nu știți cum să afișați consola, încercați să apăsați Ctrl-Shift-I.)

Acum trebuie să adăugăm regulile jocului runIteration() metodă. Potrivit Wikipedia, Jocul vieții are patru reguli:

1. Orice celulă vie cu mai puțin de doi vecini vii moare, ca și cum ar fi cauzată de sub populație.

2. Orice celulă vie cu doi sau trei vecini vii trăiește pentru generația următoare.

3. Orice celulă vie cu mai mult de trei vecini vii moare, ca și cum ar fi suprapopulată.

4. Orice celulă moartă cu exact trei vecini vii devine o celulă vie, parcă prin reproducere.

Putem adăuga o metodă calculateNeighbors() pentru a calcula numărul de vecini din dat (x, y). (Codul sursă al calcualteNeighbors() va fi omis în această postare, dar îl puteți găsi aici.) Apoi putem implementa regulile într-un mod simplu:

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;      }    }  }}

Reîncărcați browserul, plasați câteva celule inițiale, apoi apăsați butonul Executare. Este posibil să vedeți câteva animații uimitoare!

1611473828 478 Cum sa codati „Jocul vietii cu React in mai putin
Pistolul de alunecare Gosper

Concluzie

Pentru a face jocul mai distractiv, am adăugat și un buton aleatoriu și un buton Ștergere pentru a ajuta la plasarea celulelor. Codul sursă complet poate fi găsit pe GitHub.

Vă mulțumim pentru lectură! Dacă vi se pare interesant acest post, vă rugăm să îl împărtășiți cu mai multe persoane, recomandându-l.

#Cum #să #codați #Jocul #vieții #React #în #mai #puțin #oră

Cum să codați „Jocul vieții” cu React în mai puțin de o oră