贪吃蛇——面向对象版

基础HTML页面

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            padding: 0px;
            margin: 0px;
        }

        #map {
            width: 400px;
            height: 400px;
            background-color: #000;
            position: relative;
        }
    </style>
</head>

<body>
    <h2 id="grade">0</h2>
    <div id="map"></div>
    <script type="module">
        import Game from "./js/game.js";
        
        {
            let map = document.getElementById("map");
            let game = new Game(map, 20);
            let gradeEl = document.getElementById("grade");

            game.on("changegrade",(grade)=>{
                console.log(grade);
                gradeEl.innerHTML = parseInt(grade);
            });

            document.onclick = function () {
                game.start();
            }

        }
   </script>
</body>

</html>

地图类

export default class Map {
    constructor(el, rect = 10) {
        this.el = el;
        this.rect = rect;
        this.data = [];

        //行列数 = 总宽高 / 行列的大小
        this.rows = Math.ceil(Map.getStyle(el, "height") / rect);
        this.cells = Math.ceil(Map.getStyle(el, "width") / rect);

        //总宽高 = 行列数 * 行列的大小
        Map.setStyle(el, "height", this.rows * rect);
        Map.setStyle(el, "width", this.cells * rect);
    }

    //获取到宽高
    static getStyle(el, attr) {
        return parseFloat(getComputedStyle(el)[attr])
    }

    //根据行列的大小重新赋值总宽高
    static setStyle(el, attr, val) {
        el.style[attr] = val + "px";
    }

    //设置数据
    setData(newData) {
        this.data = this.data.concat(newData);
    }

    //清除数据
    clearData() {
        this.data.length = 0;
    }

    //判断指定位置,是否包含数据
    include({ x, y }) {
        return this.data.some(item => (item.x == x && item.y == y))
    }

    //通过数据渲染页面
    render() {
        this.el.innerHTML = this.data.map(item => {
            return `<span style="position:absolute; left:${item.x * this.rect}px; top:${item.y * this.rect}px; 
                            width:${this.rect}px; height:${this.rect}px; background:${item.color};"></span>`
        }).join("");
    }

}

食物类

export default class Food {
    constructor(cells, rows, colors = ["yellow", "orange", "blue", "pink"]) {
        this.cells = cells;
        this.rows = rows;
        this.data = null;
        this.colors = colors;
        this.create();
    }

    //创建食物
    create() {
        let x = Math.floor(Math.random() * this.cells);
        let y = Math.floor(Math.random() * this.rows);
        let color = this.colors[parseInt(Math.random() * this.colors.length)];
        this.data = { x, y, color };
    }
}

蛇类

export default class Snake {
    constructor() {  //先传入地图类和食物类,继承里边的方法
        this.data = [
            { x: 6, y: 4, color: "red" },
            { x: 6, y: 3, color: "blue" },
            { x: 6, y: 2, color: "blue" },
            { x: 6, y: 1, color: "blue" }
        ];
        this.direction = "right";   //默认的方向
        this.lastData = {};
    }

    //移动
    move() {
        let i = this.data.length - 1;   //声明一个变量,是他的值为this.data这个数组的长度
        //存储蛇尾巴的位置与颜色
        this.lastData = {
            x: this.data[i].x,
            y: this.data[i].y,
            color: this.data[i].color
        }

        //让后边每一格走到前一格的位置上
        for (i; i > 0; i--) {
            this.data[i].x = this.data[i - 1].x;
            this.data[i].y = this.data[i - 1].y;
        }

        //根据方向,移动蛇头
        switch (this.direction) {
            case "left":
                this.data[0].x--;
                break;

            case "right":
                this.data[0].x++;
                break;

            case "top":
                this.data[0].y--;
                break;

            case "bottom":
                this.data[0].y++;
                break;
        }
	}

    //安全判断
    changeDir(dir) {
        //当方向是左或者右时
        if (this.direction === "left" || this.direction === "right") {
            //且 传入的方向为左或者右
            if (dir === "left" || dir === "right") {
                //不能修改方向
                return false;
            }
        }
        //当方向是上或者下时
        if (this.direction === "top" || this.direction === "bottom") {
            //且 传入的方向为上或者下
            if (dir === "top" || dir === "bottom") {
                //不能修改方向
                return false;
            }
        }

        //经过安全判断后,把新方向赋值给当前方向
        this.direction = dir;
        return true;
    }

    //吃到食物后,蛇应该变大一格
    eatFood() {
        this.data.push(this.lastData)
    }

}

Evnet事件池

export default class Event {
    events = {};

    on(eventName, fn) {
        if (!this.events[eventName]) {
            this.events[eventName] = [];
        }
        if (!this.events[eventName].includes(fn)) {
            this.events[eventName].push(fn);
        }
    }

    off(eventName, fn) {
        if (!this.events[eventName]) {
            return
        }
        this.events[eventName] = this.events[eventName].filter(item => item != fn);
    }

    dispatch(eventName, ...arg) {
        if (!this.events[eventName]) {
            return;
        }
        this.events[eventName].forEach(item => {
            item.call(this, ...arg)
        });
    }
}

游戏类

import Map from "./map.js";
import Event from "./event.js";
import Snake from "./snake.js";
import Food from "./food.js";

export default class Game extends Event {
    constructor(el, rect) {
        super();
        this.map = new Map(el, rect);
        this.food = new Food(this.map.cells, this.map.rows);
        this.snake = new Snake(this.map);
        this.map.setData(this.snake.data);
        this.createFood();
        this.render();  //绘制初始化地图
        this.timer = null; //定时器
        this.interval = 200;    //定时器时间设置为一个变量,可以改变速率
        this.keyDown = this.keyDown.bind(this); //保存this
        this.grade = 0; //保存分数
        this.control(); //调用控制器(可以没有)
    }

    //将食物渲染到地图中
    createFood() {
        this.food.create();
        if (this.map.include(this.food.data)) {
            this.createFood();
        }
        this.map.setData(this.food.data);
    }

    //开始游戏
    start() {
        this.move();//调用移动
    }

    //暂停游戏
    stop() {
        clearInterval(this.timer);//清除定时器,已达到暂停的效果
    }

    //给地图渲染数据
    render() {
        this.map.clearData();   //清除旧数据
        this.map.setData(this.snake.data);  //设置蛇身数据
        this.map.setData(this.food.data);   //设置食物数据
        this.map.render();  //绘制
    }

    //控制移动
    move() {
        this.timer = setInterval(() => {
            this.snake.move();  //让蛇开始移动
            if (this.isEat()) {
                this.grade++;   //每吃到一次,分数自加1
                this.snake.eatFood();   //每吃到一次,变大一格
                this.createFood(); //每吃到一次,创建新食物
                this.changeGrade(this.grade);   //每吃到一次,改变分数
                this.interval *= 0.9999;   //每吃到一次,改变速率
                this.stop();
                this.start();   //这两步为————每吃到一次,就暂停再开始一下,实现速率的刷新
                if (this.grade >= 20) {    //当分数为3时调用this.over并且传一个参数1进去调用方法
                    this.over(1);
                }
            }

            if (this.isOver()) {  //如果结束了,那下边的方法都不执行,暂停程序运行
                this.over();
                return;
            }

            this.render();

        }, this.interval);
    }

    //判断是否吃到食物
    isEat() {
        return (this.snake.data[0].x === this.food.data.x) && (this.snake.data[0].y === this.food.data.y);
    }

    //改变分数
    changeGrade(grade) {
        this.dispatch("changegrade", grade);
    }


    //判断是否结束
    isOver() {
        if (this.snake.data[0].x < 0 || this.snake.data[0].x >= this.map.cells || this.snake.data[0].y < 0 || this.snake.data[0].y >= this.map.rows) {
            return true;
        }
        for (let i = 1; i < this.snake.data.length; i++) {
            if (this.snake.data[0].x == this.snake.data[i].x && this.snake.data[0].y == this.snake.data[i].y) {
                return true;
            }
        }
        return false;
    }

    //游戏结束
    /*
        overState   0   中间停止,玩挂了
                    1   胜利了,游戏结束
    */
    over(overState = 0) {
        if (overState) {
            this.dispatch("toWin")
        } else {
            this.dispatch("toOver")
        }

        this.stop();
    }

    //键盘按下————通过键盘编码来改变方向
    keyDown({ keyCode }) {
        switch (keyCode) {
            case 37:
                this.snake.changeDir("left");
                break;
            case 38:
                this.snake.changeDir("top");
                break;
            case 39:
                this.snake.changeDir("right");
                break;
            case 40:
                this.snake.changeDir("bottom");
                break;
        }
    }

    //控制器
    control() {
        //判断用户是否需要改键,如果是的话,就调用自身
        if (this.toControl) {
            this.toControl();
            return;
        }
        //给window添加事件监听,按下后调用keyDown()
        window.addEventListener("keydown", this.keyDown);
    }

    addControl(fn) {
        fn.call(this);
        window.removeEventListener("keydown", this.keyDown);
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值