ts实现贪吃蛇

先看效果图在这里插入图片描述

思路

ts编程思想时面向对象。所以我们也要以面向对象的形式来开发。
首先我们需要三个类,蛇的类,食物的类,记分牌的类,然后还有创建一个类,将这三个类关联再一起。
看代码

先看下布局结构

在这里插入图片描述
食物是一个div,蛇是一个大div,他的每一段都是一个小div,下面是记分牌

看最简单的食物的类

在这里插入图片描述
在构造函数中,获取食物的div,然后通过get X Y 方法获取到食物的坐标,接着需要定义一个方法来改变食物的位置,因为食物是随机的嘛。

接着看记分牌的类

在这里插入图片描述
记分牌的类也容易,主要是没得一分就调用addScore方法来基数,并且判断如果分数到达一定阶段时,调用Leveup方法来升级等级。

接着看蛇的类

// 蛇类
class Snake {
  element: HTMLElement;
  head: HTMLElement;
  bodies: HTMLCollection;
  constructor() {
    this.element = document.getElementById("snake")!;
    this.head = document.querySelector("#snake >div") as HTMLElement;
    this.bodies = document.getElementById("snake")!.getElementsByTagName("div");
  }

  get X() {
    //蛇头X
    return this.head.offsetLeft;
  }
  get Y() {
    return this.head.offsetTop;
  }
  set X(value) {
    if (this.X === value) return;
    if (value < 0 || value > 290) {
      throw new Error("设撞墙了");
    }
    if (
      this.bodies[1] &&
      (this.bodies[1] as HTMLElement).offsetLeft === value
    ) {
      if (this.X > value) value = this.X + 10;
      //新值小于旧值,证明想转头向左走
      else value = this.X - 10; //否则就是转头向右走
    }
    this.moveBody();
    this.head.style.left = value + "px";
    this.checkhead()
  }
  set Y(value) {
    if (this.Y === value) return;
    if (value < 0 || value > 290) {
      throw new Error("设撞墙了");
    }
    if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) {
      if (this.Y > value) value = this.Y + 10;
      //新值小于旧值,证明想转头向上走
      else value = this.Y - 10; //否则就是转头向下走
    }
    this.moveBody(); //跟下面一条位置不能乱,否则会出现当只有两节蛇时,蛇头的坐标先赋予最新值的话,再来赋予第二段的值,第二段会跟舌头叠在一起
    this.head.style.top = value + "px";
    this.checkhead()
  }
  //蛇增加身体方法
  addBody() {
    this.element.insertAdjacentHTML("beforeend", "<div></div>");
  }
  moveBody() {
    for (let i = this.bodies.length - 1; i > 0; i--) {
      //获取前面身体的位置
      let x = (this.bodies[i - 1] as HTMLElement).offsetLeft;
      let y = (this.bodies[i - 1] as HTMLElement).offsetTop;

      //将该值设到当前的身体
      (this.bodies[i] as HTMLElement).style.left = x + "px";
      (this.bodies[i] as HTMLElement).style.top = y + "px";
    }
  }
  checkhead() {
    //检查是否撞到自己身体
    for (let i = 1; i < this.bodies.length; i++) {
      if (
        (this.bodies[0] as HTMLElement).offsetLeft ===
          (this.bodies[i] as HTMLElement).offsetLeft &&
        (this.bodies[0] as HTMLElement).offsetTop ===
          (this.bodies[i] as HTMLElement).offsetTop
      ) {
        throw new Error("游戏结束");
      }
    }
  }
}

蛇的类是最复杂的,先一点点看
在这里插入图片描述
在构造函数中就要获取到蛇的盒子,蛇头,还有蛇的每一个div。
然后我们主要要获取蛇头的位置以及设置舌头的位置,在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里逻辑比较复杂的是,在设置蛇头的位置的时候,首先,如果当给蛇设的新值,<0,或者大于整个盒子的宽度时,就抛出错误,说明撞墙了。
接着要判断,蛇是否调头了,这个思路也比较容易,就是当蛇头与蛇的第二节位置相同时,就证明调头了。首先得判断第二节在不在,在这里插入图片描述
如果相等证明掉头,所以我们要进行操作,当新值大于旧值时,证明蛇是往左走然后突然往右调头,此时要把新值设小。反之设大,然后再把新值赋给蛇头。
接着,因为我们是没吃到一个食物增加一个蛇的div,所以要让蛇动起来的话,就得做处理,蛇的每一节我们放在了this.bodies里面,所以我们只需要遍历,从后往前设,将上一个的位置,作为下一个的位置,比如倒数第二个的位置,就是最后一个下一次走到的位置,以此类推。在这里插入图片描述
接着还要判断会不会撞到自己,在这里插入图片描述
这个也容易,只要遍历。然后判断第一个是否会撞到其他的就行。在这里插入图片描述
这里的位置有讲究的,我们必须将前一个的值给了后面后,才能赋予新值,否则先赋予新值,再将该值给后面的话,会引起有的蛇两节都是在一起的,因为他是先设新值,在获取。
最后检查撞到自己的时候,也必须是赋予值后再判断,否则没赋予新值判断的时候,肯定是会撞到自己的啦,因为蛇头的值还没改变,第二节的值此时通过moveBody是跟蛇头一样的,所以要给蛇头新值才能检查。

接着就是最后一个类,将三个类关联起来的类

//关联其他的类
class GameControl {
  snake: Snake;
  food: Food;
  scorePanel: ScorePanel;
  direction: string = "d";
  isLive: boolean = true;
  constructor() {
    this.snake = new Snake();
    this.food = new Food();
    this.scorePanel = new ScorePanel();
    this.init();
  }
  //游戏初始化
  init() {
    //绑定键盘事件
    document.addEventListener("keydown", (e: KeyboardEvent) => {
      this.keydownHandle(e);
    });
    this.run();
  }
  //检查是否吃到食物
  checkEct(X: number, Y: number) {
    if (Boolean(X === this.food.X && Y === this.food.Y)) {
      this.snake.addBody();
      this.food.change();
      this.scorePanel.addScore();
    }
  }
  keydownHandle(e: KeyboardEvent) {
    this.direction = e.key;
  }
  run() {
    let x = this.snake.X;
    let y = this.snake.Y;
    switch (this.direction) {
      case "w":
      case "ArrowUp":
        y -= 10;
        break;
      case "s":
      case "ArrowDown":
        y += 10;
        break;
      case "a":
      case "ArrowLeft":
        x -= 10;
        break;
      case "d":
      case "ArrowRight":
        x += 10;
        break;
      default:
        break;
    }
    this.checkEct(x, y);
    try {
      this.snake.X = x;
      this.snake.Y = y;
    } catch (e) {
      alert(e.message);
      this.isLive = false;
    }
    this.isLive &&
      setTimeout(() => {
        this.run();
      }, 100 - this.scorePanel.level * 20);
  }
}

const gameControl = new GameControl();

其实也很容易,在这里插入图片描述
首先看构造函数,我们先把三个类的实例都获取到了作为最后一个类的实例属性,那么就可以在这个类对其他三个类的实例进行操作。
在这里插入图片描述
游戏初始化,我们得监听键盘事件,在这里插入图片描述
获取到我们按下键盘的值,然后判断
在这里插入图片描述
先获取到当前舌头的位置,然后通过上下左右设置每一个新值,最后还要判断,是否吃到了食物,在这里插入图片描述
如果吃到了食物,就直接通过每个类的实例来操作,很方便,增加身体,改变食物位置,增加分数。
当判断完后,我们还要将新值赋予蛇头,
在这里插入图片描述
使用try,catche来俘获我们刚才设下的跑出来的错误。吃措施,设置this,isLive为false,证明蛇已死。
然后通过this,isLive来判断是否在几秒后继续执行run方法。因为这个setTimeout是在run里面调用的,所以会一直调用,除非蛇死了。
因为我们按下的值是设在一个变量的,就算我们不按,下次启动run方法还是会根据上一个我们按下的按键来判断蛇应该怎么走,所以达到一个蛇自己动的效果。

以上就是四个类,通过ts完成贪吃蛇小项目。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

coderlin_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值