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()的最后引用方法

相关推荐
缘友一世32 分钟前
深度学习系统学习系列【5】之深度学习基础(激活函数&损失函数&超参数)
人工智能·深度学习·学习
成都渲染101云渲染666637 分钟前
blender云渲染指南2025版
前端·javascript·网络·blender·maya
聆听+自律40 分钟前
css实现渐变色圆角边框,背景色自定义
前端·javascript·css
虾球xz42 分钟前
游戏引擎学习第260天:在性能分析器中实现钻取功能
网络·c++·学习·游戏引擎
行走__Wz1 小时前
计算机学习路线与编程语言选择(信息差)
java·开发语言·javascript·学习·编程语言选择·计算机学习路线
-代号95271 小时前
【JavaScript】二十九、垃圾回收 + 闭包 + 变量提升
开发语言·javascript·ecmascript
TUTO_TUTO1 小时前
【AWS+Wordpress】将本地 WordPress 网站部署到AWS
笔记·学习·云计算·aws
halo14162 小时前
vue中scss使用js的变量
javascript·vue3·scss
Mr.闻吉安2 小时前
什么是变量提升?
javascript·es6
huohuopro2 小时前
Vue3快速入门/Vue3基础速通
前端·javascript·vue.js·前端框架