制作网页版贪吃蛇

1,050 阅读2分钟

贪吃蛇规则

1、贪吃蛇碰到墙壁会结束游戏
2、头部碰到身体会结束游戏
3、吃到食物会增加长度

分析

需要一个棋盘、棋盘上会随机生成5个点(食物)、需要一个贪吃蛇、方向键控制贪吃蛇的移动方向。
最重要的是,贪吃蛇怎么移动?控制DOM移动,难度太大,因为贪吃蛇可以随意弯曲,不好计算。
如果用一个数组作为贪吃蛇,这个数组内包含棋盘上的某个些点,就让这些点变色。这样就比较容易。
可以让棋盘上的每个点都有一个有规律的坐标,
比如:
    第一排第一个[0,0]、第二个[0,1].....[0,n];
    第二排第一个[1,0]、第二个[1,1]......[1,n];
    第n排第一个[n、0]、第二个[n、1]......[n、n];
    
贪吃蛇向上移动就让头部的x坐标-1、向下移动就让头部x坐标+1、向左就让y坐标-1、向右就让y坐标+1;

每次移动会在贪吃蛇数组内添加一个点(unshift)、会在末尾去掉一个点(pop);

遇到食物,就把食物所在的坐标添加到贪吃蛇数组内,贪吃蛇的长度就增加了;

判断贪吃蛇碰到墙壁或自己的身体,游戏就结束。

代码: index.vue

<template>
<div class="wrap">
 <div class="shade" v-show="over || !begin">
   <div v-show="!begin" class="bigText">
     <span>按方向键开始</span>
   </div>
   <div v-show="over" class="bigText">
     <div v-show="over" class="text-align-center">游戏结束</div>
     <br>
     <br>
     <span>按方向键重新开始</span>
   </div>
 </div>
 <div class="text-align-center">得分:{{snake.length}}分</div>
 <div class="grid">
   <div 
     class="grid-item" 
     v-for="item,index in grid"
     :class="{snake:snake.includes(item.value), randomPoint: randomPoint.includes(item.value), special: status, over: over}" 
     :key="index">
   </div>
 </div>
</div>
</template>

<script>
import { defineComponent, ref } from 'vue'
import { useGrid, genarateRandomPoint, interval } from './hooks/grid'

export default defineComponent({
 setup() {
   let speed = 500;
   let [ grid, snake, derection, status, over, begin, { bindEvents, changeStatus } ] = useGrid();
   bindEvents(e => {
     console.log(begin.value, 'begin.value');
     console.log(over.value, 'over.value');
     if (!begin.value ) {
       console.log('未开始');
       // 若未开始,按方向键开始
       begin.value = true;
       changeSpeed(speed);// 初始速度 500
     }
     if( over.value ) {
       over.value = false;
       speed = 500;
       snake.value = ['15,15'];
       console.log('重新开始');
       changeSpeed(speed);// 初始速度 500
       return;
     }
     switch( e.keyCode ) {
       case 37:
         if (derection.value != 'right') {
           derection.value = 'left';
         }
         break;
       case 38:
         if (derection.value != 'bottom') {
           derection.value = 'top';
         }
         break;
       case 39:
         if (derection.value != 'left') {
           derection.value = 'right';
         }
         break;
       case 40:
         if (derection.value != 'top') {
           derection.value = 'bottom';
         }
         break;
       default:
         break;
     }
     console.log(derection.value, 'derection.value');
   });
   // 首次生成5个随机点
   let randomPoint = ref(genarateRandomPoint(grid, snake, 5));
   let { startInterval, stopInterval } = interval();
   var changeSpeed = startInterval(() => {
     console.log(snake.value[0], 'snake.value[0]');
     let nextGrid = {
       coordinate: snake.value[0].split(',').map(Number),
       value: snake.value[0]
     }
     let addArray = [1, 1];
     switch(derection.value){
       case 'left':
         console.log('left');
         addArray = [0, -1];
         break;
       case 'right':
         console.log('right');
         addArray = [0, 1];
         break;
       case 'top':
         console.log('top');
         addArray = [-1, 0];
         break;
       case 'bottom':
         console.log('bottom');
         addArray = [1, 0];
         break;
     }
     nextGrid.coordinate = [nextGrid.coordinate[0] + addArray[0], nextGrid.coordinate[1] + addArray[1]];
     nextGrid.value = nextGrid.coordinate.join(',');
     console.log(nextGrid, 'nextGrid');
     if (nextGrid.coordinate[0] > 29 || nextGrid.coordinate[1] > 29 || nextGrid.coordinate[0] < 0 || nextGrid.coordinate[1] < 0) {
       console.log('撞墙');
       over.value = true;
       stopInterval();
       return;
     } else if (snake.value.includes(nextGrid.value)) {
       console.log('撞到自己');
       over.value = true;
       stopInterval();
       return;
     }
     // 碰到食物
     if (randomPoint.value.includes(nextGrid.value)) {
       let newPoint = randomPoint.value.splice(randomPoint.value.indexOf(nextGrid.value),1)[0];
       snake.value.unshift(newPoint);
       changeStatus();
       // 吃够5个涨一次速度
       if (snake.value.length % 5 == 0) {
         speed -= 100;
         changeSpeed(speed)
       }
       if ( randomPoint.value.length < 1) {
         randomPoint.value = randomPoint.value.concat(genarateRandomPoint(grid, snake, 5));
       }
       
     } else {
       snake.value.pop();
       snake.value.unshift(nextGrid.value);
     }
   });
   return {
     grid,
     snake,
     derection,
     status,
     over,
     begin,
     interval,
     randomPoint
   }
 },
})
</script>


<style lang="scss" scoped>
@keyframes color{
 0% {
   background:black;
 }
 25% {
   background:red;
 }
 50% {
   background:orange;
 }
 75% {
   background:blue;
 }
 100% {
   background:black;
 }
}
.text-align-center{
 text-align: center;
 font-weight: bold;
}
.wrap{
 position: relative;
 .shade{
   position: absolute;
   top:0;
   left:0;
   bottom: 0;
   right:0;
   z-index: 1;
   background:rgba(#000, .6);
   display: flex;
   justify-content: center;
   align-items: center;
   .bigText{
     color: white;
     font-size: 30px;
     letter-spacing: 3px;
     font-weight: bold;
   }
 }
}
.grid{
 margin:20px auto;
 display: flex;
 flex-wrap: wrap;
 width:602px;
 border-top:1px solid black;
 border-left: 1px solid black;
 .grid-item{
   box-sizing: border-box;
   flex-shrink: 0;
   border-right:1px solid black;
   border-bottom: 1px solid black;
   width:20px;
   height:20px;
   &.snake{
     background:black;
     &.special{
       animation: color .5s infinite;
     }
     &.over{
       background:gray;
     }
   }
   &.randomPoint{
     background:green;
   }
 }
}
</style>

hooks/grid.js

import { ref } from 'vue'
// 生成网格
export function useGrid(){
  let grid = ref([]); // 网格
  let snake = ref(['15,15']); // 贪吃蛇
  let derection = ref('top'); // 方向
  let status = ref(false); // 状态 如果是true 则是迟到食物或者加速
  let over = ref(false); // game over
  let begin = ref(false); // 开始状态
  new Array(30).fill(1).forEach((xItem, x) => {
    new Array(30).fill(1).forEach((yItem, y) => {
      grid.value.push({
        coordinate:[x, y],
        value:`${x},${y}`
      })
    })
  });
  function bindEvents(cb){
    document.addEventListener('keydown', e => {
      cb(e)
    })
  }
  function destroyEvents(){
    document.removeEventListener('keydown');
  }
  // 改变状态
  function changeStatus(){
    status.value = true;
    setTimeout(() => {
      status.value = false;
    },2000)
  }
  
  return [
    grid,
    snake,
    derection,
    status,
    over,
    begin,
    {
      bindEvents,
      destroyEvents,
      changeStatus
    }
  ]
}

// 生成随机点
export function genarateRandomPoint (grid, nake, count) {
  let arr = [];
  let createNumberArray = (grid, nake) => {
    let randomPoint = grid.value[Math.floor(Math.random() * 901)].value;
    while(nake.value.includes(randomPoint.value)){
      randomPoint = grid.value[Math.floor(Math.random() * 901)].value;
    }
    return randomPoint;
  }
  new Array(count).fill(1).forEach(() => {
    arr.push(createNumberArray(grid, nake));
  });
  return arr;
}
// 计时器
export function interval(){
  let timer = null;
  function startInterval(cb){ // 回调函数 、 速度
    return function(speed){
        stopInterval();
        timer = setInterval(() => {
          cb()
        }, speed);
    }
  }
  function stopInterval(){
    clearInterval(timer);
    timer = null;
  }
  return {
    startInterval,
    stopInterval
  }
}