JavaScript面向对象思想实现贪吃蛇小游戏

贪吃蛇游戏

可以将这个小游戏分为几个模块:

Map地图类

clearData 清空数据

如果蛇吃到食物了那么这个食物就要消失并且重新生成一个

还有就是蛇进行移动的时候也是 清除数据在进行生成

setData 设置数据

就是将食物和蛇的位置等数据进行设置成功

include 是否包含这个数据

也就是说 生成的食物不能是在蛇的内部的

也就是说蛇所占的位置将不在生成这个食物

include(data) {
   return this.data.some(item => (item.x == data.x && item.y == data.y));
}

render 渲染数据

将数据放在html 里面进行显示

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

Food食物类

create 创建食物

进行随机生成x和y 的数值然后经过 include 方法进行判断如果包含那么就重新生成一个 如果不包含那么就渲染出来

create() {
                let data = {};
                data.x = Math.floor(Math.random() * this.map.cells);
                data.y = Math.floor(Math.random() * this.map.rows);
                if (this.map.include(data)) {
                    this.create();
                } else {
                    data.color = this.color[Math.floor(Math.random() * this.color.length)];
                    this.map.setData(data);
                    this.data = data;
                }
            }

Snake蛇类

move 移动蛇

判断移动的方向然后 进行加以

第一次蛇的位置为:

{ x: 6, y: 2, color: "blue" },
{ x: 5, y: 2, color: "white" },
{ x: 4, y: 2, color: "white" },
{ x: 3, y: 2, color: "white" },
{ x: 2, y: 2, color: "white" }

那么向右移动一个就会变成

{ x: 7, y: 2, color: "blue" },
{ x: 6, y: 2, color: "white" },
{ x: 5, y: 2, color: "white" },
{ x: 4, y: 2, color: "white" },
{ x: 3, y: 2, color: "white" }

也就是说 后面一个等于前面那个的位置

move() {
    var i = this.data.length - 1;
    this.LastData = {
        x: this.data[i].x,
        y: this.data[i].y,
        color: this.data[i].color
    }
    for (; 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 -= 1; break;
        case "right": this.data[0].x += 1; break;
        case "top": this.data[0].y -= 1; break;
        case "bottom": this.data[0].y += 1; break;
    }
}

eatFood 吃食物

判断蛇头是不是和这个食物的位置相同 如果相同就表示吃到了 那么就给蛇后面加个小方块

eatFood(food) {
    if (food.data.x == this.data[0].x
    && food.data.y == this.data[0].y) {
        this.data.push(this.LastData);
        return true;
    }
        return false;
}

Game游戏类

move 移动控制

判断这个是否

move() {
    this.stop();
    this.timer = setInterval(() => {
        this.shake.move();
        this.map.clearData();
        if (this.shake.eatFood(this.food)) {
            this.food.create();
            this.grade++;
            this.gradeChange && this.gradeChange();
            if (this.interval > 10) {
                this.stop();
                this.interval -= 5;
                this.move();
            }
        }
        this.map.setData(this.food.data);
        this.map.setData(this.shake.data);
        if (this.isOver()) {
            this.stop();
            alert("game over");
            return;
        }
        this.map.render();
    }, this.interval)
}

isEat 是否吃到东西

isOver 是否结束

检测这个蛇是否到了边上了 或者是这个蛇头是否撞到了自己的身体那么就返回true 然后

在move里面进行判断 最后调用结束方法

isOver() {
    // 检测蛇和自己相撞
    for (let i = 1; i < this.shake.data.length; i++) {
        if (this.shake.data[i].x == this.shake.data[0].x
        && this.shake.data[i].y == this.shake.data[0].y) {
                return true;
        }
    }
    // 检测蛇超出
    if (this.shake.data[0].x < 0
        || this.shake.data[0].x >= this.map.cells
        || this.shake.data[0].y < 0
        || this.shake.data[0].y >= this.map.rows) {
            return true;
    }
    return false;
}

over 结束

control 控制器

就是控制蛇上下左右的方向的

第一种:

control() {
  window.addEventListener("keydown", ({ keyCode }) => {
    if (this.shake.direction == "top" || this.shake.direction == "bottom") {
    this.shake.direction = keyCode == 37 ? "left" : (keyCode == 39 ? "right" : this.shake.direction);
    } else if (this.shake.direction == "left" || this.shake.direction == "right") {
    this.shake.direction = keyCode == 38 ? "top" : (keyCode == 40 ? "bottom" : this.shake.direction);
    }
  });
}

第二种:

keyDown({ keyCode }) {
    console.log(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() {
         window.addEventListener("keydown", this.keyDown)
 }

完整代码一

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        #map {
            position: relative;
            width: 400px;
            height: 400px;
            background: #000;
        }
    </style>
</head>
<body>
    <div id="map"></div>
    <script>
        class Map {
            constructor(el, rect) {
                this.el = el;
                this.data = [];
                this.rect = rect;
                this.rows = Math.ceil(Map.getStyle(this.el, "height") / rect);
                this.cells = Math.ceil(Map.getStyle(this.el, "width") / rect);
                Map.setStyle(this.el, "width", this.cells * rect);
                Map.setStyle(this.el, "height", this.rows * rect);
            }
            static getStyle(el, attr) {
                return parseFloat(getComputedStyle(el)[attr]);
            }
            static setStyle(el, attr, val) {
                el.style[attr] = val + "px";
            }
            clearData() {
                this.data.length = 0;
            }
            setData(data) {
                this.data = this.data.concat(data);
            }
            include(data) {
                return this.data.some(item => (item.x == data.x && item.y == data.y));
            }
            render() {
                this.el.innerHTML = this.data.map(item => {
                    return `
                <span style="width:${this.rect}px;height:${this.rect}px;position:absolute;left:${this.rect * item.x}px;top:${this.rect * item.y}px;background:${item.color};"></span>
            `;
                }).join("");
            }
        }
        class Food {
            constructor(map) {
                this.map = map;
                this.color = ["red", "green", "pink", "yellow"];
                this.data = null;
            }
            create() {
                let data = {};
                data.x = Math.floor(Math.random() * this.map.cells);
                data.y = Math.floor(Math.random() * this.map.rows);
                if (this.map.include(data)) {
                    this.create();
                } else {
                    data.color = this.color[Math.floor(Math.random() * this.color.length)];
                    this.map.setData(data);
                    this.data = data;
                }
            }
        }
        class Shake {
            constructor() {
                this.data = [
                    { x: 6, y: 2, color: "blue" },
                    { x: 5, y: 2, color: "white" },
                    { x: 4, y: 2, color: "white" },
                    { x: 3, y: 2, color: "white" },
                    { x: 2, y: 2, color: "white" }
                ];
                this.direction = "right";
            }
            move() {
                var i = this.data.length - 1;
                this.LastData = {
                    x: this.data[i].x,
                    y: this.data[i].y,
                    color: this.data[i].color
                }
                for (; 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 -= 1; break;
                    case "right": this.data[0].x += 1; break;
                    case "top": this.data[0].y -= 1; break;
                    case "bottom": this.data[0].y += 1; break;
                }
            }
            eatFood(food) {
                if (food.data.x == this.data[0].x
                    && food.data.y == this.data[0].y) {
                    this.data.push(this.LastData);
                    return true;
                }
                return false;
            }

        }
        class Game {
            constructor(el, rect) {
                this.map = new Map(map, rect);
                this.food = new Food(this.map);
                this.shake = new Shake();
                this.timer = 0;
                this.grade = 0;
                this.interval = 200;
                this.control();
            }
            start() {
                this.food.create();
                this.move();
            }
            stop() {
                clearInterval(this.timer);
            }
            move() {
                this.stop();
                this.timer = setInterval(() => {
                    this.shake.move();
                    this.map.clearData();
                    if (this.shake.eatFood(this.food)) {
                        this.food.create();
                        this.grade++;
                        this.gradeChange && this.gradeChange();
                        if (this.interval > 10) {
                            this.stop();
                            this.interval -= 5;
                            this.move();
                        }
                    }
                    this.map.setData(this.food.data);
                    this.map.setData(this.shake.data);
                    if (this.isOver()) {
                        this.stop();
                        alert("game over");
                        return;
                    }
                    this.map.render();
                }, this.interval)
            }
            isOver() {
                // 检测蛇和自己相撞
                for (let i = 1; i < this.shake.data.length; i++) {
                    if (this.shake.data[i].x == this.shake.data[0].x
                        && this.shake.data[i].y == this.shake.data[0].y) {
                        return true;
                    }
                }
                // 检测蛇超出
                if (this.shake.data[0].x < 0
                    || this.shake.data[0].x >= this.map.cells
                    || this.shake.data[0].y < 0
                    || this.shake.data[0].y >= this.map.rows) {
                    return true;
                }
                return false;
            }
            control() {
                window.addEventListener("keydown", ({ keyCode }) => {
                    if (this.shake.direction == "top" || this.shake.direction == "bottom") {
                        this.shake.direction = keyCode == 37 ? "left" : (keyCode == 39 ? "right" : this.shake.direction);
                    } else if (this.shake.direction == "left" || this.shake.direction == "right") {
                        this.shake.direction = keyCode == 38 ? "top" : (keyCode == 40 ? "bottom" : this.shake.direction);
                    }
                });
            }
        }
        {
            let map = document.querySelector("#map");
            let game = new Game(map, 10);
            game.start();
            game.gradeChange = function () {
                console.log(game.grade);
            };
        }
    </script>
</body>

</html>

完整代码二

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>贪吃蛇</title>
    <style>
        #map {
            position: relative;
            /* left: 50%; */
            width: 400px;
            height: 400px;
            background: #000;
        }
    </style>
</head>

<body>
    <h1 id="grade">0</h1>
    <div id="map"></div>

    <script>
        //地图类
        class Map {
            constructor(el, rect) {
                this.el = el
                this.rect = rect
                // 数据
                this.data = [
                    // {
                    //     x: 0,
                    //     y: 0,
                    //     color: 'red'
                    // }
                ]
                // 向上取整
                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 }) {
                // some方法用于检测数组中的元素是否满足指定条件(函数提供)
                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("")
            }
        }

        class Food {
            constructor(map) {
                this.map = map
                this.data = null
                this.create()
            }
            create() {
                // 0-39
                // 获取x
                let x = Math.floor(Math.random() * this.map.cells)
                // 获取y
                let y = Math.floor(Math.random() * this.map.rows)
                let color = 'red'
                console.log(x, y, color)
                //将data设置好
                this.data = { x, y, color }
                // 判断是否包含了这个点
                if (this.map.include(this.data)) {
                    // 包含了就重新弄下
                    this.create()
                }
                this.map.setData(this.data)
            }
        }
        class Snake {
            constructor(map, food) {
                this.data = [
                    { x: 6, y: 2, color: 'blue' },
                    { x: 5, y: 2, color: 'white' },
                    { x: 4, y: 2, color: 'white' },
                    { x: 3, y: 2, color: 'white' },
                    { x: 2, y: 2, color: 'white' },
                ]
                this.map = map
                this.food = food
                this.direction = "right"
                this.map.setData(this.data)

            }
            move() {
                let i = this.data.length - 1
                this.lastData = {
                    x: this.data[i].x,
                    y: this.data[i].y,
                    color: this.data[i].color
                }
                /*
                让后每一个走到前面的位置
                */
                for (let i = this.data.length - 1; 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 //不能修改方向
                    }
                    this.direction = dir
                } else {
                    if (dir === "top" || dir === "bottom") {
                        return false //不能修改方向
                    }
                    this.direction = dir
                }
            }
            // 吃食物
            eatFood() {
                this.data.push(this.lastData)
            }

        }

        class Game {
            constructor(el, rect) {
                this.map = new Map(el, rect)
                this.food = new Food(this.map)
                this.snake = new Snake(this.map)
                //初始化
                this.map.render()

                this.timer = 0
                this.interval = 200
                this.keyDown = this.keyDown.bind(this)
                this.grade = 0
                this.control();
            }

            // 开始游戏
            start() {
                this.move()
            }
            //暂停游戏
            stop() {
                clearInterval(this.timer)
            }
            // 控制移动
            move() {
                this.stop()
                this.timer = setInterval(() => {
                    this.snake.move()
                    this.map.clearData()
                    if (this.isEat()) {
                        console.log("吃到食物")
                        this.grade++;
                        this.snake.eatFood()
                        this.food.create()
                        this.changeGrade(this.grade)
                        this.interval *= .9
                        this.stop()
                        this.start()
                    }
                    if (this.isOver()) {
                        this.over()
                        return
                    }
                    this.map.setData(this.snake.data)
                    this.map.setData(this.food.data)
                    this.map.render()
                }, this.interval);
            }
            //是否吃到东西
            isEat() {
                return this.snake.data[0].x === this.food.data.x && this.snake.data[0].y === this.food.data.y
            }
            //是否结束
            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.cells) {
                    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

            }
            //结束
            over() {
                this.stop()
            }
            //改变分数
            changeGrade(grade) {
                console.log("分数:" + grade)

            }
            keyDown({ keyCode }) {
                console.log(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() {
                window.addEventListener("keydown", this.keyDown)
            }
        }

        {
            let map = document.querySelector('#map');
            let game = new Game(map, 10)
            game.start()
            // gameMap.render();
            // setInterval(() => {
            //     gameSnake.move()
            //     gameMap.clearData()
            //     gameMap.setData(gameSnake.data)
            //     gameMap.render()
            // }, 100);
            // setInterval(() => {
            //     gameSnake.changeDir('bottom')
            //     gameSnake.eatFood();
            // }, 500);
        }



    </script>
</body>

</html>

总结

要考虑单一原则 :要进行拆分类 将每个都单一化 (一个类应该是单一完整的东西)

低耦合:也就是说两个类直接尽量减少联系

高内聚:也就是说写蛇那么就在蛇的类里面把需要的都写齐了

上一篇

JavaScritp同步与异步和Promise学习理解