<template>
<div>
<p>贪吃蛇</p>
<!-- <el-button @click="cookFood">生成食物</el-button>-->
<!-- <el-button @click="addScore">添加分数</el-button>-->
<div class="game-box">
<div class="inner-content">
<div class="food">
<div class="food-dot"></div>
<div class="food-dot"></div>
<div class="food-dot"></div>
<div class="food-dot"></div>
</div>
<div class="snack" id="snack">
<div class="head"></div>
</div>
</div>
<div class="score-bar">
<span>score: 0</span>
<span>level: 1</span>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
/*
* 思路: 有哪些内容 游戏面板 角色蛇 食物 记分板
* 内容组成: 蛇头蛇身体 分数等级
* 内容状态: 静态 动态(位置)
* 影响状态的因素: 键盘事件 边界情况 食物被吃 蛇身体的移动
* 实现: 获取页面元素
* 构造函数内初始化对象 定义对象的属性方法行为
* 同时操纵dom实现游戏效果
* 功能: 简易版贪吃蛇(目前只有 基础的功能 后期会进行功能补充)
* 学习地址:b站超哥的视频用原生ts实现 我用vue结合ts实现的主要是为了学习ts的类和实例化
* https://www.bilibili.com/video/BV1Xy4y1v7S2/?spm_id_from=333.337.search-card.all.click&vd_source=065c02a4345d661813d7286c388468e6
* */
// 初始化dom元素
const foodDom = ref<HTMLElement | null>(); // 食物dom
const scoreDom = ref<HTMLElement | null>(); // 分数dom
const levelDom = ref<HTMLElement | null>(); // 等级dom
const snackDom = ref<HTMLElement | null>(); // 蛇dom
const snackHeadDom = ref<HTMLElement | null>(); // 蛇头dom
const snackBodies = ref<HTMLElement[]>([]); // 蛇身体dom数组
// 下面俩是辅助按钮做测试的和游戏本身没关系
// const food = ref<Food>(); // 食物类实例
// const score = ref<Score>(); // 分数类实例
// 获取dom元素 用get获取的是动态节点
const getDom=()=>{
foodDom.value = document.getElementsByClassName("food")[0] as HTMLElement;
scoreDom.value = document.getElementsByClassName("score-bar")[0] as HTMLElement;
levelDom.value = document.getElementsByClassName("score-bar")[0] as HTMLElement;
snackDom.value = document.getElementsByClassName("snack")[0] as HTMLElement;
snackHeadDom.value = document.getElementsByClassName("head")[0] as HTMLElement;
snackBodies.value = document.getElementById("snack")!.getElementsByTagName("div") as HTMLElement[];
//开始游戏
new Game();
}
// 食物类。食物dom 食物位置 获取位置 生成食物
class Food{
element: HTMLElement | null;
constructor() {
this.element = foodDom.value!;
}
// 获取食物位置
getPosition() {
return {
x: this.element?.offsetLeft,
y: this.element?.offsetTop
};
}
// sui随机生成食物位置 在父盒子容器内 且每次移动距离为10的整数倍
cook () {
const x = Math.floor(Math.random() * 30) * 10;
const y = Math.floor(Math.random() * 30) * 10;
if(this.element) {
this.element.style.left = `${x}px`;
this.element.style.top = `${y}px`;
}
}
}
// 分数类 分数 等级 分数等级dom 最大等级 最大分数 添加分数 添加等级
class Score{
scoreElement: HTMLElement | null;
levelElement: HTMLElement | null;
score: number = 0;
level: number = 1;
maxLevel: number = 100;
maxScore: number = 10;
// 分数dom 元素
constructor() {
this.scoreElement = scoreDom.value as HTMLElement;
this.levelElement = levelDom.value as HTMLElement;
}
// 添加分数
addScore(maxLevel: number = 100, maxScore: number = 10) {
if (this.scoreElement && this.levelElement) {
this.maxLevel = maxLevel;
this.maxScore= maxScore;
this.score=++this.score
this.scoreElement.children[0].innerHTML = `score: ${this.score}`;
this.addLevel()
}
}
// 添加等级
addLevel() {
if (this.scoreElement && this.levelElement) {
if (this.score % 10 === 0 && this.level < this.maxLevel) {
this.level=++this.level
this.levelElement.children[1].textContent = `level: ${this.level}`;
}
}
}
}
// 蛇类 蛇头 身体 蛇 获取位置 移动 增加身体 判断是不是撞墙了抛出异常 蛇位置移动(不能掉头) 身体位置移动(每一节身体到前一节身体的位置 从后往前改)
class Snake{
head: HTMLElement | null;
bodies: HTMLElement[] = [];
snack: HTMLElement | null;
constructor() {
this.head = snackHeadDom.value
this.bodies = snackBodies.value
this.snack = snackDom.value!
}
// 获取蛇头位置
getPosition() {
return {
x: this.head?.offsetLeft,
y: this.head?.offsetTop
};
}
// 移动
move(x: number, y: number) {
this.head!.style.left = `${x}px`;
this.head!.style.top = `${y}px`;
this.moveBody(); // 移动身体
// 检查是否撞墙
if (this.getPosition().x < 0 || this.getPosition().x > 290 || this.getPosition().y < 0 || this.getPosition().y > 290) {
throw new Error("Game Over: Snake hit the wall!");
}
}
// 增加身体
addBody() {
// 添加子元素 appendChild innerHTML += insertAdjacentHTML insertAdjacentElement
this.snack?.insertAdjacentHTML("beforeend", "<div style='position: absolute; width:10px;height:10px;background:#000000;'></div>");
}
// 蛇位置移动
moveBody() {
// this.bodies=document.getElementById("snack")!.getElementsByTagName("div") as HTMLElement[];
// 新增的身体需要移动到前一节身体的位置
for(let i = this.bodies.length - 1; i > 0; i--) {
this.bodies[i].style.left = this.bodies[i - 1].offsetLeft + 'px';
this.bodies[i].style.top = this.bodies[i - 1].offsetTop + 'px';
}
}
}
// 游戏类 蛇 食物类 蛇移动方向 监听键盘事件 蛇移动 蛇状态是不是shi了捕获异常提示游戏结束
// 判断是否吃到食物 食物位置更新 蛇啀要变长 分数增加
class Game {
snack: Snake;
food: Food;
score: Score;
direction: string = 'ArrowRight'; // 初始方向
isLive: boolean = true; // 游戏状态
constructor() {
this.snack = new Snake();
this.food = new Food();
this.score = new Score();
this.init();
}
init(){
document.addEventListener("keydown", this.handleKeyDown.bind(this));
//游戏开始蛇一直跑
this.run()
}
handleKeyDown(event: KeyboardEvent) {
this.direction = event.key
}
run(){
const directions=['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'];
let x= this.snack.getPosition().x;
let y= this.snack.getPosition().y;
console.log(`当前蛇头位置: x=${x}, y=${y}, 方向: ${this.direction}`);
if(directions.includes(this.direction)){
switch (this.direction) {
case 'ArrowUp':
y = y - 10;
break;
case 'ArrowDown':
y = y + 10;
break;
case 'ArrowLeft':
x = x - 10;
break;
case 'ArrowRight':
x = x + 10;
break;
}
this.isFoodEaten(); // 检查是否吃到食物
try {
this.snack.move(x, y);
} catch (error) {
alert("Game Over: Snake hit the wall!");
this.isLive = false;
}
}
// 如果蛇还活着就继续移动 等级越高,速度越快
this.isLive && setTimeout(this.run.bind(this), 500 - (this.score.level - 1) * 30);
}
// 判断蛇是否吃到食物
isFoodEaten() {
const snakePosition = this.snack.getPosition();
const foodPosition = this.food.getPosition();
if (snakePosition.x === foodPosition.x && snakePosition.y === foodPosition.y) {
this.food.cook(); // 生成新的食物
this.score.addScore(); // 分数增加
this.snack.addBody(); // 蛇变长
}
}
}
// 引入nextTick是为了确保DOM更新后再执行
// const cookFood=()=>{
// nextTick(() => {
// if(food.value) {
// food.value.cook();
// }else{
// food.value = new Food();
// food.value.cook();
// }
// });
// }
//
// const addScore=()=>{
// nextTick(() => {
// if(score.value) {
// score.value.addScore();
// }else{
// score.value = new Score();
// score.value.addScore();
// }
// });
// }
onMounted(()=>{
getDom();
})
</script>
<style lang="scss" scoped>
.game-box {
margin: 0 auto;
box-sizing: border-box;
height: 420px;
width: 360px;
border: 18px solid #000000;
background-color: #b7d4a8;
border-radius: 40px;
display: flex;
flex-flow: column nowrap;
justify-content: space-around;
align-items: center;
.inner-content {
border: solid 1px #000000;
width: 300px;
height: 300px;
position: relative;
margin-top: 24px;
& > div{
position: absolute;
}
.food {
top: 20px;
left: 30px;
display: flex;
width: 10px;
flex-flow: row wrap;
justify-content: space-between;
& > div{
width: 5px;
height: 5px;
background-color: #000000;
transform: rotate(45deg);
}
}
.snack {
& > div {
position: absolute;
top: 0;
left: 0;
width: 10px;
height: 10px;
background-color: #000000;
}
}
}
.score-bar {
display: flex;
justify-content: space-between;
width: 304px;
font-family: Courier New, Courier, monospace;
font-size: 20px;
color: #000000;
font-weight: bold;
}
}
</style>
vue+ts类实现简易版贪吃蛇游戏
于 2025-06-24 08:30:00 首次发布