前言
前段时间学习了vue3和typescript,跟着B站李立超老师的视频做了一下这个贪吃蛇的小游戏,简单的记录一下。
编写食物类
class Food {
element: HTMLElement;
//每个食物的分值 1-3的随机数
score: number;
constructor() {
this.element = document.getElementById("food")!;
this.score = 1;
}
// 获取食物的位置
get X() {
return this.element.offsetLeft;
}
get Y() {
return this.element.offsetTop;
}
// 获取食物的得分
get Score() {
return this.score;
}
// 随机生成食物
change(): void {
let top = Math.round(Math.random() * 29) * 10;
let left = Math.round(Math.random() * 29) * 10;
this.element.style.left = left + "px";
this.element.style.top = top + "px";
this.score = Math.round(Math.random() * 3);
}
}
编写蛇类
class Snake {
element: HTMLElement;
// 蛇头元素 蛇这个div中的第一个子元素
head: HTMLElement;
// 包括蛇头 整个蛇 HTMLCollection会实时更新
bodys: HTMLCollection;
constructor() {
this.element = document.getElementById("snake")!;
// 断言数据类型 获取snake的第一个div子元素
this.head = document.querySelector("#snake > div") as HTMLElement;
//head属于body的一部分
this.bodys = document.getElementById("snake")!.getElementsByTagName("div");
}
get X() {
return this.head.offsetLeft;
}
get Y() {
return this.head.offsetTop;
}
set X(value: number) {
if (this.X == value) {
return;
}
if (value < 0 || value > 290) {
throw new Error("蛇撞墙了x");
}
// 判断是否发生了调头
if (this.bodys[1] && (this.bodys[1] as HTMLElement).offsetLeft === value) {
// 如果发生调头 让蛇继续向反方向走
if (value > this.X) {
value = this.X - 10;
} else {
value = this.X + 10;
}
}
this.move();
this.head.style.left = value + "px";
this.checkHeadBody();
}
set Y(value: number) {
if (this.Y == value) {
return;
}
if (value < 0 || value > 290) {
throw new Error("蛇撞墙了Y");
}
// 判断是否发生了调头
if (this.bodys[1] && (this.bodys[1] as HTMLElement).offsetTop === value) {
// 如果发生调头 让蛇继续向反方向走
if (value > this.Y) {
value = this.Y - 10;
} else {
value = this.Y + 10;
}
}
this.move();
this.head.style.top = value + "px";
this.checkHeadBody();
}
move() {
for (let i = this.bodys.length - 1; i > 0; i--) {
// 获取前边身体位置 类型断言
let X = (this.bodys[i - 1] as HTMLElement).offsetLeft;
let Y = (this.bodys[i - 1] as HTMLElement).offsetTop;
// 身体改了
(this.bodys[i] as HTMLElement).style.left = X + "px";
(this.bodys[i] as HTMLElement).style.top = Y + "px";
}
}
addBody() {
body.push({
});
}
checkHeadBody() {
// 获取所有身体 检查是否和蛇头坐标重叠
for (let i = 1; i < this.bodys.length; i++) {
let bd = this.bodys[i] as HTMLElement;
if (this.X === bd.offsetLeft && this.Y === bd.offsetTop) {
throw new Error("撞到自己了");
}
}
}
}
编写游戏控制类
class Controller {
snake: Snake;
food: Food;
direction: string = "";
isAlive: boolean = true;
constructor() {
this.snake = new Snake();
this.food = new Food();
this.food.change();
this.init();
}
init(): void {
document.addEventListener<"keydown">(
"keydown",
this.keydownHandler.bind(this)
);
this.run();
}
keydownHandler(event: KeyboardEvent): void {
this.direction = event.key;
}
run(): void {
let X = this.snake.X;
let Y = this.snake.Y;
switch (this.direction) {
case "ArrowUp":
case "Up":
Y -= 10;
break;
case "ArrowDown":
case "Down":
Y += 10;
break;
case "ArrowLeft":
case "Left":
X -= 10;
break;
case "ArrowRight":
case "Right":
X += 10;
break;
}
this.checkEat(X, Y);
try {
this.snake.X = X;
this.snake.Y = Y;
} catch (error) {
throw new Error("游戏结束");
}
this.isAlive &&
setTimeout(() => {
this.run();
}, 100 - (panel.level - 1) * 30);
}
checkEat(X: number, Y: number): void {
if (this.food.X === X && this.food.Y === Y) {
changeScore(this.food.Score);
this.food.change();
this.snake.addBody();
}
}
}
编写计分板函数
let panel: {
score: number; level: number };
panel = reactive({
score: 0,
level: 1,
});
let levelUp: () => void;
levelUp = function () {
if (panel.level <= 10) {
panel.level++;
}
};
let changeScore: (a: number) => void;
changeScore = function (a: number) {
panel.score += a;
if (panel.score % 10 == 0) {
levelUp();
}
};
启动
启动必须在DOM渲染完成之后再启动即Mounted,否则无法获取到DOM元素
onMounted(() => {
const controller = new Controller();
});
页面
<template>
<div class="main-container">
<div class="body-wrapper">
<div id="snake">
<div v-for="(item, index) in body" :key="index"></div>
</div>
<div id="food">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
<div class="footer-panel">
<div>
SCORE: <span>{
{
panel.score }}</span>
</div>
<div>
level: <span>{
{
panel.level }}</span>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.main-container {
height: 420px;
width: 360px;
background-color: #b7d4a8;
border: 10px solid #000;
border-radius: 20px;
margin: 100px auto;
display: flex;
align-items: center;
flex-direction: column;
justify-content: space-around;
.body-wrapper {
height: 304px;
width: 304px;
border: 2px solid #000;
position: relative;
#snake {
& > div {
height: 10px;
width: 10px;
background: #000;
position: absolute;
}
}
#food {
height: 10px;
width: 10px;
position: absolute;
display: flex;
flex-wrap: wrap;
top: 0;
left: 0;
justify-content: space-between;
align-content: space-between;
& > div {
background: #000;
width: 4px;
height: 4px;
}
}
}
.footer-panel {
font-size: 20px;
font-weight: bold;
display: flex;
justify-content: space-between;
font-family: "Courier New", Courier, monospace;
width: 300px;
}
}
</style>