From 4c72bc4ea2901a20c96e0f26f985982cb6e54850 Mon Sep 17 00:00:00 2001 From: dyh <2825183872@qq.com> Date: Sun, 7 Aug 2022 02:34:37 +0800 Subject: [PATCH] =?UTF-8?q?version-1.6[=E5=AE=9E=E7=8E=B0=E8=9B=87+?= =?UTF-8?q?=E7=9C=BC=E7=9D=9B+=E7=A7=BB=E5=8A=A8+=E6=AD=BB=E4=BA=A1?= =?UTF-8?q?=E6=9C=BA=E5=88=B6]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/assets/scripts/Cell.js | 25 ++++ web/src/assets/scripts/GameMap.js | 82 ++++++++++++-- web/src/assets/scripts/Snake.js | 182 ++++++++++++++++++++++++++++++ web/src/components/GameMap.vue | 2 +- web/src/components/NavBar.vue | 1 + web/src/router/index.js | 1 + 6 files changed, 284 insertions(+), 9 deletions(-) create mode 100644 web/src/assets/scripts/Cell.js create mode 100644 web/src/assets/scripts/Snake.js diff --git a/web/src/assets/scripts/Cell.js b/web/src/assets/scripts/Cell.js new file mode 100644 index 0000000..378a2fa --- /dev/null +++ b/web/src/assets/scripts/Cell.js @@ -0,0 +1,25 @@ + + +export class Cell { + constructor(r,c){ + this.r=r; + this.c=c; + //转化为canvas(相对单位) + // 圆心的位置 + this.x=c+0.5; + this.y=r+0.5; + } + + start(){ + + } + + update(){ + this.render(); + } + + render(){ + + } + +} \ No newline at end of file diff --git a/web/src/assets/scripts/GameMap.js b/web/src/assets/scripts/GameMap.js index 409b42c..a3b0e00 100644 --- a/web/src/assets/scripts/GameMap.js +++ b/web/src/assets/scripts/GameMap.js @@ -1,5 +1,6 @@ import { AcGameObject } from "./AcGameObject"; +import { Snake } from "./Snake"; import { Wall } from "./Wall"; export class GameMap extends AcGameObject @@ -13,11 +14,18 @@ export class GameMap extends AcGameObject this.L = 0;//小正方形的边长 this.rows = 13; - this.cols = 13; + this.cols = 14; this.walls=[]; - this.inner_walls_count = 20; + this.inner_walls_count = 0; + + this.snakes = [ + new Snake({id:0,color:"#4876ED",r:this.rows-2,c:1},this), + new Snake({id:1,color:"#F94848",r:1,c:this.cols-2},this) + ] + + } check_connectivity(g,sx,sy,tx,ty) @@ -65,9 +73,9 @@ export class GameMap extends AcGameObject let c = parseInt(Math.random()*this.cols); // console.log(r,c); //对称 - if(g[r][c]||g[c][r])continue;//有障碍物 + if(g[r][c]||g[this.rows-1-r][this.cols-1-c])continue;//有障碍物 if(r==this.rows-2 && c==1||r==1&&c==this.cols-2)continue;//左下角右上角的蛇可能会被盖掉 - g[r][c]=g[c][r]=true;//没有 + g[r][c]=g[this.rows-1-r][this.cols-1-c]=true;//没有 break; //跳出 } @@ -89,13 +97,15 @@ export class GameMap extends AcGameObject return true;//连通 } - start(){//创建一次墙(除非刷新) - //直到创建成功 - for(let i=0;i<=100000000;i++) + start(){ + + this.add_listening_events();//聚焦&keydown事件 + + //创建一次墙(除非刷新) 直到创建成功 + for(let i=0;i<=100000;i++) { if(this.create_wall())break; } - } @@ -107,8 +117,64 @@ export class GameMap extends AcGameObject this.ctx.canvas.height = this.L * this.rows;//长 } + //判断两条蛇是否准备好走下一步 + check_ready(){ + for(let snake of this.snakes){ + if(snake.direction === -1)return false;//无指令 + if(snake.status !== "idle")return false;//不是静止状态[只能从静止移动] + } + return true; + } + + next_step(){ + //让两条蛇走入下一步 + for(let snake of this.snakes){ + snake.next_step(); + } + } + + //检测目标位置是否合法:没撞到两蛇身体 和 墙 + check_valid(cell){ + //墙 + for(let wall of this.walls) + if(wall.r === cell.r && wall.c === cell.c) return false; + // + for(let snake of this.snakes){ + let k = snake.cells.length ; + if(snake.check_tail_increasing === false)//不增,头尾一起动 + { + k--;//少判断一个 + } + + for(let i = 0; i < k; i++){ + if(snake.cells[i].r === cell.r && snake.cells[i].c === cell.c) return false; + } + } + return true; + } + + //获取键盘输入 + add_listening_events(){ + this.ctx.canvas.focus();//cts[DOM] canvas[画板] + const [snake0,snake1] = this.snakes; + //为画板绑定keydown事件 + this.ctx.canvas.addEventListener("keydown",e => { + if(e.key === "w") snake0.set_direction(0); + else if(e.key === "d") snake0.set_direction(1); + else if(e.key === 's') snake0.set_direction(2); + else if(e.key === 'a') snake0.set_direction(3); + else if(e.key === 'ArrowUp' ) snake1.set_direction(0); + else if(e.key === 'ArrowRight') snake1.set_direction(1); + else if(e.key === 'ArrowDown') snake1.set_direction(2); + else if(e.key === 'ArrowLeft') snake1.set_direction(3); + // console.log("keydown") + }) + } update(){ + + if(this.check_ready())this.next_step();//2个蛇准备好了下一步-->走下一步 + this.update_size(); this.render(); } diff --git a/web/src/assets/scripts/Snake.js b/web/src/assets/scripts/Snake.js new file mode 100644 index 0000000..a91dc2d --- /dev/null +++ b/web/src/assets/scripts/Snake.js @@ -0,0 +1,182 @@ + +import { AcGameObject } from "./AcGameObject"; +import { Cell } from "./Cell"; + + +export class Snake extends AcGameObject{ + constructor(info,gamemap) { + super(); + + this.r=info.r; + this.c=info.c; + this.id=info.id; + this.color=info.color; + + this.gamemap=gamemap; + + //存入蛇身的Cell cells[0]是蛇头 + this.cells = [new Cell(this.r,this.c)] + + this.speed = 5;//5格/s + + 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.next_cell = null;//下一步 + this.step=0; + + this.eps = 1e-2;//误差 + + + this.eye_direction = 0; + if (this.id === 1) this.eye_direction = 2; + + this.eye_dx = [ + [-1, 1], + [1, 1], + [1, -1], + [-1, -1], + ]; + this.eye_dy = [ + [-1, -1], + [-1, 1], + [1, 1], + [1, -1], + ]; + + } + + + start(){ + + } + + //为后端准备接口 + set_direction(d){ + this.eye_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])); + } + + if (!this.gamemap.check_valid(this.next_cell)) this.status = "die"; //死亡 + } + + //蛇是否增长 + check_tail_increasing(){ + if(this.step <= 10)return true; + if(this.step % 3 ===1)return true; + return false; + } + + update_move(){ + //蛇头移动 + // this.cells[0].x += this.speed * this.timedelta / 1000;//右 + // this.cells[0].y -= this.speed * this.timedelta / 1000;//上 + + //相对单位 ==>计算sin cos + 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";//走完了,停下来 + + if (this.check_tail_increasing() === false ) {//蛇不变长,尾部变化 蛇变长,尾部不变化 + this.cells.pop();//删除尾部 + } + + } + else//蛇头移动 + { + //绝对单位 + const move_distance = this.speed * this.timedelta / 1000; + this.cells[0].x = this.cells[0].x + move_distance * dx / distance; + this.cells[0].y = this.cells[0].y + move_distance * dy / distance; + + if (this.check_tail_increasing() === false) { //蛇不变长,尾部变化 蛇变长,尾部不变化 + const k = this.cells.length; + const tail = this.cells[k - 1], tail_target = this.cells[k - 2]; + //相对单位 + const tail_dx = tail_target.x - tail.x; + const tail_dy = tail_target.y - tail.y; + //绝对单位 + tail.x = tail.x + move_distance * tail_dx / distance; + tail.y = tail.y + move_distance * tail_dy / distance; + } + } + } + + update(){ + if(this.status === "move")this.update_move(); + this.render(); + } + + render(){ + const L = this.gamemap.L; + const ctx = this.gamemap.ctx; + + if(this.status === "die")this.color = "white"; + ctx.fillStyle = this.color; + + //@@@@@@@@@@粗略身体@@@@@@@@ + for(let cell of this.cells){ + ctx.beginPath(); + ctx.arc(cell.x*L, cell.y*L, L * 0.4 ,0, Math.PI * 2); + ctx.fill(); + } + + //@@@@@@@@@@细致身体@@@@@@@@ + for(let i = 0; i < this.cells.length -1 ;i ++ ){ + const a = this.cells[i] , b = this.cells[i+1]; + //两个cell近似重复 + 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)//上下关系 + { + // (a.x - 0.4) * L ===>相对单位*单位长度 + // Math.min(a.y, b.y) * L===>最左上角的 y值【因为a,b格子排放不知道】 + // L * 0.8 ===>x方向上的宽度 + // Math.abs(a.y - b.y) * L ===>不能只是L【因为卡顿生成头部,每一帧的值都不同】 + 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.5)* L , L , L); + ctx.fillRect(Math.min(a.x, b.x) * L, (a.y - 0.4) * L, Math.abs(a.x - b.x) * L, L * 0.8); + } + + } + + //@@@@@@@@@@@@@@眼睛@@@@@@@@@@@@ + ctx.fillStyle = "black"; + for (let i = 0; i < 2; i ++ ) { + const eye_x = (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.arc(eye_x, eye_y, L * 0.05, 0, Math.PI * 2); + ctx.fill(); + } + } +} \ No newline at end of file diff --git a/web/src/components/GameMap.vue b/web/src/components/GameMap.vue index 07685d2..446c1ca 100644 --- a/web/src/components/GameMap.vue +++ b/web/src/components/GameMap.vue @@ -1,6 +1,6 @@ diff --git a/web/src/components/NavBar.vue b/web/src/components/NavBar.vue index 1dd0b8a..6094f89 100644 --- a/web/src/components/NavBar.vue +++ b/web/src/components/NavBar.vue @@ -59,6 +59,7 @@ export default { //不太理解 setup(){ const route = useRoute(); + //实时计算 路由名称 let route_name = computed( () => route.name ); return { route_name diff --git a/web/src/router/index.js b/web/src/router/index.js index a304821..9d0cffb 100644 --- a/web/src/router/index.js +++ b/web/src/router/index.js @@ -50,6 +50,7 @@ const routes = [ ] +//Hash有# const router = createRouter({ history: createWebHistory(), routes