首页 前端知识 typescript学习记录-练习项目-贪食蛇

typescript学习记录-练习项目-贪食蛇

2024-10-13 19:10:12 前端知识 前端哥 643 116 我要收藏

参考文章:https://www.bilibili.com/video/BV1Xy4y1v7S2?p=22

项目搭建

将之前的package.json,tsconfig.json,webpack.config.js复制到新项目中,同时新建src跟test目录。
然后修改package.json中的name,修改成现在的项目名称。
然后使用npm i自动下载项目所需的包,或者直接使用图形化命令下载也行。
在这里插入图片描述

在这里插入图片描述
搭建完后测试一下是否正常。在src下新建main.ts与main.html,随意在main.ts中写一些代码。
写完后使用npm run build 来构建一下。正常完成后会构建一个dict文件夹。

配置less

使用npm i -D less less-loader css-loader style-loader下载。然后修改webpack.config.js文件。
在这里插入图片描述
添加的配置信息

            { // 设置less文件处理
                test: /\.less$/, // 指定规则生效的文件:以less结尾的文件
                use:[   // 加载器从后往前执行
                    "style-loader",
                    "css-loader",
                    "less-loader"
                ],
                exclude: /node-modules/ // 要排除的文件
            }

测试

创建一个less文件,随意写些内容,然后再ts中引入。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
使用npm run build 后页面变成绿色了,这代表less的代码执行成功。

css的兼容性处理

跟ts一样,css也要考虑兼容性问题。使用npm i -D postcss postcss-loader postcss-preset-env下载相关构建。然后修改webpack.config.js文件。

                    {  // 引入postcss
                        loader: "postcss-loader",
                        options: {  // 设定参数
                            postcssOptions:{
                                plugins:[  // 设置预定义的环境
                                    [
                                        "postcss-preset-env",  // 指定环境插件
                                        {  // 配置信息
                                            browsers: "last 2 versions" // 兼容所有主流浏览器的最新的版本数
                                        }
                                    ]
                                ]
                            }
                        }
                    },

最终webpack.config.js中的less配置信息如下:

            { // 设置less文件处理
                test: /\.less$/, // 指定规则生效的文件:以less结尾的文件
                use:[   // 加载器从后往前执行
                    "style-loader",
                    "css-loader",
                    {  // 引入postcss
                        loader: "postcss-loader",
                        options: {  // 设定参数
                            postcssOptions:{
                                plugins:[  // 设置预定义的环境
                                    [
                                        "postcss-preset-env",  // 指定环境插件
                                        {  // 配置信息
                                            browsers: "last 2 versions" // 兼容所有主流浏览器的最新的版本数
                                        }
                                    ]
                                ]
                            }
                        }
                    },
                    "less-loader"
                ],
                exclude: /node-modules/ // 要排除的文件
            }

项目界面

<!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 id="score-panel">
        <div>
            SCORE:<span id="score">0</span>
        </div>
        <div>
            level:<span id="level">1</span>
        </div>

    </div>

</div>

</body>
</html>

设置样式

body{
  background-color: #1E90FF;
  display: flex;
  font:bold 20px "Courier New"; // 统一设置字体
}

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

// 清除默认样式
*{
  margin: 0;
  padding: 0;
  // 改变盒子模型的计算方式, 方便自动计算内容器大小
  box-sizing: border-box;
}

// 设置主窗口的样式
#main{
  width: 360px;
  height: 420px;
  background-color: @bg-color; // 设置主容器背景颜色
  margin: 100px auto; // 位置下移100像素,水平自动居中
  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;
      border: 1px solid @bg-color;
      position: absolute; // 开启绝对定位
      display: flex;  // 开启弹性盒模型
      flex-flow: row wrap;  // row设置横轴为主轴, wrap表示会自动换行
      justify-content: space-between;  // 设置主轴和侧轴的空白空间分配到元素之间
      align-content: space-between;

      //left: 40px;
      //top: 100px;

      &>div{
        width: 4px;
        height: 4px;
        background-color: black;
        transform: rotate(45deg); // 设置旋转45度
      }
    }

  //游戏记分牌
  #score-panel{
    width: 300px;
    display: flex;
    justify-content: space-between;  // 设置主轴对齐方式
  }

}

运行逻辑

完成FOOD类

新建一个Food.ts来存放该类

// 定义食物类FOOD
class Food{
    // 定义一个属性表示食物所对应的元素
    element: HTMLElement
    stage

    constructor() {
        // 获取页面中food的元素并将其赋值给element
        this.element = document.getElementById("food")! // !强制表示没有问题,不使用则提示可能找不到该页面元素
        this.stage = document.getElementById("stage")!
        // this.stage = document.evaluate('/html/body/div[@id="main"]/div[@id="stage"]', document, null, XPathResult.ANY_TYPE, null)
    }

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

    // 定义一个获取食物Y的方法
    get Y(){
        return this.element.offsetTop
    }

    // 修改食物的位置
    change(){
        // 生成一个随机位置
        // 食物的位置最小是0,最大是290
        // 蛇移动一次是一格,一格大小是10,所以食物的位置是10的倍数

        const high = (Math.floor(this.stage.offsetHeight/10)*10 - 10)/10
        const width= (Math.floor(this.stage.offsetWidth/10)*10 - 10)/10

        let x = Math.round(Math.random() * width) * 10
        let y = Math.round(Math.random() * high) * 10

        this.element.style.left = x + "px"
        this.element.style.top =  y + "px"
    }
}

// 默认模块暴露出去
export default Food

完成ScorePanel类

新建一个ScorePanel.ts来存放该类


// 定义表示计分牌的类
class ScorePanel{
    // #score和level用来记录分数和等级
    #score:number = 0 // 私有属性在前面加#
    level:number=1
    // #score_ele和level_ele分数很等级所在的元素,在构造中初始化
    #score_ele:HTMLElement
    level_ele:HTMLElement

    // 设置变量来限制等级
    max_level:number
    // 设置一个变量表示升级分数
    up_score:number

    constructor(max_level:number = 10, up_score:number = 10) {
        this.#score_ele = document.getElementById("score")!
        this.level_ele = document.getElementById("level")!
        this.max_level = max_level
        this.up_score = up_score
    }

    //设置一个加分的方法
    addScore(){
        // 使分数自加
        this.#score_ele.innerHTML = ++this.#score + ''
        // 判断分数是多少
        if(this.#score % this.up_score === 0){
            this.levelUp()
        }
    }

    // 提升等级的方法
    levelUp(){
        if (this.level < this.max_level){
            this.level_ele.innerHTML = ++this.level + ''
        }

    }
}

// 测试代码
const sp = new ScorePanel()
for (let i=0;i<10;i++){
    sp.addScore()
}

export default ScorePanel

完成Snake类

蛇的相关属性方法

class Snake{
    // 表示蛇头的元素
    head:HTMLElement
    // 蛇的圣体
    bodis:HTMLCollectionBase
    // 蛇的容器
    snake:HTMLElement
    // 舞台对象
    stage:HTMLElement

    stage_width:number
    stage_high:number

    is_reback_way:boolean = false
    constructor() {
        this.snake = document.getElementById("snake")!
        this.head = document.querySelector('#snake > div') as HTMLElement // as强制转化类型
        // document.querySelectorAll('#snake > div') // 数组是固定死的
        // this.bodis = document.getElementById("snake")!.getElementsByTagName("div")
        this.bodis = this.snake.getElementsByTagName("div")

        this.stage = document.getElementById("stage")!

        this.stage_width= (Math.floor(this.stage.offsetWidth/10)*10 - 10) // 舞台的横向最大值
        this.stage_high = (Math.floor(this.stage.offsetHeight/10)*10 - 10)  // 舞台的纵向最大值
    }

    // 获取蛇的坐标(蛇头坐标)
    get X(){
        return this.head.offsetLeft
    }

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

    // 设置坐标
    set X(value){
        if(this.X === value){  // 如果值相同,则不再修改
            return
        }

        if (value < 0 || value > this.stage_width){
            // 进入判断则说明蛇撞墙了,抛出一个异常
            throw new Error('蛇撞墙了')
        }




        // 没有使用教程的方法
        // 修改x时,是在修改水平坐标,蛇在左右移动,在向左移动时候,不能向右掉头,反之亦然。
        // if(this.bodis[1] && (this.bodis[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 > this.stage_high){
            // 进入判断则说明蛇撞墙了,抛出一个异常
            throw new Error('蛇撞墙了')
        }

        this.moveBody()
        this.head.style.top = value + "px"
        // 检查是否撞到自己
        this.checkHeadBody()
    }



    // 设置蛇增加身体方法
    addBody(){
        // 向snake添加一个div
        this.snake.insertAdjacentHTML("beforeend","<div></div>")
    }

    // 添加蛇身体移动方法
    moveBody(){
        // 将后面的身体设置为前边身体的位置,遍历获取所有的身体
        for (let i=this.bodis.length-1; i > 0; i--){
            // 获取前一个身体的位置
            let x = (this.bodis[i - 1] as HTMLElement).offsetLeft;
            let y = (this.bodis[i - 1] as HTMLElement).offsetTop;

            // 将值设置到当前身体的位置
            (this.bodis[i] as HTMLElement).style.left = x + 'px';
            (this.bodis[i] as HTMLElement).style.top = y + 'px';
        }

    }

    checkHeadBody(){
        // 获取所有身体,检查是否和蛇头坐标是否发生重叠
        for( let i = 1; i < this.bodis.length; i++){
            let tmp = this.bodis[i] as HTMLElement
            if ( this.X === tmp.offsetLeft && this.Y === tmp.offsetTop){
                // 进入判断说明蛇头撞到身体
                throw new Error('蛇撞自己')
            }
        }
    }
}


export default Snake

GameContro键盘控制器

// 引入其他的类
import Snake from "./Snake";
import Food from "./Food";
import ScorePanel from "./ScorePanel";

// 游戏控制器,控制其他的所有类
class GameContro{
    // 定义3个属性
    snake:Snake
    food:Food
    score_panele:ScorePanel

    // 存储蛇的移动方向,即按键的方向
    direction: string = ""

    // 记录游戏退出机制
    is_live = true

    constructor() {
        this.snake= new Snake()
        this.food = new Food()
        this.score_panele = new ScorePanel()

        this.init()
    }

    // 游戏初始化方法
    init(){
        // 绑定键盘按键按下事件
        document.addEventListener("keydown", this.keyDownHandler.bind(this)) // 绑定this的对象为GameContro
        // 调用run方法,来使移动
        this.run()
    }

    // 创建一个键盘的响应函数
    keyDownHandler(event:KeyboardEvent){
        let up_key = ["ArrowUp", "Up", "w", "W"]
        let down_key = ["ArrowDown", "Down", "s", "S"]
        let right_key = ["ArrowRight", "Right", "d", "D"]
        let left_key = ["ArrowLeft", "Left", "a", "A"]
        if(up_key.indexOf(event.key) >= 0){  // number中0为False,其他都是True
            if(this.direction === "Down" && this.snake.bodis.length !== 1){}else{ // 掉头操作无效处理
                this.direction = "Up"
            }
        }
        if(down_key.indexOf(event.key) >= 0){
            if(this.direction === "Up" && this.snake.bodis.length !== 1){}else{
                this.direction = "Down"
            }
        }
        if(right_key.indexOf(event.key) >= 0){
            if(this.direction === "Left" && this.snake.bodis.length !== 1){}else{
                this.direction = "Right"
            }
        }
        if(left_key.indexOf(event.key) >= 0){
            if(this.direction === "Right" && this.snake.bodis.length !== 1){}else{
                this.direction = "Left"
            }
        }
    }

    // 检查蛇是否吃到食物
    checkEat(X:number, Y:number){
        if((X === this.food.X) && (Y === this.food.Y)){
            console.log('吃到食物')
            // 食物位置重置
            this.food.change()
            // 分数增加
            this.score_panele.addScore()
            // 蛇身体增加一节
            this.snake.addBody()
        }


    }

    // 创建蛇移动的方法
    run(){
        // 向上,top减少;向下,top增加,向左,left减少;向右,left增加;
        let x = this.snake.X
        let y = this.snake.Y

        //根据方向来计算x与y值
        switch (this.direction){
            case "ArrowUp":
            case "Up":  // ArrowUp也是沿用UP的处理方法
                y -= 10
                break
            case "Down":
                y += 10
                break
            case "Right":
                x += 10
                break
            case "Left":
                x -= 10
                break
        }

        // 检查蛇头是否碰撞到食物
        this.checkEat(x, y)

        try{
            //修改x,y值
            this.snake.X = x
            this.snake.Y = y
        }catch(e:any){
            alert(e.message + ' Game Over')
            // 将is_live设置为false
            this.is_live = false
        }



        // 开启自定时调度
        this.is_live && setTimeout(this.run.bind(this), 300 - (this.score_panele.level - 1) * 30) // 递归

    }


}

export default GameContro;

main.ts

// 引入样式
import './style/main.less'
// import Food from './mod/Food'
// import ScorePanel from './mod/ScorePanel'
// import Snake from './mod/Snake'
import GameContro from "./mod/GameContro";

new GameContro()

最终效果

在这里插入图片描述

转载请注明出处或者链接地址:https://www.qianduange.cn//article/18889.html
标签
评论
发布的文章

JSON:API Normalizer 项目教程

2024-10-30 21:10:43

json2html 项目教程

2024-10-30 21:10:41

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!