TypeScript实现贪吃蛇游戏

news2025/1/10 16:32:23

TS实现贪吃蛇游戏

文章目录

    • TS实现贪吃蛇游戏
    • @[toc]
      • 1.项目效果
      • 2.项目梳理
      • 3.项目准备
      • 4.主体页面结构
      • 5.CSS样式
      • 6.TS逻辑
        • 6.1 食物逻辑
        • 6.2 蛇逻辑
        • 6.3 记分板逻辑
        • 6.4 游戏控制器逻辑
        • 6.5 程序入口ts

1.项目效果

项目体验

在这里插入图片描述

2.项目梳理

这个小游戏主要包括积分面板,食物,蛇,还有我们的游戏控制器这四个部分,分为四个类来写。

3.项目准备

  • 1.项目环境

    • node环境
    • 采用webpack进行打包
    • 采用less进行书写样式表
    • ts用来编写逻辑
  • 2.项目配置

    • package.json

      {
        "name": "demo",
        "version": "1.0.0",
        "main": "index.js",
        "scripts": {
          "test": "echo \"Error: no test specified\" && exit 1",
          "build": "webpack",
          "start": "webpack serve"
        },
        "author": "",
        "license": "ISC",
        "keywords": [],
        "description": "",
        "devDependencies": {
          "@babel/core": "^7.21.8",
          "@babel/preset-env": "^7.21.5",
          "babel-loader": "^9.1.2",
          "clean-webpack-plugin": "^4.0.0",
          "core-js": "^3.30.2",
          "css-loader": "^6.7.4",
          "html-webpack-plugin": "^5.5.1",
          "less": "^4.1.3",
          "less-loader": "^11.1.0",
          "postcss": "^8.4.23",
          "postcss-loader": "^7.3.0",
          "postcss-preset-env": "^8.4.1",
          "style-loader": "^3.3.3",
          "ts-loader": "^9.4.3",
          "typescript": "^5.0.4",
          "webpack": "^5.83.1",
          "webpack-cli": "^5.1.1",
          "webpack-dev-server": "^4.15.0"
        }
      }
      
      
    • tsconfig.json

      {
        "compilerOptions": {
          "module": "es2015",
          "target": "es2015",
          "strict": true,
          "sourceMap": false,
          //当有错误时不生成编译后文件
          "noEmitOnError": true
        },
        "include": [
          "./src/**/*"
        ],
        "exclude": [
          "node_modules"
        ]
      }
      
    • webpack.config.js 打包配置

      //引入一个包
      const path = require("path");
      //引入html插件
      const HTMLWebpackPlugin = require("html-webpack-plugin")
      //引入编译清除上一次文件插件
      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不使用箭头
          environment: {
            arrowFunction: false
          }
        },
        // mode: 'development',// 设置mode
        mode: 'production',// 设置mode
        //指定webpack打包时要使用的模块
        module: {
          //指定要加载的规则
          rules: [
            {
              //test指定的是规则生效的文件
              test: /\.ts$/,
              //要使用的loader
              use: [
                //配置babel 适配更多浏览器
                {
                  //指定加载器
                  loader: "babel-loader",
                  //设置babel
                  options: {
                    //设置预定义的环境
                    presets: [
                      [
                        //指定环境的插件
                        "@babel/preset-env",
                        //配置信息
                        {
                          targets: {
                            "chrome": 88,
                          },
                          //指定corejs的版本
                          "corejs": "3",
                          //使用core js的方式"usage"表示按需加载
                          "useBuiltIns": "usage"
                        }
                      ]
                    ]
                  }
                }
                , "ts-loader"],
              //要排除的文件
              exclude: /node-modules/
            },
            //设置less文件的处理
            {
              test: /\.less$/,
              use: [
                "style-loader",
                "css-loader",
                //引入postcss
                {
                  loader: "postcss-loader",
                  options: {
                    postcssOptions: {
                      plugins: [
                        [
                          "postcss-preset-env",
                          {
                            browsers: "last 2 versions"
                          }
                        ]
                      ]
                    }
                  }
                },
                "less-loader"
              ]
            }
          ]
        },
      
        plugins: [
          new CleanWebpackPlugin(),
          new HTMLWebpackPlugin({
            // title:"这是一个自定义title"
            template: "./src/index.html"
          })
        ],
        //用来设置引用模块
        resolve: {
          extensions: [".ts", ".js"]
        }
      }
      

4.主体页面结构

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>贪吃蛇</title>
  </head>
  <body>

    <div id="main">

      <div class="screen">
        <!--设置蛇的样式-->
        <div id="snake">
          <div></div>
          <!--            <div></div>-->
          <!--            <div></div>-->
          <!--            <div></div>-->
        </div>
        <!--食物-->
        <div id="food">
          <div></div>
          <div></div>
          <div></div>
          <div></div>
        </div>

      </div>
      <div class="count">
        <h4 class="countText">SCORE: <span id="score">0</span></h4>
        <h4 class="countText">LEVEL: <span id="level">1</span></h4>
      </div>
    </div>

  </body>
</html>

5.CSS样式

index.less

@backColor: #cefdb5;
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  //background-color: @backColor;
  text-align: center;
  padding-top: 100px;
  //display: flex;
}

#main {
  display: inline-block;
  width: 560px;
  height: 620px;
  background-color: @backColor;
  border: #0e0e0e 18px solid;
  padding: 20px;
  border-radius: 40px;
  text-align: center;


  .screen {
    height: 415px;
    width: 460px;
    margin: 12px auto auto;
    border: #0e0e0e 4px solid;
    position: relative;

    #snake {
      & > div {
        width: 15px;
        height: 15px;
        background-color: #000;
        border: 1px @backColor solid;
        position: absolute;
      }

      div:first-child {
        background-color: #67d01b;
        border-radius: 4px;
      }
    }

    #food {
      width: 15px;
      height: 15px;
      //background-color: red;
      border: 1px @backColor solid;
      position: absolute;
      left: 40px;
      display: flex;
      flex-flow: row wrap;
      justify-content: space-between;
      align-content: space-between;


      & > div {
        width: 6px;
        height: 6px;
        background-color: #0a7142;
        transform: rotate(45deg);
      }
    }
  }

  .count {
    margin-top: 50px;
    padding: 0 10px;
    display: flex;
    width: 100%;
    flex-direction: row;
    justify-content: space-between;
    font-size: 20px;
    font-weight: 600;

    h4 {
      display: inline-block;
    }
  }
}

6.TS逻辑

6.1 食物逻辑

export class Food {
    //定义一个属性表示食物对应的元素
    elementD: HTMLElement;

    constructor(elemet: HTMLElement) {
        //获取页面中的元素给elementD
        // document.getElementById("#food")!;
        this.elementD = elemet;
        this.change()
    }

    get X() {
        return this.elementD.offsetLeft;
    }

    get Y() {
        return this.elementD.offsetTop;
    }

    //修改食物位置
    change() {
        //生成一个随机的位置
        // console.log(this.elementD)
        //食物的位置最小是 0 x 最大是460  y最大是420 390 435;
        let top = Math.round(Math.random() * 26) * 15
        let left = Math.round(Math.random() * 29) * 15;
        this.elementD.style.left = left + 'px'
        this.elementD.style.top = top + 'px'

    }

}

6.2 蛇逻辑

我们的主角,主要逻辑难点在蛇身的移动与碰撞检验

export class Snake {
  //表示蛇头元素
  head: HTMLElement;
  //蛇的身体
  bodies: HTMLCollection
  element: HTMLElement

  constructor() {
    this.element = document.getElementById("snake")!
    this.head = document.querySelector("#snake>div") as HTMLElement;
    this.bodies = this.element.getElementsByTagName('div');
  }

  get X() {
    return this.head.offsetLeft
  }

  get Y() {
    return this.head.offsetTop
  }

  //设置蛇头的坐标

  set X(val: number) {
    if (this.X == val) {
      return
    }
    if (val < 0 || val > 435) {
      throw new Error("你撞墙了")
    }

    this.head.style.left = val + "px"
  }

  set Y(val: number) {
    if (this.Y == val) {
      return
    }
    if (val < 0 || val > 390) {
      throw new Error("你撞墙了")
    }
    this.head.style.top = val + "px"
  }

  //蛇增加身体
  addBody() {
    this.element.insertAdjacentHTML("beforeend", `<div></div>`)
  }

  moveBody(x: number, y: number) {
    //遍历所有的身体 头已经改过了 不需要改

    for (let i = 1; i < this.bodies.length - 1; i++) {
      let currentEle = this.bodies[i] as HTMLElement;
      let x1 = parseInt(currentEle.style.left.substring(0, currentEle.style.left.indexOf("px")))
      let y1 = parseInt(currentEle.style.top.substring(0, currentEle.style.top.indexOf("px")))
      if (this.X == x1 && this.Y == y1 && i > 1) {
        throw new Error("你撞上了自己的身体!")
      }
    }

    for (let i = this.bodies.length - 1; i > 0; i--) {

      let houEle = this.bodies[i] as HTMLElement;
      let qianEle = this.bodies[i - 1] as HTMLElement;

      if (i == 1) {
        houEle.style.left = x + 'px';
        houEle.style.top = y + 'px';
      } else {
        houEle.style.left = qianEle.style.left;
        houEle.style.top = qianEle.style.top;
      }

    }

  }
}

6.3 记分板逻辑

export class ScorePanel {
    score: number = 0;
    level: number = 1;

    scoreEle: HTMLElement;
    levelEle: HTMLElement;

    MaxLevel:number;
    UpScore:number;

    constructor(scoreEle: HTMLElement, levelEle: HTMLElement,maxLevel:number=10,upScore:number=5) {
        this.scoreEle = scoreEle;
        this.levelEle = levelEle;
        this.MaxLevel=maxLevel;
        this.UpScore=upScore;
    }

    addScore() {
        this.score++;
        this.scoreEle.innerHTML = this.score + ""
        if (this.score%this.UpScore==0){
            this.addLevel()
        }
    }

    addLevel() {
        if (this.level >= this.MaxLevel) {
            return
        }
        this.level++;
        this.levelEle.innerHTML = this.level + ""
    }

}

6.4 游戏控制器逻辑

整个游戏的运行与控制逻辑都在这里,里面有按键触发逻辑等,是用来调度蛇与食物和记分板的一个控制类

//游戏控制器

import {Food} from "./Food";
import {Snake} from "./Snake";
import {ScorePanel} from "./ScorePanel";


export class GameControl {
  food: Food;
  snake: Snake;
  scorePanel: ScorePanel
  direction: string = ""
  time: any
  isLive: boolean = true


  constructor(food: Food, scorePanel: ScorePanel, snake: Snake) {
    this.food = food;
    this.snake = snake;
    this.scorePanel = scorePanel;
    this.init();
    this.run();

  }

  init() {
    //banging键盘按下的事件
    document.addEventListener("keydown", this.keydownHandler.bind(this))

  }

  /**
     *    ArrowUp
     *    ArrowDown
     *     ArrowLeft
     *     ArrowRight
     */
  keydownHandler(event: KeyboardEvent) {
    let a = " ArrowUp ArrowDown ArrowLeft ArrowRight "
    let b = "wasd"
    let key = event.key;
    if (a.indexOf(" " + key + " ") != -1 || b.indexOf(key) != -1) {
      // console.log(event.key)
      //上的时候不可以按下
      //左的时候不可以按右
      switch (this.direction) {
        case "ArrowUp":
        case "Up":
        case "w":
          switch (key) {
            case "ArrowDown":
            case "Down":
            case "s":
              return;
          }
          break

        case "ArrowDown":
        case "Down":
        case "s":
          switch (key) {
            case "ArrowUp":
            case "Up":
            case "w":
              return;
          }
          break

        case "ArrowLeft":
        case "Left":
        case "a":
          switch (key) {
            case "ArrowRight":
            case "Right":
            case "d":
              return;
          }
          break

        case "ArrowRight":
        case "Right":
        case "d":
          switch (key) {
            case "ArrowLeft":
            case "Left":
            case "a":
              return;
          }
          break
      }


      this.direction = event.key
      // this.run()
    }
  }

  /**
     * 蛇移动方法
     *
     */
  run() {
    //获取蛇现在的坐标
    let x = this.snake.X;
    let y = this.snake.Y;
    let x1 = this.snake.X;
    let y1 = this.snake.Y;

    switch (this.direction) {
      case "ArrowUp":
      case "Up":
      case "w":
        // 向上移动top减少
        y -= 15;
        break;

      case "ArrowDown":
      case "Down":
      case "s":
        y += 15
        break;

      case "ArrowLeft":
      case "Left":
      case "a":
        x -= 15
        break;

      case "ArrowRight":
      case "Right":
      case "d":
        x += 15
        break;
    }
    try {
      this.snake.X = x;
      this.snake.Y = y;
      //判断是否吃到食物
      this.checkEat(x, y);
      this.snake.moveBody(x1, y1)
    } catch (e: any) {
      this.isLive = false
      alert(e.message)
      return
    }

    // console.log(x, y)
    if (this.time && this.isLive) {
      clearTimeout(this.time)
    }

    this.time = setTimeout(this.run.bind(this), 250 - (this.scorePanel.level - 1) * 50)

  }

  checkEat(X: number, Y: number) {
    if (this.food.X == X && this.food.Y == Y) {
      //吃到食物了
      this.scorePanel.addScore()
      //重新刷新食物点位
      this.food.change()
      //蛇要增加一节
      this.snake.addBody()
    }
  }
}

6.5 程序入口ts

用来引入所有需要的模块,包括样式模块

import './style/index.less'
import {Food} from "./modules/Food";
import {ScorePanel} from "./modules/ScorePanel";
import {GameControl} from "./modules/GameControl";
import {Snake} from "./modules/Snake";

let foodEle = document.getElementById("food")!;
let food = new Food(foodEle);
let scorePanel = new ScorePanel(document.getElementById("score")!,
    document.getElementById("level")!)

let snake = new Snake();

let gameControl = new GameControl(food,scorePanel,snake);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/582603.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023上半年软件设计师真题评析

2023年上半年软设是2018年改版后的一次考试&#xff0c;以下内容根据考完回忆结合网上暂时流传的真题(不保证完全正确)整理&#xff0c;主要侧重相关知识点罗列&#xff0c;少讲或不讲具体的答案&#xff0c;主要给自己的计算机基础查漏补缺&#xff0c;同时也希望对大家有帮助…

基于AI技术的APP外包开发流程

AI技术发展非常迅速&#xff0c;最近一年有了巨大的技术突破&#xff0c;一些专家认为可以基于现在的AI技术将APP再重做一遍&#xff0c;这无疑将给创业者带来巨大机会。今天和大家分享使用AI重做一个APP的步骤和注意事项&#xff0c;希望对大家有所帮助。 需要遵循以下步骤和注…

5601-RIO-MCM 消除Modbus网络上的通信错误

当端口配置为从端口时&#xff0c;此参数指定内部用作Holding的零地址或起点的数据库地址 寄存器&#xff08;16位整数&#xff09;数据。Modbus功能读取保持寄存器数据代码3命令&#xff08;读取保持寄存器&#xff09;和由功能代码6写入&#xff08;预设单寄存器&#xff09;…

《论文阅读》在跨语料库上利用集成提示完成零样本的文本情感分类 COLING2022

《论文阅读》在跨语料库上利用集成提示完成零样本的文本情感分类 COLING2022 前言相关知识hubness problem零样本学习灵感来源验证点零样本情感分类的自然语言推理情感提示情感集成自我总结问题前言 你是否也对于理解论文存在困惑? 你是否也像我之前搜索论文解读,得到只是中…

【自然语言处理】- 作业6: 面向新冠肺炎的社会计算应用

课程链接: 清华大学驭风计划 代码仓库&#xff1a;Victor94-king/MachineLearning: MachineLearning basic introduction (github.com) 驭风计划是由清华大学老师教授的&#xff0c;其分为四门课&#xff0c;包括: 机器学习(张敏教授) &#xff0c; 深度学习(胡晓林教授), 计算…

Mybatis-X插件自动生成代码的使用详解(小白专用)

Mybatis-X插件自动生成代码的使用详解&#xff08;小白专用&#xff09; 1、 使用idea链接数据库 详见使用idea链接数据库并生成实体类 idea链接数据库之后也提供了一个生成实体类的方法&#xff0c;见↑ 2、安装mybatis-X插件 File–>Settings–>Plugins–>Marke…

c++11基础

文章目录&#xff1a; c11简介统一的列表初始化{}初始化std::initializer_list 声明autodecltypenullptr 范围for循环STL中的一些变化arrayforward_listunordered_map和unordered_set 字符串转换函数 c11简介 在2003年C标准委员会曾经提交了一份技术勘误表(简称TC1)&#xff0…

【JavaScript数据结构与算法】数组类(电话号码的字符组合)

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;也会涉及到服务端&#xff08;Node.js&#xff09; &#x1f4c3;个人状态&#xff1a; 在校大学生一枚&#xff0c;已拿多个前端 offer&#xff08;…

小红书达人矩阵怎么布局,达人矩阵分配

随着互联网营销学的兴起&#xff0c;一方面使得生意越来越好做&#xff0c;但同时也加大了做生意的门槛&#xff0c;属于是良币驱逐劣币。而达人矩阵就是良币的一种表现方式&#xff0c;今天来和大家来分享下小红书达人矩阵怎么布局&#xff0c;达人矩阵分配。 达人矩阵是什么?…

重塑工作场所:后疫情时代组织韧性的8个策略

经济寒冬来临&#xff0c;倒挂的收益率曲线、持续上升的利率以及层出不穷的裁员公告等等&#xff0c;让经济学家们得出一个结论&#xff1a;全球经济正在衰退。然而&#xff0c;经济下行周期可能是卓越公司改变其命运的最佳时机。有研究表明&#xff0c;相对于非经济衰退时期&a…

JavaSE_day40(字节流复制图片,字节流与File实现复制目录到另一个目录下)

1 A.java * 1.分别使用字符流和字节流复制图片&#xff08;底层是二进制文件&#xff0c;如图片 视频 音频等&#xff09; * * 二进制文件只能使用字节流进行复制&#xff08;使用windows自带记事本打开读不懂的&#xff09; * * 文本文件的复制即可使用…

【数据分享】第六次、第七次人口普查深圳各街道数据

0. 数据来源 https://tjgb.hongheiku.com/ https://www.hongheiku.com/sichuan/55201.html 手动收集整理 数据展示 数据分享 只分享人口数据&#xff0c;地理数据可能涉及隐私问题&#xff0c;暂不分享&#xff0c;有需要可以邮箱联系uncodongqq.com 链接: https://pan.baid…

Hive ---- 分区表和分桶表

Hive ---- 分区表和分桶表 1. 分区表1. 分区表基本语法2. 二级分区表3. 动态分区 2. 分桶表1. 分桶表基本语法2. 分桶排序表 1. 分区表 Hive中的分区就是把一张大表的数据按照业务需要分散的存储到多个目录&#xff0c;每个目录就称为该表的一个分区。在查询时通过where子句中…

信必优加入中国网络信息安全科技创新发展联盟

近日&#xff0c;信必优成功加入中国网络信息安全科技创新发展联盟。 中国网络信息安全科技创新发展联盟是在国务院国资委指导下&#xff0c;中国电科与公安部第一研究所、中国信息通信研究院、中国工业互联网研究院、中国科学院信息工程研究所、中国电信、中国联通、中国移动、…

2023年武汉住建厅七大员怎么报名?报名流程?精准题库一次过??

2023年武汉住建厅七大员怎么报名?报名流程&#xff1f;精准题库一次过&#xff1f;&#xff1f; 2023年武汉住建厅七大员是指施工员、质量员、资料员、材料员、机械员、标准员、劳务员&#xff0c;报的最多的可能就是施工员&#xff0c;质量员和资料员 报名流程&#xff1a; 1…

算法:回溯算法(以解决n皇后问题为例)

基本思想&#xff1a;回溯算法的基本思想是&#xff1a;从一条路往前走&#xff0c;能进则进&#xff0c;不能进则退回来&#xff0c;换一条路再试。八皇后问题就是回溯算法的典型&#xff0c;第一步按照顺序放一个皇后&#xff0c;然后第二步符合要求放第2个皇后&#xff0c;如…

自动化测试套件(RSpec)

自动化测试套件(RSpec) RSpec example RSpec 是 Ruby 编程语言的测试框架。 它旨在通过提供用于定义和执行测试的领域特定语言 (DSL) 来促进行为驱动开发 (BDD)。 RSpec 允许您编写富有表现力和可读性的测试来描述代码的预期行为。 以下是 RSpec 的一些关键特性和概念&#…

电脑怎么打开隐藏文件夹?1分钟搞定!

案例&#xff1a;我的电脑上有一些文件夹是隐藏文件夹啊&#xff0c;我不知道如何打开它们&#xff0c;有没有小伙伴知道如何打开电脑上的隐藏文件夹吗&#xff1f; 【我能正常打开电脑上的其他文件夹&#xff0c;但是打不开电脑隐藏的文件夹&#xff0c;有没有小伙伴知道打开…

浪涌保护器的工作原理(SPD)

浪涌保护器&#xff08;SPD&#xff09;的工作原理如下&#xff1a; 在正常运行期间&#xff08;例如&#xff0c;在没有浪涌的情况下&#xff09;&#xff0c;电涌保护器对安装它的电路系统没有影响。它的作用类似于开路&#xff0c;并保持有源导体和大地之间的隔离。 当发生…

2023年真无线蓝牙耳机买什么品牌好一些?盘点几款值得买的蓝牙耳机

蓝牙耳机是一种无线耳机&#xff0c;其通过蓝牙技术与其他设备进行连接&#xff0c;例如手机、电脑、平板电脑等。蓝牙耳机使得用户可以在不受线缆限制的情况下享受音频体验&#xff0c;而且还可以方便地进行通话&#xff0c;目前市场上有许多不同种类和品牌的蓝牙耳机&#xf…