typescript | 贪吃蛇小项目

时间:2022-09-29 09:53:04

贪吃蛇项目

使用typescript的基本是定义类,在类中写代码
gitee-Snake-详解.md
typescript | 贪吃蛇小项目

搭建webpack环境

  1. 用npm全局安装 typescript(前提是安装了nodejs node -v查看版本号)

    • 进入命令行
    • 输入:npm i -g typescript
  2. 搭建webpack环境

    1. 初始化项目

      进入项目根目录,执行命令 npm init -y

      主要作用:创建package.json文件

    2. 下载构建工具
      npm i -D webpack webpack-cli webpack-dev-server typescript ts-loader clean-webpack-plugin html-webpack-plugin

    3. 根目录下创建webpack的配置文件 webpack.config.js

      // webpack的配置信息
      
      // 1. 引入一个包
      const path = require('path'); // 用来拼接路径
      // 引入html 插件
      const HTMLWebpackPlugin = require('html-webpack-plugin');
      // 引入clean插件
      const { CleanWebpackPlugin } = require('clean-webpack-plugin');
      
      // webpack中的所有的配置信息都应该写在module.exports中
      module.exports = {
          // 指定入口文件
          entry: "./src/index.ts",
          // 指定打包文件所在的目录
          output: {
              // 指定打包后的目录
              path: path.resolve(__dirname, 'dist'),
              // 打包后文件的名字
              filename: "bundle.js"
          },
          // 指定webpack打包时要使用的模块
          module: {
              // 指定要加载的规则
              rules: [
                  {
                      // 指定规则生效的文件
                      test: /\.ts$/, // 匹配所有以ts结尾的文件
                      // 要使用的loader
                      use: 'ts-loader',
                      // 要排除的文件
                      exclude: /node_modules/,
                  },
              ]
          },
      
          // 配置webpack的插件
          plugins: [
              new HTMLWebpackPlugin({
                  // title: "这是一个自定义的title",
                  template: "./src/index.html", // 表示生成的网页根据这个模板生成的
              }), // 自动生成html文件
              new CleanWebpackPlugin(),
          ],
      
          // 用来设置引用模块
          resolve: {
              extensions: ['.ts', '.js'] // 表示 以这两个结尾的都可以模块来使用
          }
      };
      
    4. 根目录下创建tsconfig.json,配置可以根据自己需要

      //  tsconfig.json
      {
          // tsconfig.json 是ts编译器的配置文件,ts编译器可以根据她的信息来对代码进行编译
          /* include 用来指定哪些ts文件需要被编译
                路径:**表示任意目录
                      * 表示任意文件
           */
          "include": [
              "./src/**/*"
          ],
          /*
              exclude 表示不需要被编译的文件目录
           */
          // "exclude": [
          //     "./src/hello/**/*" // hello 下的文件都不会被编译
          // ],
          /* 
              complierOptions 编译器的选项
           */
          "compilerOptions": {
              "target": "ES6", // 用来指定 ts被编译为js的版本
              "module": "CommonJS", // 指定要使用的模块化的规范
              "lib": ["DOM","ES2015"], // 指定项目中要使用的库
              "outDir": "./dist", // 用来指定编译后文件所在的目录
              // "outFile": "", // 将代码合并为一个文件(所有的全局作用域的代码合并到一个文件中)
              "allowJs": false, // 是否对js文件进行编译,默认是false
              "checkJs": false, // 是否检查js代码是否符合语法规范
              "removeComments": false, // 是否移除注释,默认是false
              "noEmit": false, // 不生成编译后的文件
              "noEmitOnError": true, // 当有错误时不生成编译后的文件
              "sourceMap": false, 
              "strict": true, // 所有严格检查的总开关
              "alwaysStrict": true, // 用来设置编译后的文件是否使用严格模式,默认false
              "noImplicitAny": false, // 是否禁止隐式的any类型
              "noImplicitThis": false, // 是否不允许类型不明确的this
              "strictNullChecks": false, // 严格的空值检查
          },
      }
      
    5. 修改package.json添加如下配置

      {
        ......
        "scripts": {
          "test": "echo \"Error: no test specified\" && exit 1",
          "build": "webpack --mode development",
          "start": "webpack serve --open --mode development"
        },
        ......
      }
      
  3. 运行 npm run build看是否打包成功

搭建其他配置环境

我们不仅需要typescript的配置环境,还需要css,less预处理器等配置

  1. 下载依赖

    npm i -D less less-loader css-loader style-loader

  2. 设定规则

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2oLmtbWT-1664174694192)(详解.assets/image-20220924160230465.png)]

    loader的执行顺序是 从下往上!!!

  3. 查看是否配置成功

typescript | 贪吃蛇小项目

写一点less样式,然后在index.ts中引入,重新打包查看dist里面的index.html里面是否有样式

但是一些CSS3样式兼容不了旧浏览器,我们需要配置postcss,postcss-loader(加载器),postcss-preset-env(解决兼容性问题)

  1. 下载依赖

    npm i -D postcss postcss-loader postcss-preset-env

  2. 修改配置文件webpack.config.js

                    use: [
                        'style-loader',
                        'css-loader',
                        // 引入postcss
                        {
                            loader: "postcss-loader",
                            options: {
                                postcssOptions: {
                                    plugins: [
                                        [
                                            "postcss-preset-env",
                                            {
                                                // 兼容浏览器的信息
                                                browsers: "last 2 versions",// 最新的两个版本
                                            }
                                        ]
                                    ]
                                }
                            }
                        },
                        'less-loader'
                    ]
    
  3. 打包npm run build

配置环境的全部代码

typescript | 贪吃蛇小项目

tsconfig.json

//  tsconfig.json
{
    // tsconfig.json 是ts编译器的配置文件,ts编译器可以根据她的信息来对代码进行编译
    /* include 用来指定哪些ts文件需要被编译
          路径:**表示任意目录
                * 表示任意文件
     */
    "include": [
        "./src/**/*"
    ],
    /*
        exclude 表示不需要被编译的文件目录
     */
    // "exclude": [
    //     "./src/hello/**/*" // hello 下的文件都不会被编译
    // ],
    /* 
        complierOptions 编译器的选项
     */
    "compilerOptions": {
        "target": "ES6", // 用来指定 ts被编译为js的版本
        "module": "CommonJS", // 指定要使用的模块化的规范
        "lib": ["DOM","ES2015"], // 指定项目中要使用的库
        "outDir": "./dist", // 用来指定编译后文件所在的目录
        // "outFile": "", // 将代码合并为一个文件(所有的全局作用域的代码合并到一个文件中)
        "allowJs": false, // 是否对js文件进行编译,默认是false
        "checkJs": false, // 是否检查js代码是否符合语法规范
        "removeComments": false, // 是否移除注释,默认是false
        "noEmit": false, // 不生成编译后的文件
        "noEmitOnError": true, // 当有错误时不生成编译后的文件
        "sourceMap": false, 
        "strict": true, // 所有严格检查的总开关
        "alwaysStrict": true, // 用来设置编译后的文件是否使用严格模式,默认false
        "noImplicitAny": false, // 是否禁止隐式的any类型
        "noImplicitThis": false, // 是否不允许类型不明确的this
        "strictNullChecks": false, // 严格的空值检查
    },
}

webpack.config.js

// webpack的配置信息

// 1. 引入一个包
const path = require('path'); // 用来拼接路径
// 引入html 插件
const HTMLWebpackPlugin = require('html-webpack-plugin');
// 引入clean插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

// webpack中的所有的配置信息都应该写在module.exports中
module.exports = {
    // 指定入口文件
    entry: "./src/index.ts",
    // 指定打包文件所在的目录
    output: {
        // 指定打包后的目录
        path: path.resolve(__dirname, 'dist'),
        // 打包后文件的名字
        filename: "bundle.js"
    },
    // 指定webpack打包时要使用的模块
    module: {
        // 指定要加载的规则
        rules: [
            {
                // 指定规则生效的文件
                test: /\.ts$/, // 匹配所有以ts结尾的文件
                // 要使用的loader
                use: 'ts-loader',
                // 要排除的文件
                exclude: /node_modules/,
            },
            // 设置less文件的处理
            {
                test: /\.less$/, // 匹配所有以less结尾的文件
                // 要使用的loader,执行顺序从下往上
                use: [
                    'style-loader',
                    'css-loader',
                    // 引入postcss
                    {
                        loader: "postcss-loader",
                        options: {
                            postcssOptions: {
                                plugins: [
                                    [
                                        "postcss-preset-env",
                                        {
                                            // 兼容浏览器的信息
                                            browsers: "last 2 versions",// 最新的两个版本
                                        }
                                    ]
                                ]
                            }
                        }
                    },
                    'less-loader'
                ]
            },
        ]
    },

    // 配置webpack的插件
    plugins: [
        new HTMLWebpackPlugin({
            // title: "这是一个自定义的title",
            template: "./src/index.html", // 表示生成的网页根据这个模板生成的
        }), // 自动生成html文件
        new CleanWebpackPlugin(),
    ],

    // 用来设置引用模块
    resolve: {
        extensions: ['.ts', '.js'] // 表示 以这两个结尾的都可以模块来使用
    }
};

package.json

主要添加 build 和 start 这两行,这里面的都是你下载的依赖版本号

typescript | 贪吃蛇小项目

完成项目界面

npm start打开开发环境实时界面

typescript | 贪吃蛇小项目

完成Food类

Q:食物有什么属性和方法?

A: 定义一个属性表示食物所对应的元素

如何能判断蛇是否迟到了食物?蛇的偏移量和食物的偏移量一样就证明吃到了。

所以需要一个方法来获取食物的偏移量

当食物被吃掉后,食物要给随机改变位置

所以需要一个 修改食物位置的方法

定义一个属性表示食物所对应的元素

  • 如何能判断蛇是否迟到了食物?蛇的偏移量和食物的偏移量一样就证明吃到了。

  • 所以定义一个属性表示食物所对应的元素。

 element: HTMLElement;
 constructor() {      
     // 获取页面中的food元素,并将其赋值给element
     this.element = document.getElementById('food') as HTMLElement;  
 }

来获取食物的偏移量的方法

  • 如何能判断蛇是否迟到了食物?蛇的偏移量和食物的偏移量一样就证明吃到了。

  • 所以需要一个方法来获取食物的偏移量

这里用到了 属性存储器的 get方法

    // 属性存储器 get方法
    // 获取食物的x轴坐标
    get X() {
        return this.element.offsetLeft;
    }

    // 获取食物Y轴坐标
    get Y() {
        return this.element.offsetTop;
    }

typescript | 贪吃蛇小项目

修改食物位置的方法

如何随机修改食物的位置?

  1. 首先 食物的活动范围是0~294px。

    因为stage的高度和宽度是304px,而食物的高度和宽度是10px,所以食物的最大活动范围是 304-10 = 294px

  2. 其次 食物的位置只能是 10 的倍数。

    因为蛇的高度和宽度是10px,所以蛇每次移动是10px的移动,我们要让 蛇的偏移量 = 食物的偏移量,食物才被吃掉,所以食物的位置只能是 10倍数。

    Math.random()是可以获得0~1之间的数(不包括0和1)

    Math.random()*29是将获得的随机数 x 29

    Math.round(Math.random()*29)然后再将获得的数进行四舍五入,这样就是整数

    Math.round(Math.random()*29)*10这里就是我们的随机位置了

    // 随即修改食物的位置
    change() {
        // 生成一个随机的位置,食物坐标范围(0~294px 记得要减去食物的宽度和高度)
        // 要求 食物的坐标是10的倍数
        let top = Math.round(Math.random() * 29) * 10;
        let left = Math.round(Math.random() * 29) * 10;
        this.element.style.left = top + 'px';
        this.element.style.top = left + 'px';
    }

Food类-总

class Food {
    // 食物有什么属性?和方法?
    // 定义一个属性表示食物所对应的元素
    element: HTMLElement;
    constructor() {
        // 获取页面中的food元素,并将其赋值给element
        this.element = document.getElementById('food') as HTMLElement;
    }
    // 属性存储器 get方法
    // 获取食物的x轴坐标
    get X() {
        return this.element.offsetLeft;
    }

    // 获取食物Y轴坐标
    get Y() {
        return this.element.offsetTop;
    }

    // 随即修改食物的位置
    change() {
        // 生成一个随机的位置,食物坐标范围(0~294px 记得要减去食物的宽度和高度)
        // 要求 食物的坐标是10的倍数
        let top = Math.round(Math.random() * 29) * 10;
        let left = Math.round(Math.random() * 29) * 10;
        this.element.style.left = top + 'px';
        this.element.style.top = left + 'px';
    }
}

完成ScorePanel类

ScorePanel游戏的记分牌

score是分数

level是等级:从1开始,当你每获得了10分,等级就会上升,蛇的速度就会增加,有上限(10)

Q:记分牌有什么属性和方法?

A:记分牌有 积分器和 等级 属性,另外还可以设置一个变量来限制等级,和设置一个变量表示 每获得xx分就升级

当你吃到了食物,就会加分

所以有 设置加分的方法

当你每加xx分的时候的时候,就会升一个等级,蛇的速度会加快

所以有 提升等级的方法

属性

记分牌需要什么属性?

  • 需要 分数(每吃到食物加1分),等级(分数每获得xx分就生一个等级),需要一个变量来限制最高等级,需要一个变量表示每获得xx分就升级
  // score和 level用来记录分数和等级
  score = 0;
  level = 1;

  // 设置一个变量来限制等级
  maxLevel: number;
  // 设置一个变量表示每获得xx分时就升级
  upScore: number;
  // 分数和等级所在的元素,在构造函数中进行初始化
  scoreEle: HTMLElement;
  levelEle: HTMLElement;

  constructor(maxLevel: number = 10, upScore: number = 10) { // 你不传参就是10,传参的话就是你传的那个数
      this.scoreEle = document.querySelector('#score');
      this.levelEle = document.querySelector('#level');
      this.maxLevel = maxLevel;
      this.upScore = upScore;
  }

提升等级的方法

当你每加xx分的时候的时候,就会升一个等级,蛇的速度会加快

需要提升等级的方法

    // 提升等级的方法
    levelUp() {
        // 等级要有上限
        if (this.level < this.maxLevel) {
            this.level++;
            this.levelEle.innerHTML = this.level + '';
        }
    }

设置加分的方法

当你每吃到一个食物,就会加分,同时你的等级就会上升一个

    // 设置加分的方法
    addScore() {
        this.score++;
        this.scoreEle.innerHTML = this.score + ''; // 转成字符串形式
        // 判断分数是多少就开始升级
        if (this.score % this.upScore == 0) {
            this.levelUp();
        }
    }

ScorePanel类-总

// 定义记分牌的类
class ScorePanel {
    // score和 level用来记录分数和等级
    score = 0;
    level = 1;

    // 设置一个变量来限制等级
    maxLevel: number;
    // 设置一个变量表示每获得xx分时就升级
    upScore: number;
    // 分数和等级所在的元素,在构造函数中进行初始化
    scoreEle: HTMLElement;
    levelEle: HTMLElement;

    constructor(maxLevel: number = 10, upScore: number = 10) { // 你不传参就是10,传参的话就是你传的那个数
        this.scoreEle = document.querySelector('#score');
        this.levelEle = document.querySelector('#level');
        this.maxLevel = maxLevel;
        this.upScore = upScore;
    }

    // 修改score 和 level 
    // 设置加分的方法
    addScore() {
        this.score++;
        this.scoreEle.innerHTML = this.score + ''; // 转成字符串形式
        // 判断分数是多少就开始升级
        if (this.score % this.upScore == 0) {
            this.levelUp();
        }
    }
    // 提升等级的方法
    levelUp() {
        // 等级要有上限
        if (this.level < this.maxLevel) {
            this.level++;
            this.levelEle.innerHTML = this.level + '';
        }
    }
}
export default ScorePanel;

完成Snake类

Q:蛇有什么属性和方法?

A: 我们需要控制蛇,所以有一个表示 蛇和 蛇头 的元素,还需要蛇所有的身体

我们需要控制蛇来移动

所以我们需要获取蛇头 坐标的方法

我们需要设置 蛇头坐标的方法

当我们蛇吃到食物后,会增加身体一节

所以我们需要增加身体的方法

属性

我们需要控制蛇,所以有一个表示 蛇和 蛇头 的元素,还需要蛇所有的身体

// 获取蛇的容器
element:HTMLElement;
// 表示蛇头的元素
head: HTMLElement;
// 表示蛇的身体(包括舌头)
// HTMLCollection是一个集合,它会实时刷新的
bodies: HTMLCollection;

constructor() {
    this.element = document.querySelector('#snake');
    // querySelector只取一个节点,所以取到了蛇头
    this.head = document.querySelector('#snake > div')
    // querySelector不能实时变化,所以我们用getElementById
    this.bodies = this.element.getElementsByTagName('div');
}

获取和设置蛇头坐标的方法

我们需要控制蛇来移动

  • 所以我们需要获取蛇头 坐标的方法

  • 我们需要设置 蛇头坐标的方法

    // 获取蛇头的坐标(蛇头的坐标)
    get X() {
        return this.head.offsetLeft;
    }
    get Y() {
        return this.head.offsetTop;
    }
    // 设置蛇头的坐标
    set X(value: number) {
        this.head.style.left = value + 'px';
    }
    set Y(value: number) {
        this.head.style.top = value + 'px';
    }

增加蛇身体的方法

当我们蛇吃到食物后,会增加身体一节

  • 所以我们需要增加身体的方法

typescript | 贪吃蛇小项目

// 蛇增加身体的方法
addBody(){
        // 创建div节点
        // 向element中添加一个div
        this.element.insertAdjacentHTML("beforeend","<div></div>")
}

完成Snake类-总

// 定义蛇 的类
class Snake {
    // 获取蛇的容器
    element:HTMLElement;
    // 表示蛇头的元素
    head: HTMLElement;
    // 表示蛇的身体(包括舌头)
    // HTMLCollection是一个集合,它会实时刷新的
    bodies: HTMLCollection;

    constructor() {
        this.element = document.querySelector('#snake');
        // querySelector只取一个节点,所以取到了蛇头
        this.head = document.querySelector('#snake > div')
        // querySelector不能实时变化,所以我们用getElementById
        this.bodies = this.element.getElementsByTagName('div');
    }

    // 获取蛇头的坐标(蛇头的坐标)
    get X() {
        return this.head.offsetLeft;
    }
    get Y() {
        return this.head.offsetTop;
    }
    // 设置蛇头的坐标
    set X(value: number) {
        this.head.style.left = value + 'px';
    }
    set Y(value: number) {
        this.head.style.top = value + 'px';
    }

    // 蛇增加身体的方法
    addBody(){
        // 创建div节点
        const div = document.createElement('div');
        // 向element中添加一个div,放在element孩子中的第一个
        this.element.insertBefore(div,this.element.children[0]);
    }
}

export default Snake;

完成GameContro类

游戏控制器,控制其他的所有类

Q:游戏控制器的属性和方法?

A:游戏控制器,需要控制他们元素,所以需要snake,food,scorePanel他们三个属性

另外我们需要存储蛇的移动方向,和记录游戏是否结束的

游戏控制器的初始化方法,当你调用这个方法后游戏就开始

所以要有游戏的初始化方法

蛇需要动,当我们按下键盘上的↑↓←→键的时候,蛇会移动

所以我们要有一个绑定键盘按下的事件

另外我们还要 让蛇移动的方法

属性

游戏控制器,需要控制他们元素,所以需要snake,food,scorePanel他们三个属性

另外我们需要存储蛇的移动方向,和记录游戏是否结束的

    // 定义三个属性
    snake: Snake;
    food: Food;
    scorePanel: ScorePanel;

    // 存储蛇的移动方向(按键的方向)
    direction: string = 'ArrowDown';

    // 创建一个属性用来记录游戏是否结束
    isLive:boolean = true;

    constructor() {
        // 创建他们的实例,就可以运行他们了
        this.snake = new Snake();
        this.food = new Food();
        this.scorePanel = new ScorePanel();
        // 游戏开始
        this.init();
    }

键盘按下的响应函数

当我们按下按键的时候,要知道它按的是哪个键

所以要 - 存储按键的方向

    // 键盘按下的响应函数
    keydownHandler(event: KeyboardEvent) {
        // console.log(event.key);// ArrowUp,ArrowDown, ArrowLeft, ArrowRight
        // 需要检查event.key的值是否合法(是否正确按键)

        // 当我们按键的时候,就要修改direction的属性
        //   - 这里的this值指向的是document,因为绑定的document,所以我们在调用这个函数的时候用bind改变this指向
        this.direction = event.key;
    }

让蛇移动的方法

让蛇移动就是,如果你按下了→,蛇 的left变大,↓ top值变大

  1. 获取到蛇 现在的坐标

  2. 根据按键的方向来修改X和Y的值

  3. 修改蛇的X和Y值

  4. 开启定时调用,等级1的时候每300ms调用,就可以让蛇移动

    等级越高速度越快

300 - (this.scorePanel.level - 1) * 30

  • 300ms是最低速度,每升一级速度就会加快,300就会往下减少。

  • this.scorePanel.level 一开始是 1 ,然后 再 -1,就是0,(this.scorePanel.level - 1) * 30 就是0,所以一开始的是300ms。

  • 随着level 的增加,(this.scorePanel.level - 1) * 30 也会增加,定时器就调用间隔会减短,蛇移动的速度会变快。

    // 让蛇移动的方法
    run() {
        // 根据现在的方向this.direction,来让蛇的位置改变
        // 获取蛇现在的坐标
        let X = this.snake.X;
        let Y = this.snake.Y;

        // 根据按键的方向修改X和Y值
        switch (this.direction) {
            // 向上移动,top值减少
            case "ArrowUp":
                Y -= 10;
                break;
            // 向下移动,top值增加
            case "ArrowDown":
                Y += 10;
                break;
            // 向左移动,left值减少
            case "ArrowLeft":
                X -= 10;
                break;
            // 向右移动,left值增加
            case "ArrowRight":
                X += 10;
                break;
        }

        // 修改蛇的X和Y
        this.snake.X = X;
        this.snake.Y = Y;
        console.log(this.snake);

        // 开启定时调用,每300ms以后就调用run方法
        // 等级越高速度越快
        this.isLive && setTimeout(this.run.bind(this), 300 - (this.scorePanel.level - 1) * 30);
    }

游戏的初始化方法

当调用这个方法后,游戏会立即开始,在这里面 - 绑定键盘按下的事件,以及 调用让蛇run的方法

    // 游戏的初始化方法,调用后游戏即开始
    init() {
        // 绑定键盘按下的事件
        document.addEventListener('keydown', this.keydownHandler.bind(this)); // 这里面的this指向GameControl
        // 调用让蛇run的方法
        this.run();
    }

⚠ 这里绑定键盘按下的事件,会调用 keydownHandler 这个函数,因为这个函数是和document绑定的,所以 keydownHandler 这个函数里面的this指向的是document

而我们需要this指向的是GameControl 这个原本指向,所以使用了.bind来改变this指向

typescript | 贪吃蛇小项目

蛇穿墙问题

当蛇撞墙,蛇会die,给蛇加个位置范围,所以主要在蛇的类里面就行修改!!

如果蛇撞墙了,说明value不在0~294这个范围,我们就抛出异常,那么 GameControl里面的 修改蛇的X和Y 可以用trycatch来捕获异常

将isLive设置为false,表示游戏结束了

进入到catch,说明出现了异常,游戏结束,弹出一个提示信息

// Snake.ts
    // 设置蛇头的坐标
    set X(value: number) {
        // 当我们旧值和要设置的坐标一样,就return,不做修改
        if (this.X == value) {
            return;
        }
        this.head.style.left = value + 'px';

        // 是否撞墙(X值的合法范围0~294)
        if (value < 0 || value > 294) {
            // 说明蛇撞墙了(蛇die)把这个消息传给gameControl
            // 抛出异常
            throw new Error('蛇撞墙了');
        }
    }
    set Y(value: number) {
        if (this.Y == value) {
            return;
        }
        this.head.style.top = value + 'px';
        // 是否撞墙(Y值的合法范围0~294)
        if (value < 0 || value > 294) {
            // 说明蛇撞墙了(蛇die)把这个消息传给gameControl
            throw new Error('蛇撞墙了');
        }

    }
// GameControl.ts
        // 修改蛇的X和Y
        try {
            this.snake.X = X;
            this.snake.Y = Y;
        } catch (error) {
            // 将isLive设置为false,表示游戏结束了
            this.isLive = false;
            // 进入到catch,说明出现了异常,游戏结束,弹出一个提示信息
            alert(error.message + '✨ Game Over');
        }

检查蛇是否吃到了食物

蛇和食物,需要两个类,我们在GameControl里面进行修改。

吃到食物以后

  1. 食物的位置要重置
  2. 分数score要增加
  3. 蛇要增加一节
//  GameControl.ts

    // 检查蛇是否迟到了食物
    // 上面定义了X和Y是蛇现在的坐标
    this.checkEat(X, Y);

	// 定义一个方法,检查蛇是否吃到了食物
    checkEat(X: number, Y: number) {
        // 这里的X和Y是蛇的坐标
        if (X === this.food.X && Y === this.food.Y) {
            // 食物的位置要重置
            this.food.change();
            // 分数score要增加
            this.scorePanel.addScore();
            // 蛇要增加一节
            this.snake.addBody();
        }
    }

身体的移动

身体属于蛇的一部分,所以在Snake类中去写身体的部分

typescript | 贪吃蛇小项目

  1. 让蛇身体跟着移动的方法
    // 蛇身体移动的方法
    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';
        }
    }
  1. 禁止 蛇掉头

    比如:蛇在往下走的时候,不能按上走

    判断蛇头的坐标(蛇身体的第一节),和蛇身体的第二节坐标是否一样,一样的话就是掉头。

    并且在做????判断前,要先判断蛇身体是否有第二节

  1. value 为我们下一步要走的地方,

  2. 现在我们正在往左走,X正在减小,如果这时候我们要往右走,value会变大,但是我们要阻止往右走,就让他继续往左走,让X-10,继续往左走。

  3. 如果我们正在往右走,X正在增大,这时候我们要他往左走,value会变小

    // 修改蛇头的坐标
    set X(value: number) {
        // 当我们旧值和要设置的坐标一样,就return,不做修改
        if (this.X == value) {
            return;
        }

        // 是否撞墙(X值的合法范围0~294)
        if (value < 0 || value > 294) {
            // 说明蛇撞墙了(蛇die)把这个消息传给gameControl
            // 抛出异常
            throw new Error('蛇撞墙了');
        }

        // 修改X时,是在修改水平坐标,蛇在左右移动,蛇在向左移动时,不能向右掉头
        if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft == value) {
            // 说明发生水平方向掉头
            // 发生了掉头,让蛇向反方向继续移动
            if (value > this.X) {
                // 如果新值大于旧值,说明蛇在向右走,此事发生掉头我们应该使蛇向左走
                value = this.X - 10;
            } else {
                value = this.X + 10;
            }
        }

        // 移动我们的身体
        this.moveBody();
        // 蛇的最新坐标
        this.head.style.left = value + 'px';
        // 检查是否撞到自己
        this.checkHeadBody();
    }
  1. 检查蛇 是否撞到自己,撞到自己后就die

    • 其实就是蛇头的坐标和身体的坐标是否发生重复

    什么时候检查?

    当改变蛇头坐标后,进行检查。

    // 检查头和身体是否相撞
    checkHeadBody() {
        // 获取所有的身体,检查其是否和蛇头的坐标发生重叠
        for (let i = 1; i < this.bodies.length; i++) {
            // 获取到所有的body
            let bd = this.bodies[i] as HTMLElement;

            // 看蛇头的X坐标和身体的坐标发生重叠
            if (this.X == bd.offsetLeft && this.Y == bd.offsetTop) {
                // 游戏结束
                throw new Error('撞到自己啦');
            }
        }
    }