蛇的控制逻辑,增长逻辑,碰撞检测改为后端实现 + 结果计分板实现
This commit is contained in:
parent
4b3ebf9d77
commit
ae4d8ef42d
|
@ -27,7 +27,7 @@ public class WebSocketServer {
|
||||||
使用线程安全的 Hash 表 ConcurrentHashMap<>(),
|
使用线程安全的 Hash 表 ConcurrentHashMap<>(),
|
||||||
将 userId 映射到 webSocketServer
|
将 userId 映射到 webSocketServer
|
||||||
*/
|
*/
|
||||||
private static final ConcurrentHashMap<Integer, WebSocketServer> users = new ConcurrentHashMap<>();
|
public static final ConcurrentHashMap<Integer, WebSocketServer> users = new ConcurrentHashMap<>();
|
||||||
// 使用 CopyOnWriteArraySet 创建一个线程安全的匹配池 matchPool
|
// 使用 CopyOnWriteArraySet 创建一个线程安全的匹配池 matchPool
|
||||||
private static final CopyOnWriteArraySet<User> matchPool = new CopyOnWriteArraySet<>();
|
private static final CopyOnWriteArraySet<User> matchPool = new CopyOnWriteArraySet<>();
|
||||||
// 在 WebSocketServer 中注入数据库的方法演示-> 使用 static 定义为独一份的变量
|
// 在 WebSocketServer 中注入数据库的方法演示-> 使用 static 定义为独一份的变量
|
||||||
|
@ -36,6 +36,7 @@ public class WebSocketServer {
|
||||||
private Session session = null;
|
private Session session = null;
|
||||||
// 用户信息:定义一个成员变量
|
// 用户信息:定义一个成员变量
|
||||||
private User user;
|
private User user;
|
||||||
|
private Game game = null;
|
||||||
|
|
||||||
// 注入方法
|
// 注入方法
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -99,14 +100,24 @@ public class WebSocketServer {
|
||||||
matchPool.remove(b);
|
matchPool.remove(b);
|
||||||
|
|
||||||
// 匹配成功时,创建联机地图
|
// 匹配成功时,创建联机地图
|
||||||
Game game = new Game(13,14,20);
|
Game game = new Game(13,14,20,a.getId(),b.getId());
|
||||||
game.createMap(); // 初始化地图
|
game.createMap(); // 初始化地图
|
||||||
|
users.get(a.getId()).game = game; // 将 game 赋给 a 玩家
|
||||||
|
users.get(b.getId()).game = game;
|
||||||
|
|
||||||
|
game.start(); // 开启新线程,执行函数
|
||||||
|
|
||||||
JSONObject respGameData = new JSONObject();
|
JSONObject respGameData = new JSONObject();
|
||||||
respGameData.put("game_map",game.getG());
|
respGameData.put("game_map",game.getG());
|
||||||
respGameData.put("rows",game.getRows());
|
respGameData.put("rows",game.getRows());
|
||||||
respGameData.put("cols",game.getCols());
|
respGameData.put("cols",game.getCols());
|
||||||
respGameData.put("inner_walls_count",game.getInnerWallsCount());
|
respGameData.put("inner_walls_count",game.getInnerWallsCount());
|
||||||
|
respGameData.put("a_id",game.getPlayerA().getId());
|
||||||
|
respGameData.put("a_sx",game.getPlayerA().getSx());
|
||||||
|
respGameData.put("a_sy",game.getPlayerA().getSy());
|
||||||
|
respGameData.put("b_id",game.getPlayerB().getId());
|
||||||
|
respGameData.put("b_sx",game.getPlayerB().getSx());
|
||||||
|
respGameData.put("b_sy",game.getPlayerB().getSy());
|
||||||
|
|
||||||
// 将 a 配对成功的消息传回客户端
|
// 将 a 配对成功的消息传回客户端
|
||||||
JSONObject respA = new JSONObject();
|
JSONObject respA = new JSONObject();
|
||||||
|
@ -133,6 +144,18 @@ public class WebSocketServer {
|
||||||
matchPool.remove(this.user);
|
matchPool.remove(this.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// direction 传入 move(移动) 方向参数
|
||||||
|
private void move(int direction){
|
||||||
|
// 判断角色:如果是 A 角色,则将获取到的方向设置为 A 的 nextStep 方向
|
||||||
|
// user.getId() 是获取当前链接的用户 id
|
||||||
|
if(game.getPlayerA().getId().equals(user.getId())){
|
||||||
|
game.setNextStepA(direction);
|
||||||
|
}
|
||||||
|
else if(game.getPlayerB().getId().equals(user.getId())){
|
||||||
|
game.setNextStepB(direction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// @OnMessage 用于从前端接收请求: 一般 onMessage 当成路由使用,做为消息判断处理的中间部分
|
// @OnMessage 用于从前端接收请求: 一般 onMessage 当成路由使用,做为消息判断处理的中间部分
|
||||||
@OnMessage
|
@OnMessage
|
||||||
public void onMessage(String message, Session session) {
|
public void onMessage(String message, Session session) {
|
||||||
|
@ -148,6 +171,8 @@ public class WebSocketServer {
|
||||||
startMatching();
|
startMatching();
|
||||||
} else if ("stop-matching".equals(event)) {
|
} else if ("stop-matching".equals(event)) {
|
||||||
stopMatching();
|
stopMatching();
|
||||||
|
} else if ("move".equals(event)) {
|
||||||
|
move(data.getInteger("direction"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.kob.backend.consumer.utils;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Cell {
|
||||||
|
int x, y;
|
||||||
|
}
|
|
@ -1,9 +1,17 @@
|
||||||
package com.kob.backend.consumer.utils;
|
package com.kob.backend.consumer.utils;
|
||||||
|
|
||||||
import java.util.Random;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.kob.backend.consumer.WebSocketServer;
|
||||||
|
|
||||||
// 用来管理整个游戏流程
|
import javax.swing.event.InternalFrameEvent;
|
||||||
public class Game {
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
// 用来管理整个游戏流程:这里需要多线程
|
||||||
|
public class Game extends Thread {
|
||||||
// 游戏地图:行数,列数,内部障碍物数量
|
// 游戏地图:行数,列数,内部障碍物数量
|
||||||
private final Integer rows;
|
private final Integer rows;
|
||||||
private final Integer cols;
|
private final Integer cols;
|
||||||
|
@ -12,25 +20,68 @@ public class Game {
|
||||||
private final int[][] g;
|
private final int[][] g;
|
||||||
// 定义"上右下左"四个方向的 dx, dy偏移量
|
// 定义"上右下左"四个方向的 dx, dy偏移量
|
||||||
private final int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};
|
private final int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};
|
||||||
|
private final Player playerA, playerB;
|
||||||
|
// 玩家下一步操作状态定义
|
||||||
|
private Integer nextStepA = null;
|
||||||
|
private Integer nextStepB = null;
|
||||||
|
// 加锁:解决读写冲突用
|
||||||
|
private ReentrantLock lock = new ReentrantLock();
|
||||||
|
// 整个游戏的当前状态: playing(正在进行) --> finished(游戏结束)
|
||||||
|
private String status = "playing";
|
||||||
|
// 定义失败者: all(平局), A(A输), B(B输)
|
||||||
|
private String loser = "";
|
||||||
|
|
||||||
// 初始化构造函数
|
|
||||||
public Game(Integer rows, Integer cols, Integer inner_walls_count) {
|
// 初始化(有参)构造函数
|
||||||
|
public Game(Integer rows, Integer cols, Integer inner_walls_count, Integer idA, Integer idB) {
|
||||||
this.rows = rows;
|
this.rows = rows;
|
||||||
this.cols = cols;
|
this.cols = cols;
|
||||||
this.inner_walls_count = inner_walls_count;
|
this.inner_walls_count = inner_walls_count;
|
||||||
this.g = new int[rows][cols];
|
this.g = new int[rows][cols];
|
||||||
|
playerA = new Player(idA, rows - 2, 1, new ArrayList<>());
|
||||||
|
playerB = new Player(idB, 1, cols - 2, new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getRows(){
|
public Player getPlayerA() {
|
||||||
|
return playerA;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Player getPlayerB() {
|
||||||
|
return playerB;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRows() {
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
public int getCols(){
|
|
||||||
|
public int getCols() {
|
||||||
return cols;
|
return cols;
|
||||||
}
|
}
|
||||||
public int getInnerWallsCount(){
|
|
||||||
|
public int getInnerWallsCount() {
|
||||||
return inner_walls_count;
|
return inner_walls_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setNextStepA(Integer nextStepA) {
|
||||||
|
// 为了防止两方读写冲突,需要加线程锁之后操作 nextStep 值
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
this.nextStepA = nextStepA;
|
||||||
|
} finally {
|
||||||
|
// 操作完成后解锁
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNextStepB(Integer nextStepB) {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
this.nextStepB = nextStepB;
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 返回生成的地图
|
// 返回生成的地图
|
||||||
public int[][] getG() {
|
public int[][] getG() {
|
||||||
return g;
|
return g;
|
||||||
|
@ -117,4 +168,162 @@ public class Game {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取两名玩家的下一步操作
|
||||||
|
private boolean nextStep() {
|
||||||
|
try {
|
||||||
|
// 返回下一步操作之前,先睡眠 200 毫秒,用于等待前端渲染完成,避免渲染速度跟不上后端运算处理速度
|
||||||
|
// 前端定义了每 1 秒钟 5 个格子,则每 200 毫秒走一格(每一步至少需要 200 毫秒的时间)
|
||||||
|
Thread.sleep(200);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 循环 50 秒钟,每次 100 毫秒,判断用户输入(50 * 100 = 5000 ms后如果没有接收到输入,则判断输赢)
|
||||||
|
for (int i = 0; i < 50; i++) {
|
||||||
|
try {
|
||||||
|
// 两次接收输入的等待间隔
|
||||||
|
Thread.sleep(100);
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
// 判断两名玩家是否都已经读取到输入:如果读入都非空,则表示读取结束,返回 true
|
||||||
|
if (nextStepA != null && nextStepB != null) {
|
||||||
|
// 将下一步操作存下来
|
||||||
|
playerA.getSteps().add(nextStepA);
|
||||||
|
playerB.getSteps().add(nextStepB);
|
||||||
|
|
||||||
|
// 成功获取到两名玩家的下一步操作,返回 true
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 碰撞合法性检测辅助函数:需要判断 snakeA 和 snakeB 是否重合
|
||||||
|
private boolean checkValid(List<Cell> cellsA, List<Cell> cellsB) {
|
||||||
|
int n = cellsA.size(); // cellsA 的当前长度
|
||||||
|
Cell cell = cellsA.get(n - 1); // 取出 cellsA 的蛇头位置(数组最后一位)(第0个元素是蛇尾巴)
|
||||||
|
if (g[cell.x][cell.y] == 1) return false; // 判断 A 的最后一位(蛇头)的坐标是否是障碍物(值为1),如果是障碍物则返回false(判断已碰撞)
|
||||||
|
|
||||||
|
// 判断 A蛇头位置是否和A除了蛇头以外的其他身体部分坐标有重合
|
||||||
|
for (int i = 0; i < n - 1; i++) {
|
||||||
|
if (cellsA.get(i).x == cell.x && cellsA.get(i).y == cell.y)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (Cell cellB : cellsB) { // 判断 A蛇头位置是否和B蛇身体各部分坐标有重合:有重合返回不合法(表示已经碰撞)
|
||||||
|
if (cellB.x == cell.x && cellB.y == cell.y)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 以上判断条件如果都合法,则返回 true合法(表示没有碰撞)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断两名玩家下一步操作是否合法
|
||||||
|
private void judge() {
|
||||||
|
// 取出 A,B 两蛇
|
||||||
|
List<Cell> cellsA = playerA.getCells();
|
||||||
|
List<Cell> cellsB = playerB.getCells();
|
||||||
|
|
||||||
|
boolean validA = checkValid(cellsA, cellsB);
|
||||||
|
boolean validB = checkValid(cellsB, cellsA);
|
||||||
|
|
||||||
|
// 如果 A 和 B 有人这一步不合法(已经产生碰撞),将游戏状态改为 "finished",表示游戏结束
|
||||||
|
if (!validA || !validB) {
|
||||||
|
status = "finished";
|
||||||
|
|
||||||
|
// 游戏结束时,判断输赢
|
||||||
|
if (!validA && !validB) {
|
||||||
|
loser = "all";
|
||||||
|
} else if (!validA && validB) {
|
||||||
|
loser = "A";
|
||||||
|
} else if (validA && !validB) {
|
||||||
|
loser = "B";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 向两个 Client 广播信息
|
||||||
|
private void sendAllMessage(String message) {
|
||||||
|
WebSocketServer.users.get(playerA.getId()).sendMessage(message);
|
||||||
|
WebSocketServer.users.get(playerB.getId()).sendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 向两个 Client 传递移动信息
|
||||||
|
private void sendMove() {
|
||||||
|
//由于这里需要读入 nextStepA, nextStepB, 这里需要加一下线程锁
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
JSONObject resp = new JSONObject();
|
||||||
|
resp.put("event", "move"); // event(事件类型): move(移动信息)
|
||||||
|
resp.put("a_direction", nextStepA); // a_direction( playerA 移动的方向)
|
||||||
|
resp.put("b_direction", nextStepB); // b_direction( playerB 移动的方向)
|
||||||
|
sendAllMessage(resp.toJSONString());
|
||||||
|
// 本次获取完下一步操作的同时需要进行再下一步操作输入,在这之前,需要将当前的 nextStep 清空
|
||||||
|
nextStepA = nextStepB = null;
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 向两个 Client 公布结果
|
||||||
|
private void sendResult() {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
JSONObject resp = new JSONObject();
|
||||||
|
resp.put("event", "result"); // event(事件类型): result(结果)
|
||||||
|
resp.put("loser", loser); // 失败者
|
||||||
|
// 将最后碰撞时的蛇的眼睛指向传给前端
|
||||||
|
resp.put("a_eyes_finally_direction", nextStepA);
|
||||||
|
resp.put("b_eyes_finally_direction", nextStepB);
|
||||||
|
sendAllMessage(resp.toJSONString());
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重写线程函数
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// 循环 1000 步: 13格x14格x(最大)3步/格 < 1000,保证 1000 次一定可以走完
|
||||||
|
for (int i = 0; i < 1000; i++) {
|
||||||
|
if (nextStep()) { // 是否获取到两条蛇的下一步操作
|
||||||
|
// 判断下一步操作是否合法
|
||||||
|
judge();
|
||||||
|
|
||||||
|
// 如果游戏状态还是 playing,则向两个玩家广播移动信息
|
||||||
|
if ("playing".equals(status)) {
|
||||||
|
sendMove();
|
||||||
|
} else if ("finished".equals(status)) {
|
||||||
|
// 如果游戏状态已经更改为了 finished,则将结果返回给 Client 并 break 中断执行后续操作
|
||||||
|
sendResult();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// 未获取到某蛇的下一步操作,则将游戏状态改为 finished
|
||||||
|
status = "finished";
|
||||||
|
// 判断失败者:这里需要加锁,因为涉及到对 nextStep 的读操作
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
if (nextStepA == null && nextStepB == null) {
|
||||||
|
loser = "all";
|
||||||
|
} else if (nextStepA == null) {
|
||||||
|
loser = "A";
|
||||||
|
} else {
|
||||||
|
loser = "B";
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
package com.kob.backend.consumer.utils;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/// 玩家信息
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Player {
|
||||||
|
private Integer id;
|
||||||
|
private Integer sx;
|
||||||
|
private Integer sy;
|
||||||
|
// 玩家走过的路径中每一步的方向
|
||||||
|
private List<Integer> steps;
|
||||||
|
|
||||||
|
// 检验当前回合蛇的长度(蛇尾)是否需要增加
|
||||||
|
public boolean checkTailIncreasing(int step) {
|
||||||
|
// 前 10 回合每次都增加一节蛇尾,后面每 3 回合增加一节蛇尾
|
||||||
|
if (step <= 10) return true;
|
||||||
|
if (step % 3 == 1) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 蛇身 Cell 列表:记录蛇身各部分坐标
|
||||||
|
public List<Cell> getCells() {
|
||||||
|
List<Cell> res = new ArrayList<>();
|
||||||
|
|
||||||
|
// 四方向偏移量
|
||||||
|
int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};
|
||||||
|
int x = sx, y = sy; // 起始坐标
|
||||||
|
int step = 0; // 定义当前回合数: 开始时为 0 回合
|
||||||
|
res.add(new Cell(x, y)); // 现将蛇头添加进 List
|
||||||
|
for (int d : steps) { // 使用下一步方向求出下一个结点的坐标
|
||||||
|
x += dx[d];
|
||||||
|
y += dy[d];
|
||||||
|
res.add(new Cell(x, y)); // 添加新的身体结点
|
||||||
|
step++; // 回合数 +1
|
||||||
|
// 判断蛇尾要不要增加:如果本回合蛇尾不增加,则将本回合新生成的蛇尾(列表的第0个元素)删掉
|
||||||
|
if (!checkTailIncreasing(step)) {
|
||||||
|
res.remove(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,7 +33,7 @@ export class GameMap extends AcGameObject {
|
||||||
// 上面的 super() 会先将 AcGameObject 先绘制, walls 的绘制在后面执行,因此墙最后会覆盖原棋盘格进行绘制
|
// 上面的 super() 会先将 AcGameObject 先绘制, walls 的绘制在后面执行,因此墙最后会覆盖原棋盘格进行绘制
|
||||||
this.walls = [];
|
this.walls = [];
|
||||||
|
|
||||||
// 创建蛇对象数组
|
// 创建蛇对象数组(可以通过 store.state.pk.game_object.snakes 全局变量调取)
|
||||||
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),
|
||||||
|
@ -167,20 +167,35 @@ export class GameMap extends AcGameObject {
|
||||||
this.ctx.canvas.focus();
|
this.ctx.canvas.focus();
|
||||||
|
|
||||||
// 先取出两条蛇对象
|
// 先取出两条蛇对象
|
||||||
const [snake0, snake1] = this.snakes;
|
// const [snake0, snake1] = this.snakes;
|
||||||
|
|
||||||
// 获取用户信息:绑定 keydown 事件
|
// 获取用户信息:绑定 keydown 事件
|
||||||
this.ctx.canvas.addEventListener("keydown", (e) => {
|
this.ctx.canvas.addEventListener("keydown", (e) => {
|
||||||
|
// 取出移动的方向,需要将方向传给后端
|
||||||
|
let d = -1; // -1 表示没有方向, 0-上, 1-右, 2-下, 3-左
|
||||||
|
|
||||||
// 定义 snake0 的键盘绑定事件
|
// 定义 snake0 的键盘绑定事件
|
||||||
if (e.key === "w") snake0.set_direction(0);
|
if (e.key === "w") d = 0;
|
||||||
else if (e.key === "d") snake0.set_direction(1);
|
else if (e.key === "d") d = 1;
|
||||||
else if (e.key === "s") snake0.set_direction(2);
|
else if (e.key === "s") d = 2;
|
||||||
else if (e.key === "a") snake0.set_direction(3);
|
else if (e.key === "a") d = 3;
|
||||||
// 定义 snake1 的键盘绑定事件
|
|
||||||
|
// 如果进行了合法的移动操作:向后端发送方向
|
||||||
|
if(d>=0){
|
||||||
|
// 使用 socket.send() 传递信息给后端,使用 JSON.stringify() 将一个 JSON 封装成一字符串
|
||||||
|
this.store.state.pk.socket.send(JSON.stringify({
|
||||||
|
event: "move", // 事件类型: move
|
||||||
|
direction : d, // 方向: d
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// 定义 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);
|
||||||
|
*/
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -104,11 +104,12 @@ export class Snake extends AcGameObject {
|
||||||
// 这里需要使用 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() {
|
||||||
|
|
|
@ -24,8 +24,10 @@ export default {
|
||||||
// 定义挂载函数,挂载完成后执行
|
// 定义挂载函数,挂载完成后执行
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// new GameMap(canvas.value.getContext("2d"), parent.value);
|
// new GameMap(canvas.value.getContext("2d"), parent.value);
|
||||||
// 改为后端(服务器)获取生成地图
|
// 改为后端(服务器)获取生成地图:并将游戏信息存入 store
|
||||||
new GameMap(canvas.value.getContext("2d"), parent.value, store);
|
store.commit("updateGameObject",
|
||||||
|
new GameMap(canvas.value.getContext("2d"), parent.value, store),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
// 计分板
|
||||||
|
<template>
|
||||||
|
<div class="result-borad">
|
||||||
|
<div class="result-board-text" v-if="$store.state.pk.loser === 'all'">
|
||||||
|
平局!
|
||||||
|
</div>
|
||||||
|
<div class="result-board-text"
|
||||||
|
v-else-if="$store.state.pk.loser === 'A' && $store.state.pk.a_id === parseInt($store.state.user.id)">
|
||||||
|
你输了!
|
||||||
|
</div>
|
||||||
|
<div class="result-board-text"
|
||||||
|
v-else-if="$store.state.pk.loser === 'B' && $store.state.pk.b_id == $store.state.user.id">
|
||||||
|
你输了!
|
||||||
|
</div>
|
||||||
|
<div class="result-board-text"
|
||||||
|
v-else-if="$store.state.pk.loser === 'A' && $store.state.pk.b_id == $store.state.user.id">
|
||||||
|
你赢了!
|
||||||
|
</div>
|
||||||
|
<div class="result-board-text"
|
||||||
|
v-else-if="$store.state.pk.loser === 'B' && $store.state.pk.a_id == $store.state.user.id">
|
||||||
|
你赢了!
|
||||||
|
</div>
|
||||||
|
<div class="result-board-btn">
|
||||||
|
<button type="button" class="btn btn-dark" @click="restart">再来一次</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { useStore } from "vuex";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
const store = useStore();
|
||||||
|
|
||||||
|
// 重来,需要恢复全局变量
|
||||||
|
const restart = () => {
|
||||||
|
store.commit("updateStatus", "matching");
|
||||||
|
store.commit("updateLoser", "none");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
restart,
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
div.result-borad {
|
||||||
|
height: 30vh;
|
||||||
|
width: 30vw;
|
||||||
|
background-color: rgba(194, 194, 195, 0.504);
|
||||||
|
/* 位置 */
|
||||||
|
position: absolute;
|
||||||
|
top: 30vh;
|
||||||
|
left: 35vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-board-text {
|
||||||
|
text-align: center;
|
||||||
|
color: rgb(234, 234, 234);
|
||||||
|
font-size: 50px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: italic;
|
||||||
|
padding-top: 5vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-board-btn {
|
||||||
|
padding-top: 3vh;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -7,10 +7,20 @@ export default {
|
||||||
// 对手信息
|
// 对手信息
|
||||||
opponent_username: "",
|
opponent_username: "",
|
||||||
opponent_photo: "",
|
opponent_photo: "",
|
||||||
|
// 地图信息(后端传入)
|
||||||
game_map: null,
|
game_map: null,
|
||||||
rows: 0,
|
rows: 0,
|
||||||
cols: 0,
|
cols: 0,
|
||||||
inner_walls_count: 0,
|
inner_walls_count: 0,
|
||||||
|
a_id: 0,
|
||||||
|
a_sx: 0,
|
||||||
|
a_sy: 0,
|
||||||
|
b_id: 0,
|
||||||
|
b_sx: 0,
|
||||||
|
b_sy: 0,
|
||||||
|
// 游戏全局
|
||||||
|
game_object: null,
|
||||||
|
loser:"none", // all, A, B 三种结果
|
||||||
},
|
},
|
||||||
getters: {},
|
getters: {},
|
||||||
mutations: {
|
mutations: {
|
||||||
|
@ -33,7 +43,20 @@ export default {
|
||||||
state.rows = game_data.rows;
|
state.rows = game_data.rows;
|
||||||
state.cols = game_data.cols;
|
state.cols = game_data.cols;
|
||||||
state.inner_walls_count = game_data.inner_walls_count;
|
state.inner_walls_count = game_data.inner_walls_count;
|
||||||
|
state.a_id = game_data.a_id;
|
||||||
|
state.a_sx = game_data.a_sx;
|
||||||
|
state.a_sy = game_data.a_sy;
|
||||||
|
state.b_id = game_data.b_id;
|
||||||
|
state.b_sx = game_data.b_sx;
|
||||||
|
state.b_sy = game_data.b_sy;
|
||||||
},
|
},
|
||||||
|
updateGameObject(state, game_object){
|
||||||
|
// 在 GameMap.vue 中使用
|
||||||
|
state.game_object = game_object;
|
||||||
|
},
|
||||||
|
updateLoser(state,loser){
|
||||||
|
state.loser = loser;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
actions: {},
|
actions: {},
|
||||||
module: {},
|
module: {},
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<PlayGround v-if="$store.state.pk.status === 'playing'" />
|
<PlayGround v-if="$store.state.pk.status === 'playing'" />
|
||||||
<MatchGround v-else-if="$store.state.pk.status === 'matching'" />
|
<MatchGround v-else-if="$store.state.pk.status === 'matching'" />
|
||||||
|
<ResultBoard v-if="$store.state.pk.loser !== 'none'" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import PlayGround from "../../components/PlayGround.vue";
|
import PlayGround from "../../components/PlayGround.vue";
|
||||||
import MatchGround from "../../components/MatchGround.vue"
|
import MatchGround from "../../components/MatchGround.vue";
|
||||||
|
import ResultBoard from "../../components/ResultBoard.vue";
|
||||||
|
|
||||||
// onMounted 用于组件被挂载时, onUnmounted 用于组件被解除挂载时
|
// onMounted 用于组件被挂载时, onUnmounted 用于组件被解除挂载时
|
||||||
import { onMounted, onUnmounted } from "vue";
|
import { onMounted, onUnmounted } from "vue";
|
||||||
// 取出全局变量
|
// 取出全局变量
|
||||||
|
@ -14,7 +17,8 @@ import { useStore } from "vuex";
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
PlayGround,
|
PlayGround,
|
||||||
MatchGround
|
MatchGround,
|
||||||
|
ResultBoard
|
||||||
},
|
},
|
||||||
// 创建前后端连接:当当前页面组件被加载时,建立连接,故使用 onMounted 函数
|
// 创建前后端连接:当当前页面组件被加载时,建立连接,故使用 onMounted 函数
|
||||||
setup() {
|
setup() {
|
||||||
|
@ -23,6 +27,7 @@ export default {
|
||||||
const socketUrl = `ws://127.0.0.1:3000/websocket/${store.state.user.token}/`;
|
const socketUrl = `ws://127.0.0.1:3000/websocket/${store.state.user.token}/`;
|
||||||
|
|
||||||
let socket = null;
|
let socket = null;
|
||||||
|
|
||||||
// 当前组件被挂载的时候,需要创建后端连接,并且需要将连接信息存储到 store 全局变量里
|
// 当前组件被挂载的时候,需要创建后端连接,并且需要将连接信息存储到 store 全局变量里
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 定义默认匹配页面对手信息(匹配等待过程中使用)
|
// 定义默认匹配页面对手信息(匹配等待过程中使用)
|
||||||
|
@ -61,6 +66,37 @@ export default {
|
||||||
store.commit("updateGameMap", data.game_data);
|
store.commit("updateGameMap", data.game_data);
|
||||||
console.log(data.game_data);
|
console.log(data.game_data);
|
||||||
}
|
}
|
||||||
|
else if (data.event === "move") {
|
||||||
|
// console.log(data);
|
||||||
|
// 调取 store 中存入的地图全局信息
|
||||||
|
const game = store.state.pk.game_object;
|
||||||
|
// 从地图全局信息中解构出 GameMap.js 中定义的 snakes
|
||||||
|
const [snake0, snake1] = game.snakes;
|
||||||
|
// 设置两条蛇的移动:后端 sendMove 中的参数
|
||||||
|
snake0.set_direction(data.a_direction);
|
||||||
|
snake1.set_direction(data.b_direction);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (data.event === "result") {
|
||||||
|
console.log(data);
|
||||||
|
const game = store.state.pk.game_object;
|
||||||
|
const [snake0, snake1] = game.snakes;
|
||||||
|
// 修改最后一步(检测到碰撞后)蛇的眼睛方向
|
||||||
|
snake0.eye_direction = data.a_eyes_finally_direction;
|
||||||
|
snake1.eye_direction = data.b_eyes_finally_direction;
|
||||||
|
|
||||||
|
// 更新 loser 信息,存储到全局 store 中
|
||||||
|
store.commit("updateLoser",data.loser);
|
||||||
|
|
||||||
|
// 更新 loser 的 snake 对象 status 状态
|
||||||
|
if (data.loser === "all" || data.loser === "A") {
|
||||||
|
snake0.status = "die";
|
||||||
|
}
|
||||||
|
if (data.loser === "all" || data.loser === "B") {
|
||||||
|
snake1.status = "die";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.onclose = () => {
|
socket.onclose = () => {
|
||||||
|
|
Loading…
Reference in New Issue