TypeScript学习案例(1)——贪吃蛇

预览:

第一章:项目搭建

1、创建一个名为 practice 文件夹

2、配置文件

2.1 package.json

javascript 复制代码
{
  "name": "practice",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "start": "webpack serve --open chrome.exe"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.27.1",
    "@babel/preset-env": "^7.27.1",
    "babel-loader": "^8.4.1",
    "clean-webpack-plugin": "^3.0.0",
    "core-js": "^3.42.0",
    "html-webpack-plugin": "^4.5.0",
    "ts-loader": "^8.4.0",
    "typescript": "^4.9.5",
    "webpack": "^5.99.7",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^3.11.3"
  }
}

2.2 tsconfig.json

javascript 复制代码
{
  "compilerOptions": {
    "module": "ES2015",
    "target": "ES2015",
    "strict": true,
    "noEmitOnError": false
  }
}

2.3 webpack.config.js

javascript 复制代码
// 引入一个包
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不使用箭头
        environment:{
            arrowFunction: false
        }
    },

    // 指定webpack打包时要使用模块
    module: {
        // 指定要加载的规则
        rules: [
            {
                // test指定的是规则生效的文件
                test: /\.ts$/,
                // 要使用的loader
                use: [
                     // 配置babel
                     {
                         // 指定加载器
                         loader:"babel-loader",
                         // 设置babel
                         options: {
                             // 设置预定义的环境
                             presets:[
                                 [
                                     // 指定环境的插件
                                     "@babel/preset-env",
                                     // 配置信息
                                     {
                                         // 要兼容的目标浏览器
                                         targets:{
                                             "chrome":"58",
                                             "ie":"11"
                                         },
                                         // 指定corejs的版本
                                         "corejs":"3",
                                         // 使用corejs的方式 "usage" 表示按需加载
                                         "useBuiltIns":"usage"
                                     }
                                 ]
                             ]
                         }
                     },
                    'ts-loader'
                ],
                // 要排除的文件
                exclude: /node-modules/
            }
        ]
    },

    // 配置Webpack插件
    plugins: [
        new CleanWebpackPlugin(),
        new HTMLWebpackPlugin({
            // title: "这是一个自定义的title"
            template: "./src/index.html"
        }),
    ],

    // 用来设置引用模块
    resolve: {
        extensions: ['.ts', '.js']
    }

};

3、下载依赖:在终端输入 npm i

4、配置less依赖

  1. 在终端输入 npm i -D less less-loader css-loader style-loader
  2. 更改 webpack.config.js 配置文件的 module 下的 rules 后增加以下内容
javascript 复制代码
//设置less文件处理
            {
                test: /\.less$/,
                use: [
                    "style-loader",
                    "css-loader",
                    "less-loader"
                ]

            }

5、配置 postcss 依赖

  1. npm i -D postcss postcss-loader postcss-preset-env
  2. 更改 webpack.config.js 配置文件,在设置 less 文件处理中的 "css-loader", 后加入
javascript 复制代码
//引入postcss
                    {
                        loader:"postcss-loader",
                        options:{
                            postcssOptions:{
                                plugins:[
                                    ["postcss-preset-env", {
                                        browsers: "last 2 versions"
                                    }]
                                ]
                            }
                        }
                    },

6、打包: npm run build

第二章:项目界面

1、 index.html

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <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:<span id="score">0</span>
            </div>
            <div>
                LEVEL:<span id="level">1</span>
            </div>
        </div>
     </div>
</body>
</html>

2、 index.less(记得在index.ts内引入样式)

css 复制代码
//设置变量
@bg-color: #b7d4a8;

// 清除默认样式
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body{
    font: bold 20px "Courier New";
}

// 设置主窗口样式
#main {
    width: 360px;
    height: 420px;
    background-color: @bg-color;
    margin: 100px auto;
    border: 10px solid black;
    border-radius: 20px;
    // 设置主窗口的子元素为弹性布局
    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;
        flex-wrap: wrap;
        justify-content: space-between;
        align-content: space-between;
        &>div {
            width: 4px;
            height: 4px;
            background-color: #555;
        }
    }
}

// 设置游戏得分样式
#score-panel {
    width: 304px;
    display: flex;
    justify-content: space-between;
}

3、效果

第三章:完成类的定义

1、创建 moduls 文件夹,用于装不同的类

2、完成 Food 类

typescript 复制代码
//定义食物链Food
class Food {
    // 定义一个属性表示食物所对应的食物
    element : HTMLElement

    constructor() {
        this.element = document.getElementById("food")!
    }

    // 定义一个方法,获取食物的坐标
    get X() {
        return this.element.offsetLeft
    }

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

    // 修改食物的位置
    change() {
        // 生成一个随机位置
        // 食物的位置最小是0,最大是290
        // 蛇移动一次就是一格,一格的大小是10,所以食物的坐标必须是10的倍数
        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

3、完成 ScorePanel 类

typescript 复制代码
// 表示记分牌的类
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 = ++this.score + ""
        // 判断分数是多少
        if (this.score % this.upScore === 0) {
            this.levelUp()
        }
    }

    // 定义一个方法,显示等级
    levelUp() {
        if (this.level < this.maxLevel) {
            this.levelEle.innerHTML = ++this.level + ""
        }
    }
}

//测试代码
// const scorePanel = new ScorePanel()
// scorePanel.addScore()
// scorePanel.addScore()
// scorePanel.addScore()
// scorePanel.addScore()
// console.log(scorePanel.score)

export default ScorePanel

4、初步完成 Snake 类

typescript 复制代码
class Snake {
    // 定义一个属性表示蛇的头部
    head : HTMLElement

    // 定义一个属性表示蛇的身体(包括蛇头)
    bodies : HTMLCollection

    // 获取蛇的容器
    element : HTMLElement

    constructor() {
        // querySelector 只会取一个 就取的是第一个
        this.head = document.querySelector("#snake > div") as HTMLElement;
        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) {
    	// 如果蛇的坐标和value相等,则不进行设置
        if(this.X === value) {
            return
        }
        this.head.style.left = value + "px"
    }  

    set Y(value : number) {
    	// 如果蛇的坐标和value相等,则不进行设置
        if(this.Y === value) {
            return
        }
        this.head.style.top = value + "px"
    }

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

export default Snake

第四章:游戏控制器

1、创建 GameControl.ts

2、GameControl 键盘控制

typescript 复制代码
import Snake from "./Snake";
import Food from "./Food";
import ScorePanel from "./ScorePanel";

//游戏控制器,控制其他所有类
class GameControl {
    // 定义三个属性
    snake : Snake
    food : Food
    scorePanel : ScorePanel

    // 创建一个属性来存储蛇的移动方向(默认是向右移动)
    direction : string = "Right"

    constructor() {
        this.snake = new Snake()
        this.food = new Food()
        this.scorePanel = new ScorePanel()
        this.init()
    }

    // 创建一个方法来初始化游戏
    init() {
        // 绑定键盘按下的事件
        document.addEventListener("keydown", this.keydownHandler.bind(this))
    }

    /*
        ArrowUp Up w
        ArrowDown Down s
        ArrowLeft Left a
        ArrowRight Right d
    */

    // 创建一个键盘按下的响应函数
    keydownHandler(event : KeyboardEvent) {
        this.direction = event.key
    }
    
}

export default GameControl

3、GameControl 使蛇移动

typescript 复制代码
    // 创建一个属性用来记录游戏是否结束
    isLive : boolean = true
	// 创建一个控制蛇移动的方法
    run() {
        //  获取蛇现在的坐标
        let X = this.snake.X
        let Y = this.snake.Y

        // 根据方向来移动
        switch (this.direction) {
            case "ArrowUp":
            case "Up":
            case "w":
                Y -= 10;
                break;
            case "ArrowDown":
            case "Down":
            case "s":
                Y += 10;
                break;
            case "ArrowLeft":
            case "Left":
            case "a":
                X -= 10;
                break;
            case "ArrowRight":
            case "Right":
            case "d":
                X += 10;
                break;
        }

        this.snake.X = X
        this.snake.Y = Y

        // 设置定时调用
        this.isLive && setTimeout(this.run.bind(this), 300 - (this.scorePanel.level - 1) * 30)
    }

4、蛇撞墙和吃食检测

4.1 蛇撞墙

4.1.1 在 Snack 类的设置蛇的坐标内判断并的抛出异常

typescript 复制代码
	//设置蛇的坐标
    set X(value : number) {
        // 如果蛇的坐标和value相等,则不进行设置
        if(this.X === value) {
            return
        }
        // x 的值的合法范围是 0-290
        if(value < 0 || value > 290) {
            throw new Error("撞墙了")
        }
        this.head.style.left = value + "px"
    }  

    set Y(value : number) {
        // 如果蛇的坐标和value相等,则不进行设置
        if(this.Y === value) {
            return
        }
        // y 的值的合法范围是 0-290
        if(value < 0 || value > 290) {
            throw new Error("撞墙了")
        }
        this.head.style.top = value + "px"
    }

4.1.2 在 GameControl 类内捕获异常

typescript 复制代码
			this.snake.X = X
            this.snake.Y = Y

修改为

typescript 复制代码
// 检查蛇是否撞墙
        try {
            this.snake.X = X
            this.snake.Y = Y
        } catch (e) {
            // 如果蛇撞墙了,则游戏结束
            alert(e.message)
            this.isLive = false
        }

4.2 蛇吃食

4.2.1 在检查蛇是否撞墙之前检查蛇是否吃到食物

typescript 复制代码
		// 检查蛇是否吃到食物
        this.checkEat(X, Y)

4.2.2 在 GameControl 内定义方法

typescript 复制代码
	// 定义一个方法,用来检查蛇是否撞墙
    checkEat(X : number, Y : number) {
        if(X === this.food.X && Y === this.food.Y) {
            this.snake.addBody()
            this.food.change()
            this.scorePanel.addScore()
        }   
    }

5、蛇的身体移动

5.1 在 Snack 内添加一个方法

typescript 复制代码
// 添加一个蛇身体移动的方法
    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';
        }
    }

5.2 在 set X()和 set Y()内引用 moveBody 方法并处理掉头

typescript 复制代码
//设置蛇的坐标
    set X(value : number) {
        // 如果蛇的坐标和value相等,则不进行设置
        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) {
            // 如果发生掉头,让蛇反方向继续移动
            if(value > this.X) {
                //如果新值value大于旧值,则说明蛇在向右走,此时应该发生掉头,应该使蛇向左走
                value = this.X - 10
            } else {
                value = this.X + 10
            }
        }


        this.moveBody()
        this.head.style.left = value + "px"
    }  

    set Y(value : number) {
        // 如果蛇的坐标和value相等,则不进行设置
        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) {
            // 如果发生掉头,让蛇反方向继续移动
            if(value > this.Y) {
                //如果新值value大于旧值,则说明蛇在向下走,此时应该发生掉头,应该使蛇向左走
                value = this.Y - 10
            } else {
                value = this.Y + 10
            }
        }

        this.moveBody()
        this.head.style.top = value + "px"
    }

6、 检查碰撞自己

6.1 定义一个 checkBody 方法

typescript 复制代码
// 检查头和身体相撞
    checkBody() {
        for(let i = 1;i<this.bodies.length;i++) {
            let bd = this.bodies[i] as HTMLElement;
            if(this.X === bd.offsetLeft && this.Y === bd.offsetTop) {
                throw new Error("撞到自己了")
            }
        }
    }

6.2 在set X()和 set Y()的最后引用方法

相关推荐
老毛肚5 小时前
jeecg-boot-base-core 02 day
javascript·python
袁小皮皮不皮8 小时前
1.HCIP BFD 学习笔记(优化版)
服务器·网络·笔记·网络协议·学习·智能路由器·ip
装不满的克莱因瓶8 小时前
【自动驾驶领域】学习 Cityscapes 数据集——城市街景语义理解的标准基准
人工智能·pytorch·python·深度学习·学习·机器学习·自动驾驶
清辞8539 小时前
产品经理需求推进流程
大数据·深度学习·学习·产品经理
烬羽10 小时前
后端返回的 JSON 字符串,浏览器怎么"看懂"的?——Ajax 全链路拆解
javascript
YM52e10 小时前
鸿蒙PC ArkTS 声明合并问题深度解析与最佳实践
学习·华为·harmonyos·鸿蒙·鸿蒙系统
半个落月11 小时前
一个新手用 Bun + Axios 调通 DeepSeek API 的实践记录
javascript
不好听61311 小时前
深入理解链表:线性数据结构的另一面
javascript·数据结构
林希_Rachel_傻希希11 小时前
学React治好了我的焦虑症,1小时速通React 前20分钟。
前端·javascript·面试
小林ixn11 小时前
从 Ajax 到异步编程:JSON 序列化、Event Loop 与 XHR 请求完全解析
javascript