【JS基础案例】——贪吃蛇demo

做个小练习,用JS做个贪吃蛇demo~

思路:
①点击按钮开始,开始时要对UI和数据进行重置(清除上一次的游戏情况)

②蛇的位置用一个XY坐标数组表示

③蛇的身体其实就是个由多个XY坐标数组组成的大数组,蛇长大会把最新坐标不断往数组加入元素,因此蛇尾就是数组第一个元素

④爱心/星星 通过随机可以获得,设置一个定时器让爱心定时随机,其位置也是一个XY坐标数组

⑤蛇移动其实也是个定时器,移动时会把蛇尾位置去去除(蛇吃星星长度+1不用去除),也就是删除蛇身体数组第一个元素;

⑥判断蛇头和星星位置是否相等就是是否吃星星

⑦蛇触边、吃自己身体即死,也就是判断是否在格子内

⑧设置一个监听事件用来监听用户按键用以操作蛇移动方向

 

CSS
 * {
  box-sizing: border-box;
}

/* :root {
  height: 100%;
} */

body {
  margin: 0;
}

body>div {
  border: 1px solid red;
  background-color: bisque;
}

.parent {
  margin: 300px auto;
  width: 305px;
  position: relative;
}

h1 {
  text-align: center;
  font-family: cursive;
  color: #a919a1;
}

.gameBox {
  width: 100%;
  height: 305px;
  border-collapse: collapse;
  margin-bottom: 10px;
  position: relative;
  background-color: white;
}

.gameBox>tbody>tr>td {
  border: 1px solid transparent;
}

#state,
#btnStart {
  padding: 8px;
}

.control {
  width: 305px;
  position: relative;
}

#record {
  outline: none;
  width: 140px;
  position: absolute;
  right: 0;
  left: auto;
  font-size: 27px;
  font-weight: bold;
  text-align: center;
  color: crimson;
}

#state {
  user-select: none;
  visibility: hidden;
  outline: none;
}

 

HTML
 <!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title></title>
        <link rel="stylesheet" href="./css/viperstyle.css">
    </head>
    <body>
        <div>
            <div class="parent">
                <h1>贪吃蛇蛇</h1>
                <!-- 游戏格子,使用了table表格 -->
                <table class="gameBox">
                <!-- 格子11*11,table补全即可 -->
                tr*11>td*11
                </table>
                <div class="control">
                    <input id="btnStart" type="button" value="开始" placeholder="" />
                    <span id="state">游戏状态</span>
                    <input id="record" type="text" readonly="readonly" placeholder="计分板" value="0">
                </div>
            </div>
        </div>
    <body>
</html>

部分以table为例

 

JS
 // 游戏结束?
let over = false;
// 获取按钮元素对象
let btn = document.getElementById("btnStart");
// 获取单元格DOM元素对象
let gBox = document.getElementsByClassName("gameBox")[0];

// 开始按钮点击事件
btn.onclick = () => {
    btn.disabled = true;
    state.innerHTML = "游戏已开始";
    state.style.backgroundColor = "green";
    state.style.visibility = 'visible';


    /**** 界面重置代码 ****/
    // 防止下次开始游戏时遗留上次游戏的数据,先对数据进行重置
    resetGames();

    // 鼠标失去焦点,表格gBox获得焦点
    btn.blur()
    gBox.focus();

    // 开始~
    play();
};

// 重置游戏函数,UI、Data
function resetGames() {
    /**** 界面重置代码 ****/
    _arrHeart = [];
    _arrViper = [];
    headX = 6;
    headY = 6;
    first = false;
    _keyCode = 0;
    over = false;
    press = true;
    record.value = '0';
    for (let i = 0; i < rangeXY; i++) {
        for (let j = 0; j < rangeXY; j++) {
            gBox.rows[i].cells[j].style.backgroundColor = 'white';
        }
    }
}

//方格大小,X、Y最大都是11
let rangeXY = 11;

// 蛇初始坐标
let headX = 6,
    headY = 6;
// 记录蛇的身体数据坐标的数组
let _arrViper = [];

// 记录爱心坐标的数组
let _arrHeart = [];

// 玩~
function play() {
    // 生成蛇XY坐标数组,初始位置6,6
    let arrStart = [headX, headY];
    // 生成蛇
    createPoint(arrStart, 'indigo');
    // 保存到蛇身体记录数组中
    _arrViper.push(arrStart);

    // 调用随机生成爱心函数生成爱心
    randomHeart();

    // 蛇移动,调用点移动函数
    viperMove();
}

// 用来在指定范围内随机的函数,返回一个随机数
function randNum(start, end) {
    return Math.floor(start + Math.random() * (end - start - 10));
}

// 给表格中指定坐标单元格进行颜色设置,arrXY指定必须传入一个XY坐标数组
function createPoint(arrXY, strColor) {
    // 因为表格其实就是数组,下标默认从0开始,所以要目标的坐标都要-1		后续代码中所有看到有要-1差不多都是这个情况
    // arrXY[0] X
    // arrXY[1] Y
    let cell = gBox.rows[arrXY[0] - 1].cells[arrXY[1] - 1];
    cell.style.backgroundColor = strColor;
}

// 生成爱心位置函数
function randomHeart() {
    // 定时器,每隔10秒重新生成一次,10000毫秒=10秒
    let timerHeart = setInterval(() => {
        // 判断游戏是否game over
        if (over) {
            clearInterval(timerHeart);
            return;
        }

        // 每次生成前移除之前的爱心点
        // (也就是当爱心记录数组不为空时)
        if (_arrHeart.length !== 0) {
            //消除前还得考虑,如果这个爱心已经被蛇吃掉了,那么就不要消除,也就是格子是印度紫色的话
            removePoint(_arrHeart, 'green');
        }

        // 将随机的坐标保存到爱心坐标数组
        // 但随机之前要先判断随机的点是否是蛇的身体————思路:判断格子是否是印度紫色即可
        _arrHeart = checkHeart(rangeXY, 'indigo');
        // 生成爱心,爱心是绿色
        createPoint(_arrHeart, "green");
    }, 7000); //7000毫秒
}

// 移除爱心位置的函数,传入的参数为爱心XY坐标数组,colorIF:如果格子是这个颜色的话就消掉
function removePoint(arrXY, colorIF) {
    let cell = gBox.rows[arrXY[0] - 1].cells[arrXY[1] - 1];
    if (cell.style.backgroundColor === colorIF) {
        // 消除颜色,也就是设为白色即可
        cell.style.backgroundColor = 'white';
    }
}

// 给爱心的位置进行随机并检查是否在蛇的身体内
function checkHeart(arrXY, color) {
    let x = 0;
    let y = 0;
    let cell = null;
    do {
        x = randNum(arrXY, arrXY);
        y = randNum(arrXY, arrXY);
        cell = gBox.rows[x - 1].cells[y - 1];
    }
    // 判断格子颜色是否为印度紫色即可
    while (cell.style.backgroundColor === color)
    return [x, y];
}

// 第一次按下按键,false 未,true 已
let first = false;
// 记录按键代码的变量,37上,38左,39下,40右
let _keyCode = 0;
// press时候允许用户按下按键
let press = true;

// 事件监听用户按下按键
window.addEventListener("keydown", (e) => {
    // JS是没有枚举类型的,因此使用了switch
    if (btnStart.disabled) {
        // press:是否接收用户按下按键?true按下的按键代码会被记录下来,false:按下没卵用~
        // 这个主要是为了防止界面还未处理完,用户再次按下按键然后_keyCode被改变,导致后续界面处理的时候触发了bug
        if (press) {
            switch (e.keyCode) {
                case 37:
                    // 蛇不能向反方向移动
                    if (_keyCode !== 39) { //下
                        _keyCode = 37; // 上
                    }
                    break;
                case 38:
                    // 蛇不能向反方向移动
                    if (_keyCode !== 40) { //右
                        _keyCode = 38; // 左
                    }
                    break;
                case 39:
                    // 蛇不能向反方向移动
                    if (_keyCode !== 37) { //上
                        _keyCode = 39; // 下
                    }
                    break;
                case 40:
                    // 蛇不能向反方向移动
                    if (_keyCode !== 38) { //左
                        _keyCode = 40; // 右
                    }
                    break;
                default:
                    return; //   ****这里按了别的按键直接就返回了,后面的就不再判断了****
            }

            // 第一次按的时候会更改first为true;后面再按的时候,不会重复进行 first = true
            if (_keyCode !== 0 && !first) {
                first = !first;
            }

            // 当用户按下了按键,我们把接收用户按下按键设为false,也就是用户再次按下按键无效
            // 只有当界面处理完成时才会将press 设为true,然后用户继续按下的按键就能被记录了
            press = false;
        }
    }
});

// 蛇移动函数
function viperMove() {
    // 只有当点击了开始,并且用户按下第一次指定的按键才生效
    if (btnStart.disabled) {
        // 创建一个定时器,用来做蛇移动
        let timerMove = setInterval(() => {
            switch (_keyCode) {
                case 37:
                    headY -= 1; // 上	坐标x - 1
                    break;
                case 38:
                    headX -= 1; // 左	坐标y - 1
                    break;
                case 39:
                    headY += 1; // 下	坐标x + 1
                    break;
                case 40:
                    headX += 1; // 右	坐标y + 1
                    break;
                default:
                    return;
            }

            // 判断是否触边,触边直接Game Over...
            // 也就是会干掉定时器
            //直接try{}catch{}拉闸了
            try {
                // 发现格子是印度紫色的话,就意味着吃到自己的身体了,直接挂掉~
                let cell = gBox.rows[headX - 1].cells[headY - 1];
                if (cell.style.backgroundColor === 'indigo') {
                    throw err;
                }

                //判断是否吃掉爱心,吃掉长度+1,也就是不做清除;判断方法:对比坐标,坐标是数组嘛~那也就是对比数组是否相等咯
                if ([headX, headY].join() !== _arrHeart.join()) {
                    // 先移除蛇尾,移除蛇尾前先把界面改了,再动记录蛇身体的数组,不然界面没法改
                    removePoint(_arrViper[0], 'indigo');
                    // 弹出蛇尾巴,也就是数组第一个元素
                    _arrViper.shift();
                } else {
                    // 吃到爱心,计分板+1
                    let score = parseInt(record.value);
                    record.value = `${++score}`;
                }

                // 将移动位置记录到蛇身体数组中
                createPoint([headX, headY], 'indigo');
                _arrViper.push([headX, headY]);

                // 当界面处理完成,就允许用户继续按键了;这里把press设为true即可~
                press = true;
            } catch {
                /****** 游戏结束时要做的事 ******/

                // Game Over~~~提示
                state.innerHTML = "游戏结束!";
                state.style.backgroundColor = 'crimson';
                alert('Game over~~~');

                // over = true,告诉前边的生成爱心的定时器,游戏结束撩~~~
                over = true;

                // 恢复按钮,以及一些提示
                btn.disabled = false;

                // 定时器消除
                clearInterval(timerMove);
                return;
            }
        }, 300); //300毫秒
    }
}

...其实后续还可以封装成一个类~

 

posted @ 2022-07-17 20:59  Myotsuki  阅读(194)  评论(0编辑  收藏  举报