Bootstrap

html+js实现贪吃蛇小游戏

思路:

  1. 绘制一张40×40的格子,一共1600个格子,也就是一个长度为1600的数组,每个格子代表数组里的一个值,作为游戏区。
  2. 蛇的默认值为:[[0, 12], [0, 13], [0, 14]]的一个二位数组,每一项都代表着蛇身的一部分,例如:[0,12]代表从左上角开始x轴坐标为12,y轴坐标为0的哪一个格子,数组的最后一项为蛇头。
  3. 食物:随机生成一个二维数组,例如:[[5,5]]
  4. 绘制:从第一个格子开始绘制,碰到蛇身变色,碰到蛇头变色,碰到食物变色。
  5. 移动:监听键盘的方向键,判断当前方向,例如:按上键,从蛇数组最后一项开始,蛇头x-1,y不变,以后每一项等于前一项,就形成了蛇前进的效果,,不允许直接掉头。
  6. 设定计时器,每隔一定时间重新绘制一次,就形成了,蛇一直移动的效果。
  7. 蛇头碰到食物,从蛇头开始成长。
  8. 蛇碰到游戏区边缘或者碰到自己均死亡。

1. 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>Document</title>
  <style>
    .box {
      width: 400px;
      height: 400px;
      border: 1px #999 solid;
      margin: 10px auto 0;
    }

    .item {
      width: 9px;
      height: 9px;
      background-color: #fff;
      float: left;
      margin: 0 1px 1px 0;
    }

    .snakeHead {
      background-color: #f00;
    }

    .snakeBody {
      background-color: #f0f;
    }

    .food {
      background-color: #ff0;
    }

    .score {
      margin: 10px auto;
      text-align: center;
      font-size: 26px;
      font-weight: 600;
    }

    .bot-box {
      display: flex;
      justify-content: space-around;
    }

    .handle-box {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      width: 140px;
      grid-gap: 10px;
      height: 100px;
    }

    .handle-box div,
    .key {
      border: 1px #f00 solid;
      width: 40px;
      height: 40px;
      display: flex;
      justify-content: center;
      align-items: center;
      font-size: 18px;
      font-weight: 600;
    }

    .direction-1 {
      grid-column: span 3 / auto;
      margin: 0 auto;
    }

    .key-box {
      display: flex;
      align-items: center;
      margin: 10px 0;

    }

    .key-box .key {
      margin-right: 10px;
    }
  </style>
</head>

<body>
  <!-- 游戏区 -->
  <div class="box" id="box"></div>
  <!-- 得分 -->
  <div class="score">0</div>
  <!-- 操作按钮提示 -->
  <div class="bot-box">
    <!-- 方向键 -->
    <div class="handle-box">
      <div class="direction-1"></div>
      <div class="direction-2"></div>
      <div class="direction-3"></div>
      <div class="direction-4"></div>
    </div>
    <!-- 功能键 -->
    <div>
      <div class="key-box">
        <div class="key">J</div>
        <span>:开始游戏</span>
      </div>
      <div class="key-box">
        <div class="key">K</div>
        <span>:游戏暂停</span>
      </div>
      <div class="key-box">
        <div class="key">R</div>
        <span>:重新开始游戏</span>
      </div>
    </div>
  </div>
</body>

</html>

2、 设置初始值设置

    // 游戏区dom
    const box = document.querySelector('.box')
    // 得分dom
    const score = document.querySelector('.score')
    // 游戏格子
    let gameBox = [40, 40]
    // 游戏开始标志(计时器)
    let start = null
    // 当前速度
    let velocity = 200
    // 食物
    let food = [[5, 5]]
    // 蛇身
    let snake = [[0, 12], [0, 13], [0, 14]]
    // 方向
    let direction = 3

3. 绘制方法

由于y轴对应的游戏区位置是40的倍数,所以使用transferPosition方法进行坐标转换,例如点[5,6],对应的游戏区格子是第40*5+6个格子。

    // 数组转化为相应点坐标
    function transferPosition(array) {
      let ary = []
      for (let i = 0; i < array.length; i++) {
        const e = [...array[i]];
        ary.push(e[0] * gameBox[0] + e[1])
      }
      return ary
    }
    // 绘制
    function draw() {
      // 蛇身+蛇头对应的坐标
      const snakeAry = transferPosition(snake)
      // 食物对应的坐标
      const foodAry = transferPosition(food)
      box.innerHTML = ''
      // 游戏区格子总长度
      const allBox = gameBox[0] * gameBox[0]
      for (let i = 0; i < allBox; i++) {
        let div = document.createElement('div')
        if (snakeAry.indexOf(i) === snakeAry.length - 1) { 
          // 蛇头样式
          div.className = 'item snakeHead'
        } else if (snakeAry.indexOf(i) !== -1) {
          // 蛇身样式
          div.className = 'item snakeBody'
        } else if (foodAry.indexOf(i) !== -1) {
          // 食物样式
          div.className = 'item food'
        } else {
          // 空白区
          div.className = 'item'
        }
        box.appendChild(div)
      }
    }

4. 蛇移动

蛇移动前要先记录蛇当前的位置,蛇头按照方向键的方向进行前进,蛇身要按照蛇身轨迹进行移动,例如:向下走一步,[[0, 12], [0, 13], [0, 14]] ---->[[0, 13], [0, 14], [1, 14]]

    // 蛇移动
   function move() {
     // 深拷贝蛇行走之前的数组
     let inventedSnake = []
     for (let n = 0; n < snake.length; n++) {
       const e = snake[n];
       inventedSnake.push([...e])
     }
     // 判断行进方向,蛇身前进 direction:1左 2上 3右 4下
     const lng = snake.length;
     for (let i = lng - 1; i >= 0; i--) {
       if (i === lng - 1) { // 蛇头前进
         if (direction === 1) {
           snake[i][1] = snake[i][1] - 1
         } else if (direction === 2) {
           snake[i][0] = snake[i][0] - 1
         } else if (direction === 3) {
           snake[i][1] = snake[i][1] + 1
         } else if (direction === 4) {
           snake[i][0] = snake[i][0] + 1
         }
       }
       else { // 蛇身前进
         snake[i] = inventedSnake[i + 1]
       }
     }
   }

5. 随机生成食物

此处每次仅生成一个食物

  // 生成随机数
  function randomNumber() {
    return Number.parseInt(Math.random() * 40)
  }
  // 随机生成食物
  function createFood() {
    food = [[randomNumber(), randomNumber()]]
  }

6. 判断吃到食物,进行成长

	// 判断是否吃到食物
    function eatFood() {
      if (food.length === 0) return
      // 蛇头和食物重合及吃到食物
      if (snake[snake.length - 1].toString() === food[0].toString()) {
        // 成长
        growUp()
        // 重新生成食物
        createFood()
      }
    }

    // 吃到食物 进行成长
    function growUp() {
      let snakeFood = []
      let [x, y] = snake[snake.length - 1]
      // 按照蛇头的方向在蛇头处成长
      if (direction === 1) {
        snakeFood = [x, y - 1]
      } else if (direction === 2) {
        snakeFood = [x - 1, y]
      } else if (direction === 3) {
        snakeFood = [x, y + 1]
      } else if (direction === 4) {
        snakeFood = [x + 1, y]
      }
      snake.push(snakeFood)
      // 重算积分(积分=蛇当前长度-初始长度)
      score.innerHTML = snake.length - 3
    }

7. 判断死亡

	// 判断死亡
    function judgingDeath() {
      // 撞墙死亡
      if (
        snake[snake.length - 1][0] > (gameBox[0] - 1) ||
        snake[snake.length - 1][0] < 0 ||
        snake[snake.length - 1][1] > (gameBox[1] - 1) ||
        snake[snake.length - 1][1] < 0
      ) {
        // 游戏结束
        endGame()
        return true
      }
      // 咬到自己死亡
      for (let i = 0; i < snake.length; i++) {
        for (let j = 0; j < i - 1; j++) {
          if (snake[i].toString() === snake[j].toString()) {
            // 游戏结束
            endGame()
            return true
          }
        }
      }
      return false
    }

8. 游戏结束

// 游戏结束(蛇死亡)
    function endGame() {
      clearInterval(start)
      alert(`本局得分:${snake.length - 3}分,再接再厉`, '游戏结束', {
        confirmButtonText: '确定',
        callback: action => {

        }
      });
    }

9. 游戏开始、暂停、重新开始

	// 游戏开始 蛇开始爬行
    function beginGame() {
      // 游戏开始计时器
      start = setInterval(() => {
        // 判断死亡
        if (judgingDeath()) return clearInterval(start)
        // 蛇移动
        move()
        // 判断是否吃到食物
        eatFood()
        // 重绘
        draw()
      }, velocity);
    }
    
	// 游戏暂停
    function suspendGame() {
      clearInterval(start)
    }

	// 重新开始
    function reBeginGame() {
      food = [[5, 5]]
      snake = [[0, 12], [0, 13], [0, 14]]
      direction = 3
      score.innerHTML = 0
      beginGame()
    }

10. 全部代码

<!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>Document</title>
  <style>
    .box {
      width: 400px;
      height: 400px;
      border: 1px #999 solid;
      margin: 10px auto 0;
    }

    .item {
      width: 9px;
      height: 9px;
      background-color: #fff;
      float: left;
      margin: 0 1px 1px 0;
    }

    .snakeHead {
      background-color: #f00;
    }

    .snakeBody {
      background-color: #f0f;
    }

    .food {
      background-color: #ff0;
    }

    .score {
      margin: 10px auto;
      text-align: center;
      font-size: 26px;
      font-weight: 600;
    }

    .bot-box {
      display: flex;
      justify-content: space-around;
    }

    .handle-box {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      width: 140px;
      grid-gap: 10px;
      height: 100px;
    }

    .handle-box div,
    .key {
      border: 1px #f00 solid;
      width: 40px;
      height: 40px;
      display: flex;
      justify-content: center;
      align-items: center;
      font-size: 18px;
      font-weight: 600;
    }

    .direction-1 {
      grid-column: span 3 / auto;
      margin: 0 auto;
    }

    .key-box {
      display: flex;
      align-items: center;
      margin: 10px 0;

    }

    .key-box .key {
      margin-right: 10px;
    }
  </style>
</head>

<body>
  <!-- 游戏区 -->
  <div class="box" id="box"></div>
  <!-- 得分 -->
  <div class="score">0</div>
  <!-- 操作按钮提示 -->
  <div class="bot-box">
    <!-- 方向键 -->
    <div class="handle-box">
      <div class="direction-1"></div>
      <div class="direction-2"></div>
      <div class="direction-3"></div>
      <div class="direction-4"></div>
    </div>
    <!-- 功能键 -->
    <div>
      <div class="key-box">
        <div class="key">J</div>
        <span>:开始游戏</span>
      </div>
      <div class="key-box">
        <div class="key">K</div>
        <span>:游戏暂停</span>
      </div>
      <div class="key-box">
        <div class="key">R</div>
        <span>:重新开始游戏</span>
      </div>
    </div>
  </div>
</body>

</html>
<script type="text/javascript">
  (function () {
    // 游戏区dom
    const box = document.querySelector('.box')
    // 分值dom
    const score = document.querySelector('.score')
    // 游戏格子
    let gameBox = [40, 40]
    // 游戏开始标志(计时器)
    let start = null
    // 当前速度
    let velocity = 200
    // 食物
    let food = [[5, 5]]
    // 蛇身
    let snake = [[0, 12], [0, 13], [0, 14]]
    // 方向
    let direction = 3
    // 数组转化为相应点坐标add
    function transferPosition(array) {
      let ary = []
      for (let i = 0; i < array.length; i++) {
        const e = [...array[i]];
        ary.push(e[0] * gameBox[0] + e[1])
      }
      return ary
    }
    // 随机生成食物
    function createFood() {
      food = [[randomNumber(), randomNumber()]]
    }
    // 生成随机数
    function randomNumber() {
      return Number.parseInt(Math.random() * 40)
    }
    // 绘制
    function draw() {
      // 蛇身+蛇头对应的坐标
      const snakeAry = transferPosition(snake)
      // 食物对应的坐标
      const foodAry = transferPosition(food)
      box.innerHTML = ''
      // 游戏区格子总长度
      const allBox = gameBox[0] * gameBox[0]
      for (let i = 0; i < allBox; i++) {
        let div = document.createElement('div')
        if (snakeAry.indexOf(i) === snakeAry.length - 1) { 
          // 蛇头样式
          div.className = 'item snakeHead'
        } else if (snakeAry.indexOf(i) !== -1) {
          // 蛇身样式
          div.className = 'item snakeBody'
        } else if (foodAry.indexOf(i) !== -1) {
          // 食物样式
          div.className = 'item food'
        } else {
          // 空白区
          div.className = 'item'
        }
        box.appendChild(div)
      }
    }

    // 开始游戏之前先绘制一次
    draw()
    // 游戏结束(蛇死亡)
    function endGame() {
      clearInterval(start)
      alert(`本局得分:${snake.length - 3}分,再接再厉`, '游戏结束', {
        confirmButtonText: '确定',
        callback: action => {

        }
      });
    }
    // 蛇移动
    function move() {
      // 深拷贝蛇行走之前的数组
      let inventedSnake = []
      for (let n = 0; n < snake.length; n++) {
        const e = snake[n];
        inventedSnake.push([...e])
      }
      // 判断行进方向,蛇身前进 direction:1左 2上 3右 4下
      const lng = snake.length;
      for (let i = lng - 1; i >= 0; i--) {
        if (i === lng - 1) { // 蛇头前进
          if (direction === 1) {
            snake[i][1] = snake[i][1] - 1
          } else if (direction === 2) {
            snake[i][0] = snake[i][0] - 1
          } else if (direction === 3) {
            snake[i][1] = snake[i][1] + 1
          } else if (direction === 4) {
            snake[i][0] = snake[i][0] + 1
          }
        }
        else { // 蛇身前进
          snake[i] = inventedSnake[i + 1]
        }
      }
    }
    // 判断死亡
    function judgingDeath() {
      // 撞墙死亡
      if (
        snake[snake.length - 1][0] > (gameBox[0] - 1) ||
        snake[snake.length - 1][0] < 0 ||
        snake[snake.length - 1][1] > (gameBox[1] - 1) ||
        snake[snake.length - 1][1] < 0
      ) {
        // 游戏结束
        endGame()
        return true
      }
      // 咬到自己死亡
      for (let i = 0; i < snake.length; i++) {
        for (let j = 0; j < i - 1; j++) {
          if (snake[i].toString() === snake[j].toString()) {
            // 游戏结束
            endGame()
            return true
          }
        }
      }
      return false
    }
    // 游戏开始 蛇开始爬行
    function beginGame() {
      // 游戏开始计时器
      start = setInterval(() => {
        // 判断死亡
        if (judgingDeath()) return clearInterval(start)
        // 蛇移动
        move()
        // 判断是否吃到食物
        eatFood()
        // 重绘
        draw()
      }, velocity);
    }
    // 重新开始
    function reBeginGame() {
      food = [[5, 5]]
      snake = [[0, 12], [0, 13], [0, 14]]
      direction = 3
      score.innerHTML = 0
      beginGame()
    }

    // 游戏暂停
    function suspendGame() {
      clearInterval(start)
    }

    // 监听键盘事件 爬行方向
    window.onkeydown = function (e) {
      const { keyCode } = e
      if (keyCode === 74) {
        return beginGame()
      }

      if (keyCode === 75) {
        return suspendGame()
      }

      if (keyCode === 82) {
        return reBeginGame()
      }

      if (keyCode === 37 && direction !== 3) {
        return direction = 1
      }

      if (keyCode === 38 && direction !== 4) {
        return direction = 2
      }
      if (keyCode === 39 && direction !== 1) {
        return direction = 3
      }
      if (keyCode === 40 && direction !== 2) {
        return direction = 4
      }
    }

    // 判断是否吃到食物
    function eatFood() {
      if (food.length === 0) return
      // 蛇头和食物重合及吃到食物
      if (snake[snake.length - 1].toString() === food[0].toString()) {
        // 成长
        growUp()
        // 重新生成食物
        createFood()
      }
    }

    // 吃到食物 进行成长
    function growUp() {
      let snakeFood = []
      let [x, y] = snake[snake.length - 1]
      // 按照蛇头的方向在蛇头处成长
      if (direction === 1) {
        snakeFood = [x, y - 1]
      } else if (direction === 2) {
        snakeFood = [x - 1, y]
      } else if (direction === 3) {
        snakeFood = [x, y + 1]
      } else if (direction === 4) {
        snakeFood = [x + 1, y]
      }
      snake.push(snakeFood)
      // 重算积分(积分=蛇当前长度-初始长度)
      score.innerHTML = snake.length - 3
    }
  })()
</script>

后续继续补充…

;