js实现贪吃蛇

news2024/12/24 2:05:46

文章目录

  • 实现方法_1
    • 1实现效果
    • 2 实现步骤
      • 2.1 移动场地
      • 2.2 游戏难度
      • 2.3 造蛇和食物
      • 2.4 蛇的移动
      • 2.5 产生食物的随机位置
    • 3 全部代码
  • 实现方法_2
    • 1 实现效果
    • 2实现想法
      • 2.1 蛇的存储和显示
      • 2.2 蛇的移动(`重难点`)
      • 2.3 吃食物
    • 3 完整代码

实现方法_1

1实现效果

在这里插入图片描述

2 实现步骤

html部分忽略,布局写的太辣眼了

2.1 移动场地

用的表格td,利用双层for循环加上字符串的拼接,最终利用innnerHTML将布局显示

for (let i = 0; i < height; i++) {
            str += '<tr>';
            for (let j = 0; j < width; j++) {
                if (i == 0) {
                    str += '<td>' + j + '</td>';
                } else if (j == 0) {
                    str += '<td>' + i + '</td>';
                } else {
                    str += '<td></td>';
                }
            }
            str += '</tr>';
        }
        document.querySelector('table').innerHTML = str;

2.2 游戏难度

更改定时器的时间(简单)

let difficulty = document.querySelector("#difficulty");
        difficulty.onchange = function () {
            if (!isPlay) {
                time = difficulty.value;
                clearInterval(timeId);
                difficulty.preventDefault();
            }

        }

2.3 造蛇和食物

利用一维数组存放蛇,但是因为需要确定蛇头和蛇身的位置需要横纵坐标,所以存放的是键值对形式的数据 {key_x: x_value , key_y: y_value}; ,如何显示蛇呢,道理和js实现动漫拼图1.0版中initEvent函数哪里说的的原理一样。snake一维数组记录的蛇身位置,移动场地是有n个tds组成的,通过snake记录的横纵坐标计算出对应的tds[index]中的index值(index=行×width+列),然后将这个td更换背景,来显示蛇。同理显示食物也是如此。

// 初始化蛇
        function initSnake() {
            let snake = [];
            snake[0] = getRandom();
            let x = snake[0].x;
            let y = snake[0].y;
            snake[1] = {
                x: x - 1,
                y: y
            };
            snake[2] = {
                x: x - 2,
                y: y
            };
            return snake;
        }
 //显示画面
        function show(snake, food) {
            // 1 清除所有蛇和食物
            tds.forEach(function (item) {
                item.style.backgroundColor = '';
            });
            // 2 显示蛇
            snake.forEach(function (item, id) {
                let i = parseInt(item.x);
                let j = parseInt(item.y);
                console.log(i, j);
                let tdId = j * width + i;
                console.log(tdId);
                if (id == 0) {
                    tds[tdId].style.backgroundColor = 'blue';
                } else {
                    tds[tdId].style.backgroundColor = 'green';
                }
            });
            // 3 显示食物 
            let tdFoodId = food.y * width + food.x;
            tds[tdFoodId].style.backgroundColor = 'red';
        }

2.4 蛇的移动

监听键盘的按键按下事件

通过按对应的wasd上左下右,来更改d的值,表示蛇头的移动方向,调用自己写的move函数,实现对应的行向或列向的加加减减实现上下移动,在配合上定时器,便实现了蛇的移动,但这是仅限于单个块的移动(蛇头),那其他块(蛇身)怎么正确随蛇身移动呢?(需要注意越界问题)

这里方法就很巧妙了,我们存放蛇用的是一维数组,而且一维数组有两个方法
pop:移除数组的尾部元素,并返回该值。
unshift:在数组头部添加新的元素。
思路:这里我们先不去考虑越界的情况,假设蛇头的下一个位置都是合法的。
首先,通过我们的按键事件,获得我们预使蛇头向那个方向移动,然后计算出新的蛇头,利用unshift将新蛇头加入snake数组,在利用pop移除最后面的一个(前面加一个,右面移除一个,这样就实现了蛇的移动,我们可以很容易想到整体一条直线的时候确实可以,那要是拐弯呢?也是可以的)

在这里插入图片描述
蛇在吃食物时,食物的位置肯定和新蛇头是重合,吃过食物之后蛇的长度应该加一,此时呢就只需要调用unshift函数,加入新蛇头就行了,不用移除后一个,在随机一个食物位置即可。
在这里插入图片描述

 // 4.1 蛇的移动方向
        document.addEventListener("keydown", direction);

        function direction(event) {
            if (event.keyCode == 65 && d != "RIGHT") {
                d = "LEFT";
            } else if (event.keyCode == 87 && d != "DOWN") {
                d = "UP";
            } else if (event.keyCode == 68 && d != "LEFT") {
                d = "RIGHT";
            } else if (event.keyCode == 83 && d != "UP") {
                d = "DOWN";
            } else if (event.keyCode == 32) {
                // 暂停
                suspendGame();
            } else if (event.keyCode == 82) {
                // 重新开始
                restartGame();
            } else if (event.keyCode == 66 && !isPlay) {
                // 开始
                startGame();
            }
        }
        // 4.2 蛇的移动
        function move(snake) {
            let snakeX = snake[0].x;
            let snakeY = snake[0].y;
            let isEat = false;
            if (d == "LEFT") snakeX -= speed;
            if (d == "UP") snakeY -= speed;
            if (d == "RIGHT") snakeX += speed;
            if (d == "DOWN") snakeY += speed;
            if (snakeX == food.x && snakeY == food.y) {
                isEat = true;
                let tdId = food.y * width + food.x;
                tds[tdId].style.backgroundColor = 'lightgray';
                score += 5;
                scoreSpan.innerHTML = score;
                food = getFood(snake);
            }
            let newHead = {
                x: snakeX,
                y: snakeY
            };
            // 检测是否越界,或者自己碰到自己
            if (snakeX < 0 || snakeX >= width || snakeY < 0 || snakeY >= height || collision(newHead, snake)) {
                endGame();
            } else {
                // 没有吃到,移除最后一个,在头部加一个,蛇长不变
                // 吃到,if不成立,蛇长不减,蛇头加一,整体变长一
                if (!isEat) {
                    snake.pop();
                }
                snake.unshift(newHead);
                console.log("newHead=" + newHead.x + "," + newHead.y);
            } 

越界问题
就是构造蛇头的时候,当前的蛇头如果已经处在上下左右某个边界了,肯定是不能在朝这个蛇头方向在移动了(这时候构造出来的新的蛇头的横纵坐标,肯定是不合法的,这里用的一维数组感觉不明显,下面的实现方法2,利用二维数组存储蛇时,这种情况,直接会索引越界报错的),而且根据游戏规则,出现越界问题时,也就是游戏结束之时(所谓的撞墙了)。所以呢,我们在产生新的蛇头坐标时,先去检查是否合法,确保不越界了,再去unshift加入数组首部,不然直接结束游戏;还有一种结束游戏,就是新的蛇头的位置,是在蛇的身体某个位置,就是自己碰自己,也直接结束游戏。

2.5 产生食物的随机位置

食物的随机位置不能在蛇身上

 // 产生随机数字(游戏开始时蛇的随机位置和食物的随机位置)
        function getRandom() {
            // 蛇头的产生范围限定在(2-44)
            let ran_x = parseInt(Math.random() * (width - 4)) + 2;
            let ran_y = parseInt(Math.random() * height);
            return {
                x: ran_x,
                y: ran_y
            };
        }
        // 检查食物随机产生的位置是否在蛇的身体上
        function getFood(snake) {
            while (true) {
                let food = getRandom();
                let flag = true;
                for (let i = 0; i < snake.length; i++) {
                    let item = snake[i];
                    if (item.x == food.x && item.y == food.y) {
                        flag = false;
                        break;
                    }
                }
                if (flag) {
                    return food;
                }
            }
        }

其他就是一些繁琐的获取元素,添加事件,测试逻辑,该bug了

3 全部代码

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>贪吃蛇</title>
    <style>
        table {
            border-collapse: collapse;
        }

        .body {
            display: flex;
            height: 600px;
            background-color: lightblue;
        }

        td {
            border: 1px solid black;
            width: 15px;
            height: 15px;
            margin-left: 4px;
            font-size: 10px;
        }

        .print {
            flex: 70%;
            background-color: aliceblue;
            border: 1px solid red;
            width: 800px;
        }

        .content {
            margin: 0 auto;
            margin-top: 30px;
            width: 800px;
            background-color: lightgray;
        }

        .option {
            margin-top: 40px;
            flex: 20%;
        }

        .text1 {
            text-align: center;
        }

        button {
            margin-top: 10px;
            margin-bottom: 10px;
            width: 100px;
            height: 40px;
            background-color: orange;
            border: none;
            border-radius: 20px;
        }

        button:hover {
            background-color: yellow;
        }

        .snake,
        .snake1 {
            display: inline-block;
            width: 20px;
            height: 20px;
            background-color: blue;

        }

        .snake1 {
            background-color: green;
        }

        .food {
            display: inline-block;
            width: 20px;
            height: 20px;
            background-color: red;
        }

        .text2 {
            display: inline-block;
            width: 170px;
            font-size: 14px;
            width: 120px;
        }

        .bt {
            padding-left: 30px;
            box-sizing: border-box;
        }

        .score {
            text-align: center;
        }

        .score_span {
            display: block;
            margin-top: 5px;
            color: red;
            font-size: 30px;
        }

        select {
            margin-top: 10px;
            margin-bottom: 10px;
            width: 100px;
            height: 40px;
            background-color: lightcoral;
            border: none;
            text-align: center;
            border-radius: 20px;
        }
        .move span{
            width: 20px;
            height: 20px;
            display: inline-block;
            border: 1px solid #000;
            color: black;
            background-color: white;
            box-shadow: 2px 2px  #000;
        }
       
        .w{
            margin-bottom: 2px;
            margin-right: 14%;
        }
    </style>
</head>

<body>
    <div class="body">
        <div class="print">
            <div class="content">
                <table>

                </table>
            </div>
        </div>
        <div class="option">
            <div class="text1">
                蛇:<span class="snake"></span><span class="snake1"></span><span class="snake1"></span>
                &nbsp;&nbsp;&nbsp;食物:<span class="food"></span>
            </div>
            <div class="bt">
                <span class="text2">按B键开始/继续:</span><button>开始游戏</button>
            </div>
            <div class="bt">
                <span class="text2">按空格键暂停:</span> <button>暂停游戏</button>
            </div>
            <div class="bt">
                <span class="text2">按R键重开:</span> <button>重新开始</button>
            </div>
            <div class="bt">
                <span class="text2">游戏难度:</span>
                <select id="difficulty">
                    <option value="500">简单</option>
                    <option value="300">一般</option>
                    <option value="100">困难</option>
                    <option value="50">噩梦</option>
                </select>
                <p style="color: red;">选择好难度后,按开始游戏(或B键)</p>
            </div>

            <div class="score">
                游戏得分
                <span class="score_span">0</span>
                <div class="move">
                    移动:
                    <span class="w">W</span>
                    <div>
                        <span class="A">A</span>
                        <span class="S">S</span>
                        <span class="D">D</span>
                    </div>
                </div>
                <p style="font-size: small;">
                    说明:在游戏过程中更换游戏难度无效。<br>
                    若要更换难度:<br>
                    可在开始游戏前选择好难度<br>
                    或者暂停游戏选好难度后,再点击开始<br>
                    或者点击重新开始,然后选择难度后,再点击开始

                </p>
            </div>
        </div>
    </div>
    <script>
        // 1 初始化场景
        let str = '';
        let width = 45;
        let height = 30;
        let isPlay = false;
        // 获取分数
        let score = 0;
        let scoreSpan = document.querySelector('.score_span');
        // 蛇的移动速度(定时器的时间)
        let timeId = 0;
        let time = 300;
        const speed = 1;
        let d = "RIGHT";
        for (let i = 0; i < height; i++) {
            str += '<tr>';
            for (let j = 0; j < width; j++) {
                if (i == 0) {
                    str += '<td>' + j + '</td>';
                } else if (j == 0) {
                    str += '<td>' + i + '</td>';
                } else {
                    str += '<td></td>';
                }
            }
            str += '</tr>';
        }
        document.querySelector('table').innerHTML = str;
        let difficulty = document.querySelector("#difficulty");
        difficulty.onchange = function () {
            if (!isPlay) {
                time = difficulty.value;
                clearInterval(timeId);
                difficulty.preventDefault();
            }

        }
        // 2 初始化蛇
        let snake = initSnake();
        // 3 初始化食物,食物产生的随机位置不要在蛇身上
        let food = getFood(snake);
        // 获取按钮
        let bt = document.querySelectorAll('button');
        // 获取所有td
        let tds = document.querySelectorAll('td');
        bt[0].onclick = startGame;
        bt[1].onclick = suspendGame;
        bt[2].onclick = restartGame;
        // 初始化
        show(snake, food);
        // 4 蛇的移动
        // 4.1 蛇的移动方向
        document.addEventListener("keydown", direction);

        function direction(event) {
            if (event.keyCode == 65 && d != "RIGHT") {
                d = "LEFT";
            } else if (event.keyCode == 87 && d != "DOWN") {
                d = "UP";
            } else if (event.keyCode == 68 && d != "LEFT") {
                d = "RIGHT";
            } else if (event.keyCode == 83 && d != "UP") {
                d = "DOWN";
            } else if (event.keyCode == 32) {
                // 暂停
                suspendGame();
            } else if (event.keyCode == 82) {
                // 重新开始
                restartGame();
            } else if (event.keyCode == 66 && !isPlay) {
                // 开始
                startGame();
            }
        }

        // 4.2 蛇的移动
        function move(snake) {
            let snakeX = snake[0].x;
            let snakeY = snake[0].y;
            let isEat = false;
            if (d == "LEFT") snakeX -= speed;
            if (d == "UP") snakeY -= speed;
            if (d == "RIGHT") snakeX += speed;
            if (d == "DOWN") snakeY += speed;
            if (snakeX == food.x && snakeY == food.y) {
                isEat = true;
                let tdId = food.y * width + food.x;
                tds[tdId].style.backgroundColor = 'lightgray';
                score += 5;
                scoreSpan.innerHTML = score;
                food = getFood(snake);
            }
            let newHead = {
                x: snakeX,
                y: snakeY
            };
            // 检测是否越界,或者自己碰到自己
            if (snakeX < 0 || snakeX >= width || snakeY < 0 || snakeY >= height || collision(newHead, snake)) {
                endGame();
            } else {
                // 没有吃到,移除最后一个,在头部加一个,蛇长不变
                // 吃到,if不成立,蛇长不减,蛇头加一,整体变长一
                if (!isEat) {
                    snake.pop();
                }
                snake.unshift(newHead);
                console.log("newHead=" + newHead.x + "," + newHead.y);
            }


        }
        //显示画面
        function show(snake, food) {
            // 1 清除所有蛇和食物
            tds.forEach(function (item) {
                item.style.backgroundColor = '';
            });
            // 2 显示蛇
            snake.forEach(function (item, id) {
                let i = parseInt(item.x);
                let j = parseInt(item.y);
                console.log(i, j);
                let tdId = j * width + i;
                console.log(tdId);
                if (id == 0) {
                    tds[tdId].style.backgroundColor = 'blue';
                } else {
                    tds[tdId].style.backgroundColor = 'green';
                }
            });
            // 3 显示食物 
            let tdFoodId = food.y * width + food.x;
            tds[tdFoodId].style.backgroundColor = 'red';
        }
        // TODO 功能按钮的实现
        // 开始游戏
        function startGame() {
            // clearInterval(timeId);
            isPlay = true;
            time = difficulty.value;
            timeId = setInterval(() => {
                move(snake);
                show(snake, food);
            }, time)
        }
        // 游戏结束
        function endGame() {
            clearInterval(timeId);
            isPlay = false;
            alert("游戏结束");
        }
        // 暂停游戏
        function suspendGame() {
            if (isPlay) {
                clearInterval(timeId);
                isPlay = false;
                alert("游戏暂停");
            }
        }

        // 重新开始
        function restartGame() {
            // 关闭之前的定时器
            clearInterval(timeId);
            score = 0;
            scoreSpan.innerHTML = score;
            snake = initSnake();
            timeId = 0;
            time = 300;
            d = 'RIGHT';
            isPlay = false;
            food = getFood(snake);
            show(snake, food);
        }
        // 产生随机数字(游戏开始时蛇的随机位置和食物的随机位置)
        function getRandom() {
            // 蛇头的产生范围限定在(2-44)
            let ran_x = parseInt(Math.random() * (width - 4)) + 2;
            let ran_y = parseInt(Math.random() * height);
            return {
                x: ran_x,
                y: ran_y
            };
        }
        // 检查食物随机产生的位置是否在蛇的身体上
        function getFood(snake) {
            while (true) {
                let food = getRandom();
                let flag = true;
                for (let i = 0; i < snake.length; i++) {
                    let item = snake[i];
                    if (item.x == food.x && item.y == food.y) {
                        flag = false;
                        break;
                    }
                }
                if (flag) {
                    return food;
                }
            }
        }
        // 初始化蛇
        function initSnake() {
            let snake = [];
            snake[0] = getRandom();
            let x = snake[0].x;
            let y = snake[0].y;
            snake[1] = {
                x: x - 1,
                y: y
            };
            snake[2] = {
                x: x - 2,
                y: y
            };
            return snake;
        }
        // 检查是否碰到自己
        function collision(head, array) {
            for (let i = 0; i < array.length; i++) {
                if (head.x == array[i].x && head.y == array[i].y) {
                    return true;
                }
            }
            return false;
        }
    </script>
</body>

</html>

实现方法_2

完全不同的想法,很考验逻辑思维哟,做好准备

1 实现效果

没有做很好的美化,只是实现功能

在这里插入图片描述

2实现想法

2.1 蛇的存储和显示

这里蛇的存储是借助了二维数组,利用二维数组的行列情况来记录位置,达到方式一中的直接在一维数组中记录{x,y}的效果;然后在二维数组里面全部初始化为undefined(其他的<0的值也行,主要是一种初始化标记),为甚小于0呢?因为这里我们给会在后面给二维数组里面赋值(0代表食物,1到n代表蛇,1是蛇头,后面紧邻的2,3,4,…n是蛇身)

在这里插入图片描述
显示的时候,就是普通的遍历二维数组获取里面的值,做不同的处理,更换背景色。

    function show() {
            snake.forEach((subArr, i) => {
                subArr.forEach((s, j) => {
                    let index = i * width + j;
                    if (s == undefined) {//未定义的就是普通的td
                        tds[index].style.backgroundColor = '';
                    } else if (s == 0) {//食物
                        tds[index].style.backgroundColor = 'red';
                    } else {
                        if (s == 1) {//蛇头
                            tds[index].style.backgroundColor = 'black';
                        } else {//蛇身
                            tds[index].style.backgroundColor = 'green';
                        }
                    }
                })
            })
        }

2.2 蛇的移动(重难点)

与上面的完全不同,上面有点取巧了,这里的考验思维了哟!

不用多说,首先肯定是去监听键盘,获取移动方向,根据不同的方向,构造出新的蛇头位置。这里构造出的新的蛇头和上面一样也要考虑越界情况(同上面)。下面我们默认新蛇头都是合法的,那么如何实现移动呢?交换

我们把新的蛇头称作目标点,把当前的蛇头称为原始点,用两个变量nextX和nextY来记录新的蛇头的横纵坐标,用x0和y0来记录当前蛇头的坐标,我们就是通过有限次数(蛇长度)的交换记录蛇的二维数组的snake[x0][y0] 和snake[nextX][nextY]里面的值,来实现蛇的移动的,当然并没这么简单(我们之前说了蛇头处存的值是1,蛇身上存的值是2-n,这还没用到的呢!)

先来个简单的示例:(右移,图中标错了)
在这里插入图片描述
通过上述我们可以知道,我们用的nextX和nextY的值和x0和y0的值,肯定是动态变化的,如何变?
显然:第一步的移动中(蛇头的移动)nextX和nextY的值记录的就是新蛇头的位置坐标,在后面的过程中,nextX和nextY的值就是前一次交换过程中原始块的值(比如,蛇头前移一下,原始蛇头的位置,就是下一个原始块的目标位【用目标块记录】)。

整体在一条线上时,我们都好理解。
那么问题来了? 要是蛇身盘踞呢?还有就是我们通过for循环控制交换实现蛇的移动,我们每次循环多少次呢?
先说简单的,交换多少次,我们定义一个变量用来记录蛇长,for循环的次数就是当前蛇的长度(蛇头蛇身都要和目标块交换)。
再来说这个最不好想的,就是蛇的转向和盘踞移动,这里就用到了我们前面给蛇身上的标号,看下图详解。

(右移,图中标错了)
在这里插入图片描述

找寻标号,我们只需要找寻上一个原始块所处位置的下左右四个方位的即可。
当然找的时候也要注意到越界的情况:

上下越界:
我们定义的snake是一维数组,在初始化时,动态的给他添加行(里面放一维的数组),在再一维里面放undefined,这样构成的二维数组,这样当我们上下找寻标号的时候,就可能出现arr[i][j]i的索引值的越界情况,这个时候找寻的arr[i]是undefined,我们在去找arr[i][j],就是去找undefined中的j位置的值,肯定是语法错误的,会报错,这时我们就需要对这种情况做处理。
左右越界:
这种情况是,我们找的i索引肯定是不会出现越界的,但是j可能会越界。但是呢,这时就算越界了,我们arr[i][j]的结果最多就是得到一个undefined,然而这并没什么大的影响,所以这里我们就不处理了。

在这里插入图片描述

// 根据方向做不同的操作
                let nextX, nextY;
                if (direction == 37) {
                    // 左
                    nextX = snakeHeadX;
                    nextY = snakeHeadY - 1;
                } else if (direction == 38) {
                    // 上
                    nextX = snakeHeadX - 1;
                    nextY = snakeHeadY;
                } else if (direction == 39) {
                    // 右
                    nextX = snakeHeadX;
                    nextY = snakeHeadY + 1;
                } else if (direction == 40) {
                    // 下
                    nextX = snakeHeadX + 1;
                    nextY = snakeHeadY;
                }
                // 判断游戏结束
                if (nextX >= height || nextX < 0 || nextY >= width || nextY < 0 || snake[nextX][nextY] > 0) {
                    clearInterval(timeId);
                    alert("游戏结束");
                    return;
                }
                // 原点记录每一个蛇身小格的前一个
                let x0 = snakeHeadX;
                let y0 = snakeHeadY;
                // 记录新的蛇头
                snakeHeadX = nextX;
                snakeHeadY = nextY;
                for (let i = 1; i <= snakeLength; i++) {
                    let temp = snake[x0][y0];
                    snake[x0][y0] = snake[nextX][nextY];
                    snake[nextX][nextY] = temp;
                    // 蛇身交换后,原点记录的蛇身小格的前一个,就是蛇身下个小格要前移的目标
                    nextX = x0;
                    nextY = y0;
                    // 查找上下左右四个方位,看snake二维数组中的值,是不是上面刚交换的下一个值(在蛇身上放的数值1,2,3...)
                    // 向上找
                    if ((nextX - 1) > -1 && snake[nextX - 1][nextY] == i + 1) {
                        x0--;
                    }
                    // 向下找
                    if ((nextX + 1) < height && snake[nextX + 1][nextY] == i + 1) {
                        x0++;
                    }
                    // 向左找
                    if (snake[nextX][nextY - 1] == i + 1) {
                        y0--;
                    }
                    // 向右找
                    if (snake[nextX][nextY + 1] == i + 1) {
                        y0++;
                    }
                }

2.3 吃食物

这里不同于上面的方法一。同样,当新蛇头的位置与食物的位置重合时(食物身上的值为0),说明吃到食物,我们将记录蛇长的变量+1,同时在完成最后一次交换后x0和y0的值,就是之前蛇的尾部的位置,我们将这个位置在二维数组中做上标号(上边提到的2-n的标号),这也就是新的蛇尾,然后在随机一个食物位置即可。

当然这里随机食物,和之前的想法一样,不能随机到蛇身上,而我们给存放蛇的二维数组做了初始化,除了蛇之外都是undefined,当随机的位置是undefined时,就说明不在蛇身上。

if (snake[x0][y0] == 0) {
	snake[x0][y0] = snakeLength + 1;
    snakeLength++;
    randomFood();
}

其他的就没什么了,就是绑定事件,测试调整逻辑

3 完整代码

<!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>
        * {
            margin: 0;
            padding: 0;
        }

        td {
            border: 1px solid #000;
            width: 20px;
            height: 20px;
            font-size: 13px;
        }


        .div1 {
            background-color: lightblue;
            height: 30px;
            padding-top: 5px;
            padding-left: 30px;
            box-sizing: border-box;
        }
    </style>
</head>

<body>
    <div class="div1">
        <button id="start">开始游戏</button>
        <button id="stop">暂停游戏</button>
        难度等级
        <select id="select">
            <option value="500">简单</option>
            <option value="300">一般</option>
            <option value="100">困难</option>
            <option value="40">噩梦</option>
        </select>
        场地大小
        <select id="size">
            <option value="15">15x15</option>
            <option value="29">29x29</option>
            <option value="35">35x29</option>
            <option value="50">50x29</option>
            <option value="60">60x29</option>
        </select>
    </div>
    <div class="div2">
        <table>

        </table>
    </div>
    <script>
        let tableContent = '';
        let width;
        let height = 29;
        let snake = [];
        let timeId = 0;
        let time = 500;
        let table = document.querySelector('table');
        let snakeLength;
        let direction;
        let tds;
        let snakeHeadX;
        let snakeHeadY;
        let isStop = false;
        let isPlay = false;
        let select = document.querySelector('#select');
        select.onchange = function () {
            time = this.value;
        }
        let sizeSelect = document.querySelector('#size');
        sizeSelect.onchange = function () {
            width = this.value;
        }

        window.onkeyup = function (e) {
            // 左37 上38 右39 下40
            if (e.keyCode >= 37 && e.keyCode <= 40) {
                if (((direction == 37 || direction == 39) && (e.keyCode == 38 || e.keyCode == 40)) ||
                    ((direction == 38 || direction == 40) && (e.keyCode == 37 || e.keyCode == 39))) {
                    direction = e.keyCode;
                }
            }
        }
        let startBtn = document.querySelector('#start');
        startBtn.onclick = function () {
            clearInterval(timeId);
            tableContent = '';
            snake = [];
            snakeLength = 2;
            direction = 39;
            initTable();
            startGame();
            randomFood();
            if (!isPlay) {
                startBtn.innerHTML = '重新开始';
            } else {

                startBtn.innerHTML = '开始游戏';
            }
            isPlay = !isPlay;
        }
        let stopBtn = document.querySelector('#stop');
        stopBtn.onclick = function () {
            if (!isStop) {
                clearInterval(timeId);
                stopBtn.innerHTML = '继续游戏';
            } else {
                startGame();
                stopBtn.innerHTML = '暂停游戏';
            }
            isStop = !isStop;
        }

        function initTable() {
            width = sizeSelect.value;
            if(width==15) {
                height=15;
            }else{
                height=29;
            }
            time = select.value;
            for (let i = 0; i < height; i++) {
                tableContent += '<tr>';
                snake[i] = [];
                for (let j = 0; j < width; j++) {
                    if (i == 0) {
                        tableContent += '<td>' + j + '</td>';
                    } else if (j == 0) {
                        tableContent += '<td>' + i + '</td>';
                    } else {
                        tableContent += '<td></td>';
                    }
                    snake[i][j] = undefined;
                }
                tableContent += '</tr>';
            }
            table.innerHTML = tableContent;
            snakeHeadX = parseInt(height/2);
            snakeHeadY = parseInt(width/2);
            snake[snakeHeadX][snakeHeadY] = 1;
            snake[snakeHeadX][snakeHeadY - 1] = 2;
            tds = document.querySelectorAll('td');
        }

        function show() {
            snake.forEach((subArr, i) => {
                subArr.forEach((s, j) => {
                    let index = i * width + j;
                    if (s == undefined) {
                        tds[index].style.backgroundColor = '';
                    } else if (s == 0) {
                        tds[index].style.backgroundColor = 'red';
                    } else {
                        if (s == 1) {
                            tds[index].style.backgroundColor = 'black';
                        } else {
                            tds[index].style.backgroundColor = 'green';
                        }
                    }
                })
            })
        }

        function startGame() {
            timeId = setInterval(() => {
                // 根据方向做不同的操作
                let nextX, nextY;
                if (direction == 37) {
                    // 左
                    nextX = snakeHeadX;
                    nextY = snakeHeadY - 1;
                } else if (direction == 38) {
                    // 上
                    nextX = snakeHeadX - 1;
                    nextY = snakeHeadY;
                } else if (direction == 39) {
                    // 右
                    nextX = snakeHeadX;
                    nextY = snakeHeadY + 1;
                } else if (direction == 40) {
                    // 下
                    nextX = snakeHeadX + 1;
                    nextY = snakeHeadY;
                }
                // 判断游戏结束
                if (nextX >= height || nextX < 0 || nextY >= width || nextY < 0 || snake[nextX][nextY] > 0) {
                    clearInterval(timeId);
                    alert("游戏结束");
                    return;
                }
                // 原点记录每一个蛇身小格的前一个
                let x0 = snakeHeadX;
                let y0 = snakeHeadY;
                // 记录新的蛇头
                snakeHeadX = nextX;
                snakeHeadY = nextY;
                for (let i = 1; i <= snakeLength; i++) {
                    let temp = snake[x0][y0];
                    snake[x0][y0] = snake[nextX][nextY];
                    snake[nextX][nextY] = temp;
                    // 蛇身交换后,原点记录的蛇身小格的前一个,就是蛇身下个小格要前移的目标
                    nextX = x0;
                    nextY = y0;
                    // 查找上下左右四个方位,看snake二维数组中的值,是不是上面刚交换的下一个值(在蛇身上放的数值1,2,3...)
                    // 向上找
                    if ((nextX - 1) > -1 && snake[nextX - 1][nextY] == i + 1) {
                        x0--;
                    }
                    // 向下找
                    if ((nextX + 1) < height && snake[nextX + 1][nextY] == i + 1) {
                        x0++;
                    }
                    // 向左找
                    if (snake[nextX][nextY - 1] == i + 1) {
                        y0--;
                    }
                    // 向右找
                    if (snake[nextX][nextY + 1] == i + 1) {
                        y0++;
                    }
                }

                if (snake[x0][y0] == 0) {
                    snake[x0][y0] = snakeLength + 1;
                    snakeLength++;
                    randomFood();
                }
                //显示蛇和食物
                show();
            }, time)
        }

        function randomFood() {
            while (true) {
                let x = Math.floor(Math.random() * height);
                let y = Math.floor(Math.random() * width);
                console.log(x, y);
                if (snake[x][y] == undefined) {
                    snake[x][y] = 0;
                    break;
                }
            }
        }
    </script>
</body>

</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1419080.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

酒鬼酒2024年展望:稳发展动能,迈入恢复性增长轨道

文 | 琥珀酒研社 作者 | 渡过 最近几个月来&#xff0c;白酒估值回落到近十年来低位&#xff0c;反映出了整个白酒行业的市场低迷和虚弱现状。不管是头部企业五粮液、泸州老窖&#xff0c;还是区域酒企口子窖、金种子酒等&#xff0c;最近都通过“回购”或“增持”&#xff0…

python之组合数据类型-列表

列表操作 列表增删改查列表增加元素的方法列表删除元素的方法列表修改元素的方法列表查找元素的方法 列表其他常用方法列表的切片用法列表修改排序的方法列表的常用符号、常用函数 列表是什么&#xff1f; 列表是有序集合&#xff0c;列表可以一次性存储几个或几万个元素&#…

【安装指南】HBuilder X 下载、安装详细教程

目录 &#x1f33a;1. 概述 &#x1f33b;2. HBuilder X 安装包下载 &#x1f33c;3. 安装详细教程 &#x1f33a;1. 概述 HBuilder X 是一款由DCloud开发的基于Electron框架的集成开发环境&#xff08;IDE&#xff09;&#xff0c;主要用于Web和移动应用程序的开发。以下是…

表贴式PMSM的直接转矩控制(DTC)MATLAB仿真模型

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 模型简介 表贴式PMSM的直接转矩控制(DTC),直接使用滞环控制对转矩和磁链进行控制&#xff0c;相对于传统的FOC控制而言&#xff0c;其不需要进行解耦变换&#xff0c;在此次的有以下几点需要注意&#xff1a…

uniCloud ---- JQL语法 连表查询

目录 JQL数据库操作 JQL流程图解 JQL的限制 单表查询 联表查询 新增 修改 删除 联表查询 例子 字段过滤field 字段别名as 限制查询记录的条数limit 只查一条记录getone JQL数据库操作 JQL&#xff0c;全称 javascript query language&#xff0c;是一种js方式操…

Chapter 8 - 1. Congestion Management in TCP Storage Networks

This chapter covers the following topics. 本章包括以下主题。 Understanding congestion in TCP storage networks. Detecting congestion in TCP storage networks. Traffic patterns with iSCSI and NVMe/TCP and correlation with network congestion. Preventing co…

Linux——文本编辑器Vim

Linux中的所有内容以文件形式管理&#xff0c;在命令行下更改文件内容&#xff0c;常常会用到文本编辑器。我们首选的文本编辑器是Vim&#xff0c;它是一个基于文本界面的编辑工具&#xff0c;使用简单且功能强大&#xff0c;更重要的是&#xff0c;Vim是所有Linux发行版本的默…

【项目设计】仿muduo实现高性能服务器组件

尊敬的玩家您好 系统检查到您的主线任务进展缓慢 请您不要过度沉迷日常任务&#xff0c;尽快完成您的主线任务 请勿在npc身上浪费太多时间&#xff0c;避免影响玩家自身情绪 《地球Online》祝您游戏愉快 文章目录 一、实现目标1. 项目定位2. Reactor模式 二、前置知识1. C11的b…

【数据分析】numpy基础第一天

文章目录 前言本文代码&#xff1a;使用jupyter notebook打开本文的代码操作示例步骤1.打开Anaconda Powershell Prompt步骤2.复制代码文件地址步骤3.在Anaconda Powershell Prompt中打开jupyter notebook步骤3.5.解决一个可能的问题步骤4.在浏览器中查看ipynb文件步骤5.运行代…

【面试深度解析】哔哩哔哩后端面试:JDK 集合源码、线程状态及转换、Future和CompletableFuture、JVM生产命令(下)

欢迎关注公号&#xff08;通过文章导读关注&#xff1a;【11来了】&#xff09;&#xff0c;及时收到 AI 前沿项目工具及新技术的推送&#xff01; 文中所有相关面试资料可从公号领取 文章导读地址&#xff1a;点击查看文章导读&#xff01; 感谢你的关注&#xff01; 前言&…

Android创建工程

语言选择Java&#xff0c;我用的Java 最小SDK&#xff1a;就是开发的APP支持的最小安卓版本 Gradle 是一款Google 推出的基于 JVM、通用灵活的项目构建工具&#xff0c;支持 Maven&#xff0c;JCenter 多种第三方仓库;支持传递性依赖管理、废弃了繁杂的xml 文件&#xff0c;转而…

【JaveWeb教程】(32)SpringBootWeb案例之《智能学习辅助系统》的详细实现步骤与代码示例(5)文件上传的实现

目录 SpringBootWeb案例052. 文件上传2.1 简介2.2 本地存储 SpringBootWeb案例05 前面我们已经实现了员工信息的条件分页查询以及删除操作。 关于员工管理的功能&#xff0c;还有两个需要实现新增和修改员工。 本节的主要内容&#xff1a; 文件上传 2. 文件上传 在我们完成…

GMS测试BTSfail-CVE-2022-20451

描述&#xff1a; 项目需要过GMS兼容性测试&#xff0c;BTS这块我们环境没有&#xff0c;送检之后出现了一个BTS的Alert&#xff0c;这个是必须要解决的。下面的warning可以不考虑。 这个是patch问题&#xff0c;根据代理提供的pdf文件找到一个id:为A-235098883的补丁&#xf…

前缀和入门(c++语言)

在讲算法之前&#xff0c;我们先来思考一个问题&#xff1a;小明有n个编号为1~n的篮子&#xff0c;每个篮子里装有ai个苹果&#xff0c;求从 x至y 的篮子里的苹果数量之和。 如果没学过前缀和的同学&#xff0c;可能会打出这样的代码&#xff1a; 这种算法要得出一个区间之和&…

数字艺术展厅有什么好处,搭建数字艺术展厅要注意什么

引言&#xff1a; 数字艺术展厅是一种利用数字科技手段搭建的艺术展览空间&#xff0c;通过数字化展示艺术品&#xff0c;能够为观众带来全新的艺术体验。那么数字艺术展厅有什么好处&#xff0c;搭建数字艺术展厅要注意什么呢&#xff1f; 一、数字艺术展厅的好处 1.创新艺术…

(2023)逆转诅咒:由“A is B”训练的LLM没有学到“B is A”

The Reversal Curse: LLMs trained on “A is B” fail to learn “B is A” 公和众和号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 2. 实验和结果 4. 讨论和未来工作 0. 摘要 我们…

动手学深度学习(一)深度学习介绍2

目录 二、起源 三、深度学习的成功案例&#xff1a; 四、特点&#xff1a; 五、小结&#xff1a; 二、起源 为了解决各种各样的机器学习问题&#xff0c;深度学习提供了强大的工具。 虽然许多深度学习方法都是最近才有重大突破&#xff0c;但使用数据和神经网络编程的核心思…

代码随想录算法训练营DAY6 | 哈希表(1)

DAY5休息一天&#xff0c;今天重启~ 哈希表理论基础&#xff1a;代码随想录 Java hash实现 &#xff1a;java 哈希表-CSDN博客 一、LeetCode 242 有效的字母异位词 题目链接&#xff1a;242.有效的字母异位词 思路&#xff1a;设置字典 class Solution {public boolean isAnag…

C/C++ - 内存管理(C++)

堆栈 C中的栈和堆是用于存储变量和对象​​的两个主要内存区域。栈是一种自动分配和释放内存的区域&#xff0c;用于存储局部变量和函数调用的上下文。栈上的内存分配和释放是自动进行的&#xff0c;无需手动管理。堆是动态分配内存的区域&#xff0c;用于存储动态创建的对象和…

Blender教程(基础)-内插面、分离、环切、倒角-08

一、内插面 菜单位置如下图位置。 单击需要处理的面&#xff0c;出现一个黄色的圈。 1、菜单选中内插 鼠标悬停在黄色圈内单击左键可以来回实现内插&#xff0c;但是发现并不好操作。 2、快捷键内插 在选中需要操作的面之后&#xff0c;鼠标移动到外面&#xff0c;键盘在英…