规格化 js 文件

This commit is contained in:
flykhan 2023-02-22 10:26:52 +08:00
parent 0badc0f83d
commit f1101db237
8 changed files with 702 additions and 669 deletions

View File

@ -5,67 +5,59 @@ const AC_GAME_OBJECTS = [];
// 导出类 // 导出类
export class AcGameObject { export class AcGameObject {
// 构造函数 // 构造函数
constructor(){ constructor() {
// push(this) 是将当前对象存下来的意思 // push(this) 是将当前对象存下来的意思
// 每创建一个,就 push 一个,先创建先 push,后创建后 push // 每创建一个,就 push 一个,先创建先 push,后创建后 push
// 先创建的先执行 update ,后创建的会把先创建的给覆盖掉 // 先创建的先执行 update ,后创建的会把先创建的给覆盖掉
AC_GAME_OBJECTS.push(this); AC_GAME_OBJECTS.push(this);
// 帧与帧执行的时间间隔,单位:秒 // 帧与帧执行的时间间隔,单位:秒
this.timedelta=0; this.timedelta = 0;
// 是否执行过 start 函数 // 是否执行过 start 函数
this.has_called_start = false; this.has_called_start = false;
} }
// start 函数只执行一次 // start 函数只执行一次
start(){ start() {}
}
// 除第一帧之外,每一帧执行一遍 // 除第一帧之外,每一帧执行一遍
update(){ update() {}
}
// 删除之前执行 // 删除之前执行
on_destroy(){ on_destroy() {}
}
// 删除 // 删除
destroy(){ destroy() {
// 删除之前调用 on_destroy 函数 // 删除之前调用 on_destroy 函数
this.on_destroy(); this.on_destroy();
// 在 js 里,使用 of 遍历的是数组里的值;使用 in 遍历的是数组的下标。 // 在 js 里,使用 of 遍历的是数组里的值;使用 in 遍历的是数组的下标。
for(let i in AC_GAME_OBJECTS){ for (let i in AC_GAME_OBJECTS) {
const obj = AC_GAME_OBJECTS[i]; const obj = AC_GAME_OBJECTS[i];
// 如果 obj 等于当前对象,则删除该对象 // 如果 obj 等于当前对象,则删除该对象
if(obj === this){ if (obj === this) {
// 使用 splice 删除数组里的对象 // 使用 splice 删除数组里的对象
AC_GAME_OBJECTS.splice(i); AC_GAME_OBJECTS.splice(i);
break; break;
} }
} }
} }
} }
// 上一帧执行的时刻 // 上一帧执行的时刻
let last_timestamp; let last_timestamp;
// step 函数需要传入当前帧执行的时刻 timestamp // step 函数需要传入当前帧执行的时刻 timestamp
const step = (timestamp) => { const step = (timestamp) => {
// 遍历所有的物品 // 遍历所有的物品
// 在 js 里,使用 of 遍历的是数组里的值;使用 in 遍历的是数组的下标。 // 在 js 里,使用 of 遍历的是数组里的值;使用 in 遍历的是数组的下标。
for(let obj of AC_GAME_OBJECTS){ for (let obj of AC_GAME_OBJECTS) {
// 如果当前物品没有执行 start 函数,则该物品执行一次 start 函数 // 如果当前物品没有执行 start 函数,则该物品执行一次 start 函数
if(!obj.has_called_start){ if (!obj.has_called_start) {
// 将该物品的 has_called_start 赋值为 true,表示其已经执行过了 // 将该物品的 has_called_start 赋值为 true,表示其已经执行过了
obj.has_called_start = true; obj.has_called_start = true;
obj.start(); obj.start();
} }
// 如果执行过 start ,则接下来应该执行 update 函数 // 如果执行过 start ,则接下来应该执行 update 函数
else{ else {
// 当前帧与上一帧的时间间隔:当前帧执行时刻减去上一帧执行时刻 // 当前帧与上一帧的时间间隔:当前帧执行时刻减去上一帧执行时刻
obj.timedelta = timestamp - last_timestamp; obj.timedelta = timestamp - last_timestamp;
obj.update(); obj.update();
@ -75,9 +67,8 @@ const step = (timestamp) => {
// 更新 last_timestamp ,作为下一次更新的“上一帧执行的时刻” // 更新 last_timestamp ,作为下一次更新的“上一帧执行的时刻”
last_timestamp = timestamp; last_timestamp = timestamp;
// 递归调用 // 递归调用
requestAnimationFrame(step) requestAnimationFrame(step);
};
}
// 定义需要的刷新次数,传入的函数step会在下一帧浏览器渲染之前执行一遍。 // 定义需要的刷新次数,传入的函数step会在下一帧浏览器渲染之前执行一遍。
requestAnimationFrame(step) requestAnimationFrame(step);

View File

@ -1,7 +1,7 @@
// 定义一个格子 // 定义一个格子
export class Cell { export class Cell {
// 参数是格子的行数和列数 // 参数是格子的行数和列数
constructor(r,c){ constructor(r, c) {
this.r = r; this.r = r;
this.c = c; this.c = c;
// 坐标转换,用于确定蛇的每节身体所在格子中心点的坐标 // 坐标转换,用于确定蛇的每节身体所在格子中心点的坐标

View File

@ -5,9 +5,9 @@ import { Snake } from "./Snake";
import { Wall } from "./Wall"; import { Wall } from "./Wall";
// 导出定义的 GameMap 游戏地图类 // 导出定义的 GameMap 游戏地图类
export class GameMap extends AcGameObject{ export class GameMap extends AcGameObject {
// 构造函数参数: ctx 画布; parent 画布的父元素,用来动态修改画布的长宽 // 构造函数参数: ctx 画布; parent 画布的父元素,用来动态修改画布的长宽
constructor(ctx,parent){ constructor(ctx, parent) {
// super() 用于先执行基类的构造函数 // super() 用于先执行基类的构造函数
super(); super();
@ -33,24 +33,26 @@ export class GameMap extends AcGameObject{
// 创建蛇对象数组 // 创建蛇对象数组
this.snakes = [ this.snakes = [
// 注意这里的对象生成方式和传参方式 // 注意这里的对象生成方式和传参方式
new Snake({id: 0, color: "#4876ec", r: this.rows-2, c: 1}, this), new Snake({ id: 0, color: "#4876ec", r: this.rows - 2, c: 1 }, this),
new Snake({id: 1, color: "#f94848", r: 1, c: this.cols-2}, this), new Snake({ id: 1, color: "#f94848", r: 1, c: this.cols - 2 }, this),
]; ];
} }
// 判断函数:判断角色路径是否联通。传入参数:g数组,起点和终点的横纵坐标 // 判断函数:判断角色路径是否联通。传入参数:g数组,起点和终点的横纵坐标
check_connectivity(g, sx, sy, tx, ty){ check_connectivity(g, sx, sy, tx, ty) {
// 当起点坐标和中点坐标一致时,判断联通,直接返回 // 当起点坐标和中点坐标一致时,判断联通,直接返回
if(sx == tx && sy == ty) return true; if (sx == tx && sy == ty) return true;
g[sx][sy] = true; g[sx][sy] = true;
// 定义四方向偏移量 // 定义四方向偏移量
let dx = [-1, 0, 1, 0], dy = [0, 1, 0 ,-1]; let dx = [-1, 0, 1, 0],
dy = [0, 1, 0, -1];
// 枚举上下左右四个方向,求当前点下一个相邻点的坐标 // 枚举上下左右四个方向,求当前点下一个相邻点的坐标
for(let i = 0; i < 4; i++){ for (let i = 0; i < 4; i++) {
let x = sx + dx[i], y = sy + dy[i]; let x = sx + dx[i],
y = sy + dy[i];
// 判断是否撞墙,如果没有撞墙,且可以搜到终点的话,返回 true ,否则返回 false // 判断是否撞墙,如果没有撞墙,且可以搜到终点的话,返回 true ,否则返回 false
if(!g[x][y] == true && this.check_connectivity(g, x, y, tx, ty)) if (!g[x][y] == true && this.check_connectivity(g, x, y, tx, ty))
return true; return true;
} }
// 搜不到终点,返回 false // 搜不到终点,返回 false
@ -58,49 +60,49 @@ export class GameMap extends AcGameObject{
} }
// 创建墙函数 // 创建墙函数
create_wall(){ create_wall() {
// 创建一个墙格进行测试 // 创建一个墙格进行测试
// new Wall(0,0,this); // new Wall(0,0,this);
// 开一个布尔数组,有墙为 true // 开一个布尔数组,有墙为 true
// 一开始先将所有墙初始化为 false // 一开始先将所有墙初始化为 false
const g = []; const g = [];
for(let r = 0; r < this.rows; r ++){ for (let r = 0; r < this.rows; r++) {
g[r] = []; g[r] = [];
for(let c = 0; c < this.cols; c ++){ for (let c = 0; c < this.cols; c++) {
g[r][c] = false; g[r][c] = false;
} }
} }
// 给左右加上墙 // 给左右加上墙
for(let r = 0; r < this.rows; r ++){ for (let r = 0; r < this.rows; r++) {
g[r][0] = g[r][this.cols-1] = true; g[r][0] = g[r][this.cols - 1] = true;
} }
// 给上下加上墙 // 给上下加上墙
for(let c = 0; c < this.cols; c ++){ for (let c = 0; c < this.cols; c++) {
g[0][c] = g[this.rows-1][c] = true; g[0][c] = g[this.rows - 1][c] = true;
} }
// 创建内部随机障碍物 // 创建内部随机障碍物
// 因为每次计算都会生成两个障碍物,因此这里的循环次数 this.inner_walls_count 需要处以 2 // 因为每次计算都会生成两个障碍物,因此这里的循环次数 this.inner_walls_count 需要处以 2
for(let i = 0; i < this.inner_walls_count / 2; i ++){ for (let i = 0; i < this.inner_walls_count / 2; i++) {
// 避免位置重复:重复 1000 次,只要找到了就禁止随机 // 避免位置重复:重复 1000 次,只要找到了就禁止随机
for(let j = 0; j < 1000; j ++){ for (let j = 0; j < 1000; j++) {
let r = parseInt(Math.random()*this.rows); let r = parseInt(Math.random() * this.rows);
let c = parseInt(Math.random()*this.cols); let c = parseInt(Math.random() * this.cols);
// 主对角线对称 g[r][c] 和 g[c][r] 完成两种联合判断 // 主对角线对称 g[r][c] 和 g[c][r] 完成两种联合判断
// 当此位置已经有障碍物了,则重新计算下一个位置 // 当此位置已经有障碍物了,则重新计算下一个位置
// if(g[r][c] || g[c][r]) continue; // if(g[r][c] || g[c][r]) continue;
// 解决中心对称问题,需要将注释代码修改为下一行 // 解决中心对称问题,需要将注释代码修改为下一行
if(g[r][c] || g[this.rows-1-r][this.cols-1-c]) continue; if (g[r][c] || g[this.rows - 1 - r][this.cols - 1 - c]) continue;
// 将计算求得的随机障碍物的位置置为 true ,以对该位置进行绘制 // 将计算求得的随机障碍物的位置置为 true ,以对该位置进行绘制
// g[r][c] 和 g[c][r] 的坐标在对角线位置会重合,会被绘制为一个障碍物 // g[r][c] 和 g[c][r] 的坐标在对角线位置会重合,会被绘制为一个障碍物
// g[r][c] || g[c][r] = true; // g[r][c] || g[c][r] = true;
// 解决中心对称问题,需要将注释代码修改为下一行 // 解决中心对称问题,需要将注释代码修改为下一行
g[r][c] = g[this.rows-1-r][this.cols-1-c] = true; g[r][c] = g[this.rows - 1 - r][this.cols - 1 - c] = true;
// 1000 次中,规定数量的内部障碍物已经够了之后就 break 掉 // 1000 次中,规定数量的内部障碍物已经够了之后就 break 掉
break; break;
@ -108,22 +110,23 @@ export class GameMap extends AcGameObject{
} }
// 避免内部障碍物覆盖掉左下角和右上角的角色出发点 // 避免内部障碍物覆盖掉左下角和右上角的角色出发点
g[this.rows-2][1] = g[1][this.cols-2] = false; g[this.rows - 2][1] = g[1][this.cols - 2] = false;
// 保证两个对角角色的运动区域是联通的 // 保证两个对角角色的运动区域是联通的
// 检测联通需要把 g[][] 传过去给 check_connectivity() 函数进行判断,传过去之前需要把当前 g[][] 状态复制一份,避免当前数据被修改掉 // 检测联通需要把 g[][] 传过去给 check_connectivity() 函数进行判断,传过去之前需要把当前 g[][] 状态复制一份,避免当前数据被修改掉
// 深度复制方法:先转换数据为 JSON ,再把 JSON 解析出来 // 深度复制方法:先转换数据为 JSON ,再把 JSON 解析出来
const copy_g = JSON.parse(JSON.stringify(g)); const copy_g = JSON.parse(JSON.stringify(g));
// 检测到不连通,则直接在生成对象之前 return false 退出函数 // 检测到不连通,则直接在生成对象之前 return false 退出函数
if(!this.check_connectivity(copy_g, this.rows-2, 1, 1, this.cols-2)) return false; if (!this.check_connectivity(copy_g, this.rows - 2, 1, 1, this.cols - 2))
return false;
// 枚举数组,将 g[r][c] == true 的部分绘制出来 // 枚举数组,将 g[r][c] == true 的部分绘制出来
// 如果上一步连通性检测失败,则退出 this.create_wall() 函数,本步骤不再执行生成新对象的操作 // 如果上一步连通性检测失败,则退出 this.create_wall() 函数,本步骤不再执行生成新对象的操作
for(let r = 0; r < this.rows; r ++){ for (let r = 0; r < this.rows; r++) {
for(let c = 0; c < this.cols; c ++){ for (let c = 0; c < this.cols; c++) {
if(g[r][c]){ if (g[r][c]) {
// 将每个新生成的 Wall 对象 push 存入 walls 数组中 // 将每个新生成的 Wall 对象 push 存入 walls 数组中
this.walls.push(new Wall(r,c,this)); this.walls.push(new Wall(r, c, this));
} }
} }
} }
@ -133,7 +136,7 @@ export class GameMap extends AcGameObject{
} }
// 添加监听:用于绑定键盘输入,以便获取用户操作控制蛇 // 添加监听:用于绑定键盘输入,以便获取用户操作控制蛇
add_listening_events () { add_listening_events() {
// 聚焦到获取输入的画布页面 // 聚焦到获取输入的画布页面
this.ctx.canvas.focus(); this.ctx.canvas.focus();
@ -141,36 +144,38 @@ export class GameMap extends AcGameObject{
const [snake0, snake1] = this.snakes; const [snake0, snake1] = this.snakes;
// 获取用户信息:绑定 keydown 事件 // 获取用户信息:绑定 keydown 事件
this.ctx.canvas.addEventListener("keydown", e => { this.ctx.canvas.addEventListener("keydown", (e) => {
// 定义 snake0 的键盘绑定事件 // 定义 snake0 的键盘绑定事件
if (e.key === 'w') snake0.set_direction(0); if (e.key === "w") snake0.set_direction(0);
else if (e.key === 'd') snake0.set_direction(1); else if (e.key === "d") snake0.set_direction(1);
else if (e.key === 's') snake0.set_direction(2); else if (e.key === "s") snake0.set_direction(2);
else if (e.key === 'a') snake0.set_direction(3); else if (e.key === "a") snake0.set_direction(3);
// 定义 snake1 的键盘绑定事件 // 定义 snake1 的键盘绑定事件
else if (e.key === 'ArrowUp') snake1.set_direction(0); else if (e.key === "ArrowUp") snake1.set_direction(0);
else if (e.key === 'ArrowRight') snake1.set_direction(1); else if (e.key === "ArrowRight") snake1.set_direction(1);
else if (e.key === 'ArrowDown') snake1.set_direction(2); else if (e.key === "ArrowDown") snake1.set_direction(2);
else if (e.key === 'ArrowLeft') snake1.set_direction(3); else if (e.key === "ArrowLeft") snake1.set_direction(3);
}); });
} }
start(){ start() {
// 开始时调用一次创建墙的函数 // 开始时调用一次创建墙的函数
// 循环 1000 次,如果成功创建则 break ,否则继续循环创建 // 循环 1000 次,如果成功创建则 break ,否则继续循环创建
for(let i = 0; i < 1000; i ++) for (let i = 0; i < 1000; i++) if (this.create_wall()) break;
if(this.create_wall())
break;
// 开始时启动监听方法 // 开始时启动监听方法
this.add_listening_events(); this.add_listening_events();
} }
// 每一帧都更新一下小正方格的边长 // 每一帧都更新一下小正方格的边长
update_size(){ update_size() {
// 计算当前帧每个格子的宽度, parseInt 取整是为了避免渲染出的格子之间出现小空隙 // 计算当前帧每个格子的宽度, parseInt 取整是为了避免渲染出的格子之间出现小空隙
this.L = parseInt(Math.min(this.parent.clientWidth / this.cols, this.parent.clientHeight / this.rows)); this.L = parseInt(
Math.min(
this.parent.clientWidth / this.cols,
this.parent.clientHeight / this.rows
)
);
// 计算当前画布的宽度 // 计算当前画布的宽度
this.ctx.canvas.width = this.L * this.cols; this.ctx.canvas.width = this.L * this.cols;
// 计算当前画布的高度 // 计算当前画布的高度
@ -198,11 +203,10 @@ export class GameMap extends AcGameObject{
} }
// 增加碰撞检测:检测目标位置是否合法:没有撞到蛇的身体和墙 // 增加碰撞检测:检测目标位置是否合法:没有撞到蛇的身体和墙
check_valid (cell) { check_valid(cell) {
// 碰墙检测 // 碰墙检测
for (const wall of this.walls){ for (const wall of this.walls) {
if (wall.r === cell.r && wall.c === cell.c) if (wall.r === cell.r && wall.c === cell.c) return false;
return false;
} }
// 蛇身碰撞检测 // 蛇身碰撞检测
@ -211,12 +215,12 @@ export class GameMap extends AcGameObject{
let k = snake.cells.length; let k = snake.cells.length;
// 当蛇尾可以前进(蛇尾可以前进说明此回合蛇尾没有增加)的时候,蛇尾不要判断 // 当蛇尾可以前进(蛇尾可以前进说明此回合蛇尾没有增加)的时候,蛇尾不要判断
if (!snake.check_tail_increasing()) { if (!snake.check_tail_increasing()) {
k --; k--;
} }
// 判断蛇身现有结点是否碰撞 // 判断蛇身现有结点是否碰撞
for (let i = 0; i < k; i++) { for (let i = 0; i < k; i++) {
if (snake.cells[i].r === cell.r && snake.cells[i].c === cell.c) if (snake.cells[i].r === cell.r && snake.cells[i].c === cell.c)
return false return false;
} }
} }
@ -224,7 +228,7 @@ export class GameMap extends AcGameObject{
return true; return true;
} }
update(){ update() {
this.update_size(); this.update_size();
// 当两条蛇都准备好进入下一回合后 // 当两条蛇都准备好进入下一回合后
if (this.check_ready()) { if (this.check_ready()) {
@ -235,24 +239,25 @@ export class GameMap extends AcGameObject{
} }
// 渲染函数,把当前的游戏对象绘制到地图上 // 渲染函数,把当前的游戏对象绘制到地图上
render(){ render() {
// b47226 棕色 aad751 浅绿 a2d048 深绿 // b47226 棕色 aad751 浅绿 a2d048 深绿
// this.ctx.fillStyle = 'green'; // this.ctx.fillStyle = 'green';
// this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); // this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
// 定义偶数格even、奇数格odd的颜色 // 定义偶数格even、奇数格odd的颜色
const color_even = "#aad751", color_odd = "#a2d048"; const color_even = "#aad751",
color_odd = "#a2d048";
for(let r = 0; r < this.rows; r++){ for (let r = 0; r < this.rows; r++) {
for(let c = 0; c < this.cols; c++){ for (let c = 0; c < this.cols; c++) {
// 当列标加行标: r + c 是偶数时,选取偶数颜色,否则选取奇数颜色。 // 当列标加行标: r + c 是偶数时,选取偶数颜色,否则选取奇数颜色。
if((r + c) % 2 == 0){ if ((r + c) % 2 == 0) {
this.ctx.fillStyle = color_even; this.ctx.fillStyle = color_even;
}else{ } else {
this.ctx.fillStyle = color_odd; this.ctx.fillStyle = color_odd;
} }
// 绘制小方格:起始坐标x,起始坐标y,水平边长,竖直边长 // 绘制小方格:起始坐标x,起始坐标y,水平边长,竖直边长
this.ctx.fillRect(c*this.L, r*this.L, this.L, this.L); this.ctx.fillRect(c * this.L, r * this.L, this.L, this.L);
} }
} }
} }

View File

@ -16,7 +16,7 @@ export class Snake extends AcGameObject {
// 蛇初始只有一个点(蛇头),初始时只需要定义出蛇头即可。初始坐标为每条蛇的起始位置 // 蛇初始只有一个点(蛇头),初始时只需要定义出蛇头即可。初始坐标为每条蛇的起始位置
// cells[] 存放蛇的身体, cells[0] 存放蛇头 // cells[] 存放蛇的身体, cells[0] 存放蛇头
this.cells = [new Cell(info.r, info.c)] this.cells = [new Cell(info.r, info.c)];
this.next_cell = null; // 下一步的目标位置 this.next_cell = null; // 下一步的目标位置
this.speed = 5; // 蛇的速度:每秒走五个格子 this.speed = 5; // 蛇的速度:每秒走五个格子
@ -57,12 +57,9 @@ export class Snake extends AcGameObject {
[1, 1], [1, 1],
[1, -1], [1, -1],
]; ];
} }
start() { start() {}
}
// 定义方向设置接口 // 定义方向设置接口
set_direction(d) { set_direction(d) {
@ -73,8 +70,8 @@ export class Snake extends AcGameObject {
// 检测当前回合,蛇尾是否增加 // 检测当前回合,蛇尾是否增加
check_tail_increasing() { check_tail_increasing() {
// 前 10 回合每次都增加,后面每 3 回合增加一节蛇尾 // 前 10 回合每次都增加,后面每 3 回合增加一节蛇尾
if(this.step <= 10) return true; if (this.step <= 10) return true;
if(this.step % 3 === 1) return true; if (this.step % 3 === 1) return true;
// 否则 return false // 否则 return false
return false; return false;
} }
@ -84,7 +81,10 @@ export class Snake extends AcGameObject {
// 当前的蛇头方向 // 当前的蛇头方向
const d = this.direction; const d = this.direction;
// 下一节蛇身体的坐标计算 // 下一节蛇身体的坐标计算
this.next_cell = new Cell(this.cells[0].r + this.dr[d], this.cells[0].c + this.dc[d]); this.next_cell = new Cell(
this.cells[0].r + this.dr[d],
this.cells[0].c + this.dc[d]
);
// 更新蛇眼睛的方向:就是下一步的蛇头方向 // 更新蛇眼睛的方向:就是下一步的蛇头方向
this.eye_direction = d; this.eye_direction = d;
@ -95,24 +95,23 @@ export class Snake extends AcGameObject {
this.status = "move"; this.status = "move";
// 增加回合数 // 增加回合数
this.step ++; this.step++;
// 计算新的蛇身体结点 // 计算新的蛇身体结点
const k = this.cells.length; const k = this.cells.length;
for(let i = k; i > 0; i --) { for (let i = k; i > 0; i--) {
// 每个身体结点都要往后移动一位,配合头部新生成的一位结点,共同组成一个新的蛇身 // 每个身体结点都要往后移动一位,配合头部新生成的一位结点,共同组成一个新的蛇身
// 这里需要使用 JSON 方法进行深度复制,以产生新的对象避免数据出错 // 这里需要使用 JSON 方法进行深度复制,以产生新的对象避免数据出错
this.cells[i] = JSON.parse(JSON.stringify(this.cells[i - 1])); this.cells[i] = JSON.parse(JSON.stringify(this.cells[i - 1]));
} }
// 如果下一步操作的目标位置碰撞检测不合法,则蛇直接去世 // 如果下一步操作的目标位置碰撞检测不合法,则蛇直接去世
if (!this.gamemap.check_valid(this.next_cell)){ if (!this.gamemap.check_valid(this.next_cell)) {
this.status = "die"; this.status = "die";
} }
} }
update_move() {
update_move(){
// 计算目标方向 dx , dy : 使用目标点的坐标减去当前蛇头的坐标 // 计算目标方向 dx , dy : 使用目标点的坐标减去当前蛇头的坐标
const dx = this.next_cell.x - this.cells[0].x; const dx = this.next_cell.x - this.cells[0].x;
const dy = this.next_cell.y - this.cells[0].y; const dy = this.next_cell.y - this.cells[0].y;
@ -137,12 +136,12 @@ export class Snake extends AcGameObject {
// 不重合表示尚未移动到下一步,还可以继续移动 // 不重合表示尚未移动到下一步,还可以继续移动
else { else {
// 按时间(second)定义移动距离: 每一帧走过的距离 = 速度 * 两帧时间间隔 / 1000(ms) // 按时间(second)定义移动距离: 每一帧走过的距离 = 速度 * 两帧时间间隔 / 1000(ms)
const move_distance = this.speed * this.timedelta / 1000; // 处以 1000 ,将毫秒单位转换成秒单位 const move_distance = (this.speed * this.timedelta) / 1000; // 处以 1000 ,将毫秒单位转换成秒单位
this.cells[0].x += move_distance * dx / distance; this.cells[0].x += (move_distance * dx) / distance;
this.cells[0].y += move_distance * dy / distance; this.cells[0].y += (move_distance * dy) / distance;
// 更新蛇尾位置 // 更新蛇尾位置
if(!this.check_tail_increasing()) { if (!this.check_tail_increasing()) {
const k = this.cells.length; const k = this.cells.length;
// 取出当前的蛇尾 // 取出当前的蛇尾
const tail = this.cells[k - 1]; const tail = this.cells[k - 1];
@ -155,8 +154,8 @@ export class Snake extends AcGameObject {
const tail_dy = tail_target.y - tail.y; const tail_dy = tail_target.y - tail.y;
// 移动蛇尾 // 移动蛇尾
tail.x += move_distance * tail_dx / distance; tail.x += (move_distance * tail_dx) / distance;
tail.y += move_distance * tail_dy / distance; tail.y += (move_distance * tail_dy) / distance;
} }
} }
} }
@ -187,20 +186,21 @@ export class Snake extends AcGameObject {
} }
// 蛇的身体不止一节,需要枚举画出所有的肢结。of 遍历 cells 值 // 蛇的身体不止一节,需要枚举画出所有的肢结。of 遍历 cells 值
for(const cell of this.cells){ for (const cell of this.cells) {
// 画成正方形 // 画成正方形
// ctx.fillRect(cell.c * L, cell.r * L, L, L); // ctx.fillRect(cell.c * L, cell.r * L, L, L);
// 画成圆形 // 画成圆形
ctx.beginPath(); ctx.beginPath();
ctx.arc(cell.x * L, cell.y * L, L * 0.5 * 0.8, 0, 2*Math.PI); ctx.arc(cell.x * L, cell.y * L, L * 0.5 * 0.8, 0, 2 * Math.PI);
// 填充颜色 // 填充颜色
ctx.fill(); ctx.fill();
} }
// 使得蛇身体丰满一点 // 使得蛇身体丰满一点
for (let i = 1; i < this.cells.length; i++) { for (let i = 1; i < this.cells.length; i++) {
const a = this.cells[i - 1], b = this.cells[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) if (Math.abs(a.x - b.x) < this.eps && Math.abs(a.y - b.y) < this.eps)
continue; continue;
@ -209,11 +209,21 @@ export class Snake extends AcGameObject {
const snake_node_width = 0.7; const snake_node_width = 0.7;
// 如果两个目标点在竖方向重合(横坐标一致,纵坐标不重合)时的画法 // 如果两个目标点在竖方向重合(横坐标一致,纵坐标不重合)时的画法
if (Math.abs(a.x - b.x) < this.eps) { if (Math.abs(a.x - b.x) < this.eps) {
ctx.fillRect((a.x - snake_node_width / 2) * L, Math.min(a.y, b.y) * L, L * snake_node_width, Math.abs(a.y - b.y) * L); ctx.fillRect(
(a.x - snake_node_width / 2) * L,
Math.min(a.y, b.y) * L,
L * snake_node_width,
Math.abs(a.y - b.y) * L
);
} }
// 横方向的画法 // 横方向的画法
else { else {
ctx.fillRect(Math.min(a.x, b.x) * L, (a.y - snake_node_width / 2) * L, Math.abs(a.x - b.x) * L, L * snake_node_width); ctx.fillRect(
Math.min(a.x, b.x) * L,
(a.y - snake_node_width / 2) * L,
Math.abs(a.x - b.x) * L,
L * snake_node_width
);
} }
} }
@ -223,12 +233,14 @@ export class Snake extends AcGameObject {
// 定义蛇眼睛大小 // 定义蛇眼睛大小
const snake_eye_size = 0.07 * L; const snake_eye_size = 0.07 * L;
// 画眼睛 // 画眼睛
for (let i = 0; i < 2; i ++) { for (let i = 0; i < 2; i++) {
// 眼睛的横纵坐标(乘以 L 是绝对距离) // 眼睛的横纵坐标(乘以 L 是绝对距离)
const eye_x = (this.cells[0].x + this.eye_dx[this.eye_direction][i] * 0.15) * L; const eye_x =
const eye_y = (this.cells[0].y + this.eye_dy[this.eye_direction][i] * 0.15) * L; (this.cells[0].x + this.eye_dx[this.eye_direction][i] * 0.15) * L;
const eye_y =
(this.cells[0].y + this.eye_dy[this.eye_direction][i] * 0.15) * L;
ctx.beginPath() ctx.beginPath();
ctx.arc(eye_x, eye_y, snake_eye_size, 0, 2 * Math.PI); ctx.arc(eye_x, eye_y, snake_eye_size, 0, 2 * Math.PI);
ctx.fill(); ctx.fill();
} }

View File

@ -3,7 +3,7 @@ import { AcGameObject } from "./AcGameObject";
export class Wall extends AcGameObject { export class Wall extends AcGameObject {
// 构造函数定义,参数为墙的坐标 r 行, c 列,gamemap 用于绘制 // 构造函数定义,参数为墙的坐标 r 行, c 列,gamemap 用于绘制
constructor(r,c,gamemap){ constructor(r, c, gamemap) {
// 先执行基类的构造函数 // 先执行基类的构造函数
super(); super();
@ -14,13 +14,13 @@ export class Wall extends AcGameObject {
} }
// 墙的更新 // 墙的更新
update(){ update() {
// 执行渲染 // 执行渲染
this.render(); this.render();
} }
// 墙的渲染 // 墙的渲染
render(){ render() {
// 从 gamemap 对象中拿到小格(墙)的边长 // 从 gamemap 对象中拿到小格(墙)的边长
const L = this.gamemap.L; const L = this.gamemap.L;
@ -30,7 +30,6 @@ export class Wall extends AcGameObject {
// 设置 ctx 画布填充色 // 设置 ctx 画布填充色
this.ctx.fillStyle = this.color; this.ctx.fillStyle = this.color;
// 绘制矩形 // 绘制矩形
this.ctx.fillRect(this.c*L, this.r*L, L, L); this.ctx.fillRect(this.c * L, this.r * L, L, L);
} }
} }

View File

@ -1,67 +1,105 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from "vue-router";
// 导入所有 view 页面 // 导入所有 view 页面
import PkIndexView from '../views/pk/PkIndexView.vue' import PkIndexView from "../views/pk/PkIndexView.vue";
import RanklistIndexView from '../views/ranklist/RanklistIndexView.vue' import RanklistIndexView from "../views/ranklist/RanklistIndexView.vue";
import RecordIndexView from '../views/record/RecordIndexView.vue' import RecordIndexView from "../views/record/RecordIndexView.vue";
import UserBotIndexView from '../views/user/bot/UserBotIndexView.vue' import UserBotIndexView from "../views/user/bot/UserBotIndexView.vue";
import NotFound from '../views/error/NotFound.vue' import NotFound from "../views/error/NotFound.vue";
import UserAccountLoginView from '@/views/user/account/UserAccountLoginView.vue' import UserAccountLoginView from "@/views/user/account/UserAccountLoginView.vue";
import UserAccountRegisterView from '@/views/user/account/UserAccountRegisterView.vue' import UserAccountRegisterView from "@/views/user/account/UserAccountRegisterView.vue";
// 读入 store 信息
import store from "../store/index";
// 定义所有页面的 URL 路由 // 定义所有页面的 URL 路由
const routes = [ const routes = [
{ {
path:'/', path: "/",
name:'home', name: "home",
// 重定向:将 home 重定向到 pk 页面 // 重定向:将 home 重定向到 pk 页面
redirect:'/pk/' redirect: "/pk/",
// meta 存其他信息
meta: {
// 页面是否需要授权
requestAuth: true,
},
}, },
{ {
path:'/pk/', path: "/pk/",
name:'pk_index', name: "pk_index",
component:PkIndexView component: PkIndexView,
meta: {
requestAuth: true,
},
}, },
{ {
path:'/ranklist/', path: "/ranklist/",
name:'ranklist_index', name: "ranklist_index",
component:RanklistIndexView component: RanklistIndexView,
meta: {
requestAuth: true,
},
}, },
{ {
path:'/record/', path: "/record/",
name:'record_index', name: "record_index",
component:RecordIndexView component: RecordIndexView,
meta: {
requestAuth: true,
},
}, },
{ {
path:'/user/bot/', path: "/user/bot/",
name:'user_bot_index', name: "user_bot_index",
component:UserBotIndexView component: UserBotIndexView,
meta: {
requestAuth: true,
},
}, },
{ {
path:'/user/account/login/', path: "/user/account/login/",
name:'user_account_login', name: "user_account_login",
component:UserAccountLoginView component: UserAccountLoginView,
meta: {
requestAuth: false,
},
}, },
{ {
path:'/user/account/register/', path: "/user/account/register/",
name:'user_account_register', name: "user_account_register",
component:UserAccountRegisterView component: UserAccountRegisterView,
meta: {
requestAuth: false,
},
}, },
{ {
path:'/404/', path: "/404/",
name:'404', name: "404",
component:NotFound component: NotFound,
meta: {
requestAuth: false,
},
}, },
{ {
// 正则匹配所有其他非法页面到 404 // 正则匹配所有其他非法页面到 404
path:'/:catchAll(.*)', path: "/:catchAll(.*)",
redirect:'/404/' redirect: "/404/",
} },
] ];
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHistory(),
routes routes,
}) });
export default router // to 表示从那个页面跳转, from 表示从哪个页面跳转出去, next 表示页面要不要执行下一步操作
router.beforeEach((to, from, next) => {
// 如果页面需要授权而且未登录,则跳转到用户登录页面
if (to.meta.requestAuth && !store.state.user.is_login) {
next({ name: "user_account_login" });
} else {
// next() 跳转到默认页面
next();
}
});
export default router;

View File

@ -1,17 +1,12 @@
import { createStore } from 'vuex' import { createStore } from "vuex";
import ModuleUser from './user' import ModuleUser from "./user";
export default createStore({ export default createStore({
state: { state: {},
}, getters: {},
getters: { mutations: {},
}, actions: {},
mutations: {
},
actions: {
},
modules: { modules: {
user: ModuleUser, user: ModuleUser,
} },
}) });

View File

@ -1,4 +1,4 @@
import $ from 'jquery' import $ from "jquery";
export default { export default {
state: { state: {
@ -8,35 +8,33 @@ export default {
token: "", token: "",
is_login: false, is_login: false,
}, },
getters: { getters: {},
},
// 同步事件 // 同步事件
mutations: { mutations: {
// 更新用户信息 // 更新用户信息
updateUser(state, user){ updateUser(state, user) {
state.id = user.id; state.id = user.id;
state.username = user.username; state.username = user.username;
state.photo = user.photo; state.photo = user.photo;
state.is_login = user.is_login; state.is_login = user.is_login;
}, },
// 更新用户 Token // 更新用户 Token
updateToken(state, token){ updateToken(state, token) {
state.token = token; state.token = token;
}, },
// 退出登录 // 退出登录
logout(state){ logout(state) {
state.id = ""; state.id = "";
state.username = ""; state.username = "";
state.photo = ""; state.photo = "";
state.token = ""; state.token = "";
state.is_login = false; state.is_login = false;
} },
}, },
// 异步事件 // 异步事件
actions: { actions: {
// 登录函数 // 登录函数
login(context, data){ login(context, data) {
$.ajax({ $.ajax({
url: "http://localhost:3000/user/account/token/", url: "http://localhost:3000/user/account/token/",
type: "POST", type: "POST",
@ -46,7 +44,7 @@ export default {
}, },
success(resp) { success(resp) {
// console.log(resp.token, "\n成功了\n", resp.error_message); // console.log(resp.token, "\n成功了\n", resp.error_message);
if(resp.error_message === "success"){ if (resp.error_message === "success") {
/* /*
登录成功则将获取到的 resp 里的 token 传给 mutations 里的 登录成功则将获取到的 resp 里的 token 传给 mutations 里的
updateToken 方法,对用户 token 信息进行更新 updateToken 方法,对用户 token 信息进行更新
@ -56,7 +54,6 @@ export default {
} else { } else {
data.error(resp); data.error(resp);
} }
}, },
error(resp) { error(resp) {
data.error(resp); data.error(resp);
@ -64,18 +61,17 @@ export default {
}); });
}, },
// 获取登录成功后的用户信息 // 获取登录成功后的用户信息
getinfo(context,data){ getinfo(context, data) {
$.ajax({ $.ajax({
url: "http://localhost:3000/user/account/info/", url: "http://localhost:3000/user/account/info/",
type: "GET", type: "GET",
headers: { headers: {
Authorization: Authorization: "Bearer " + context.state.token,
"Bearer " + context.state.token,
}, },
success(resp) { success(resp) {
if(resp.error_message === "success"){ if (resp.error_message === "success") {
// 更新用户信息 // 更新用户信息
context.commit("updateUser",{ context.commit("updateUser", {
...resp, // 解构 resp 中的内容 ...resp, // 解构 resp 中的内容
is_login: true, // 登录成功,将 is_login 置为 true is_login: true, // 登录成功,将 is_login 置为 true
}); });
@ -89,12 +85,9 @@ export default {
}, },
}); });
}, },
logout(context){ logout(context) {
context.commit("logout"); context.commit("logout");
}
}, },
modules: { },
modules: {},
} };
}