目录
修改地图
确保两个蛇的头结点一定不会重合——改为中心对称
如果两条蛇,同时走到相同的格子,会造成平局,这种情况会对优势者不利!
需要把地图变为 偶数 乘 奇数
修改代码:
x = rows - 1 - x;
y = cols - 1 - y;
画出蛇的头部
如何画蛇 -> 本质上蛇是由一堆格子组成的序列。
新建 Cell.js 存储一堆格子的序列 –> 存储蛇。
新建 Snake.js 对象,方便我们进行操作。
在 GameMap.js 中创建两条蛇的对象。
实现蛇的移动
移动应该是连贯的。身体有多个部分,如何让保持连贯?
中间保持不动,头和尾动,在头部创建一个新的节点,朝着目的地移动。尾巴朝着目的地动 ---------加头去尾即可
蛇什么时候可以动?
同时获取到两个人 / 两个机器 的操作才能动。
基本的移动
在 Snake.js 中添加代码,实现蛇头的向右移动。
效果
连贯的移动
由于可能会产生一些问题, 也就是中间某个状态,没有完全移出去,蛇的身子会出现问题。
中间不动,首尾动!创建的虚拟节点朝着目的地移动。只有两个点动。
考虑蛇什么时候动? 回合制游戏,两个人都有输入的时候,才可以移动。
修改 Snake.js
需要有一个裁判来判断两条蛇的移动,我们放在 GameMap.js 中
在 Snake.js 中更新一下蛇的状态:
在 GameMap.js 中更新
读取键盘的操作:从键盘获取操作 w a s d 和 ↑ ↓ ← → 来控制两条蛇。
在 GameMap.vue中修改 这样才能获取输入
<canvas ref="canvas" tabindex="0"></canvas>
绑定事件: 在 Snake.js 中加入一个辅助函数,用来获取方向。
//辅助函数
set_direction(d) {
this.direction = d;
}
在 GameMap.js 中修改,添加事件。
在 Snake.js 中更新状态:
实现真正的移动
在 snake.js 中修改 :
import { AcGameObject } from "./AcGameObject";
import { Cell } from "./Cell";
export class Snake extends AcGameObject {
constructor(info, gamemap) {
super();
this.id = info.id;
this.color = info.color;
this.gamemap = gamemap;
this.cells = [new Cell(info.r, info.c)]; // 存放蛇的身体,cells[0]存放蛇头
this.next_cell = null; // 下一步的目标位置
this.speed = 5; // 蛇每秒走5个格子
this.direction = -1; // -1表示没有指令,0、1、2、3表示上右下左
this.status = "idle"; // idle表示静止,move表示正在移动,die表示死亡
this.dr = [-1, 0, 1, 0]; // 4个方向行的偏移量
this.dc = [0, 1, 0, -1]; // 4个方向列的偏移量
this.step = 0; // 表示回合数
this.eps = 1e-2; // 允许的误差
}
start() {
}
set_direction(d) {
this.direction = d;
}
next_step() { //蛇的状态变为走下一步
const d = this.direction;
this.next_cell = new Cell(this.cells[0].r + this.dr[d], this.cells[0].c + this.dc[d]);
this.direction = -1;
this.status = "move";
this.step ++ ;
// 求长度
const k = this.cells.length;
for (let i = k; i > 0; i -- ) { // 初始元素不变 每一个元素往后移动一位
this.cells[i] = JSON.parse(JSON.stringify(this.cells[i - 1]));
}
}
update_move() {
const dx = this.next_cell.x - this.cells[0].x;
const dy = this.next_cell.y - this.cells[0].y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < this.eps) { // 走到目标点了
this.cells[0] = this.next_cell; // 添加一个新蛇头
this.next_cell = null;
this.status = "idle"; // 走完了,停下来
} else {
const move_distance = this.speed * this.timedelta / 1000;
this.cells[0].x += move_distance * dx / distance;
this.cells[0].y += move_distance * dy / distance;
}
}
update() { // 每一帧执行一次
if (this.status === 'move') {
this.update_move();
}
this.render();
}
render() {
const L = this.gamemap.L;
const ctx = this.gamemap.ctx;
ctx.fillStyle = this.color;
for (const cell of this.cells) {
ctx.beginPath();
ctx.arc(cell.x * L, cell.y * L, L / 2, 0, Math.PI * 2);
ctx.fill();
}
}
}
蛇尾移动
在 Snake.js 中添加代码,判断蛇尾是否增长
check_tail_increasing(){
if(this.step <= 10) return true;//前10步 不增
if(this.step%3 === 1) return true;// 每3步增一单位
return false;
}
修改 Snake.js , 判断蛇尾是否在下一步是否增长
基本效果
美化蛇
修改 Snake.js ,让蛇变得连贯、缩小一点。添加下列代码:
render(){
//画出基本蛇头
const L = this.gamemap.L; //单位长度
const ctx = this.gamemap.ctx;//画笔
ctx.fillStyle = this.color;
for(const cell of this.cells){
ctx.beginPath();
ctx.arc(cell.x*L ,cell.y*L,L*0.8/2,0,Math.PI*2);
ctx.fill();
}
for(let i = 1;i<this.cells.length;i++){
const a = this.cells[i-1],b = this.cells[i];
if(Math.abs(a.x-b.x)<this.eps && Math.abs(a.y - b.y) < this.eps) //重合 就不要画了
continue;
if(Math.abs(a.x-b.x)<this.eps){
ctx.fillRect((a.x-0.4)*L,Math.min(a.y, b.y) * L, L * 0.8, Math.abs(a.y - b.y) * L);
}
else {
ctx.fillRect(Math.min(a.x, b.x) * L, (a.y - 0.4) * L, Math.abs(a.x - b.x) * L, L * 0.8);
}
}
}
基本效果:
检测非法逻辑
在GameMap.js中更新
在snake.js中更新
实现眼睛
比如说蛇头朝上的时候,让蛇头cell中心点向做偏移一个,向右偏移一个就是蛇的双眼.
给蛇眼睛设置dx,dy偏移量数组,因为两只眼,所以数组设置应是这样的:
this.eye_dx = [ // 蛇眼不同方向x偏移量 (从圆心向左向右的偏移量)
[-1, 1], //向上,右,左,下
[1, 1],
[-1, 1],
[-1, -1],
]
this.eye_dy = [ // 蛇眼不同方向y偏移量
[-1, -1],
[-1, 1],
[1, 1],
[-1, 1],
]
修改snake.js
效果:
至此,我们实现了最基本的地图和两条蛇的移动。
实现点击地图即刷新
实现异步刷新
刷新整个组件就行了
修改 PkIndexView.vue 如下:
<template>
<button type="button" @click="refresh" class="btn btn-warning refresh-map">
重置地图
</button>
<PlayGround :key="n">
</PlayGround>
</template>
<script>
import PlayGround from '../../components/PlayGround.vue';
import {ref} from "vue"
export default{
components:{
PlayGround,
},
setup(){
let n = ref(0)
const refresh = () => {
n.value++;
};
return {
n,
refresh,
}
}
}
</script>
<style scoped>
.refresh-map{
position: absolute;
left: 50%;
}
</style>