TypeScript实现贪吃蛇

一.TS实现的优势


        其实本人接触TypeScript的时间并不长,之前总是道听途说TypeScript的检验十分严格,其余的就没有什么过人之处;但经过一段时间的了解,发现这只是里面的冰山一角,从 TypeScript 的名字就可以看出来,「类型」是其最核心的特性。像JavaScript就是一个弱类型的语言,它本身是支持面向对象编程的,但是逻辑处理起来会很繁琐。而typescript的出现增强了它这方面的能力,使代码的逻辑更加清晰,也更好维护。

二.TS实现贪吃蛇


        接下来这个贪吃蛇的例子会让大家对我说的东西能有一个具象化的感受,既然是面向对象编程,那就少不了类的出现。

1.配置TypeScript环境

在命令行终端输入:

npm install -g typescript

tsc --init

 然后就可以在终端使用tsc编译ts文件了.

2. 界面样式

 index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet/less" type="text/css" href="./index.less">
    <title>贪吃蛇</title>
</head>

<body>
    <script src="//cdnjs.cloudflare.com/ajax/libs/less.js/3.8.1/less.min.js"></script>
    <div id="main">
        <div id="up">
            <div id="snake">
                <div></div>
            </div>
            <div id="food">
                <div></div>
                <div></div>
                <div></div>
                <div></div>
            </div>
        </div>
        <div id="down">
            <div id="one">
                SCORE:<span id="score">0</span>
            </div>
            <div id="two">
                LEVEL:<span id="level">1</span>
            </div>
        </div>
    </div>
</body>

</html>

 index.less

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font: bold 20px "Courier"
}

#main {
    width: 360px;
    height: 420px;
    background-color: #6a9f4f;
    margin: 100px auto;
    border: 10px solid black;
    border-radius: 10px;
    display: flex;
    flex-flow: column;
    align-items: center;
    justify-content: space-around;
}

#up {
    width: 304px;
    height: 304px;
    border: 2px solid #000000;
    position: relative;

    #snake {
        &>div {
            width: 10px;
            height: 10px;
            background-color: #000;
            border: 1px solid #b7d4a8;
            position: absolute;
        }
    }
}


#food {
    width: 10px;
    height: 10px;
    position: absolute;
    left: 50px;
    top: 30px;
    // background-color: red;
    display: flex;
    flex-flow: row wrap;
    justify-content: space-between;


    &>div {
        width: 4px;
        height: 4px;
        background-color: #000;
        transform: rotate(45deg);
        // border: 1px solid #b7d4a8;
    }
}



#down {
    width: 300px;
    display: flex;
    flex-flow: row;
    justify-content: space-between;
}

3.根据分工创建类 

        最好是将每个类都放到一个单独的ts文件下,但是我觉得这个是个小项目,所以将它放到了一个ts文件下。

prac.ts

//界面的食物类
class Food {
    // 定义一个属性表示食物所对应的元素
    private element: HTMLElement;
    constructor() {
        // 获取页面中的food元素并将其赋值给element
        // 末尾加上叹号,表示id为food的元素必定存在(非空)
        this.element = document.getElementById('food')!;

    }

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

    // 定义一个获取食物Y轴坐标的方法
    get Y() {
        return this.element.offsetTop;
    }
    // 修改食物的位置
    change() {
        // 生成一个随机的位置
        // 食物的位置最小是0 最大是290 因为定位父级元素是300*300
        // 蛇移动一次就是一格,一格的大小就是10,所以就要求食物的坐标必须是整10
        let top = Math.floor(Math.random() * 30) * 10;
        let left = Math.floor(Math.random() * 30) * 10;

        this.element.style.left = left + 'px';
        this.element.style.top = top + 'px';
    }
}


// 定义表示记分牌的类
class ScorePanel {
    // score和level用来记录分数和等级
    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 + '';
        }
    }
}



//运动的蛇类
class Snake {
    // 表示蛇头的元素
    head: HTMLElement;
    // 蛇的身体(包括蛇头)
    bodies: HTMLCollection;
    // 获取蛇的容器
    element: HTMLElement;

    constructor() {
        this.element = document.getElementById('snake')!;
        this.head = document.querySelector('#snake > div') as HTMLElement;
        this.bodies = this.element.getElementsByTagName('div');
    }

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

    // 获取蛇的Y轴坐标
    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('蛇撞墙了!');
        }

        // 修改x时,是在修改水平坐标,蛇在左右移动,蛇在向左移动时,不能向右掉头,反之亦然
        if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetLeft === value) {
            // console.log('水平方向发生了掉头');
            // 如果发生了掉头,让蛇向反方向继续移动
            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;
        }
        // Y的值的合法范围0-290之间
        if (value < 0 || value > 290) {
            // 进入判断说明蛇撞墙了,抛出一个异常
            throw new Error('蛇撞墙了!');
        }

        // 修改y时,是在修改垂直坐标,蛇在上下移动,蛇在向上移动时,不能向下掉头,反之亦然
        if (this.bodies[1] && (this.bodies[1] as HTMLElement).offsetTop === value) {
            if (value > this.Y) {
                value = this.Y - 10;
            } else {
                value = this.Y + 10;
            }
        }

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

    // 蛇增加身体的方法
    addBody() {
        // 向element中添加一个div
        // beforeend:结束标签之前的位置
        this.element.insertAdjacentHTML("beforeend", "<div></div>");
    }

    // 添加一个蛇身体移动的方法
    moveBody() {
        /*
        *   将后边的身体设置为前边身体的位置
        *       举例子:
        *           第4节 = 第3节的位置
        *           第3节 = 第2节的位置
        *           第2节 = 蛇头的位置
        */
        // 遍历获取所有的身体
        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';
        }
    }

    // 检查蛇头是否撞到身体的方法
    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('撞到自己了!');
            }
        }
    }
}


// 游戏控制器,控制其他的所有类
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() {
        // 绑定键盘按键按下的事件
        document.addEventListener('keydown', this.keydownHandler.bind(this));

    }

    // 创建一个键盘按下的响应函数
    keydownHandler(event: KeyboardEvent) {
        // 需要检查event.key的值是否合法(用户是否按了正确的按键)
        // 修改direction属性
        this.direction = event.key;
        // 调用run方法,使蛇移动
        this.run();
    }

    // 创建一个控制蛇移动的方法
    run() {
        /*
        *   根据方向(this.direction)来使蛇的位置改变
        *       向上 top  减少
        *       向下 top  增加
        *       向左 left 减少
        *       向右 left 增加
        */
        // 获取蛇现在坐标
        let X = this.snake.X;
        let Y = this.snake.Y;


        // 根据按键方向来计算X值和Y值(未更新)
        switch (this.direction) {
            case "ArrowUp":
            case "Up":
                // 向上移动 top 减少
                Y -= 10;
                break;
            case "ArrowDown":
            case "Down":
                // 向下移动 top 增加
                Y += 10;
                break;
            case "ArrowLeft":
            case "Left":
                // 向左移动 left 减少
                X -= 10;
                break;
            case "ArrowRight":
            case "Right":
                // 向右移动 left 增加
                X += 10;
                break;
        }

        // 检查蛇是否吃到了食物
        this.checkEat(X, Y);

        //修改蛇的X和Y值
        try {
            this.snake.X = X;
            this.snake.Y = Y;
        } catch (e: any) {
            // 进入到catch,说明出现了异常,游戏结束,弹出一个提示信息
            alert(e.message + ' GAME OVER!');
            // 将isLive设置为false
            this.isLive = false;
        }

        // 开启一个定时调用(定时器调用自身)
        // 会再次创建一个定时器
        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();
        }
    }
}


new GameControl()

 4.利用tsc将ts代码解析成js代码

命令行输入

tsc prac.ts

生成的prac.js文件可以再直接引入HTML文件中,就可以实现相应功能

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值