<template>
<div flex items-center justify-center h-100vh w-full relative>
<div class="">
<div class="header">
<div>得分: {{ score }}div>
<div class="flex">
<div w-100>速度: {{ speed }}div>
<el-slider
v-model="speed"
:min="2"
:max="9"
:step="0.1"
@change="changeSpeed"
/>
div>
div>
<div class="flex" v-for="(i, idx) in array" :key="idx">
<div
class="block flex items-center justify-center"
v-for="(j, idx2) in i"
:key="idx"
>
<div
v-if="j"
class="w-13 h-13"
:class="{ food: j == 2, snake: j == 1 }"
:style="{
backgroundColor:
j == 2
? foodColor
: idx === head[0] && idx2 === head[1]
? snakeHeadColor
: snakeColor,
border: j == 2 ? '' : '1px solid #bdbdbd',
borderRadius:
j == 2
? '12px'
: idx === head[0] && idx2 === head[1]
? headRadius()
: '4px',
}"
>div>
div>
div>
div>
<div
class="absolute left-0 bottom-0 right-0 top-0 flex items-center justify-center pause-bg"
v-if="isPause"
>
<div text-center>
<div text-36 mb-20 class="text-#fff">游戏暂停div>
<div text-14 class="text-#eee">按空格键开始/暂停div>
div>
div>
<div
class="absolute left-0 bottom-0 right-0 top-0 flex items-center justify-center"
v-if="isOver"
>
<el-button type="success" size="large" @click="refresh">重新开始el-button>
div>
div>
template>
<script setup lang="ts">
import {
ref,
reactive,
onMounted,
toRefs,
watch,
computed,
onUnmounted,
} from "vue";
let ROW = 22;
let COL = 18;
let direction = 1;
let directionTemp = direction;
let food = ref([0, 0]);
let isEat = false;
let timer: any = null;
const isOver = ref(false);
const isPause = ref(false);
let score = 0;
const speed = ref(3.5);
const array = ref<any>([]);
const snake = ref<any>([]);
const snakeColor = ref("#67C23A");
const snakeHeadColor = ref("#3C9212");
const foodColor = ref("#F56C6C");
console.log("array", array);
watch(
() => snake.value,
(v) => {
v.forEach((item) => {
array.value[item[0]][item[1]] = 1;
});
},
{ deep: true }
);
watch(
() => food.value,
(v) => {
array.value[v[0]][v[1]] = 2;
}
);
const init = () => {
timer = setInterval(() => {
if (isOver.value) {
clearInterval(timer);
return;
}
if (isPause.value) {
return;
}
let next = [head.value[0], head.value[1]];
direction = directionTemp;
switch (direction) {
case 0:
next[0] -= 1;
break;
case 1:
next[1] += 1;
break;
case 2:
next[0] += 1;
break;
case 3:
next[1] -= 1;
break;
}
if (next[0] < 0 || next[0] >= ROW || next[1] < 0 || next[1] >= COL) {
isOver.value = true;
alert("game over");
return;
}
if (array.value[next[0]][next[1]] == 1) {
isOver.value = true;
alert("game over");
return;
}
if (array.value[next[0]][next[1]] == 2) {
isEat = true;
score++;
if (score == 5) {
speed.value += 0.4;
} else if (score == 10) {
speed.value += 0.4;
} else if (score == 15) {
speed.value += 0.4;
} else if (score == 20) {
speed.value += 0.5;
} else if (score == 25) {
speed.value += 0.5;
} else if (score == 30) {
speed.value += 0.5;
} else if (score == 35) {
speed.value += 0.6;
} else if (score == 40) {
speed.value += 0.6;
}
speed.value = Math.round(speed.value * 10) / 10;
}
snake.value.unshift(next);
if (!isEat) {
array.value[snake.value[snake.value.length - 1][0]][
snake.value[snake.value.length - 1][1]
] = 0;
snake.value.pop();
} else {
isEat = false;
newFoodPosition();
}
array.value[next[0]][next[1]] = 1;
}, 1000 / speed.value);
};
const newFoodPosition = () => {
let x = Math.floor(Math.random() * ROW);
let y = Math.floor(Math.random() * COL);
while (snake.value.some((item) => item[0] == x && item[1] == y)) {
x = Math.floor(Math.random() * ROW);
y = Math.floor(Math.random() * COL);
}
food.value = [x, y];
};
const head = computed(() => {
return snake.value[0];
});
const keydown = (e: { keyCode: number; preventDefault: any }) => {
e.preventDefault();
if (e.keyCode == 37) {
if (direction == 1) return;
directionTemp = 3;
} else if (e.keyCode == 38) {
if (direction == 2) return;
directionTemp = 0;
} else if (e.keyCode == 39) {
if (direction == 3) return;
directionTemp = 1;
} else if (e.keyCode == 40) {
if (direction == 0) return;
directionTemp = 2;
} else if (e.keyCode == 32) {
isPause.value = !isPause.value;
}
};
const changeSpeed = (v) => {};
const headRadius = () => {
switch (direction) {
case 0:
return "12px 12px 0px 0px";
case 1:
return "0px 12px 12px 0px";
case 2:
return "0px 0px 12px 12px";
case 3:
return "12px 0px 0px 12px";
}
};
watch(
() => speed.value,
(v) => {
clearInterval(timer);
init();
}
);
const start = () => {
clearInterval(timer);
array.value = Array.from({ length: ROW }, () =>
Array.from({ length: COL }, () => 0)
);
snake.value = [
[2, 7],
[2, 6],
[2, 5],
[2, 4],
[2, 3],
];
newFoodPosition();
init();
};
const refresh = () =>{
window.location.reload();
}
onMounted(() => {
start();
document.addEventListener("keydown", keydown);
});
onUnmounted(() => {
clearInterval(timer);
document.removeEventListener("keydown", keydown);
});
script>
<style lang="scss" scoped>
$width: 14px;
.block {
width: $width;
height: $width;
background-color: #e4e4e4;
margin: 1px;
}
.food {
clip-path: polygon(10% 10%, 90% 10%, 90% 90%, 10% 90%);
animation: food alternate 1.5s ease-in-out infinite;
}
@keyframes food {
0% {
transform: scale(1);
}
25% {
transform: scale(0.92);
filter: brightness(0.9);
}
50% {
transform: scale(1.1);
}
75% {
transform: scale(1.24);
filter: brightness(1.15);
}
100% {
transform: scale(1.16);
}
}
.snake {
background-image: linear-gradient(
45deg,
rgba(255, 255, 255, 0.16) 25%,
transparent 25%,
transparent 50%,
rgba(255, 255, 255, 0.16) 50%,
rgba(255, 255, 255, 0.16) 75%,
transparent 75%,
transparent
);
}
.game {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
background-color: #f0f0f0;
.score {
font-size: 30px;
margin-bottom: 20px;
}
.speed {
font-size: 30px;
margin-bottom: 20px;
}
}
.pause-bg {
background-color: rgba(0, 0, 0, 0.5);
z-index: 2;
}
style>