TS的使用案例——贪吃蛇


TS思维:所有代码都写在类中

项目环境搭建

package.json

{
  "name": "webpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "start": "webpack server"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.18.6",
    "@babel/preset-env": "^7.18.6",
    "babel-loader": "^8.2.5",
    "clean-webpack-plugin": "^4.0.0",
    "core-js": "^3.23.4",
    "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.73.0",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^4.9.3"
  }
}

tsconfig.json

{
    "compilerOptions": {
        "module": "ES2015",
        "target": "ES6",
        "strict": true,
        "noEmitOnError": true
    } 
}

webpack.config.js

const path = require("path")
// 引入html插件
const HTMLWebpackPlugin = require("html-webpack-plugin")
const { options } = require("nodemon/lib/config")
const {CleanWebpackPlugin} = require("clean-webpack-plugin")

// webpack中的所有的配置信息都应该写在module . exports中
module.exports = {
    // 入口文件
    entry: "./src/index.ts",

    output: {
        // 指定打包文件所在的目录
        path: path.resolve(__dirname, 'dist'),
        // 打包后的文件名
        filename: "boundle.js",
        // 高速webpack不是用箭头函数
        environment: {
            arrowFunction:false
        }
    },
    mode: 'development', // 设置mode

    // 指定webpack打包是要使用的模块
    module: {
        // 指定加载规则
        rules: [
            {
                // test指定的是规则生效的文件(以.ts结尾的文件)
                test: /\.ts$/,
                // 要使用的loader(use从后向前执行,先执行ts-loader)
                use: [
                    // 配置babel-loader
                    {
                        // 指定加载器
                        loader: "babel-loader",
                        // 设置babel
                        options: {
                            // 设置预定义环境
                            presets: [
                                 [
                                    // 指定环境的插件
                                    "@babel/preset-env",
                                    // 配置信息
                                    {
                                        // 要兼容的目标浏览器
                                        targets:{
                                            // "chrome": "88",
                                            "ie":"11"
                                        },
                                        "corejs": "3",
                                        // s使用corejs方式“usage”表示按需加载
                                        "useBuiltIns":"usage"
                                    }
                                ]
                            ]
                        }
                    },
                    'ts-loader'
                ],
                // 编译要排除的文件,
                exclude:/node-modules/
            },
            {
                // 设置less文件的处理
                test: /\.less$/,
                use: [
                    "style-loader",
                    "css-loader",
                     // 引入postcss
                    {
                        loader: "postcss-loader",
                        options: {
                            postcssOptions: {
                                plugin: [
                                    "postcss-preset-env",
                                    {
                                        // 兼容浏览器的最新两个版本
                                        browsers:'last 2 versions'
                                    }
                                ]
                            }
                        }
                    },
                    "less-loader"
                ]

            }
        ]
    },

    // 配置webpack插件
    plugins: [
        new CleanWebpackPlugin(),
        // 自动生成相关文件并配置相关资源
        new HTMLWebpackPlugin({
           template:"./src/index.html"
        })
    ],

    // 配置置引用模块
    resolve: {
        // 声明.ts,.js结尾的文件为模块
        extensions:['.ts','.js']
    }
}

贪吃蛇页面

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>贪吃蛇</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:0</div>
            <div>level:1</div>
        </div>

    </div>
</body>
</html>

index.less

// 设置变量
@bg-color:#b7d4a8;

*{
    margin:0;
    padding: 0;

    // 设置盒子样式,width代表总宽度的
    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: black;
            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;
            transform: rotate(45deg);
        }
    }
}

#score-panel{
    width: 300px;
    display: flex;
    justify-content: space-between;
}

在这里插入图片描述

完成食物类

// 定义类
class Food{
    // 定义一个属性表示食物所对应的元素
    element: HTMLElement
    constructor() {
        // 获取页面的父元素并赋值给element
        this.element = document.getElementById("food")!;
    }
    // 获取食物的坐标
    get x() {
        return this.element.offsetLeft
    }
    get y() {
        return this.element.offsetTop
    }

    change() {
        // 随机数
        // 事物的位置 0-290
        // 设移动一次应该是10,所以食物的位置需要时10的倍数


        //  Math.round(Math.random() * 29):四舍五入取到0-29之间的数
        let top = Math.round(Math.random() * 29) * 10
        let left = Math.round(Math.random() * 29) * 10
        
        this.element.style.left = left+'px';
        this.element.style.top = top+'px'
    }
}

// const food = new Food();
// console.log(food.x, food.y)
// food.change()
// console.log(food.x, food.y)

export default Food

完成记分牌类

// 计分牌
class ScorePanel{
    score = 0;
    level = 1;
    scoreEle: HTMLElement;
    levelEle: HTMLElement;
    maxLevel: number;
    upScore:number//每几分升一级
    constructor(maxLevel:number=10,upScore:number=10) {
        this.scoreEle = document.getElementById("score")!
        this.levelEle = document.getElementById("level")!
        this.maxLevel = maxLevel
        this.upScore = upScore
    }
    // 加分方法
    addScore(){
        this.scoreEle.innerHTML = 'SCORE:'+ (++this.score)
        // 每10分升一级
        if (this.score % this.upScore === 0) {
            this.levelUp();
        }
    }

    // 等级提升
    levelUp() {
        if (this.level < this.maxLevel ) {
            this.levelEle.innerHTML = 'level:'+(++this.level)
        }
    }
}

// const scorePanel = new ScorePanel()
// scorePanel.addScore()
// console.log(scorePanel.score)

// for (let i = 0; i < 200; i++){
//     scorePanel.addScore();
// }

export default ScorePanel

snake类

class Snake{
    // 表示蛇的元素
    head: HTMLElement;
    bodies: HTMLCollection

    // 获取蛇的容器
    element:HTMLElement

    constructor() {
        this.head = document.querySelector('#snake>div')! as HTMLElement
        //  document.getElementById('snake')!.getElementsByTagName("div")这样获取的是一个集合
        this.bodies = document.getElementById('snake')!.getElementsByTagName("div")

        this.element=document.getElementById('snake')!

    }

    // 获取蛇(蛇头)的坐标

    get x() {
        return this.head.offsetLeft;
    }

    get y() {
        return this.head.offsetTop;
    }

    set x(value: number) {
        if (this.x == value) {
            return;
        }
        // 是否撞墙
        // x合法值 0-290
        if(value < 0 || value > 290) {
            throw new Error("蛇撞墙了")
        }

        // 蛇在左右移动,蛇在向左移动时,不能向右掉头,反之亦然
        if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) {
            // console.log("水平方向发生了掉头")
            // 蛇按原方向继续移动
            if (value > this.x) {
                // 想要向右走,说明现在是向左走,为了控制蛇不能掉头,所以继续设置他向左走
                value = this.x-10
            } else {
                value = this.x+10
            }

        }


        // 蛇的移动是通过后面的结点替代前面的结点,所以一定从后向前进行替换,所以先处理身体再处理蛇头
        // 身体移动
        this.moveBody()

        this.head.style.left = value + 'px'

        // 蛇头修改完成之后在判断是否撞到自己
        this.cheackHeadBody()
    }

    set y(value: number) {
        if (this.y == value) {
            return;
        }
        // 是否撞墙
        // y合法值 0-290
        if(value < 0 || value > 290) {
            throw new Error("蛇撞墙了")
        }

         // 蛇在上下移动,蛇在向上移动时,不能向下掉头,反之亦然
        if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) {
            // console.log("垂直方向发生了掉头")
            // 蛇按原方向继续移动
            if (value > this.y) {
                // 想要向下走,说明现在是向上走,为了控制蛇不能掉头,所以继续设置他向上走
                value = this.y-10
            } else {
                value = this.y+10
            }

        }

        // 身体移动
        this.moveBody()

        this.head.style.top = value + 'px'

        // 蛇头修改完成之后在判断是否撞到自己
        this.cheackHeadBody()

    }

    // 蛇增长
    addBody() {
        // beforeend是说把内容加到element结束标签之前
        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';
        }
    }

    cheackHeadBody() {
        // 获取所有的身体,检查其是否和蛇头的坐标发生重叠
        for(let i = 1; i < this.bodies.length; i++) {
            if (this.x === (this.bodies[i] as HTMLElement).offsetLeft && this.y === (this.bodies[i] as HTMLElement).offsetTop) {
                throw new Error("蛇撞到自己了")
            }
        }
    }

}

export default Snake

游戏控制类

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

// 游戏控制器
class GameController{

    snake: Snake
    food: Food
    scorePanel: ScorePanel
    // 创建属性来记录游戏是否结束
    isLive = true

    // 创建属性存储按键方向
    direction:string='Right'
    constructor() {
        this.snake = new Snake()
        this.food = new Food()
        // 可以自己设置等级,和几分升一级
        this.scorePanel = new ScorePanel(10,10)
        this.init()
        this.run()
    }

    // 游戏初始化
    init() {
        this.food.change()
        // 监听键盘按下事件
        // 这里有一个问题,回调函数keydownHander中的this是谁,在js中谁调用这个函数this就是谁,这里显然是document,但是我们希望是这个类中的this。所以可以使用bind函数进行this绑定
        // this.keydownHander.bind(this)表示将当前函数的this绑定给keydownHander
        document.addEventListener('keydown',this.keydownHander.bind(this))
    }

    keydownHander(event: KeyboardEvent) {
        /**
         * this.direction:
         * ArrowUp Up
         * ArrowDown Down
         * ArrowLeft Left
         * ArrowRight Right
        */
        // 检查方向是否合法

        this.direction = event.key
    }

    run() {
        // 获取蛇头位置
        let x = this.snake.x
        let y = this.snake.y
        
        /**
         * 根据this.direction来使蛇的位置改变
         * 向上 top减少
         * 向下 top增加
         * 向左 left减少
         * 向右 left增加
         */
        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 {
            this.snake.x = x
            this.snake.y = y
        } catch (e) {
            console.log(e)
            alert(e+",游戏结束!")
            this.isLive = false
        }
        

        // 由于函数嵌套的作用,每隔300ms调用一次run函数,就会一直移动
        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) {
            console.log("吃到食物")
            // 食物位置改变
            this.food.change()
            // 分数增加
            this.scorePanel.addScore();
            // 蛇长的增加
            this.snake.addBody()
       }
        
    }
}

export default GameController

入口

index.ts

// 引入样式(在ts中引入)
import './style/index.less'

import GameController from './moduls/GameController'
const gameController = new GameController()

实现效果:
在这里插入图片描述

源码获取:https://github.com/wangliyang-max/qiandaun/tree/master/TS/%E8%B4%AA%E5%90%83%E8%9B%87
运行方式:

npm i
npm start   
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值