【React-Redux】自制简易贪吃蛇小游戏

初始化

用脚手架新建一个react项目:

npx create-react-app snake-demo

关于安装和创建、启动,可以看我的这篇文章:React入门:基本环境搭建

首先画一个框,当作边界,再初始化贪吃蛇,就像这样:
左上角两个小黑方块就是贪吃蛇了

在这里插入图片描述
这里贪吃蛇的移动是以网格做单位的,把游戏区域的宽高各分成100份,贪吃蛇每次移动就前进2个网格,即2%

先写一个具体的div来测试一下将要实现的效果和样式

<div className="App">
  <div className="snake-dot" style={
     
     {top:0, left:0}}></div>
  <div className="snake-dot" style={
     
     {top:0, left:'2%'}}></div>
</div>

样式:

.App {
    
    
  height: 600px;
  width: 600px;
  border: 1px solid #ccc;
  margin: 10px auto;
  position: relative;
}

.snake-dot {
    
    
  height: 2%;
  width: 2%;
  background-color: black;
  border: 1px solid #fff;
  position: absolute;
}

得到上述图片的效果后,我们把这个初始snake的定位数据放到state中,方便管理;并把snake-dot单独拎出来,改成动态生成

import React, {
    
     Component } from 'react'
import {
    
     Snake } from './components/Snake';

const initState = {
    
    
  snakeDots:[
    [0, 0],
    [2, 0]
  ]
}

class App extends Component {
    
    
  state = initState;

  render() {
    
    
    return (
      <div className="App">
        <Snake snakeDots={
    
    this.state.snakeDots} />
      </div>
    );
  }
}

export default App;

把snake-dot的动态生成放到子组件Snake.js中:

import React from 'react'

export const Snake = ({
     
     snakeDots}) => {
    
    
    return (
        <>
            {
    
    
                snakeDots.map((dot, index)=>{
    
    
                    let dotStyle = {
    
    
                        left:`${
      
      dot[0]}%`,
                        top:`${
      
      dot[1]}%`
                    }
                    console.log(dotStyle)
                    return (
                        <div className="snake-dot" key={
    
    index} style={
    
    dotStyle}></div>
                    )
                })
            }
        </>
    )
}

这样初始化就完成了

贪吃蛇的移动

贪吃蛇的上下左右移动根据键盘输入的上下左右(↑↓←→)键来确定,初始化时默认是向右移动的
先来看看键盘上下左右键的code值

Key Code
左箭头 37
右箭头 39
上箭头 38
下箭头 40

游戏开始时,默认是向右走的,因此,我们增加一个表示方向的state:direction,走的速度是每隔200ms前进一步

const initState = {
    
    
  snakeDots: [
    [0, 0],
    [2, 0]
  ],
  direction: "RIGHT",
  speed: 200
}

当检测到上下左右键按下时,更新state中的方向,贪吃蛇沿该方向前进一步;
若没有按键按下,就沿着当前方向一路前进
贪吃蛇的头部(head)是小黑方块的最后一块,不断地删去第一个小黑方块并在direction方向上新增一小黑方块;视觉上,贪吃蛇就有不停向前移动的效果
在这里插入图片描述

  componentDidMount() {
    
    
  	// 监听键盘按下事件
    document.onkeydown = this.onkeydown;
    // 每隔200ms前进一步
    setInterval(this.onMove, this.state.speed);
  }
  onkeydown = (e) => {
    
    
  	// 根据按下的键盘键更新direction
    switch (e.keyCode) {
    
    
      case 37:
        this.setState({
    
     direction: "LEFT" });
        break;
      case 38:
        this.setState({
    
     direction: "UP" });
        break;
      case 39:
        this.setState({
    
     direction: "RIGHT" });
        break;
      case 40:
        this.setState({
    
     direction: "DOWN" });
        break;
      default:
        this.setState({
    
     direction: "RIGHT" });
    }

  }

  onMove = () => {
    
    
  	// 根据direction指示的方向前进
    let newSnakeDots = [...this.state.snakeDots];
    let header = newSnakeDots[newSnakeDots.length - 1];
    switch (this.state.direction) {
    
    
      case "UP":
        header = [header[0], header[1] - 2];
        break;
      case "DOWN":
        header = [header[0], header[1] + 2];
        break;
      case "LEFT":
        header = [header[0] - 2, header[1]];
        break;
      case "RIGHT":
        header = [header[0] + 2, header[1]];
        break;
      default:
        header = [header[0] + 2, header[1]];
    }
    // 删去第一个小黑块,并在direction方向上新增新小黑块,更新snakeDots
    newSnakeDots.shift();
    newSnakeDots.push(header);
    this.setState({
    
     snakeDots: newSnakeDots })
  }

效果:
在这里插入图片描述

边界限制与得分计算

当触碰边界或者撞击到自身时,游戏结束,弹窗提示,分数为贪吃蛇的长度-2(初始长度为2,其余均是吃到食物后变长的,吃一个食物长长1格)

  componentDidUpdate() {
    
    
    this.checkIfBordered();
  }
  checkIfBordered = () => {
    
    
    let newSnakeDots = [...this.state.snakeDots];
    let header = newSnakeDots[newSnakeDots.length - 1];
    if (header[0] < 0 || header[0] > 98 || header[1] < 0 || header[1] > 98) {
    
    
      alert(`触碰边界,游戏结束,你的得分是:${
      
      newSnakeDots.length - 2}`);
      this.setState(initState);
    }
  }

食物随机出现

由于我们规定,贪吃蛇触碰到边界时算游戏结束,因此,食物不能出现在边界上,否则,一吃到食物就代表触壁结束游戏(可以改进为:贪吃蛇每次前进1%,当食物被触碰比例达50%时可以当作贪吃蛇吃掉该食物,马上掉头避免触壁,以后再改吧~)
因此,食物出现的范围在top:2%~96%,left的范围也是如此
在这里插入图片描述

const getRandomFood = ()=>{
    
    
  // 食物出现的范围是:[2, 96] 推算过程:[0,95)->[2, 97)->[1, 48.5]->[1,48]->[2,96]
  let max = 95;
  let min = 2;
  let x = Math.floor((Math.random() * max + min) / 2) * 2;
  let y = Math.floor((Math.random() * max + min) / 2) * 2;
  return [x,y]
}
const initState = {
    
    
  snakeDots: [
    [0, 0],
    [2, 0]
  ],
  direction: "RIGHT",
  speed: 200,
  food:getRandomFood() // 随机生成食物的位置
}

食物的展示:

  import {
    
     Food } from './components/Food';
  render() {
    
    
    return (
      <div className="App">
        <Snake snakeDots={
    
    this.state.snakeDots} />
        <Food food={
    
    this.state.food} />
      </div>
    );
  }
import React from 'react'

export const Food = ({
     
     food}) => {
    
    
    let foodStyle = {
    
    
        left:`${
      
      food[0]}%`,
        top:`${
      
      food[1]}%`
    }
    return (
        <div className="food" style={
    
    foodStyle}></div>
    )
}

食物的样式:

.food {
    
    
  height: 2%;
  width: 2%;
  background-color: red;
  border: 1px solid #fff;
  position: absolute;
}

判断是否吃到食物

贪吃蛇的头(head)和食物重叠时,代表吃到
贪吃蛇变长,食物的位置变成新的head,重新放置食物

  componentDidUpdate() {
    
    
  	// 每次更新都判断一下
    this.checkIfBordered();
    this.checkIfEated();
  }
  checkIfEated = ()=>{
    
    
    let newSnakeDots = [...this.state.snakeDots];
    let header = newSnakeDots[newSnakeDots.length - 1];
    // head与食物重叠时,代表吃到食物
    if(header[0]==this.state.food[0] && header[1]==this.state.food[1]){
    
    
      console.log("吃到了")
      newSnakeDots.unshift([]);
      this.setState({
    
    snakeDots:newSnakeDots});
      this.setState({
    
    food:getRandomFood()});
    }
  }

效果:
在这里插入图片描述
完整代码:
新建文件夹component存放组件:
在这里插入图片描述

App.js

import React, {
    
     Component } from 'react'
import {
    
     Snake } from './components/Snake';
import {
    
     Food } from './components/Food';
// 随机获取食物位置
const getRandomFood = ()=>{
    
    
  // [2, 96]: [0,95)->[2, 97)->[1, 48.5]->[1,48]->[2,96]
  let max = 95;
  let min = 2;
  let x = Math.floor((Math.random() * max + min) / 2) * 2;
  let y = Math.floor((Math.random() * max + min) / 2) * 2;
  console.log("food-position",x,y)
  return [x,y]
}
// 另存state以便重新初始化
const initState = {
    
    
  snakeDots: [
    [0, 0],
    [2, 0]
  ],
  direction: "RIGHT",
  speed: 200,
  food:getRandomFood()
}

class App extends Component {
    
    
  state = initState;
  // 监听与计时器
  componentDidMount() {
    
    
    document.onkeydown = this.onkeydown;
    setInterval(this.onMove, this.state.speed);
  }
  // 更新时检测是否出界或吃到食物
  componentDidUpdate() {
    
    
    this.checkIfBordered();
    this.checkIfEated();
  }
  // 判断键盘按键,更新direction
  onkeydown = (e) => {
    
    
    switch (e.keyCode) {
    
    
      case 37:
        this.setState({
    
     direction: "LEFT" });
        break;
      case 38:
        this.setState({
    
     direction: "UP" });
        break;
      case 39:
        this.setState({
    
     direction: "RIGHT" });
        break;
      case 40:
        this.setState({
    
     direction: "DOWN" });
        break;
      default:
        this.setState({
    
     direction: "RIGHT" });
    }

  }
  // 根据direction移动
  onMove = () => {
    
    
    let newSnakeDots = [...this.state.snakeDots];
    let header = newSnakeDots[newSnakeDots.length - 1];
    switch (this.state.direction) {
    
    
      case "UP":
        header = [header[0], header[1] - 2];
        break;
      case "DOWN":
        header = [header[0], header[1] + 2];
        break;
      case "LEFT":
        header = [header[0] - 2, header[1]];
        break;
      case "RIGHT":
        header = [header[0] + 2, header[1]];
        break;
      default:
        header = [header[0] + 2, header[1]];
    }
    newSnakeDots.shift();
    newSnakeDots.push(header);
    this.setState({
    
     snakeDots: newSnakeDots })
    // console.log(this.state.snakeDots)
  }
  // 判断是否出界
  checkIfBordered = () => {
    
    
    let newSnakeDots = [...this.state.snakeDots];
    let header = newSnakeDots[newSnakeDots.length - 1];
    if (header[0] < 0 || header[0] > 98 || header[1] < 0 || header[1] > 98) {
    
    
      alert(`触碰边界,游戏结束,你的得分是:${
      
      newSnakeDots.length - 2}`);
      this.setState(initState);
    }
  }
  // 判断是否吃到食物
  checkIfEated = ()=>{
    
    
    let newSnakeDots = [...this.state.snakeDots];
    let header = newSnakeDots[newSnakeDots.length - 1];
    if(header[0]==this.state.food[0] && header[1]==this.state.food[1]){
    
    
      console.log("吃到了")
      newSnakeDots.unshift([]);
      this.setState({
    
    snakeDots:newSnakeDots});
      this.setState({
    
    food:getRandomFood()});
    }
  }
  render() {
    
    
    return (
      <div className="App">
        <Snake snakeDots={
    
    this.state.snakeDots} />
        <Food food={
    
    this.state.food} />
      </div>
    );
  }
}

export default App;

Snake.js:

import React from 'react'

export const Snake = ({
     
     snakeDots}) => {
    
    
    return (
        <>
            {
    
    
                snakeDots.map((dot, index)=>{
    
    
                    let dotStyle = {
    
    
                        left:`${
      
      dot[0]}%`,
                        top:`${
      
      dot[1]}%`
                    }
                    return (
                        <div className="snake-dot" key={
    
    index} style={
    
    dotStyle}></div>
                    )
                })
            }
        </>
    )
}

Food.js:

import React from 'react'

export const Food = ({
     
     food}) => {
    
    
    let foodStyle = {
    
    
        left:`${
      
      food[0]}%`,
        top:`${
      
      food[1]}%`
    }
    return (
        <div className="food" style={
    
    foodStyle}></div>
    )
}

index.css:

.App {
    
    
  height: 600px;
  width: 600px;
  border: 1px solid #ccc;
  margin: 10px auto;
  position: relative;
}

.snake-dot {
    
    
  height: 2%;
  width: 2%;
  background-color: black;
  border: 1px solid #fff;
  position: absolute;
}
.food {
    
    
  height: 2%;
  width: 2%;
  background-color: red;
  border: 1px solid #fff;
  position: absolute;
}

猜你喜欢

转载自blog.csdn.net/qq_43523725/article/details/119996985