likes
comments
collection
share

小玩意——贪吃蛇

作者站长头像
站长
· 阅读数 18

年底无事做,看各种资料也都看腻了,索性自己写点小玩意消遣一下时间。

贪吃蛇用cavans写的,那么我们首先就初始化一个canvas

<template>
  <div>    
    <canvas id="canvas"></canvas>
  </div>
</template>
<script setup lang="ts">
import { onMounted } from "vue";
let ctx: CanvasRenderingContext2D;
let canvas: HTMLCanvasElement;
onMounted(() => {
  init(); 
});
let init = () => {
  canvas = document.querySelector("#canvas") as HTMLCanvasElement;
  canvas.width = window.innerWidth;
  canvas.height = 600;
  ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
}; 
</script>

然后就开始考虑用下蛇怎么画,当然就是一个一个的小方块了,不然怎么画(狗头保命):

let darwBox = (x: number, y: number ): void => {
  ctx?.beginPath();
  ctx?.lineTo(x + 0, y + 0);
  ctx?.lineTo(x + 10, y + 0);
  ctx?.lineTo(x + 10, y + 10);
  ctx?.lineTo(x + 0, y + 10);  
  ctx?.closePath();
  ctx?.stroke();
};

然后定义一个数组,用数组渲染成一条蛇(假装是蛇):

//清空画布
let clearCanvas = () => {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
};
interface position {
  x: number;
  y: number;
}
let Data = reactive([
  { x: 0, y: 0 },
  { x: 10, y: 0 },
]);
//依靠数据画蛇
let darwSneck = (arr: position[]) => {
  clearCanvas();//如果不清空就很难受
  arr.forEach(({ x, y }) => {
    darwBox(x, y, false);
  });
};

此时我们就有了一条蛇,但是它是静态的,所以要通过操控数据来使它动起来:

let time: any;
let run = () => {
  time = setInterval(async () => {
    MathArr(Data, direction);//处理运动数据
    darwSneck(Data);//根据数据绘画渲染
    renderFood(food);//随机食物
    await checkData(Data);//检查是否游戏失败
  }, 50);
};

其中MathArr是来处理数据的,direction为蛇的走向:

enum Direction {
  Up = "Up",
  Down = "Down",
  Left = "Left",
  Right = "Right",
}
let direction = Direction["Right"];
//MathArr
let MathArr = (arr: position[], direction: Direction) => {
  arr.shift(); 
  let point: position = { x: 0, y: 0 };
  switch (direction) {
    case "Up":
      point = { x: arr[arr.length - 1].x, y: arr[arr.length - 1].y - 10 };
      break;
    case "Down":
      point = { x: arr[arr.length - 1].x, y: arr[arr.length - 1].y + 10 };
      break;
    case "Left":
      point = { x: arr[arr.length - 1].x - 10, y: arr[arr.length - 1].y };
      break;
    case "Right":
      point = { x: arr[arr.length - 1].x + 10, y: arr[arr.length - 1].y };
      break;
  }
  arr.push(point);
};

接下来就是准备监听键盘来改变蛇的走向direction了:

document.addEventListener("keydown", ({ key }) => {
  switch (key) {
    case "w":
      direction =
        direction === Direction["Down"] ? Direction["Down"] : Direction["Up"];
      break;
    case "a":
      direction =
        direction === Direction["Right"]
          ? Direction["Right"]
          : Direction["Left"];
      break;
    case "s":
      direction =
        direction === Direction["Up"] ? Direction["Up"] : Direction["Down"];
      break;
    case "d":
      direction =
        direction === Direction["Left"]
          ? Direction["Left"]
          : Direction["Right"];
      break;
  }
});

那接下来就是检查游戏是否有失败了,我只做了自己碰撞,没有做边界碰撞:

//检查数据
let checkData = (value: position[]) => {
  return new Promise((reslove, reject) => {
    value.forEach((v, index) => {
      if (
        value.findIndex((e, indx) => {
          return indx != index && v.x == e.x && v.y == e.y;
        }) != -1
      ) {
        clearInterval(time);
        time = null;
        open();//游戏结束的对话框,也可以用alert来代替
        reject(false);
      } else if (v.x == food.x && v.y == food.y) {
        //拿到食物
        value.unshift({ x: food.x + 10000, y: food.y + 10000 });
        randomFood();
        reslove(true);
      }
    });
  });
};

接下来就是食物的问题了,很简单,就是随机一个位置出来就OK:

interface Food {
  x: number;
  y: number;
  food: boolean;
}
//初始食物
let food: Food = { x: 100, y: 0, food: true };
//随机食物
let randomFood = () => {
  food.x = Math.floor((canvas.width / 100) * Math.random()) * 100;
  food.y = Math.floor((canvas.height / 100) * Math.random()) * 100;
};
//渲染食物
let renderFood = (food: Food) => {
  darwBox(food.x, food.y, food.food);
};

下面是源码,其中还有一些有趣的小玩意希望你能发现~

<template>
  <div>
    <p>分数:{{ Data.length }}</p>
    <el-button type="primary" size="small" @click="suspend"
      >暂停/恢复</el-button
    >
    <canvas id="canvas"></canvas>
  </div>
</template>

<script setup lang="ts">
import { onMounted, reactive } from "vue";

import { ElMessageBox } from "element-plus";

let ctx: CanvasRenderingContext2D;
let canvas: HTMLCanvasElement;

onMounted(() => {
  init();
  darwSneck(Data);
  run();
});

let init = () => {
  canvas = document.querySelector("#canvas") as HTMLCanvasElement;

  canvas.width = window.innerWidth;
  canvas.height = 600;
  ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
};
//画盒子
let darwBox = (x: number, y: number, food: boolean): void => {
  ctx?.beginPath();
  ctx?.lineTo(x + 0, y + 0);
  ctx?.lineTo(x + 10, y + 0);
  ctx?.lineTo(x + 10, y + 10);
  ctx?.lineTo(x + 0, y + 10);
  if (food) {
    ctx.fillStyle = "#aaa";
    ctx.fill();
  }
  ctx?.closePath();
  ctx?.stroke();
};
//清空画布
let clearCanvas = () => {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
};

interface position {
  x: number;
  y: number;
}
let Data = reactive([
  { x: 0, y: 0 },
  { x: 10, y: 0 },
]);
//依靠数据画蛇
let darwSneck = (arr: position[]) => {
  clearCanvas();

  arr.forEach(({ x, y }) => {
    darwBox(x, y, false);
  });
};
enum Direction {
  Up = "Up",
  Down = "Down",
  Left = "Left",
  Right = "Right",
}
let direction = Direction["Right"];

let MathArr = (arr: position[], direction: Direction) => {
  arr.shift();
  let point: position = { x: 0, y: 0 };
  switch (direction) {
    case "Up":
      point = { x: arr[arr.length - 1].x, y: arr[arr.length - 1].y - 10 };

      break;
    case "Down":
      point = { x: arr[arr.length - 1].x, y: arr[arr.length - 1].y + 10 };

      break;
    case "Left":
      point = { x: arr[arr.length - 1].x - 10, y: arr[arr.length - 1].y };

      break;
    case "Right":
      point = { x: arr[arr.length - 1].x + 10, y: arr[arr.length - 1].y };

      break;
    default:
      break;
  }
  arr.push(point);
};
let time: any;
let run = () => {
  time = setInterval(async () => {
    MathArr(Data, direction);
    darwSneck(Data);
    renderFood(food);
    await checkData(Data);
  }, 50);
};

document.addEventListener("keydown", ({ key }) => {
  switch (key) {
    case "w":
      direction =
        direction === Direction["Down"] ? Direction["Down"] : Direction["Up"];

      break;
    case "a":
      direction =
        direction === Direction["Right"]
          ? Direction["Right"]
          : Direction["Left"];
      break;
    case "s":
      direction =
        direction === Direction["Up"] ? Direction["Up"] : Direction["Down"];

      break;
    case "d":
      direction =
        direction === Direction["Left"]
          ? Direction["Left"]
          : Direction["Right"];
      break;

    default:
      break;
  }
});
//检查数据
let checkData = (value: position[]) => {
  return new Promise((reslove, reject) => {
    value.forEach((v, index) => {
      if (
        value.findIndex((e, indx) => {
          return indx != index && v.x == e.x && v.y == e.y;
        }) != -1
      ) {
        clearInterval(time);
        time = null;

        open();
        reject(false);
      } else if (v.x == food.x && v.y == food.y) {
        //拿到食物

        value.unshift({ x: food.x + 10000, y: food.y + 10000 });
        randomFood();

        reslove(true);
      }
    });
  });
};

let openStatus = false;
const open = () => {
  if (!openStatus) {
    openStatus = true;
    ElMessageBox.confirm("游戏失败了,是否重新开始?", "提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        openStatus = false;
        Data = [
          { x: 0, y: 0 },
          { x: 10, y: 0 },
        ];
        direction = Direction["Right"];
        darwSneck(Data);
        run();
      })
      .catch(() => {
        openStatus = false;
      });
  }
};
interface Food {
  x: number;
  y: number;
  food: boolean;
}
//初始食物
let food: Food = { x: 100, y: 0, food: true };
//随机食物
let randomFood = () => {
  food.x = Math.floor((canvas.width / 100) * Math.random()) * 100;
  food.y = Math.floor((canvas.height / 100) * Math.random()) * 100;
};
//渲染食物
let renderFood = (food: Food) => {
  darwBox(food.x, food.y, food.food);
};
//暂停/恢复
let suspend = () => {
  if (time) {
    clearTimeout(time);
    time = null;
  } else {
    run();
  }
};
//切换页面自动暂停
window.addEventListener("pagehide", () => {
  clearTimeout(time);
  time = null;
});
window.addEventListener("keydown", ({ key }) => {
  if (key == "Alt") {
    clearTimeout(time);
    time = null;
  }
});
</script>

<style lang="scss" scoped>
#canvas {
  border: 1px solid #aaa;
}
</style>