JavaScript 贪吃蛇

310 阅读2分钟

已实现功能

snake.png

  • 键盘可控制蛇移动方向,并且蛇无法反向
  • 蛇头触碰边界即游戏结束
  • 蛇吃食物会身体增长
  • 蛇吃到自己身体即游戏结束
  • 根据分数增长会升级蛇的样式,也会加快移动速度,增加游戏难度

Html部分

body部分

<div class="container"></div>
<div class="score">
    <h3>Score:<span>0</span></h3>
    <h4>等级:<span>1</span></h4>
</div>

CSS样式部分

<style>
    *{
        margin: 0;
        padding: 0;
    }
    .container {
        width: 680px;
        height: 680px;
        border: 1px solid;
        background: lightgrey;
        margin: 0 auto;
        position: relative;
    }
    .score{
        width: 80px;
        background: lightgrey;
        position: fixed;
        top: 10px;
        left: 320px;
        text-align: center;
        height: 60px;
        line-height: 30px;
    }
</style>

JavaScript部分

获取元素

let containerEle = document.querySelector('.container');
let scoreSpan = document.querySelector('h3 span');
let gradeSpan = document.querySelector('h4 span');

蛇类

class Snake {
    constructor() {
        //创建身体数组
        this.body = [];
        this.createBody();
        // 设置蛇移动的初始方向为right
        this.dir = "ArrowRight";
        this.nowDir = "ArrowRight";
        //设置初始分数和等级
        this.score = 0;
        this.grade = 1;
    }
    // 创建蛇的头和身体
    createBody() {
        for (let i = 0; i < 5; i++) {
            // 1 -- 0
            // 2 -- 20
            // 3 -- 40
            let rect;
            // body的最后一个方块为蛇头,蛇头颜色设置为red,身体为orange
            if (i == 4) {
                rect = new Rect(20 * i, 0, "red");
            } else {
                rect = new Rect(20 * i, 0, "orange");
            }
            // 将每个小方块push进body
            this.body.push(rect);
        }
    }

    // 渲染Dom
    renderDom(containerEle) {
        // 接收container元素
        this.containerEle = containerEle;
        // 将body中的每个方块添加到Dom中
        this.body.forEach(rect => {
            rect.addDom(containerEle);
        })
    }

    // 蛇移动的方法
    move() {
        // 先移动蛇头一个距离,删除最后一个蛇的身体,在之前蛇头的位置添加一个身体
        // 把最后一个方块设为蛇头
        let head = this.body[this.body.length - 1];
        // 取之前蛇头位置的x和y
        let numX = parseInt(head.ele.style.left);
        let numY = parseInt(head.ele.style.top);
        // 创建新方块
        let rect = new Rect(numX, numY, "orange");
        rect.addDom(this.containerEle);
        // 放在之前蛇头的位置上
        this.body.splice(this.body.length - 1, 0, rect);

        // 若此次键盘输入this.dir与上次输入this.nowDir相反,则继续沿着原方向移动,不反向
        if((this.dir == "ArrowRight"&& this.nowDir == "ArrowLeft") || (this.dir == "ArrowLeft" && this.nowDir == "ArrowRight")|| (this.dir == "ArrowUp" && this.nowDir == "ArrowDown")|| (this.dir == "ArrowDown" && this.nowDir == "ArrowUp")){
            this.dir = this.nowDir;
        }else{
            this.dir = this.dir;
        }

        // 根据this.dir接收的键盘方向进行移动
        switch (this.dir) {
            case 'ArrowRight':
                // 以20px为单位进行移动
                head.ele.style.left = numX + 20 + "px";
                this.nowDir = "ArrowRight";
                break;
            case 'ArrowLeft':
                head.ele.style.left = numX - 20 + "px";
                this.nowDir = "ArrowLeft";
                break;
            case 'ArrowUp':
                head.ele.style.top = numY - 20 + "px";
                this.nowDir = "ArrowUp";
                break;
            case 'ArrowDown':
                head.ele.style.top = numY + 20 + "px";
                this.nowDir = "ArrowDown";
                break;
            default:
                console.log("请按方向键");
                break;
        }

        // 检测蛇头是否与食物碰撞
        // 调用crash函数判断
        if (crash(head.ele, food.food.ele)) {
            // 若碰撞,删除food元素,创建新food
            food.food.ele.remove();
            food = new Food();
            food.food.addDom(this.containerEle);
            // 积分+1 并显示在页面
            this.score++;
            scoreSpan.innerHTML = this.score;
        } else {
            // 把蛇尾(数组的第一个元素)删除从页面删除
            let obj = this.body[0];
            obj.ele.remove();
            // 把this.body里的对象删除;
            this.body.shift();
        }

        // 检测蛇头是否碰撞自己身体
        let eat = false; // 设置eat代表是否吃到自己身体
        // 和除了头之外的每个方块检测
        for(let i = 0;i<this.body.length-1;i++){
            if(crash(head.ele,this.body[i].ele)){
                eat = true;
            }
        }
        // 若碰到自己身体,则游戏结束
        if(eat){
            // 先清除之前的定时器
            clearInterval(t1);
            alert('Game Over!');
        }

        // 根据分数升级和改变蛇样式
        if (this.score == 5) {
            // 蛇头设成红橘相间条纹状
            head.ele.style.background = 'linear-gradient(red 0%, red 20%, orange 20%, orange 40%, red 40%, red 60%, orange 60%, orange 80%, red 80%, red 100%)';
            // 等级变为2
            gradeSpan.innerHTML = 2;
            // 清除之前的定时器,再设置新的定时器加快移动速度
            clearInterval(t1);
            controlSpeed(100);
        }
        if (this.score == 10) {
            head.ele.style.background = 'linear-gradient(black 0%, black 20%, orange 20%, orange 40%, black 40%, black 60%, orange 60%, orange 80%, black 80%, black 100%)';
            gradeSpan.innerHTML = 3;
            clearInterval(t1);
            controlSpeed(80);
        }

        // 检测蛇头是否出界
        var headX = parseInt(head.ele.style.left) + parseInt(head.ele.clientWidth);
        var headY = parseInt(head.ele.style.top) + parseInt(head.ele.clientHeight);
        // 获取容器(地图)宽高
        var conW = parseInt(this.containerEle.clientWidth);
        var conH = parseInt(this.containerEle.clientHeight);

        // 若snake出界,则游戏结束
        if (headX == 0 || headX > conW || headY == 0 || headY > conH) {
            clearInterval(t1);
            alert('Game Over!');
        }

    }
}

食物类

class Food {
    constructor() {
        // 食物在地图内随机出现
        let x = this.randomNum();
        let y = this.randomNum();
        // 依照x和y值产生红色的食物
        this.food = new Rect(x, y, "red");
        // 调用自身类内的方法
        this.isAppearBody();
    }
    
    // 检测食物是否出现在蛇身体上
    isAppearBody(){
        // 跟身体的每个方块都检测
        for (let i = 0; i < snake.body.length; i++) {
            // 若碰撞,则再次产生随机数
            if (crash(this.food.ele, snake.body[i].ele)) {
                x = this.randomNum();
                y = this.randomNum();
                // 把之前的食物删掉,对象内food属性重新赋予新方块
                this.food.ele.remove();
                this.food = new Rect(x, y, "red");
            }
        }
    }

    randomNum(){
        // 以20为单位产生随机数
        return Math.max(0, parseInt(Math.random() * 33)) * 20;
    }
}

小方块类

class Rect {
    constructor(snakeX, snakeY, color) {
        // 在dom中创建一个div
        this.ele = document.createElement('div');
        this.ele.style.width = "20px";
        this.ele.style.height = "20px";
        // 定位
        this.ele.style.position = "absolute";
        // 根据接收的color设置方块的颜色
        this.ele.style.background = color;
        this.ele.style.left = snakeX + 'px';
        this.ele.style.top = snakeY + 'px';
    }
    // 将div添加到地图中
    addDom(containerEle) {
        containerEle.appendChild(this.ele);
    }
}

控制速度函数

let t1;
// 参数为定时器间隔时间,可调用时自行传参设置
function controlSpeed(delay){
    t1 = setInterval(function () {
        // 每隔delay蛇移动一次
        snake.move();
    }, delay)
}

检测碰撞函数

function crash(ele1, ele2) {
    // 元素1的数据
    var AminX = ele1.offsetLeft; // 元素1相对于父级left的偏移量
    var AmaxX = ele1.offsetLeft + ele1.offsetWidth; // 偏移量+元素1的宽度(包括边框)

    var AminY = ele1.offsetTop;
    var AmaxY = ele1.offsetTop + ele1.offsetHeight;

    // 元素2的数据
    var BminX = ele2.offsetLeft;
    var BmaxX = ele2.offsetLeft + ele2.offsetWidth;

    var BminY = ele2.offsetTop;
    var BmaxY = ele2.offsetTop + ele2.offsetHeight;

    if (AmaxX > BminX && AminX < BmaxX && AmaxY > BminY && AminY < BmaxY) {
        return true;
    } else {
        return false;
    }
}

根据 蛇类 和 食物类 实例化对象 snake 和 food

let snake = new Snake();
// 将snake渲染到dom中
snake.renderDom(containerEle);
let food = new Food();
// 向地图中添加食物方块
food.food.addDom(containerEle);
// 给蛇设置初始移动速度:每200毫秒移动一个单位
controlSpeed(200);

键盘事件:获取键盘按下的按键

document.onkeydown = function (e) {
    let key = e.key;
    // 将获取的键盘按键名赋给snake中的dir属性
    snake.dir = key;
}

[Gitee源码](Month2/Day05/html/snake(melon).html · Muskmelon/HTML5 - 码云 - 开源中国 (gitee.com))