// 在 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); } } } }