JS 实现贪吃蛇小游戏(带注释)

先看效果:

这里只是实现了贪吃蛇游戏的基础功能,没有修饰蛇、食物、背景,食物随机出现,蛇吃掉食物身体增长一节,蛇不能撞墙不能撞到自己,同时食物不会随机出现在蛇身上。总之比较基础和粗糙。

游戏的地图、食物、蛇的移动以及游戏规则其实相对都比较好理解,自己理一理问题应该就不大了。这个游戏带给我最大的收获是,锁,的应用,比如0.5s内键盘按下多次方向改变蛇该如何移动,蛇撞墙之后蛇头正常会移动出去才会游戏停止,都是通过添加锁来解决的,下面的代码种有详细说明。

完整代码如下:

html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>贪吃蛇小游戏</title>
    <link rel="stylesheet" href="index.css">
</head>
<body>
    <div id="app"></div>
    <script src="map.js"></script>
    <script src="food.js"></script>
    <script src="game.js"></script>
    <script src="snake.js"></script>
    <script>
        // 地图
        let map = new Map(20,20);
        // 蛇
        let snake = new Snake();
        // 食物
        let food = new Food(12,6);
        // 游戏
        let game = new Game(map,food,snake);
    </script>
</body>
</html>

css:

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

.box{
    width: 600px;
    height: 600px;
    margin: auto;
}

.row{
    overflow: hidden;
}

.col{
    width: 30px;
    height: 30px;
    float: left;
    /* border: 1px solid #ccc; */
}

map.js:

/*
Map 地图
*/
function Map(x,y){
    this.arr = [];
    this.x = x;
    this.y = y;
    // 创建地图DOM格子
    this.dom = document.createElement("div");

}

// 把地图渲染进页面,把地图格子渲染出来
Map.prototype.fill = function(){
    for(let i = 0; i < this.x; i++){
        // 创建div标签
        let dom_div = document.createElement("div");
        // 给创建的div标签加类名
        dom_div.classList.add("row");
        // 创建一个行数组
        let row_arr = [];
        for(let j = 0; j < this.y; j++){
            // 创建span标签
            let dom_span = document.createElement("span");
            // 给创建的span标签加类名
            dom_span.classList.add("col");
            // 把创建的span追加到行数组里
            row_arr.push(dom_span);
            // 把创建的span标签追加到创建的横向div里
            dom_div.appendChild(dom_span);
        }
        // 把外层for循环创建的行数组追加到this.arr里
        this.arr.push(row_arr);
        // 把创建的横向div追加到this.dom里面
        this.dom.appendChild(dom_div); 
    }
    // 给this.dom 加类名
    this.dom.classList.add("box");
    // 把追加好的this.dom放进#app里
    document.querySelector("#app").appendChild(this.dom);
}

// 清屏, 把所有的小方格的颜色都清掉
Map.prototype.clear = function(){
    for(let i = 0; i < this.arr.length; i++){
        for(let j = 0; j < this.arr[i].length; j++){
            this.arr[i][j].style.background = "white";
        }

    }
}

food.js:

/*
Food 食物
@x 食物的横向坐标
@y 食物的纵向坐标
*/
function Food(x,y){
    this.x = x;
    this.y = y;

}

// 更新食物坐标的方法
Food.prototype.change = function(x,y){
    this.x = x;
    this.y = y;
}

snake.js:

/*
Snake 蛇
*/
function Snake(){
    // 蛇的数组
    this.arr = [
        {x: 4, y: 4},
        {x: 4, y: 5},
        {x: 4, y: 6},
        {x: 4, y: 7},
        {x: 4, y: 8}
    ];

    // 蛇的移动方向
    // 左 37,上38,右39,下40,这是js里的keyCode
    this.direction = 39;
    // 加锁
    this.lock = true;
}

// 蛇的移动
Snake.prototype.move = function(){
    // 克隆一个蛇的头部
    let new_head = {
        x: this.arr[this.arr.length-1].x, 
        y: this.arr[this.arr.length - 1].y
    };
    // 判断蛇头的移动方向
    if(this.direction === 37){
        new_head.y--;
    }else if(this.direction === 38){
        new_head.x--;
    }else if(this.direction === 39){
        new_head.y++;
    }else if(this.direction  === 40){
        new_head.x++;
    }

    // 将新的头部添加到数组
    this.arr.push(new_head);
    // 移除数组的尾部
    this.arr.shift();
    this.lock = true;
    // 蛇的移动就是蛇头增加一节,蛇尾删除一节
    
}

/**
 * 为什么要加锁呢?
 * 这个游戏设置的是每0.5s进行一次渲染,包括蛇、地图、食物等。
 * 那如果在0.5s内按了很多次不同的方向键,蛇要怎么移动呢?
 * 这个时候给蛇本身设置一个锁的属性为true,
 * 当蛇的方向要改变时,如果锁为true,方向正常改变,同时将lock设置为false,如果lock为false,蛇的方向不能改变
 * 当蛇移动完,move()函数执行到最后,lock设置为true。
 * 
 */
// 蛇的移动方向
Snake.prototype.change = function(e){

    if(!this.lock){
        // 跳出语句,下面后续代码不再执行
        return;
    }
    // 解锁
    this.lock = false;
    // 当前的方向 - 想要移动的新方向
    let num = this.direction - e;
    if(num === 0 || num === -2 || num === 2){
        // 观察37,38,39,40,键盘输入方向和之前相反或相同时
        // 两个方向相减的数值的差为2或0,所以此时return跳出即可。
        return;
        // return是跳出不再执行的意思
    }else{ // 否则,修改方向direction
        this.direction = e;
    }
}

// 蛇的尾巴增长
Snake.prototype.grow = function(){
    // 获取蛇的尾巴位置
    let s = this.arr[0];
    this.arr.unshift(s);
}

game.js:

/*
Game 游戏类
@map 地图
@food 食物
@snake 蛇
*/
function Game(map,food,snake){
    this.map = map;
    this.food = food;
    this.snake = snake; 
    // 计时器
    this.timer = null;

    // 是否开始游戏
    this.play = true;
    // 初始化
    this.init();
}

// 初始化
Game.prototype.init = function(){
    // 把地图渲染进页面
    this.map.fill();
    // 把食物渲染到地图里
    this.renderFood();
    // 把蛇渲染到地图里
    this.renderSnake();
    // 开始游戏
    this.start();
    // 监听键盘事件
    this.eventKey();


}

// 渲染食物的方式  render 理解为渲染
Game.prototype.renderFood = function(){
    let x = this.food.x;
    let y = this.food.y;
    // 渲染食物是给指定的格子渲染背景颜色
    // this.map.dom.childNodes[x].childNodes[y].style.background = "red";

    // 使用数组填充食物的颜色
    this.map.arr[x][y].style.background = "red";
    // this.map.arr[x][y].style.backgroundSize = "cover";

}
// 渲染蛇
Game.prototype.renderSnake = function(){
    for(let i = 0; i < this.snake.arr.length; i++){
        // 蛇的身体数组
        let x = this.snake.arr[i].x;
        let y = this.snake.arr[i].y;
        this.map.arr[x][y].style.background = "green";
    }
}

// 开始游戏
Game.prototype.start = function(){
    // 这里使用了箭头函数和setInterval(),因为箭头函数不绑定this!!
    // 使用计时器的箭头函数每隔0.5s执行一次
    this.timer = setInterval(() => {
        // 让蛇移动
        this.snake.move();
        // 检测蛇头是否撞到了墙
        this.checkWall();
        // 检测蛇头是否撞到自己的身体
        this.checkSnake();
        // 检测蛇头是否吃到食物
        this.checkFood();
        
        // 检测游戏是否在运行,这里也加入了锁
        if(this.play){
            // 其实蛇的数组,蛇头已经移动出去了,
            // 但由于没有清屏渲染,所以页面没动而已
            // 清屏
            this.map.clear();
            // 渲染食物
            this.renderFood();
            // 渲染蛇
            this.renderSnake();
        }
    },500)
}


// 监听方向键
Game.prototype.eventKey = function(){
    document.onkeydown = (e) => {
        let code = e.keyCode;
        // console.log(e); 
        if(code === 37 || code === 38|| code === 39 || code ===40){
            // 如果按了上下左右键则调用snake里面的change,其余键盘按键没用
            // console.log(this);
            this.snake.change(code);
        }
    }
} 

// 判断蛇头是否吃到食物
Game.prototype.checkFood = function(){
    // 食物位置
    let food = this.food;
    // 蛇头的位置
    let head = this.snake.arr[this.snake.arr.length - 1];
    // console.log(food);
    // console.log(head);

    // 判断蛇头是否和食物位置重合
    if(food.x === head.x && food.y == head.y){
        // console.log("蛇头吃到食物了");
        // 蛇的尾巴增长1节
        this.snake.grow();
        // 食物位置随即更新
        this.changeFood();
    }
}
// 更新食物的坐标
Game.prototype.changeFood = function(){
    // 随机生成食物的坐标
    let x = parseInt(Math.round(Math.random() * this.map.x));
    let y = parseInt(Math.round(Math.random() * this.map.y));
    
    // 循环蛇的每一节身体
    for(let i = 0; i < this.snake.arr.length; i++){
        // 获取蛇的单节
        let one = this.snake.arr[i];
        // 检测生成的食品是否与蛇的身体重合
        if(one.x === x && one.y === y){
            // 说明随机生成的食物与蛇的身体重合了 - 
            // 重新更新一遍食物的坐标
            // console.log("重合")
            this.changeFood();
            return;
        }
    }
    // 更新食物的方法
    this.food.change(x,y);
}

// 游戏结束
Game.prototype.gameOver = function(){
    // play锁
    this.play = false;
    // 停止计时器
    clearInterval(this.timer);
    // 告别用户
    alert("游戏结束");
}

// 检测蛇头吃到自己的身体
Game.prototype.checkSnake = function(){
    // 找到蛇头
    let head = this.snake.arr[this.snake.arr.length - 1];

    // 循环蛇的每一节身体
    for(let i = 0; i < this.snake.arr.length - 1; i++){
        // 蛇的每节身体
        let snakeBody = this.snake.arr[i];
        if(head.x === snakeBody.x && head.y === snakeBody.y){
            // 表示蛇头和自己的身体重合,游戏结束
            this.gameOver();
        }
    }
}

// 检测蛇头是否撞墙
Game.prototype.checkWall = function(){
    let head = this.snake.arr[this.snake.arr.length - 1];
    // console.log(head.x);
    // 与墙的四个边缘做对比
    if(head.x < 0 || head.y < 0 || head.x >= this.map.x || head.y >= this.map.y){
        this.gameOver();
    }

}

更多css小动画,js小案例见我的主页:

Javascript小案例

CSS小动画

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值