项目背景及简介

typescript系列到这篇文章正式进入尾声了,我们通过以上学习ts的知识,想要熟悉的掌握必须要写一个小demo综合运用所学的知识,这个项目的目的就是综合ts所学知识,实现面向对象的实际开发!

项目地址 : https://gitee.com/liuze_quan/ts-greedy-snake

多模块需求分析

场景模块需求

  1. 具有长和宽的容器,容器内分成蛇移动和记分牌两个板块
  2. 蛇移动的场景设置边界线,边界线一旦触碰直接结束游戏
  3. 记分牌记录蛇吃到食物的分数以及等级
  4. 蛇吃到食物分数涨一分,每涨一定分数等级提高一级
  5. 等级设有上限
  6. 等级和蛇移动速度有关

食物类模块需求

  1. 在游戏开始时候食物生成在随机位置
  2. 当蛇吃掉食物后食物会再次随机出现(出现的位置不能与蛇身重合)

记分牌模块需求

  1. 设置限制等级
  2. 可以提升等级
  3. 可以增加获取的分数

蛇类模块需求

  1. 游戏开始的时候只有一个方块(蛇头),随后每吃掉一个食物则增加一节身体
  2. 当游戏进行过程中蛇头碰到身体则结束游戏
  3. 蛇的前进是持续的,不能停顿下来,只能去改变方向

控制模块需求

  1. 按下方向键开始游戏
  2. 只能通过四个方向键改变蛇前进的方向
  3. 判断游戏是否结束,蛇是否吃到食物
  4. 控制分数和等级是否相应增长,食物是否刷新

项目搭建

ts转译为js代码

我们需要创建tsconfig.json文件,文件代码如下:

{
  "compilerOptions": {
    "module": "es6",
    "target": "es6",
    "strict": true,
    "noEmitOnError": true
  }
}

package.json包配置文件

  1. 在这个小项目中我们需要webpack打包工具,所以我们要对package.json文件进行一些配置。
  2. 选择该项目在集成终端中打开并输入代码npm init -y进行项目初始化,这个时候会在你的项目中生成一个初步的package.json文件,然后我们进一步完善
  3. 在集成终端中输入指令npm i -D webpack webpack-cli typescript ts-loader用来下载相关依赖(如果可以看见package.json的depDependencies中更新了你下载的依赖表示下载成功)。i表示install下载的意思,-D意思是下载的作为依赖使用
  4. 继续输入指令npm i -D css-loader 等依赖,这些后面都有用
  5. 请注意上述代码中scripts中的"build": "webpack"键值对,这个设置说明我们可以用npm run build的代码来启用webpack打包工具
{
  "name": "part2",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "start": "webpack serve --open"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.18.9",
    "@babel/preset-env": "^7.18.9",
    "babel-loader": "^8.2.5",
    "clean-webpack-plugin": "^4.0.0",
    "core-js": "^3.24.0",
    "css-loader": "^6.7.1",
    "html-webpack-plugin": "^5.5.0",
    "less": "^4.1.3",
    "less-loader": "^11.0.0",
    "postcss": "^8.4.14",
    "postcss-loader": "^7.0.1",
    "postcss-preset-env": "^7.7.2",
    "style-loader": "^3.3.1",
    "ts-loader": "^9.3.1",
    "typescript": "^4.7.4",
    "webpack": "^5.74.0",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^4.9.3"
  }
}

webpack.config.js打包工具配置

webpack打包文件配置中,代码注释非常清楚,代码如下:

//引入一个包
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',
    //指定webpack打包时要使用的模块
    module: {
        //指定要加载的规则
        rules: [
            {
                //test指定规则生成的文件
                test: /\.ts$/,
                //要使用的loader
                use : [
                    //配置babel
                    {
                        // 指定加载器
                        loader: "babel-loader",
                        //设置babel
                        options: {
                            //设置预定义的环境
                            presets:[
                                [
                                    //指定环境的插件
                                    "@babel/preset-env",
                                    //配置信息
                                    {
                                        //要兼容的目标浏览器
                                        targets : {
                                            "chrome" : "101"
                                        },
                                        //指定core.js的版本
                                        "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"
                ]
            }
        ]
    },
    //配置webpack插件
    plugins: [
        new CleanWebpackPlugin(),
        new HTMLWebpackPlugin({
            // title: "自定义的title"
            template: "./src/index.html"
        }),
    ],
    //用来设置引用模块
    resolve: {
        extensions: ['.ts', '.js']
    }
}

到这里,我们要配置的文件都已经配置结束,接下来正式进入项目的开发

项目结构搭建

首先要进行我们的html和css样式搭建,搭建出来项目的页面!

html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>贪吃蛇</title>
</head>
<body>
<!--创建游戏的主容器-->
<div id="main">
    <!--设置游戏的舞台-->
    <div id="stage">
        <!--设置蛇-->
        <div id="snake">
            <!--snake内部的div 表示蛇的各部分-->
            <div></div>
        </div>
        <!--设置食物-->
        <div id="food">
            <!--添加四个小div 来设置食物的样式-->
            <div></div>
            <div></div>
            <div></div>
            <div></div>
        </div>
    </div>
    <!--设置游戏的积分牌-->
    <div id="score-panel">
        <div>
            SCORE:<span id="score">0</span>
        </div>
        <div>
            level:<span id="level">1</span>
        </div>
    </div>
</div>
</body>
</html>

css文件(这里使用的是less)

// 设置变量
@bg-color: #b7d4a8;
//清除默认样式
* {
  margin: 0;
  padding: 0;
  //改变盒子模型的计算方式
  box-sizing: border-box;
}
body{
  font: bold 20px "Courier";
}
//设置主窗口的样式
#main{
  width: 360px;
  height: 420px;
  // 设置背景颜色
  background-color: @bg-color;
  // 设置居中
  margin: 100px auto;
  border: 10px solid black;
  // 设置圆角
  border-radius: 40px;
  // 开启弹性盒模型
  display: flex;
  // 设置主轴的方向
  flex-flow: column;
  // 设置侧轴的对齐方式
  align-items: center;
  // 设置主轴的对齐方式
  justify-content: space-around;
  // 游戏舞台
  #stage{
    width: 304px;
    height: 304px;
    border: 2px solid black;
    // 开启相对定位
    position: relative;
    // 设置蛇的样式
    #snake{
      &>div{
        width: 10px;
        height: 10px;
        background-color: #000;
        border: 1px solid @bg-color;
        // 开启绝对定位
        position: absolute;
      }
    }
    // 设置食物
    #food{
      width: 10px;
      height: 10px;
      position: absolute;
      left: 40px;
      top: 100px;
      // 开启弹性盒
      display: flex;
      // 设置横轴为主轴,wrap表示会自动换行
      flex-flow: row wrap;
      // 设置主轴和侧轴的空白空间分配到元素之间
      justify-content: space-between;
      align-content: space-between;
      &>div{
        width: 4px;
        height: 4px;
        background-color: black;
        // 使四个div旋转45度
        transform: rotate(45deg);
      }
    }
  }
  // 记分牌
  #score-panel{
    width: 300px;
    display: flex;
    // 设置主轴的对齐方式
    justify-content: space-between;
  }
}

项目页面

多模块搭建

在项目开发中我们不可能把所有的代码写到一个文件中,所以项目开发必须会灵活运用模块化开发思想,把实现的功能细化成一个个模块。

完成Food(食物)类

//定义食物类
class Food {
    element : HTMLElement;
    constructor() {
        //获取页面中的food元素并赋给element
        this.element = document.getElementById('food')!;
    }
    //获取食物x轴坐标的方法
    get X() {
        return this.element.offsetLeft;
    }
    //获取食物y轴坐标的方法
    get Y() {
        return this.element.offsetTop;
    }
    //修改食物位置的方法
    change() {
        //生成随机位置
        //食物的最小位置是0 最大是290
        let left = Math.round(Math.random() * 29) * 10
        let top = Math.round(Math.random() * 29) * 10
        this.element.style.left = left   'px';
        this.element.style.top = top   'px';
    }
}
export default Food

代码分析:

由于在配置typescript时我们设置了strict(严格)模式,因此

  1. this.element = document.getElementById('food')!中如果我们不加!会让编译器不确定我们是否会获取到food的dom元素而发生报错
  2. 准备了get()方法可以在控制模块中随时获取food的具体定位
  3. change()方法为随机刷新一次food的位置
  4. export default Food 代码加在最后。为的是把food成为全局模块暴露出去,这样的话其他的模块可以调用这个food模块

完成ScorePanel(记分牌)类

//定义表示记分牌的类
class ScorePanel {
    score : number = 0;
    level : number = 1;
    scoreSpan :HTMLElement;
    levelEle : HTMLElement;
    //设置变量限制等级
    maxLevel : number;
    //设置一个变量多少分升级
    upScore : number;
    constructor(maxLevel : number = 10,Score : number = 10) {
        this.scoreSpan = document.getElementById('score')!;
        this.levelEle  = document.getElementById('level')!;
        this.maxLevel = maxLevel
        this.upScore = Score
    }
    //设置加分的方法
    AddScore() {
        this.score  ;
        this.scoreSpan.innerHTML = this.score   ''
        if (this.score % this.upScore === 0 ) {
            this.AddLevel()
        }
    }
    //提升等级
    AddLevel() {
        if (this.level < this.maxLevel) {
            this.levelEle.innerHTML =   this.level  ''
        }
    }
}
export default ScorePanel

代码分析:

在记分牌模块主要是两种方法AddScore()AddLevel(),分别用来控制分数增加和等级提升,重点也有设置一个变量来限制等级和设置变量来判断多少分上升一个等级

完成Snake(蛇)类

class Snake {
    //表示蛇头的元素
   head : HTMLElement;
   bodies : HTMLCollectionOf<HTMLElement>;
   //获取蛇的容器
    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(value) {
        if(this.X === value) {
            return;
        }
        if(value < 0 || value > 290) {
            throw new Error('蛇撞墙了!')
        }
        //修改x时,是在修改水平坐标,蛇在左右移动,蛇在向左移动时,不能向右掉头
        if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) {
            //如果发生的掉头,让蛇向反方向继续移动
            if(value > this.X) {
                //如果value大于旧值X,则说明蛇在向右走,此时应该发生掉头,应该使蛇继续向左走
                value = this.X - 10
            } else {
                value = this.X   10
            }
        }
        this.moveBody()
        this.head.style.left = value  'px'
        this.checkHeadBody()
    }
    set Y(value) {
        if(this.Y === value) {
            return;
        }
        if(value < 0 || value > 290) {
            throw new Error('蛇撞墙了!')
        }
        //修改Y时,是在修改水平坐标,蛇在上下移动,蛇在向上移动时,不能向下掉头
        if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) {
            //如果发生的掉头,让蛇向反方向继续移动
            if(value > this.Y) {
                //如果value大于旧值X,则说明蛇在向右走,此时应该发生掉头,应该使蛇继续向左走
                value = this.Y - 10
            } else {
                value = this.Y   10
            }
        }
        this.moveBody()
        this.head.style.top = value   'px'
        this.checkHeadBody()
    }
    //蛇增加身体的方法
    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';
        }
    }
    //检查蛇头是否撞到身体
    checkHeadBody() {
        //获取所有的身体,检查其是否和蛇头的坐标发生重叠
        for(let i =1;i<this.bodies.length;i  ) {
            if(this.X === this.bodies[i].offsetLeft && this.Y === this.bodies[i].offsetTop) {
                //进入判断说明蛇头撞到了身体,游戏结束
                throw new Error('撞到自己了')
            }
        }
    }
}
export default Snake

代码分析:

首先它自身只添加了三个功能函数addbody,movebody和checkHeadBody

movebody的实现逻辑非常的巧妙,根据从后往前的顺序来确定位置,根据前一节的位置,从而让后边的位置替换到前一节的位置上,从而实现蛇可以移动的逻辑。

为什么get,set,判断蛇是否死亡机制以及之后的蛇移动的代码一定要写在constructor()函数中而不是写在外面?

在后面还有一个控制模块中

首先利用get()方法获得蛇头坐标,当蛇头移动一次以后,立刻刷新后的蛇头坐标反馈给蛇对象

蛇这个对象更新以后constructor代码就会执行一遍,执行过程中首先蛇头的坐标用set()函数重新设置,然后蛇的movebody函数就会执行一次。最后对蛇进行判断死没死。

这样一次代码就执行完成啦。此时整条蛇都前进了一次。然后我们通过定时器定个时间不断让蛇移动就可以了。

完成GameControl(控制)类

import Food from "./food";
import Snake from "./Snake";
import ScorePanel from "./ScorePanel";
//游戏控制器,控制其他所有类
class GameControl {
    snake : Snake;
    food : Food;
    scorePanel : ScorePanel
    direction : string = '';
    //创建一个变量来判断游戏是否结束
    isLive : boolean = true;
    constructor() {
        this.snake = new Snake()
        this.food = new Food()
        this.scorePanel = new ScorePanel()
        this.init()
    }
    //游戏的初始化,调用后游戏将开始
    init() {
        document.addEventListener('keydown',this.keydownHandler.bind(this))
        //调用run
        this.run()
    }
    /*ArrowUp
     ArrowDown
     ArrowLeft
     ArrowRight
     */
    //创建一个键盘按下的响应函数
    keydownHandler(event: KeyboardEvent) {
        console.log(event.key)
        this.direction = event.key
    }
    //创建一个控制蛇移动的方法
    /*
    *   根据方向(this.direction)来使蛇位置发生改变
    *
    * */
    run() {
        let X = this.snake.X;
        let Y = this.snake.Y;
        //根据方向修改值
        switch (this.direction) {
            case 'ArrowUp':
            case 'Up':
                Y-=10;
                break;
            case 'ArrowDown':
            case 'Down':
                Y =10;
                break;
            case 'ArrowLeft':
            case 'Left':
                X -=10;
                break;
            case 'ArrowRight':
            case 'Right':
                X  = 10;
                break;
        }
        (this.checkEat(X,Y))
        try {
            //修改X和Y的值
            this.snake.X = X;
            this.snake.Y = Y;
        }catch (e) {
            //进入到catch出现异常
            alert((e as any).message   '游戏结束了,老表!');
            this.isLive = false;
        }
        this.isLive && setTimeout(this.run.bind(this),300 - (this.scorePanel.level-1)*30)
    }
    //定义方法检查蛇是否吃到食物
    checkEat(X:number,Y:number) {
        if (X === this.food.X && Y === this.food.Y) {
            //食物的位置要进行重置
            this.food.change()
            //分数增加
            this.scorePanel.AddScore()
            //蛇要增加一节
            this.snake.addBody()
        }
    }
}
export  default  GameControl

代码分析:

我们设置控制类主要目的在于整合之前的三个类,从而在这个类中调用之前声明的类,在该类中重点在于初始化游戏、控制蛇的移动、检查蛇是否吃到食物,这个类相当于一个总开关。

这里还有一个重点在于this指向问题,这里使用了bind()函数,bind最直接的定义就是将this指向到当前的对象。

完成index类(启动项目)

import './style/index.less'
import GameControl from './modules/GameControl'
new GameControl()

代码分析:

大家都非常清楚,想要让对象执行,我们必须要进行实例化,这里只用new一下进行调用即可,项目就可以执行了

项目启动

最后我们打开终端输入npm start或者npm run build,项目就跑起来了,它可以自动打开浏览器进行执行

总结

学习完了typescript,其实最主要的在于运用它实现面向对象的开发,我们在日常开发中基本不会用到面向对象,就算es6中涉及到类、接口等等,但是在实际中很少人去使用,面向对象的开发中其实使得项目变得更加的严谨和合理化,我们在书写代码的时候会更加的规范,ts的类型严格更加的使它方便大型项目开发!

到此这篇关于JavaScript TypeScript实现贪吃蛇游戏完整详细流程的文章就介绍到这了,更多相关JS TypeScript贪吃蛇内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

JavaScript TypeScript实现贪吃蛇游戏完整详细流程的更多相关文章

  1. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  2. HTML5数字输入仅接受整数的实现代码

    这篇文章主要介绍了HTML5数字输入仅接受整数的实现代码,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  3. amaze ui 的使用详细教程

    这篇文章主要介绍了amaze ui 的使用详细教程,本文通过多种方法给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  4. html5简介_动力节点Java学院整理

    这篇文章主要介绍了html5简介,用于指定构建网页的元素,这些元素中的大多数都用于描述网页内容,有兴趣的可以了解一下

  5. H5 canvas实现贪吃蛇小游戏

    本篇文章主要介绍了H5 canvas实现贪吃蛇小游戏,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  6. ios 8 Homescreen webapp,关闭和打开iPad停止javascript

    我有一个适用于iPad的全屏HTML5网络应用程序,并且刚刚安装了IOS8来试用它,它一切正常,直到你关闭并重新启动iPad.一旦web应用程序重新启动javascript就会停止并加载新页面不会重新启动它.在iPad上的Safari中打开同一页面时,关闭和打开iPad会继续按预期工作.其他人注意到了这个或想出了一个解决方案吗?解决方法这似乎是我在iOS8.1.1更新中解决的.

  7. iOS 6 javascript与object.defineProperty的间歇性问题

    当访问使用较新的Object.defineProperty语法定义属性的对象的属性时,有没有其他人注意到新iOS6javascript引擎中的间歇性错误/问题?https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/defineProperty我正在看到javascript失败的情况,说

  8. ios – 如何使用JSExport导出内部类的方法

    解决方法似乎没有办法将内部类函数导出到javascript.我将内部类移出并创建了独立的类,它起作用了.

  9. 静音iOS推送通知与React Native应用程序在后台

    我有一个ReactNative应用程序,我试图获得一个发送到JavaScript处理程序的静默iOS推送通知.我看到的行为是AppDelegate中的didReceiveRemoteNotification函数被调用,但是我的JavaScript中的处理程序不会被调用,除非应用程序在前台,或者最近才被关闭.我很困惑的事情显然是应用程序正在被唤醒,并且它的didReceiveRemoteNotifi

  10. ios – 内存泄漏与UIWebView和Javascript

    清楚地包含一个Javascript文件到我的HTML是使UIWebView泄漏内存.当我重复使用相同的UIWebView对象时,或者每当我有内容实例化一个新的漏洞时,会出现泄漏的事实,导致我认为必须有一些JavaScript文件被loadHTMLString处理,导致泄漏.有人知道如何解决这个问题吗?

随机推荐

  1. js中‘!.’是什么意思

  2. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  4. jquery点赞功能实现代码 点个赞吧!

    点赞功能很多地方都会出现,如何实现爱心点赞功能,这篇文章主要为大家详细介绍了jquery点赞功能实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. AngularJs上传前预览图片的实例代码

    使用AngularJs进行开发,在项目中,经常会遇到上传图片后,需在一旁预览图片内容,怎么实现这样的功能呢?今天小编给大家分享AugularJs上传前预览图片的实现代码,需要的朋友参考下吧

  6. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  7. jQuery中的通配符选择器使用总结

    通配符在控制input标签时相当好用,这里简单进行了jQuery中的通配符选择器使用总结,需要的朋友可以参考下

  8. javascript 动态调整图片尺寸实现代码

    在自己的网站上更新文章时一个比较常见的问题是:文章插图太宽,使整个网页都变形了。如果对每个插图都先进行缩放再插入的话,太麻烦了。

  9. jquery ajaxfileupload异步上传插件

    这篇文章主要为大家详细介绍了jquery ajaxfileupload异步上传插件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. React学习之受控组件与数据共享实例分析

    这篇文章主要介绍了React学习之受控组件与数据共享,结合实例形式分析了React受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部