目录
一、效果展示
二、代码展示
三、原理讲解
3.1、页面创建
3.2、创建蛇与食物
3.3、移动与边界判断
3.4、吃、得分总结
二、代码展示
view的本地文件:可直接运行。
<template>
<div class="game">
<div class="game-div">
<div class="game-min">
<div v-for="(e, i) in frame" :key="i" class="row">
<p
v-for="(b, i) in e"
:key="i"
class="element"
:style="{ background: b.bg }"
></p>
</div>
</div>
<!-- 分数计算 -->
<div class="right-div">
<div>
<p>
<span>得分:</span> <span style="color: red">{{ score }}</span>
</p>
</div>
<div>
<p>
<span>等级:</span> <span style="color: red">{{ level }}</span>
</p>
</div>
<div>
<p>
<span>吃:</span> <span style="color: red">{{ times }}</span>
</p>
</div>
<div class="ztks" @click="autoMove">暂停/开始</div>
</div>
</div>
<!-- 控制台 -->
<div class="control">
<p @click="moveTop">向上</p>
<div class="control-center">
<div @click="moveLeft">向左</div>
<div @click="moveRight">向右</div>
</div>
<p @click="moveBottom">向下</p>
</div>
</div>
</template>
<script>
import { color } from "@/utils/color.js";
export default {
data() {
return {
row: 20,
col: 20, //列
frame: [], //界面
bg: "#eee", //默认背景色
snake: {}, //蛇
food: {}, //食物
color: color[0],
over: false, //边界判断
timer: "",
speed: 600, //速度
fx: 0, //方向:0左 1右 2上 3下
times: 0,
level: 1,
score: 0,
site: 0, //食物生成位置
};
},
mounted() {
this.gameFrame();
this.initSnake();
this.initFood();
this.autoMove();
},
methods: {
// 游戏框架
gameFrame() {
for (let i = 0; i < this.row; i++) {
let a = [];
for (let j = 0; j < this.col; j++) {
let b = {
bg: this.bg,
};
a.push(b);
}
this.frame.push(a);
// console.log(this.frame);
}
},
//初始化蛇
initSnake() {
this.snake = {
// site: [9, 0, 9, 1, 9, 2],
site: [9, 8, 9, 9, 9, 10],
color: this.color[6],
};
this.renderBlock(this.snake, this.frame, 1);
},
//方块渲染
//a:方块 b:渲染位置 n:类型(0清除 1渲染)
renderBlock(a, b, n) {
let c = a.site;
if (n == 1) {
for (let i = 0; i < c.length; i += 2) {
b[c[i]][c[i + 1]].bg = a.color;
}
} else if (n == 0) {
for (let i = 0; i < c.length; i += 2) {
b[c[i]][c[i + 1]].bg = this.bg;
}
}
},
// 食物
initFood() {
this.sFood();
let site = this.site;
let color = this.color[Math.floor(Math.random() * 7)];
this.food = {
site,
color,
};
this.renderBlock(this.food, this.frame, 1);
},
// 防止食物生成在蛇身shang
sFood() {
this.site = [
Math.floor(Math.random() * this.row),
Math.floor(Math.random() * this.col),
];
for (let i = 0; i < this.snake.site.length; i += 2) {
if (
this.snake.site[i] == this.site[0] &&
this.snake.site[i + 1] == this.site[1]
) {
this.sFood();
}
}
},
// 向左
moveLeft() {
if (!this.over && this.fx != 1) {
this.fx = 0;
clearInterval(this.timer); //避免定时器造成死循环
this.timer = setInterval(() => {
this.renderBlock(this.snake, this.frame, 0); //同时清空,否则的蛇长一直加1
// for (let i = 0; i < this.snake.site.length; i += 2) {
// this.snake.site[i + 1]--;
// }//一起动
this.eat(this.snake.site[0], this.snake.site[1] - 1);
this.move(); //先判断再动
this.snake.site[1]--;
if (this.snake.site[1] < 0) {
this.over = true;
console.log("撞墙了");
this.snake.site[1]++;
this.oneself();
clearInterval(this.timer);
}
this.renderBlock(this.snake, this.frame, 1); //向左动
}, this.speed);
}
},
// 向右
moveRight() {
if (!this.over && this.fx != 0) {
this.fx = 1;
clearInterval(this.timer);
this.timer = setInterval(() => {
this.renderBlock(this.snake, this.frame, 0); //同时清空
this.eat(this.snake.site[0], this.snake.site[1] + 1);
this.move();
this.snake.site[1]++;
if (this.snake.site[1] >= this.col) {
this.over = true;
this.snake.site[1]--;
this.oneself();
clearInterval(this.timer);
}
this.renderBlock(this.snake, this.frame, 1);
}, this.speed);
}
},
// 向上
moveTop() {
if (!this.over && this.fx != 3) {
this.fx = 2;
clearInterval(this.timer);
this.timer = setInterval(() => {
this.renderBlock(this.snake, this.frame, 0);
this.eat(this.snake.site[0] - 1, this.snake.site[1]);
this.move();
this.snake.site[0]--;
if (this.snake.site[0] < 0) {
this.over = true;
this.snake.site[0]++;
this.oneself();
clearInterval(this.timer);
}
this.renderBlock(this.snake, this.frame, 1);
}, this.speed);
}
},
// 向下
moveBottom() {
if (!this.over && this.fx != 2) {
this.fx = 3;
clearInterval(this.timer);
this.timer = setInterval(() => {
this.renderBlock(this.snake, this.frame, 0);
this.eat(this.snake.site[0] + 1, this.snake.site[1]);
this.move();
this.snake.site[0]++;
if (this.snake.site[0] >= this.row) {
this.over = true;
this.snake.site[0]--;
this.oneself();
clearInterval(this.timer);
}
this.renderBlock(this.snake, this.frame, 1);
}, this.speed);
}
},
// 蛇身运动
move() {
//原理:上面4个方法是第一个方块移动,当第一个移动,后面的方块依次转化为前面的坐标
for (let i = this.snake.site.length - 1; i > 1; i -= 2) {
this.snake.site[i] = this.snake.site[i - 2];
this.snake.site[i - 1] = this.snake.site[i - 3]; //纵坐标
}
},
// 碰到自己
oneself() {
//拿当前头部来与身体对比
let t = [this.snake.site[0], this.snake.site[1]];
for (let i = this.snake.site.length - 1; i > 1; i -= 2) {
if (this.snake.site[i] == t[1] && this.snake.site[i - 1] == t[0]) {
clearInterval(this.timer);
this.over = true;
console.log("碰到自己");
}
}
},
// 吃
eat(i, j) {
if (i == this.food.site[0] && j == this.food.site[1]) {
// 从蛇头部插入该点
this.snake.site.unshift(this.food.site[0], this.food.site[1]);
// 重新生成下一个食物
this.initFood();
this.times++;
let lev = Math.floor(this.times / 10) + 1;
if (lev > this.level) {
this.level = lev;
// 算速度
if (this.level < 15) {
// 20级以内用简单的减法
this.speed = 600 - (this.level - 1) * 40;
} else {
// 当大于该等级时速度不变
this.speed = 30;
}
clearInterval(this.timer);
this.autoMove();
}
// 算分
this.score += this.level * 100;
}
},
// 自动移动
autoMove() {
if (this.timer) {
// 暂停
clearInterval(this.timer);
this.timer = "";
} else {
// 移动
if (this.fx == 0) {
this.moveLeft();
} else if (this.fx == 2) {
this.moveTop();
} else if (this.fx == 1) {
this.moveRight();
} else if (this.fx == 3) {
this.moveBottom();
}
}
},
},
};
</script>
<style lang='less' scoped>
.game {
.control {
width: 300px;
p {
width: 300px;
height: 40px;
line-height: 40px;
background-color: yellow;
}
.control-center {
align-items: center;
display: flex;
justify-content: space-between;
div {
width: 100px;
height: 40px;
line-height: 40px;
background-color: yellow;
}
}
}
.game-div {
display: flex;
.game-min {
p {
padding: 0;
margin: 0;
}
.row {
display: flex;
padding-bottom: 2px;
.element {
width: 15px;
height: 15px;
margin-right: 2px;
}
}
}
.right-div {
div {
height: 20px;
line-height: 20px;
}
.ztks {
width: 100px;
height: 40px;
background-color: palevioletred;
text-align: center;
line-height: 40px;
}
}
}
}
</style>
上面代码引入的工具值:utils里的js文件
这里是刷新页面会获取的不同渐变色
// 渐变色
export const color = [
[
'linear-gradient(180deg,#FFA7EB 0%,#F026A8 100%)',
'linear-gradient(180deg,#DFA1FF 0%,#9A36F0 100%)',
'linear-gradient(180deg,#9EAAFF 0%,#3846F4 100%)',
'linear-gradient(180deg,#7BE7FF 0%,#1E85E2 100%)',
'linear-gradient(180deg,#89FEDB 0%,#18C997 100%)',
'linear-gradient(180deg,#FFED48 0%,#FD9E16 100%)',
'linear-gradient(180deg,#FFBA8D 0%,#EB6423 100%)',
],
]
三、原理讲解
3.1、页面创建
20x20方格阵:由双层for循环得到一个20x20的数组进行渲染,即代码的gameFrame()方法。
3.2、创建蛇与食物
(1)创建蛇:见initSnake()方法,通过坐标渲染。
这里的site: [9, 8, 9, 9, 9, 10]原理见下图:
(2)创建食物:见initFood()()方法,通过坐标渲染。
这里在随机生成食物位置时,要考虑食物会生成在蛇身上,导致食物看不见的情况,所以在sFood()方法里排除蛇身体的所有位置,再生成食物。
(3)统一渲染:
renderBlock(a, b, n):a:方块 b:渲染位置 n:类型(0清除 1渲染)。通过考虑行、列进行渲染。
3.3、移动与边界判断
moveLeft()、moveRight()、moveTop()、moveBottom()四个方向的移动对应fx: -1, //方向:0左 1右 2上 3下的4个方向。
以moveLeft()方法为例:
this.over是边界判断,即是否到达矩阵的边界,this.fx是向左对应的方向(0);
在使用循环定时器时要先进行停止的条件,不然代码会一直执行;
先让蛇的第一个方块移动,后面的方块依次转化为前面的坐标,这样就会有蛇‘移动’的效果,这里方块移动一个,后面的就得少一个(即变为背景色,不然蛇身长度会一直加1,这种情况应该 在蛇‘吃掉食物’后再进行变化),这个参考move()方法;
在一个方向移动的过程中应考虑‘碰到自己’的情况,即oneself()方法,就是拿当前头部与身体对比。
3.4、吃、得分总结
在eat(i, j)方法里如果蛇头即将与食物触碰,就要考虑将食物吃掉时,把食物变成蛇的一部分,即从蛇头部插入该点,重新生成下一个食物。每吃掉一次,右侧的等级与分数就会增加,30级截止。
现在页面加载出来,蛇是运动的,通过autoMove()方法可以将运动‘暂停/开始’。
总结:目前,这个模拟游戏在向右时有一个方块会先消失,再变为正常,而且在任何一个方向‘碰到自己’/'到达边界'时就无法再触发四个方法的函数,属于bug。希望大家可以提出建议!