三百行代码简单写一个贪吃蛇游戏

756 阅读5分钟

废话不多说直接上代码,大家有时间摸鱼的话可以复制代码到本地过一遍代码,没时间摸鱼的点个赞再走吧哈哈哈哈哈哈哈。❤

HTML页面代码

<!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>
<h1 id="grade">0</h1>    
<div id="map"></div>
</body>
</html>

script脚本代码之Map类

/*
    渲染map页面数据

    --setData(newData) 拿到需要渲染到页面的数据(蛇和食物)

    --clearData() 清空所有数据(蛇和食物)


    --include({x,y}) 判断已经在map上的坐标是否包含传进来的数据

    --render()  把数据渲染上(map)页面
*/
class Map {
    /*
        el是<div id="map"></div>
        rect是每一个格子的大小
        this.data是在地图中的蛇和食物的对象数组
    */
    constructor(el,rect = 10){
            this.el = el;
            this.rect = rect;
            this.data = [
                // {
                //     x: 10,
                //     y: 5,
                //     color: "red"
                // }
            ];
            //几行几列
            this.rows = Math.ceil(Map.getStyle(el,"height") / rect);
            this.cells = Math.ceil(Map.getStyle(el,"width") / rect);
            //设置map的宽高
            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";
    }
    //将数据(包括蛇和食物的数组)拼接在this.data中
    setData(newData){
            this.data = this.data.concat(newData);
    }
    //清空数据
    clearData(){
        this.data.length = 0;
    }
    //判断已经在map上的坐标是否包含传进来的数据
    include({x,y}){
            return !!this.data.find(item=>(item.x==x&&item.y==y));
            // return this.data.some(item=>(item.x==x&&item.y==y));
    }
    //把数据渲染到map(页面)上面
    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("");
    }
}

script脚本代码之Food类

/*
    创建map上的食物类

    --create()  每一次调用该方法都创建一个食物
*/
class Food {
    //在食物这map的作用是传递cells和rows
    //可以通过传值的方式把这两个参数传递进去

    //把食物渲染到哪张地图上
    //map是实例化对象
    constructor(map,colors = ['red',"blue","yellow","pink"]){
        this.map = map;
        //食物默认随机的颜色有红、蓝、黄、粉
        this.colors = colors;
        //创建出来的食物存在这里(Food类)的this.data中
        //食物每次只有一个
        this.data = null;
        this.create();
    }
    //创建出在页面上随机生成的食物
    create(){
        //以cells为6是为例随机位置为0、1、2、3、4、5
        let x = Math.floor(Math.random()*this.map.cells);
        let y = Math.floor(Math.random()*this.map.rows);
        //食物已经可以随机出现在地图上的某处
        // console.log(x,y);
        let color = this.colors[parseInt(Math.random()*this.colors.length)];
        //食物已经可以出现默认的随机颜色了
        // console.log(x,y,color);


        //这里的this.data是食物的数据(包括坐标和颜色)
        this.data = {x,y,color};
        //下面的四条语句可以拆离出去在控制器中完成
        //自己决定能不能用不能用就在创建一个

        //当map上的数据已经包含了该随机生成的食物数据
        //我们需要重新再重新创建一个食物数据
        if(this.map.include(this.data)){
            this.create();
        }
        //然后加入map类中的(总)this.data
        this.map.setData(this.data);
    }
}

script脚本代码之Snake类

/*
    --this.data 蛇的数据
    --move() 蛇能移动的逻辑
    --changeDir(dir) 改变蛇移动的方向
    --eatFood()  蛇吃到食物
*/
class Snake {
    //初始化一条蛇
    constructor(map){
        //第一条数据是绿色的蛇头
        //头尾尾尾尾
        //如果 蛇头 吃到了食物增加这里的数据
        this.data = [
            {x: 6, y: 4, color: "green"},
            {x: 5, y: 4, color: "white"},
            {x: 4, y: 4, color: "white"},
            {x: 3, y: 4, color: "white"},
            {x: 2, y: 4, color: "white"}
        ];
        this.map = map;
        //蛇头移动的方向
        this.direction = "right";
        //地图只有在初始化渲染蛇的数据的时候用到了
        //可以把它抽出来在game中做
        //这里的this.data是蛇的数据
        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(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; // 不能修改方向
            }
        } else {
            //如果蛇正在上下移动我们在让其上下移动返回false
            //返回false即修改不成功
            if(dir==="top"||dir==="bottom"){
                return false; // 不能修改方向
            }
        }
        this.direction = dir;
        return true;
        // 如果蛇正在上下移动,我们只能修改让蛇左右移动
    }
    // 吃到了食物蛇应该变大了
    eatFood(){
        this.data.push(this.lastData);
    }
}

script脚本代码之Game类

/*
    start 开始游戏
    stop 暂停游戏
    isOver 判断是否结束
    control 游戏控制器
*/
class Game {
    //什么时候吃到食物
    constructor(el,rect,toControl = null,toGrade=null){
        // let gameMap = new Map(map,10);
        // let gameFood = new Food(gameMap);
        // let gameSnake = new Snake(gameMap);
        //前三句变为下面一句
        //let game = new Game(map,10);


        //实现方法为下面几句:
        this.map = new Map(el,rect);
        this.food = new Food(this.map);
        this.snake = new Snake(this.map);

        //初始化渲染蛇和食物到页面上
        this.map.render();


        //Game
        //timer不断增长
        this.timer = 0;
        this.interval = 200;
        this.toControl = toControl;
        //绑定事件
        this.keyDown = this.keyDown.bind(this);
        //分数
        this.grade = 0;
        this.toGrade = toGrade;
        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;
                //提高速度后重启定时--clearInterval
                this.stop();
                //提高速度后重开定时器--this.move()
                this.start();
                if(this.grade >= 5){
                    //调用over告知游戏结束你赢了
                    this.over(1);
                }
            }
            if(this.isOver()){
                //调用over告知游戏结束你输了
                this.over(0);
                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.rows){
            return true; //返回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;  //返回true则重叠了
            }
        }
        return false;
    }
    // 游戏结束
    /*
        overState 
            0 中间停止,完挂了
            1 胜利了游戏结束 
    */
    over(overState = 0){
        if(overState){
            this.toWin&&this.toWin();
        } else {
            this.toOver&&this.toOver();
        }
        
        this.stop();
    }
    // 分数改变
    changeGrade(grade){
        this.toGrade&&this.toGrade(grade);
    }
    keyDown({keyCode}){
        //console.log(keyCode);
        //这里是上下左右,默认的控制方向键
        let isDir;
        switch(keyCode){
            case 37:
                isDir = this.snake.changeDir("left");
                break;
            case 38:
                isDir = this.snake.changeDir("top");
                break;
            case 39:
                isDir = this.snake.changeDir("right");
                break;
            case 40:
                isDir = this.snake.changeDir("bottom");
                break;            
        }
    }
    // 控制器 
    control(){
        if(this.toControl){
            this.toControl();
            return ;
        }
        window.addEventListener("keydown",this.keyDown);
    }
    addControl(fn){
        fn.call(this);
        window.removeEventListener("keydown",this.keyDown);
    }
}

script脚本代码之执行逻辑

{
    let map = document.querySelector("#map");
    let game = new Game(map,10);
    //
    let gradeEl = document.querySelector("#grade")
    game.toGrade = function(grade){
        console.log(grade);
        gradeEl.innerHTML = grade;
    };
    document.onclick = function(){
        game.start();
    };
    game.toOver = function(){
        alert("游戏结束");
    }
    game.toWin = function(){
        alert("太棒了,您完成了游戏");
    }
}