diff --git a/web/src/App.vue b/web/src/App.vue
index f73ff78..6aabd64 100644
--- a/web/src/App.vue
+++ b/web/src/App.vue
@@ -17,7 +17,8 @@ export default {
diff --git a/web/src/assets/background.png b/web/src/assets/images/background.png
similarity index 100%
rename from web/src/assets/background.png
rename to web/src/assets/images/background.png
diff --git a/web/src/assets/scripts/AcGameObject.js b/web/src/assets/scripts/AcGameObject.js
new file mode 100644
index 0000000..764e489
--- /dev/null
+++ b/web/src/assets/scripts/AcGameObject.js
@@ -0,0 +1,83 @@
+// 该类作为基类使用,用于刷新绘制
+// 定义绘制对象数组,存放每一帧绘制的对象
+const AC_GAME_OBJECTS = [];
+
+// 导出类
+export class AcGameObject {
+ // 构造函数
+ constructor(){
+ // push(this) 是将当前对象存下来的意思
+ // 每创建一个,就 push 一个,先创建先 push,后创建后 push
+ // 先创建的先执行 update ,后创建的会把先创建的给覆盖掉
+ AC_GAME_OBJECTS.push(this);
+ // 帧与帧执行的时间间隔
+ this.timedelta=0;
+ // 是否执行过 start 函数
+ this.has_called_start = false;
+ }
+
+ // start 函数只执行一次
+ start(){
+
+ }
+
+ // 除第一帧之外,每一帧执行一遍
+ update(){
+
+ }
+
+ // 删除之前执行
+ on_destroy(){
+
+ }
+
+ // 删除
+ destroy(){
+ // 删除之前调用 on_destroy 函数
+ this.on_destroy();
+
+ // 在 js 里,使用 of 遍历的是数组里的值;使用 in 遍历的是数组的下标。
+ for(let i in AC_GAME_OBJECTS){
+ const obj = AC_GAME_OBJECTS[i];
+ // 如果 obj 等于当前对象,则删除该对象
+ if(obj === this){
+ // 使用 splice 删除数组里的对象
+ AC_GAME_OBJECTS.splice(i);
+ break;
+ }
+ }
+
+ }
+}
+
+
+// 上一帧执行的时刻
+let last_timestamp;
+// step 函数需要传入当前帧执行的时刻 timestamp
+const step = (timestamp) => {
+ // 遍历所有的物品
+ // 在 js 里,使用 of 遍历的是数组里的值;使用 in 遍历的是数组的下标。
+ for(let obj of AC_GAME_OBJECTS){
+ // 如果当前物品没有执行 start 函数,则该物品执行一次 start 函数
+ if(!obj.has_called_start){
+ // 将该物品的 has_called_start 赋值为 true,表示其已经执行过了
+ obj.has_called_start = true;
+ obj.start();
+ }
+ // 如果执行过 start ,则接下来应该执行 update 函数
+ else{
+ // 当前帧与上一帧的时间间隔:当前帧执行时刻减去上一帧执行时刻
+ obj.timedelta = timestamp - last_timestamp;
+ obj.update();
+ }
+ }
+
+ // 更新 last_timestamp ,作为下一次更新的“上一帧执行的时刻”
+ last_timestamp = timestamp;
+ // 递归调用
+ requestAnimationFrame(step)
+
+}
+
+// 定义需要的刷新次数,传入的函数step会在下一帧浏览器渲染之前执行一遍。
+requestAnimationFrame(step)
\ No newline at end of file
diff --git a/web/src/assets/scripts/GameMap.js b/web/src/assets/scripts/GameMap.js
new file mode 100644
index 0000000..9487cf0
--- /dev/null
+++ b/web/src/assets/scripts/GameMap.js
@@ -0,0 +1,170 @@
+// 在 AcGameObject.js 里使用的是 export class ,因此这里需要使用 {} 括起来引用;如果是 export default 则不需要用括号括起来
+import { AcGameObject } from "./AcGameObject";
+// 导入墙组件
+import { Wall } from "./Wall";
+
+// 导出定义的 GameMap 游戏地图类
+export class GameMap extends AcGameObject{
+ // 构造函数参数: ctx 画布; parent 画布的父元素,用来动态修改画布的长宽
+ constructor(ctx,parent){
+ // super() 用于先执行基类的构造函数
+ super();
+
+ // 存下 ctx 和 parent
+ this.ctx = ctx;
+ this.parent = parent;
+
+ // 存下每个格子的绝对距离
+ this.L = 0;
+
+ // 定义棋盘格的行数和列数
+ this.rows = 13;
+ this.cols = 13;
+
+ // 绘制棋盘内部区域的障碍物(墙)的数量
+ this.inner_walls_count = 50;
+
+ // 存储所有的墙
+ // 上面的 super() 会先将 AcGameObject 先绘制, walls 的绘制在后面执行,因此墙最后会覆盖原棋盘格进行绘制
+ this.walls = [];
+ }
+
+ // 判断函数:判断角色路径是否联通。传入参数:g数组,起点和终点的横纵坐标
+ check_connectivity(g, sx, sy, tx, ty){
+ // 当起点坐标和中点坐标一致时,判断联通,直接返回
+ if(sx == tx && sy == ty) return true;
+ g[sx][sy] = true;
+
+ // 定义四方向偏移量
+ let dx = [-1, 0, 1, 0], dy = [0, 1, 0 ,-1];
+ // 枚举上下左右四个方向,求当前点下一个相邻点的坐标
+ for(let i = 0; i < 4; i++){
+ let x = sx + dx[i], y = sy + dy[i];
+ // 判断是否撞墙,如果没有撞墙,且可以搜到终点的话,返回 true ,否则返回 false
+ if(!g[x][y] == true && this.check_connectivity(g, x, y, tx, ty))
+ return true;
+ }
+ // 搜不到终点,返回 false
+ return false;
+ }
+
+ // 创建墙函数
+ create_wall(){
+ // 创建一个墙格进行测试
+ // new Wall(0,0,this);
+
+ // 开一个布尔数组,有墙为 true
+ // 一开始先将所有墙初始化为 false
+ const g = [];
+ for(let r = 0; r < this.rows; r ++){
+ g[r] = [];
+ for(let c = 0; c < this.cols; c ++){
+ g[r][c] = false;
+ }
+ }
+
+ // 给左右加上墙
+ for(let r = 0; r < this.rows; r ++){
+ g[r][0] = g[r][this.cols-1] = true;
+ }
+
+ // 给上下加上墙
+ for(let c = 0; c < this.cols; c ++){
+ g[0][c] = g[this.rows-1][c] = true;
+ }
+
+ // 创建内部随机障碍物
+ // 因为每次计算都会生成两个障碍物,因此这里的循环次数 this.inner_walls_count 需要处以 2
+ for(let i = 0; i < this.inner_walls_count / 2; i ++){
+ // 避免位置重复:重复 1000 次,只要找到了就禁止随机
+ for(let j = 0; j < 1000; j ++){
+ let r = parseInt(Math.random()*this.rows);
+ let c = parseInt(Math.random()*this.cols);
+
+ // 主对角线对称 g[r][c] 和 g[c][r] 完成两种联合判断
+ // 当此位置已经有障碍物了,则重新计算下一个位置
+ if(g[r][c] || g[c][r]) continue;
+
+ // 将计算求得的随机障碍物的位置置为 true ,以对该位置进行绘制
+ // g[r][c] 和 g[c][r] 的坐标在对角线位置会重合,会被绘制为一个障碍物
+ g[r][c] = g[c][r] = true;
+
+ // 1000 次中,规定数量的内部障碍物已经够了之后就 break 掉
+ break;
+ }
+ }
+
+ // 避免内部障碍物覆盖掉左下角和右上角的角色出发点
+ g[this.rows-2][1] = g[1][this.cols-2] = false;
+
+ // 保证两个对角角色的运动区域是联通的
+ // 检测联通需要把 g[][] 传过去给 check_connectivity() 函数进行判断,传过去之前需要把当前 g[][] 状态复制一份,避免当前数据被修改掉
+ // 深度复制方法:先转换数据为 JSON ,再把 JSON 解析出来
+ const copy_g = JSON.parse(JSON.stringify(g));
+ // 检测到不连通,则直接在生成对象之前 return false 退出函数
+ if(!this.check_connectivity(copy_g, this.rows-2, 1, 1, this.cols-2)) return false;
+
+ // 枚举数组,将 g[r][c] == true 的部分绘制出来
+ // 如果上一步连通性检测失败,则退出 this.create_wall() 函数,本步骤不再执行生成新对象的操作
+ for(let r = 0; r < this.rows; r ++){
+ for(let c = 0; c < this.cols; c ++){
+ if(g[r][c]){
+ // 将每个新生成的 Wall 对象 push 存入 walls 数组中
+ this.walls.push(new Wall(r,c,this));
+ }
+ }
+ }
+
+ // 绘制成功则 return turn
+ return true;
+ }
+
+
+ start(){
+ // 开始时调用一次创建墙的函数
+ // 循环 1000 次,如果成功创建则 break ,否则继续循环创建
+ for(let i = 0; i < 1000; i ++)
+ if(this.create_wall())
+ break;
+ }
+
+ // 每一帧都更新一下小正方格的边长
+ update_size(){
+ // 计算当前帧每个格子的宽度, parseInt 取整是为了避免渲染出的格子之间出现小空隙
+ 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.height = this.L * this.rows;
+ }
+
+
+ update(){
+ this.update_size();
+ // 每次更新都重新执行渲染
+ this.render();
+ }
+
+ // 渲染函数,把当前的游戏对象绘制到地图上
+ render(){
+ // b47226 棕色 aad751 浅绿 a2d048 深绿
+ // this.ctx.fillStyle = 'green';
+ // this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
+
+ // 定义偶数格even、奇数格odd的颜色
+ const color_even = "#aad751", color_odd = "#a2d048";
+
+ for(let r = 0; r < this.rows; r++){
+ for(let c = 0; c < this.cols; c++){
+ // 当列标加行标: r + c 是偶数时,选取偶数颜色,否则选取奇数颜色。
+ if((r + c) % 2 == 0){
+ this.ctx.fillStyle = color_even;
+ }else{
+ this.ctx.fillStyle = color_odd;
+ }
+ // 绘制小方格:起始坐标x,起始坐标y,水平边长,竖直边长
+ this.ctx.fillRect(c*this.L, r*this.L, this.L, this.L);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/src/assets/scripts/Wall.js b/web/src/assets/scripts/Wall.js
new file mode 100644
index 0000000..b62cfab
--- /dev/null
+++ b/web/src/assets/scripts/Wall.js
@@ -0,0 +1,36 @@
+// 定义墙组件
+import { AcGameObject } from "./AcGameObject";
+
+export class Wall extends AcGameObject {
+ // 构造函数定义,参数为墙的坐标 r 行, c 列,gamemap 用于绘制
+ constructor(r,c,gamemap){
+ // 先执行基类的构造函数
+ super();
+
+ this.r = r;
+ this.c = c;
+ this.gamemap = gamemap;
+ this.color = "#b47226";
+ }
+
+ // 墙的更新
+ update(){
+ // 执行渲染
+ this.render();
+ }
+
+ // 墙的渲染
+ render(){
+ // 从 gamemap 对象中拿到小格(墙)的边长
+ const L = this.gamemap.L;
+
+ // 拿到ctx画布
+ this.ctx = this.gamemap.ctx;
+
+ // 设置 ctx 画布填充色
+ this.ctx.fillStyle = this.color;
+ // 绘制矩形
+ this.ctx.fillRect(this.c*L, this.r*L, L, L);
+
+ }
+}
\ No newline at end of file
diff --git a/web/src/components/ContentBase.vue b/web/src/components/ContentBase.vue
index 26422a5..b3f73e1 100644
--- a/web/src/components/ContentBase.vue
+++ b/web/src/components/ContentBase.vue
@@ -14,7 +14,7 @@
-
diff --git a/web/src/components/NavBar.vue b/web/src/components/NavBar.vue
index f7da1fd..b656648 100644
--- a/web/src/components/NavBar.vue
+++ b/web/src/components/NavBar.vue
@@ -73,4 +73,4 @@ export default {
};
-
+
diff --git a/web/src/components/PlayGround.vue b/web/src/components/PlayGround.vue
new file mode 100644
index 0000000..aa11a2e
--- /dev/null
+++ b/web/src/components/PlayGround.vue
@@ -0,0 +1,28 @@
+// 定义游戏区域
+
+
+
+
+
+
+
+
+
diff --git a/web/src/views/pk/PkIndexView.vue b/web/src/views/pk/PkIndexView.vue
index bb20eee..9dcb296 100644
--- a/web/src/views/pk/PkIndexView.vue
+++ b/web/src/views/pk/PkIndexView.vue
@@ -1,13 +1,13 @@
- 对战
+ 对战