TypeScript配置文件讲解和贪吃蛇小游戏实现

TypeScript introduce

在我们使用JS的时候,有很多痛点,比如它是一个动态类型语言,没有变量类型的定义,没有严格的书写规范和IDE的错误提示。TS是在JS的基础上,可以理解为有类型的JS,这也是名字type script的由来。但是实际运行TS代码的时候,还是需要转化成为js运行。

TypeScript config.json detail

{
  "include": ["./src/**/*"], //指定了文件的目录地址,ts会watch这个目录下的所有文件,并转化为js
  "compilerOptions": { //编译选项
    "target": "ES6", //编译目标语言,可以转化为不同的版本,默认是ES3
    "module": "ES6", 
    "outDir": "./dist", // 编译转化成为JS后存放的目录
    "strict": true,   //是否开启严格模式
    "allowJs": false, 
    "checkJs": false,
    "removeComments": false,  //编译的时候是否移除注释
    "noEmit": false, 
    "noEmitOnError": false,  //存在错误的时候不进行编译
    "alwaysStrict": true,   //始终保持严格模式
    "noImplicitAny": true,  //禁止使用隐式的any类型
    "noImplicitThis": true, //禁止使用隐式的this指针
    "strictNullChecks": true, 
  }
}

我们需要使用tsc命令完成TS -》 JS语言的转化,但是这样每次都得手动执行编译,太麻烦了。所以出现了webpack,它能帮我们自动进行编译。并且检测到代码更新重新编译。

TypeScript compile via webpack

package.json file

{
  "name": "ts-learn",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "start": "webpack-dev-server --open"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.21.8",
    "@babel/preset-env": "^7.21.5",
    "@types/node": "^20.1.7",
    "babel-loader": "^9.1.2",
    "core-js": "^3.30.2",
    "css-loader": "^6.7.3",
    "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.3.2",
    "style-loader": "^3.3.2",
    "ts-loader": "^9.4.2",
    "typescript": "^5.0.4",
    "webpack": "^5.82.1",
    "webpack-clean-plugin": "^0.2.3",
    "webpack-cli": "^5.1.1",
    "webpack-dev-server": "^4.15.0"
  }
}
​
const path = require('path'); //path是node js自带的path 包
const {nodeModules} = require("ts-loader/dist/constants"); 
const HTMLWebpackPlugin = require('html-webpack-plugin'); //可以自动将JS写入Html文件中运行在浏览器
const CleanWebPackPlugin= require('webpack-clean-plugin'); //可以在每次重新编译的时候清除上一次生成的文件
​
​
module.exports = {
    mode: "development", //运行模式,可以设置为development和production或者none
    entry: "./src/", //watch的文件的目录路径
    output: {
        path: path.resolve(__dirname, 'dist'), //编译后文件的目录
        filename: "bundle.js", //编译后文件的输出名称
​
    },
    module: {
        rules: [{
            test: /\.ts$/, //选择文件的规则
            use: [{
                loader: "babel-loader", //babel可以将代码转化为不同的版本以支持不同的浏览器,
                options: {
                    presets:[
                        [
                            "@babel/preset-env", //为了支持不同的浏览器进行的配置
                            {
                                targets: {  //浏览器类型
                                    "chrome":"88",
                                    "ie": "11"
                                },
                                "corejs":"3", //提供一种node js运行环境
                                "useBuiltIns": "usage", //按需build
                            },
                        ]
                    ]
                }
            },'ts-loader'], //加载器,在下面的加载器会被优先执行
            exclude: /nodeModules/, //不编译的文件目录
        },
            {
                test: /\.less$/, //对于style的文件编译
                use: [
                    "style-loader", //加载器
                    "css-loader", //加载器
                    {
                        loader: "postcss-loader", //和babel同理,使得样式适应浏览器的
                        options: {
                            plugins:[
                                [
                                    "postcss-preset-env",
                                    {
                                        browsers:'last 2 versions'
                                    }
                                ]
                            ]
                        }
                    },
                    "less-loader"
                ]
            }
        ],
    },
​
    plugins: [
        new CleanWebPackPlugin(),
        new HTMLWebpackPlugin({
            template: "./src/index.html" 编译后的js文件可以按照这个html模板生成html文件
        })
],
    resolve: {
        extensions: ['.ts', '.js']
    }
​
}
​

TypeScript class attribute and Encapsulation and Inheritance

TS具备其他面向对象语言的各种特性, 封装继承

interface animal_interface{
    _name : string
    _age : number
}
​
class animal implements animal_interface{
     _name : string
     _age : number
    constructor(name: string, age : number) {
        this._name = name
        this._age = age
    }
    getName(){
        return this._name
    }
    setName(name: string){
        this._name = name
    }
    getAge(){
        return this._age
    }
    setAge(age: number){
        this._age = age
    }
    Animal_Sounds(bark: string){
        console.log(bark)
    }
}
​
class Dog extends animal{
    run: string
    constructor(name : string, age: number, run : string) {
        super(name, age);
        this.run = run
    }
}
​
let dog = new Dog('小黄', 3 , "he is running")
console.log(dog.getAge());
dog.Animal_Sounds('汪汪汪汪!')
console.log(dog.getName());

TypeScript abstract class

抽象类就是只有方法不能实例,定义的时候需要增加abstract关键字。

abstract class cat{
    face : string
    mouth : string
    beard : number
    constructor(face: string, mouth : string, beard: number) {
        this.face = face
        this.mouth = mouth
        this.beard = beard
​
    }
    cat_food(food_kinds : string){
        if (food_kinds === 'aikendi'){
            console.log("only" + this.face + "can eat" + food_kinds)
        }
    }
}
​
class toy_cat extends cat{
}
let little_cat = new toy_cat("black", "little", 6)
little_cat.cat_food("aikendi")

TypeScript super keyword

super的用法比较简单,我们可以把super认为是父类的一个实例,当我们想要使用父类的某些函数的时候,直接使用关键字super就可以了。最常用的地方在于,当我们在子类中重写构造函数的时候,Type Script要求我们调用父类的构造函数才能实现重写。这时候就可以使用super()

constructor(name : string, age: number, run : string) {
    super(name, age);
    this.run = run
}

TypeScript interface

接口定义的形式和一个object的形式基本一致,但是接口需要声明关键字interface,接口和object的区别在于接口是可以作为限制类实现的工具被class实现的。类要实现接口的话需要使用关键字implements, 类中只能定义接口中存在的数据对象和类型。不能多也不能少。

interface animal_interface{
    _name : string
    _age : number
}
​
class animal implements animal_interface{
     _name : string
     _age : number

TypeScript practice

贪吃蛇设计整体思路以及要求

  1. 贪吃蛇场景的布局

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>zhao hansi-Gluttonous Snake</title> //标题
</head>
<body>
<div id ="main"> //主场景
    <div id = "stage"> //游戏场景
        <div id = "snake" > //蛇的主体
            <div></div>
        </div>
        <div id ="food"> //食物的主体
            <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>
  1. 贪吃蛇食物的相关设置

对于食物而言,我们可以创建食物类来存储食物相关的属性和功能。

class Food {
    element: HTMLElement; //食物
​
    constructor() {
        this.element = document.getElementById('food')!;
    }
​
    get_food_X(){
        return this.element.offsetLeft; //食物的横坐标
    }
    get_food_Y(){
        return this.element.offsetTop; //食物的纵坐标
    }
​
    change_food_position(){  //食物随机出现位置的函数
        let X = Math.round(Math.random() * 29) * 10 ;
        let Y =Math.round(Math.random() * 29) * 10 ;
        this.element.style.left = X + 'px';
        this.element.style.top = Y + 'px';
    }
​
}
​
export default Food;
​
  1. 贪吃蛇级别和分数的设定

class ScorePanel{
    score: number = 0; //分数
    level: number = 1; //等级
    scoreEle: HTMLElement; //分数按钮
    levelEle: HTMLElement; //等级按钮
    maxLevel: number; //最大等级
    upScore : number  //升级分数
​
    constructor(maxLevel: number =10, upScore: number = 1) { //初始化构造函数
        this.scoreEle = document.getElementById('score')!;
        this.levelEle = document.getElementById('level')!;
        this.maxLevel = maxLevel;
        this.upScore = upScore;
​
    }
​
    add_score(){ //加分函数,当分数/升级分数余数为0的时候,升级
        this.scoreEle.innerHTML = ++ this.score + '';
        if (this.score % this.upScore ===0){
            this.level_up()
        }
    }
​
    level_up(){ //升级函数
        if(this.level < this.maxLevel){
            this.levelEle.innerHTML = ++ this.level + '';
        }
    }
​
}
​
export default ScorePanel;
  1. 贪吃蛇自身的相关设定

class Snake{
    snake_head: HTMLElement; //蛇头
    bodies: HTMLCollection; //蛇身体
    element: HTMLElement; //初始化蛇
    constructor() { //构造函数
        this.element = document.getElementById('snake')!;
        this.snake_head = document.querySelector(('#snake > div')) as HTMLElement;
        this.bodies = this.element.getElementsByTagName('div');
    }
    get_X(){ //获取蛇横坐标
        return this.snake_head.offsetLeft;
    }
    get_Y(){ //获取蛇纵坐标
        return this.snake_head.offsetTop;
    }
    set_X(value: number){ //设置横坐标
        if (this.get_X() === value){ //横坐标和当前的值一样就不重复设置
            return;
        }
        if (value < 0 || value > 290){ //设置横坐标范围
            throw new Error("This snake kit the wall, Game Over!"); //超出后直接抛出异常,并提示
        }
        if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value){ //判断蛇是不是反方向移动,禁止这种操作
            if(value > this.get_X()){
                value = this.get_X() - 10;
            }else {
                value = this.get_X() + 10;
            }
        }
        this.moveBodies(); 
        this.snake_head.style.left = value + 'px';
        this.checkHeadBody();
    }
    set_Y(value: number){
        if (this.get_Y() === value){
            return;
        }
        if (value < 0 || value > 290){
            throw new Error("This snake hit the wall, Game Over!");
        }
        if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value){
            if(value > this.get_Y()){
                value = this.get_Y() - 10;
            }else {
                value = this.get_Y() + 10;
            }
        }
        this.moveBodies();
        this.snake_head.style.top = value + 'px';
        this.checkHeadBody();
    }
    addBodies(){ //增加蛇身体的函数
        this.element.insertAdjacentHTML('beforeend', "<div></div>");
​
    }
​
    moveBodies(){ //蛇身体移动的函数,遍历整个蛇的身体,让后面一节的坐标等于前面一个
        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++){
            let bd = this.bodies[i] as HTMLElement;
            if( this.get_X() === bd.offsetLeft && this.get_Y() ===bd.offsetTop){
                throw new Error("This snake eat itself !!!")
            }
        }
    }
​
​
}
​
export default Snake;
  1. 游戏控制相关设定

import Food from "./Food";
import ScorePanel from "./ScorePanel";
import Snake from "./Snake";
import snake from "./Snake";
class GameControl{
    snake: Snake; //蛇类的对象
    food: Food; //食物类的对象
    score: ScorePanel; //记分类的对象
    direction: string = ''; //初始位置
    isLive: boolean = true; //蛇是否存活的状态标记,控制游戏结束
​
    constructor() {
        this.snake = new Snake();
        this.food = new Food();
        this.score = new ScorePanel();
        this.init()
    }
​
    init(){
        document.addEventListener('keydown',this.keyDownHandler.bind(this) ); //初始化接受键盘事件和蛇位移
        this.run_snake();
    }
    keyDownHandler(event: KeyboardEvent){
        if (event.key === 'ArrowUp' || event.key === 'ArrowDown' || event.key === 'ArrowLeft' || event.key === 'ArrowRight') {//只接受方向键
            this.direction = event.key;
        }
    }
​
    run_snake(){ //按不同的方向键蛇向不同的方向位移
        let X = this.snake.get_X();
        let Y = this.snake.get_Y();
        switch (this.direction){
            case "ArrowUp":
                Y -= 10;
                break;
            case "ArrowDown":
                Y += 10;
                break;
            case "ArrowLeft":
                X -= 10;
                break;
            case "ArrowRight":
                X += 10;
                break;
        }
        this.checkEat(X, Y);
        try{
            this.snake.set_X(X);
            this.snake.set_Y(Y);
        }catch (e: any){ //捕获异常并抛出错误
            alert(e.message);
            this.isLive = false;
        }
​
        this.isLive && setTimeout(this.run_snake.bind(this), 300 - (this.score.level -1) * 30)
    }
​
    checkEat(X: number, Y: number){ //当蛇头的位置和食物的位置重叠则吃到食物,食物更改位置,蛇增加身体,得分
        if (X === this.food.get_food_X() && Y === this.food.get_food_Y()){
            this.food.change_food_position();
            this.score.add_score();
            this.snake.addBodies();
        }
    }
}
​
export default GameControl;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值