TypeScript学习笔记(三)——贪吃蛇

配置文件

1、将之前写好的package.json、webpack.config.js、tsconfig.json 复制到当前目录下

2、使用 npm i 初始化环境

3、在src目录下新建 index.html 和 index.ts

4、安装工具:npm i -D less less-loader css-loader style-loader

5、修改相关配置文件:

(1)webpack.config.js:

	// 指定webpack打包时要使用的模块
    module:{
        // 指定要加载(loader)的规则
        rules:[
        	......
        	// 设置less文件的处理
            {
                test:/\.less$/,
                use:[
                    "style-loader",
                    "css-loader",
                    "less-loader"
                ]
            }
        ]
    },

为了使css样式能够兼容各个浏览器,还要使用 postcss:npm i -D postcss postcss-loader postcss-preset-env

            // 设置less文件的处理
            {
                test:/\.less$/,
                use:[
                    "style-loader",
                    "css-loader",
                    {
                        loader:"postcss-loader",
                        options:{
                            postOptions:{
                                plugins:[
                                    [
                                        "postcss-preset-env",
                                        {
                                            browsers:'last 2 versions'
                                        }
                                    ]
                                ]
                            }
                        }
                    },
                    "less-loader"
                ]
            }

这样会自动把样式处理成各个浏览器可兼容的样式
在这里插入图片描述

实现基本样式

1、index.html

<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-pannel">
            <div>
                SCORE:<span id="score">0span>
            div>
            <div>
                LEVEL:<span id="level">1span>
            div>
        div>
    div>
<script src="./index.js">script>
body>

2、index.less

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

// 清除默认样式
*{
    margin: 0;
    padding: 0;
    // 改变盒子模型的计算方式
    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: #000;
                // 使身体各部分间产生间距
                border: 1px solid @bg-color;
                // 蛇的位置会改变,因此开启绝对定位.相对于游戏窗口定位,所以为stage开启相对定位
                position: absolute;
            }
        }
        // 设置食物
        #food{
            width: 10px;
            height: 10px;
            position: absolute;
            left: 40px;
            top: 100px;
            display: flex;
            flex-flow: row wrap;
            justify-content: space-between;
            align-content: space-between;
            &>div{
                width: 4px;
                height: 4px;
                background-color: #000;
                transform: rotate(45deg);
            }
        }
    }

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

效果:
TypeScript学习笔记(三)——贪吃蛇_第1张图片

ts文件编写

1、 index.ts

//引入样式
import './style/index.less'
//引入模块化文件
import Food from './modules/Food'
import ScorePanel from './modules/ScorePanel'

2、modules/Food.ts : 定义食物类(Food)

class Food{
    // 定义一个属性表示食物所对应的元素(div)
    element:HTMLElement;
    constructor(){
        //不加叹号会报错,提示这个值可能为空。在我们的场景存在food,所以这个值不可能为空,加上!表示这个值不可能为空
        this.element = document.getElementById('food')!; 
    }

    // 获取食物当前的x坐标
    get X(){
        return this.element.offsetLeft;
    }
    // 获取食物当前的y坐标
    get Y(){
        return this.element.offsetTop;
    }

    // 修改食物的位置
    change(){
        // 生成随机位置
        // 食物的位置(左偏移量和上偏移量)最小是0,最大是290
        // 因为蛇的大小10px,每次只能移动1格10px.所以食物的位置必须是整10的,蛇才能吃到
        // Math.round(Math.random()*290)   //Math.random()*290 表示 [0,290).Math.round()四舍五入,可以表示[0,290].但是这种方式并不能去除掉整10的
        let top = Math.round(Math.random()*29) * 10  // Math.random()*29 [0,29); Math.round(Math.random()*29) [0,29]. 这时整体乘10即可
        // 或者 Math.floor(Math.random()*30) * 10
        let left = Math.round(Math.random()*29) * 10
        this.element.style.left = left + 'px'
        this.element.style.top = top + 'px'
    }
}

3、modules/ScorePanel.ts : 定义记分牌的类(ScorePanel)

class ScorePanel{
    // score,level记录分数和等级
    score = 0;
    level = 1;
    // 分数和等级所在元素,在构造函数中初始化
    scoreEle:HTMLElement;
    LevelEle:HTMLElement;

    // 设置变量限制等级
    maxLevel:number;
    // 设置变量表示多少分时升级
    upScore:number;

    constructor(maxLevel: number = 10, upScore:number = 10){    //给maxLevel一个默认值
        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 + '';
        }
    }
}

export default ScorePanel

注:在使用到常量的地方尽量将其设置成一个变量,方便后续修改

4、modules/snake.ts : 定义 蛇类(snake)(初步代码,有待完善)

class snake{
    // 表示蛇头的元素
    head : HTMLElement;
    // 表示蛇的身体(包括蛇头)
    bodies:HTMLCollection;
    // 表示蛇的容器
    element:HTMLElement;

    constructor(){
        // 获取snake中的第一个div,就是蛇头 
        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){
        this.head.style.left = value + 'px';
    }
    set Y(value){
        this.head.style.top = value + 'px';
    }

    // 蛇吃到食物后增加身体
    addBody(){
        // 向element中添加一个div
        this.element.insertAdjacentHTML("beforeend","
"
) //把
加到element结束标签的前面
} } export default snake

5、整合

现在各个类是相互独立的,需要新建一个类将这些类整合到一起。

在modules下新建 GameControl.ts (游戏控制器,控制其他所有类)

初始代码:

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

// 游戏控制器,控制其他所有类
class GameControl{
    // 定义三个属性
    // 蛇
    snake:Snake;
    // 食物
    food:Food;
    // 记分牌
    scorePanel:ScorePanel;
    // 创建一个属性来存储蛇的移动方向(也就是按键的方向)
    direction:string = '';
    // 创建一个属性来记录游戏是否结束。(蛇撞墙)
    isLive = true;

    constructor(){
        this.snake = new Snake();
        this. food = new Food();
        this.scorePanel = new ScorePanel();
        // 开始游戏
        this.init()
    }

    // 游戏初始化方法,调用后游戏即开始
    init(){
        // 绑定键盘的按下事件
        // 如果不绑定的话,this是指document。使用bind后this指的是GameControl
        document.addEventListener('keydown',this.keydownHandler.bind(this))
        // 调用run方法
        this.run()
    }

    // 创建键盘按下的响应函数
    keydownHandler(event: KeyboardEvent){
        // 获取当前用户按键的名字
        /* 
            event.key 返回的是字符串
                ArrowUp   方向键 上    
                ArrowDown   下
                ArrowRight  左
                ArrowLeft   右
                但在IE中是: Up, Down, Left, Right
        */
        //检查用户是否按了方向键
        // if()
        this.direction = event.key;
    }

    // 创建一个控制蛇移动的方法
    run(){
        // 根据方向(this.direction)修改蛇的位置
        // 向上:top减少; 向下:top增加; 向左:left减少;向右:left增加

        // 获取蛇当前的坐标
        let X = this.snake.X
        let Y = this.snake.Y
        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.snake.X = X
        this.snake.Y = Y

        // 开启定时调用 run方法
        this.isLive && setTimeout(this.run.bind(this),300 - (this.scorePanel.level - 1) * 30)
    }
    
}

export default GameControl

6、继续完善各功能:

(1)限制蛇的位置,不能超过主窗口。修改 snake.ts:

// 设置蛇头的坐标
    set X(value){
        // 如果新值和旧值相同,则直接返回不再修改
        if(this.X === value) return;

        // 是否撞墙(X的范围是否在 0-290之间)
        if(value < 0 || value > 290){
            // 进入判断说明蛇撞墙,抛出一个异常。此时游戏会终止
            // 在GameControl中 使用try catch来捕获异常
            throw new Error('蛇撞墙了')
        }

        this.head.style.left = value + 'px';
    }
    set Y(value){
        if(this.Y === value) return;
        if(value < 0 || value > 290){
            // 进入判断说明蛇撞墙
            throw new Error('蛇撞墙了')
        }
        this.head.style.top = value + 'px';
    }

(2)完善GameControl.ts

// 游戏控制器,控制其他所有类
	class GameControl{
	    ......
	    // 创建一个控制蛇移动的方法
    run(){
        ......
        // 检查蛇是否吃到食物
        this.checkEat(X,Y)

        try {
            // 修改蛇的位置
            this.snake.X = X
            this.snake.Y = Y
        } catch (error) {
            // 进入catch说明出现了异常,即弹出提示信息
            alert((error as any).message + 'Game Over!')
            // 将isLive设为false,终止游戏。否则会一直弹窗
            this.isLive = false
        }
        // 开启定时调用 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){
            // 吃到食物后,食物的位置要重置
            this.food.change()
            // 分数增加
            this.scorePanel.addScore()
            // 蛇增加一节
            this.snake.addBody()
        }
    }
}
export default GameControl

7、身体的移动

(1)直至上一步,蛇新增加的身体还是静止不动的。

(2)蛇是可以掉头的。游戏中不应该掉头(即向上走时不能向下走…)

(3)蛇可以撞自己身体。游戏中应该不能

解决: snake.ts

class snake{
    ......
    // 设置蛇头的坐标
    set X(value){
        // 如果新值和旧值相同,则直接返回不再修改
        if(this.X === value) return;

        // 是否撞墙(X的范围是否在 0-290之间)
        if(value < 0 || value > 290){
            // 进入判断说明蛇撞墙,抛出一个异常。此时游戏会终止
            // 在GameControl中 使用try catch来捕获异常
            throw new Error('蛇撞墙了')
        }

        // 修改x时是在修改水平坐标,蛇在左右移动.蛇向左时不能向右掉头,反之亦然
        // 蛇头的坐标和第二节身体的坐标一致,则表明掉头。刚开始只有一节身体,所以要先判断是否有第二节
        if(this.bodies[1] && (this.bodies[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 > 290){
            // 进入判断说明蛇撞墙
            throw new Error('蛇撞墙了')
        }

        if(this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value){
            // 如果发生了掉头,让蛇向反方向移动
            if(value > this.Y){
                // 如果新值 value 大于 X, 则说明蛇在向右走,此时发生掉头,应该让蛇继续向左走
                value = this.Y - 10
            }else{
                value = this.Y + 10
            }
        }

        // 移动身体
        this.moveBody();
        this.head.style.top = value + 'px';
        // 检查有没有撞到自己
        this.checkHeadBody();
    }

    // 蛇吃到食物后增加身体
    addBody(){
        // 向element中添加一个div
        this.element.insertAdjacentHTML("beforeend","
"
) //把
加到element结束标签的前面
} // 添加蛇身体移动的方法,在 set中调用 moveBody(){ // 蛇是一节一节的向前移。最好的方式是将后边身体的位置 设置为 前边身体的位置 // 第4节 = 第三节位置 // 遍历获取所有的身体(从后往前改) for(let i = this.bodies.length-1; i > 0; i--){ // 获取前边身体的位置 let X = (this.bodies[i-1] as HTMLElement).offsetLeft; //不加的话 .offset会报错 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.X === bd.offsetLeft && this.Y === bd.offsetTop){ // 进入判断说明蛇头撞到身体 throw new Error('撞到自己') } } } } export default snake

你可能感兴趣的:(typescript,typescript,学习,javascript)