建设部网站查造价师,wordpress自定义字段调用,app模板下载网站模板,页面设计高度目录
一、约定前后端交互接口的参数
1、房间准备就绪
#xff08;1#xff09;配置 websocket 连接路径
#xff08;2#xff09;构造 游戏就绪 的 响应对象
2、“落子” 的请求和响应
#xff08;1#xff09;“落子” 请求对象
#xff08;2#xff09;“落子…目录
一、约定前后端交互接口的参数
1、房间准备就绪
1配置 websocket 连接路径
2构造 游戏就绪 的 响应对象
2、“落子” 的请求和响应
1“落子” 请求对象
2“落子” 响应对象
二、UserMapper
三、处理 websocket 请求、返回的响应GameAPI
1、afterConnectionEstablished
1通知玩家玩家进入房间noticeGameReady
2afterConnectionEstablished
1、首先通过Session拿到玩家信息
2、通过玩家信息拿到游戏房间
3、判断用户是不是多开了
4、设置房间上线
5、把玩家加入对应的房间中
6、处理不存在的操作
2、handleTextMessage
3、handleTransportError
1noticeThatUserWin通知获胜者
4、afterConnectionClosed
四、Room
1、棋盘
2、自动注入
3、putChess处理落子请求、构造返回响应
4、打印棋盘
5、checkWinner判断输赢
行顶点为左边
列顶点在上边
主对角线顶点在左上
副对角线顶点在右上
五、前端代码的逻辑处理
1、处理游戏就绪响应
2、处理落子请求
3、处理落子响应
六、梳理前后端交互流程
七、代码以及线上云服务器的URL 一、约定前后端交互接口的参数
1、房间准备就绪 当我们从 游戏大厅页面 跳转到 游戏房间页面这也意味着游戏大厅页面的 websocket 连接断开了跳转到 游戏房间页面要建立一个新的 websocket 连接。 那么也就说明我们之前的 websocket 连接不能用了这里新的 websocket 连接最好使用新的 URL不要和游戏大厅的一样这样能起到 “解耦合” 的效果。 匹配成功后是服务器主动给客户端发起响应客户端就不必发起请求了。约定如图 1配置 websocket 连接路径 前端代码如下使用动态路径更加灵活方便后部署在服务器上
let websocketUrl ws:// location.host /game;
let websocket new WebSocket(websocketUrl); 后端代码
Configuration
EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {Autowiredprivate MatchAPI matchAPI;Autowiredprivate GameAPI gameAPI;Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {webSocketHandlerRegistry.addHandler(matchAPI, /findMatch).addInterceptors(new HttpSessionHandshakeInterceptor());webSocketHandlerRegistry.addHandler(gameAPI, /game).addInterceptors(new HttpSessionHandshakeInterceptor());}
}
注意配置路径的同时还要拿到之前HttpSession信息保存在 新的 websocket连接中。
2构造 游戏就绪 的 响应对象 后端需要构造一个响应对象把其转换为JSON格式的文本信息再发送给客户端响应对象如下
// 客户端连接到游戏房间后返回的响应
Data
public class GameReadyResponse {private String message;private boolean ok;private String reason;private String roomId;private int thisUserId;private int thatUserId;private int whiteUser;
}
2、“落子” 的请求和响应 双方玩家进入游戏房间后也就是准备就绪后那就要开始对弈了所以我们要针对玩家 “落子”的操作构造请求和响应。 因为玩家是主动落子所以要发送给服务器请求服务器接收到请求后也需要处理这个请求构造响应把最新棋盘布局发给另一个玩家同时这个响应也要发送给我让我知道到对方落子了。 1“落子” 请求对象
// 这个类表示落子请求
Data
public class GameRequest {private String message;private int userId;private int row;private int col;
}
2“落子” 响应对象
// 这个类表示一个落子响应
Data
public class GameResponse {private String message;private int userId;private int row;private int col;private int winner;
} 二、UserMapper 在游戏结束之后我们要给玩家结算胜败那么玩家的天梯积分和游戏场数相对也会改变说明我们也就要对数据库进行操作了这里增加两个修改数据库的操作代码如下
Mapper
public interface UserMapper {// 根据用户名查询用户的详情信息用于登录功能Select(select * from user where user_name #{username})User selectByName(String username);// 往数据库里插入信息用于注册功能Insert(insert into user values (null, #{username}, #{password}, 1000, 0, 0);)void register(User userInfo);// 总比赛场数 1 获胜场数 1 天梯积分 30Update(update user set total_count total_count 1, win_count win_count 1, score score 30 where user_id #{userId})void userWin(int userId);// 总比赛场数 1 获胜场数 不变 天梯积分 - 30Update(update user set total_count total_count 1, score score - 30 where user_id #{userId})void userLose(int userId);
} 一个是针对玩家 游戏胜利 后的修改操作一个是针对玩家 对局失败 后的修改操作。 三、处理 websocket 请求、返回的响应GameAPI 这里主要涉及的方法有四个 afterConnectionEstablished在建立 websocket 连接时要做的处理。 handleTextMessage接收玩家的 请求返回对应的 响应。 handleTransportError当 websocket 连接出现错误时要做的处理。 afterConnectionClosed当 websocket 连接关闭时要做的处理。 1、afterConnectionEstablished 建立了新的websocket连接需要把就绪的玩家加入到房间中这里就是处理玩家加入房间的逻辑。
1通知玩家玩家进入房间noticeGameReady 这里把通知玩家的逻辑单独封装成一个方法代码如下 private void noticeGameReady(Room room, User thisUser, User thatUser) throws IOException {GameReadyResponse resp new GameReadyResponse();resp.setMessage(gameReady);resp.setOk(true);resp.setReason();resp.setRoomId(room.getRoomId());resp.setThisUserId(thisUser.getUserId());resp.setThatUserId(thatUser.getUserId());resp.setWhiteUser(room.getWhiteUser());// 把当前的响应数据传回给对应的玩家WebSocketSession webSocketSession onlineUserManager.getFromGameRoom(thisUser.getUserId());webSocketSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));} 构造对应的响应数据然后通过 Session 发送给客户端。
2afterConnectionEstablished 建立连接时并不可以直接把玩家加入房间需要经过一系列的校验。
1、首先通过Session拿到玩家信息 但拿到后还要判断用户是不是null // 1、先获取到用户的身份信息从 HttpSession 里拿到)User user (User) session.getAttributes().get(user);if (user null) {resp.setOk(false);resp.setReason(用户尚未登录!);String jsonString objectMapper.writeValueAsString(resp);session.sendMessage(new TextMessage(jsonString));return;}
2、通过玩家信息拿到游戏房间 也要对房间进行校验如果为空就要返回对应响应给前端。 // 2、判定当前用户是否已经进入房间拿着房间管理器进行查询Room room roomManager.getRoomByUserId(user.getUserId());if (room null) {// 如果为 null当前没有找到对应的房间该玩家还没匹配到resp.setOk(false);resp.setReason(用户尚未匹配到);String jsonString objectMapper.writeValueAsString(resp);session.sendMessage(new TextMessage(objectMapper.writeValueAsBytes(jsonString)));return;}
3、判断用户是不是多开了 进行对局时发现该玩家已经在房间里了说明前面已经有玩家在登录游戏中我再登录就视为多开行为还有是玩家一边在游戏大厅一边是在游戏房间也视为多开。 // 3、判定当前是不是多开该用户是不是已经在其他地方进行游戏了// 前面多准备了一个 OnlineUserManagerif (onlineUserManager.getFromGameHall(user.getUserId()) ! null|| onlineUserManager.getFromGameRoom(user.getUserId()) ! null) {// 如果一个账号一边是在游戏大厅一边是在游戏房间也视为多开~resp.setOk(true);resp.setReason(禁止多开游戏页面);resp.setMessage(repeatConnection);String jsonString objectMapper.writeValueAsString(resp);session.sendMessage(new TextMessage(jsonString));return;}
4、设置房间上线 经过上面一系列校验后没有问题才能设置玩家在房间中上线。 // 4、经过一些列校验都没问题后设置当前玩家上线房间中上线onlineUserManager.enterGameRoom(user.getUserId(), session);
5、把玩家加入对应的房间中 把玩家加入对应的房间中这里设置先手的操作是先加入房间的玩家。 当双方玩家都加入房间后要对客户端进行通知构造对应的响应发送回去。 synchronized (room) {// 5、把两个玩家加入到游戏房间中// 前面的创建房间/匹配过程是在 game_hall.html 页面中完成的// 因此前面匹配到对手之后需要经过页面跳转来到 game_room.html 才算正式进入游戏房间才算玩家准备就绪// 当前这个逻辑是在 game_room.html 页面加载的时候进行的// 执行到当前逻辑说明玩家已经页面跳转成功了// 页面跳转其实是个大活~很有可能出现 “失败” 的情况的if (room.getUser1() null) {// 第一个玩家尚未加入房间// 就把当前连上 WebSocket 的玩家作为 user1加入到房间中room.setUser1(user);// 把先连入房间的玩家作为先手方room.setWhiteUser(user.getUserId());log.info(玩家 user.getUsername() 已经准备就绪! 作为玩家1);return;}if (room.getUser2() null) {// 如果进入这个房间说明玩家1 已经加入房间现在要把玩家2 加入房间room.setUser2(user);log.info(玩家 user.getUsername() 已经准备就绪! 作为玩家2);// 当两个玩家都加入成功之后就要让服务器给这两个玩家都返回 WebSocket 的响应数据// 通知两个玩家说游戏双方都已经准备好了// 通知玩家1noticeGameReady(room, room.getUser1(), room.getUser2());// 通知玩家2noticeGameReady(room, room.getUser2(), room.getUser1());return;}}
6、处理不存在的操作 经过上述操作后把玩家加入房间中双方玩家都已经就绪了这时候还有玩家在尝试连接这个房间就要给客户端提示报错。理论上这种情况是不存在的 // 6、此处如果又有一个玩家尝试连接同一个房间就会提示报错// 这种情况理论上是不存在的为了让程序更加健壮还是做一个判定和提示resp.setOk(false);resp.setReason(当前房间已满您不能加入房间);session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));
2、handleTextMessage 在处理请求和构造响应时根据用户的Session拿到用户信息然后根据用户再拿到房间在房间中进行落子的操作。 其中落子的请求处理和响应构造放在Room里了。 protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {// 1、先从 Session 拿到当前用户的身份信息User user (User) session.getAttributes().get(user);if (user null) {log.info([handleTextMessage] 当前玩家尚未登录);return;}// 2、根据 玩家id 获取到房间对象Room room roomManager.getRoomByUserId(user.getUserId());// 3、通过 room对象 来处理这次具体的请求room.putChess(message.getPayload());}
3、handleTransportError 出现异常时根据Session拿到该用户的信息再去房间管理器中找该用户的Session看连接中的Session和房间管理器的Session是不是一样的 是一样的说明是我们预期想要在房间管理器中删除的如果不一样说明用户可能是多开防止该用户退出导致前面用户的Session被删除。 当我掉线后就要判定对方获胜通知封装成了一个方法逻辑操作在该方法中完成。 public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {User user (User) session.getAttributes().get(user);if (user null) {//此处就简单处理在断开连接的时候就不给客户端返回响应了return;}WebSocketSession exitSession onlineUserManager.getFromGameRoom(user.getUserId());// 加上这个判定目的是为了在多开的情况下第二个用户退出连接动作导致第一个登录在线的用户会话删除if (session exitSession) {onlineUserManager.exitGameRoom(user.getUserId());log.info(当前这个用户 {}, user.getUsername() 游戏房间连接异常);}// 通知对手获胜noticeThatUserWin(user);}
1noticeThatUserWin通知获胜者 1、拿到当前玩家找到该房间 2、根据房间找到对手 3、根据房间管理器找到对手的Session 4、构造响应告诉客户端你赢了 5、更新玩家的分数信息 private void noticeThatUserWin(User user) throws IOException {// 1、根据当前玩家找到对应房间再找到当前玩家的对手Room room roomManager.getRoomByUserId(user.getUserId());if (room null) {// 这个情况意味着房间已经被释放也就没有对手了log.info(当前房间已经释放, 无需通知对手);return;}// 2、根据房间找到对手User thatUser (user room.getUser1()) ? room.getUser2() : room.getUser1();// 3、找到对手的在线状态WebSocketSession webSocketSession onlineUserManager.getFromGameRoom(thatUser.getUserId());if(webSocketSession null) {// 这就意味着对手也掉线了log.info(对手也已经掉线了, 无需通知);return;}// 4、构造一个响应来通知对手你是获胜方GameResponse resp new GameResponse();resp.setMessage(putChess);resp.setUserId(thatUser.getUserId());resp.setWinner(thatUser.getUserId());webSocketSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));// 5、更新玩家的分数信息int winUserId thatUser.getUserId();int loseUserId user.getUserId();userMapper.userWin(winUserId);userMapper.userLose(loseUserId);// 6、释放房间对象roomManager.remove(room.getRoomId(), room.getUser1().getUserId(), room.getUser2().getUserId());}
4、afterConnectionClosed 关闭连接后处理的逻辑也和连接错误一样。 public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {User user (User) session.getAttributes().get(user);if (user null) {//此处就简单处理在断开连接的时候就不给客户端返回响应了return;}WebSocketSession exitSession onlineUserManager.getFromGameRoom(user.getUserId());// 加上这个判定目的是为了在多开的情况下第二个用户退出连接动作导致第一个登录在线的用户会话删除if (session exitSession) {onlineUserManager.exitGameRoom(user.getUserId());log.info(当前这个用户 {}, user.getUsername() 已经离开游戏房间);}// 通知对手获胜noticeThatUserWin(user);} 四、Room
1、棋盘 在Room对象中我们把落子的下标存在一个二维数组中 // 行 | 列private static final int MAX_ROW 15;private static final int MAX_COL 15;// 这个二维数组表示棋盘服务端这边数组的状态有三种但客户端那边只有两种主要是用来判断当前棋盘有没有棋子用来避免一个问题同一个位置重复落子的情况// 约定:// 1) 使用 0 表示当前位置未落子(初始化好的二维数组相对于 全都是0)// 2) 使用 1 表示 user1 的落子位置// 3) 使用 2 表示 user2 的落子位置private int[][] board new int[MAX_ROW][MAX_COL];
2、自动注入 因为要在落子的请求后可能会决出胜负也就是说处理落子请求时我们也需要维护roomManager、onlineUserManager这也意味着我们需要注入这两个对象。还有更新用户信息—— 维护userMapper。 仔细想想我们能使用 Autowired 注入这两个对象吗如果要使用 Autowired 注解也意味着 Room对象 也要被Spring管理起来但 Room对象 能使用Spring的注解交给Spring进行管理吗 明显是不能的因为 如果加入对应的注解那么被Spring管理起来的这个对象就是单例的了。 Room对象 能是单例的吗明显不能吧因为有很多玩家都在进行游戏时这时候房间也会有很多肯定不可能让Room变成单例的。 那能不能既实现多例又能注入这两个对象呢当然也有办法——自动注入。 在Spring启动方法里添加context
SpringBootApplication
public class SpringGobangApplication {public static ConfigurableApplicationContext context;public static void main(String[] args) {context SpringApplication.run(SpringGobangApplication.class, args);}} 通过Room的构造方法里手动注入这两个对象。 // 引入 OnlineUserManager
// Autowiredprivate OnlineUserManager onlineUserManager;// 引入 RoomManager, 用于房间销毁
// Autowiredprivate RoomManager roomManager;// 引入 UserMapper, 用于更新用户数据
// Autowiredprivate UserMapper userMapper;public Room() {// 构造 Room 的时候生成一个唯一的字符串表示房间 id// 使用 UUID 来作为房间 idroomId UUID.randomUUID().toString();// 通过入口类记录中的 context来手动获取到前面的 RoomManager 和 OnlineUserManageronlineUserManager SpringGobangApplication.context.getBean(OnlineUserManager.class);roomManager SpringGobangApplication.context.getBean(RoomManager.class);userMapper SpringGobangApplication.context.getBean(UserMapper.class);}
3、putChess处理落子请求、构造返回响应 1、根据请求拿到当前落子的位置然后判断当前这个棋子是玩家1落的棋子、还是玩家2落的棋子。 2、在控制台上打印出棋盘信息方便观察 3、进行胜负判断此时也可能是胜负未分也封装成一个方法了后面介绍 4、给房间的所有客户端都返回响应根据房间的用户分别获取不同用户的Session有了Session就能对客户端发送消息了当获取不到用户的Session说明用户下线了那就判断对方赢 5、当数据发送完毕后再判断现在是不是胜负已分如果比赛已经结束了那也要更新玩家数据这个房间也没有必要再存在了要进行销毁。 // 通过这个方法来处理一次落子操作// 要做的事情:// 1、记录当前落子的位置// 2、进行胜负判定// 3、给客户端返回响应public void putChess(String reqJson) throws IOException {// 1、记录当前落子的位置GameRequest request objectMapper.readValue(reqJson, GameRequest.class);GameResponse response new GameResponse();// 当前这个棋子是玩家1落子还是玩家2落子根据玩家1 和 玩家2 来决定往数组中放1还是2int chess request.getUserId() user1.getUserId() ? 1 : 2;int row request.getRow();int col request.getCol();// 判断当前位置是不是已经有棋子了if (board[row][col] ! 0) {// 在客户端已经针对重复落子进行判定过了此处为了程序更加稳健在服务器再判断一次log.info(当前位置 row: row col: col 已经有棋子了);}board[row][col] chess;// 2、打印出当前的棋盘信息方便来观察局势也方便后面验证胜负关系的判定printBoard();// 3、进行胜负判定int winner checkWinner(row, col, chess);// 4、给房间的所有客户端都返回响应response.setMessage(putChess);response.setUserId(request.getUserId());response.setRow(row);response.setCol(col);response.setWinner(winner);// 要想给用户发送 WebSocket 数据就需要获取到这个用户的 WebSocketSessionWebSocketSession session1 onlineUserManager.getFromGameRoom(user1.getUserId());WebSocketSession session2 onlineUserManager.getFromGameRoom(user2.getUserId());// 万一当前查到的会话为空(玩家下线了), 特殊处理一下if (session1 null) {// 玩家1 下线了直接认为 玩家2 获胜response.setWinner(user2.getUserId());log.info(玩家: {}, user1.getUsername() 下线, 直接判定玩家1获胜);}if (session2 null) {// 玩家2 下线了直接认为 玩家1 获胜response.setWinner(user1.getUserId());log.info(玩家: {}, user2.getUsername() 下线, 直接判定玩家1获胜);}// 把响应构造成 JSON 字符串通过 Session进行传输String respJson objectMapper.writeValueAsString(response);if (session1 ! null) {session1.sendMessage(new TextMessage(respJson));}if (session2 ! null) {session2.sendMessage(new TextMessage(respJson));}// 5、如果当时胜负已分, 这个房间就已经失去存在的意义了就把这个房间从房间管理器中删除if (response.getWinner() ! 0) {log.info(游戏结束, 当前房间即将销毁! rommId {}, roomId 获胜方为: response.getWinner());// 更新获胜方和失败方的信息int winUserId response.getWinner();int loseUserId (response.getWinner() user1.getUserId()) ? user2.getUserId() : user1.getUserId();userMapper.userWin(winUserId);userMapper.userLose(loseUserId);// 销毁房间roomManager.remove(roomId, user1.getUserId(), user2.getUserId());}}
4、打印棋盘 两层for循环遍历数组 private void printBoard() {// 打印出棋盘log.info([打印棋盘信息] roomId: {}, roomId);System.out.println();for (int r 0; r MAX_ROW; r) {for (int c 0; c MAX_COL; c) {// 针对一行之内的若干列不要打印换行System.out.print(board[r][c] );}// 遍历完一行之后再换行System.out.println();}System.out.println();}
5、checkWinner判断输赢 判断输赢也就是要检查 是否有 “五子连珠” 的情况。 五子连珠的情况可能是 行有5个子、列有5个子、主对角线有5个子、副对角线有5个子 那怎么进行判断呢其实非常简单在我们拿到落子的下标后就固定一个顶点判断这个顶点所在的这一行往下有没有五子连珠的情况列、对角线同理没有就继续下一点直到该坐标。 其他情况也同理。 了解了如何判断那现在我们就固定一下这个 “顶点” 吧。
行顶点为左边 列顶点在上边 主对角线顶点在左上 副对角线顶点在右上 具体实现代码如下下面的这种方法可能会有空指针异常但不要紧我们进行捕获再continue进入下一个循环就好了 // 使用这个方法来判定当前落子后是否分出胜负// 约定:// 1) 如果 玩家1 获胜就返回 玩家1 的userId// 2) 如果 玩家2 获胜就返回 玩家2 的userId// 3) 如果 胜负未分就返回 0private int checkWinner(int row, int col, int chess) {// 1、检查所有的行// 先遍历这五种情况for (int c col - 4; c col; c) {// 针对其中的一种情况来判定这五个棋子是不是连在一起了// 不光是这五个字得连着而且还得和玩家落的子是一样才算是获胜try {if (board[row][c] chess board[row][c 1] chess board[row][c 2] chess board[row][c 3] chess board[row][c 4] chess) {// 构成 五子连珠! 胜负已分!log.info(行 五子连珠! 胜负已分!);return chess 1 ? user1.getUserId() : user2.getUserId();}} catch (ArrayIndexOutOfBoundsException e) {// 如果出现数组下标越界的情况就在这里直接忽略这个异常继续下一次循环判断continue;}}// 2、检查所有的列for(int r row - 4; r row; r) {try {if (board[r][col] chess board[r 1][col] chess board[r 2][col] chess board[r 3][col] chess board[r 4][col] chess) {// 构成 五子连珠! 胜负已分!log.info(列 五子连珠! 胜负已分!);return chess 1 ? user1.getUserId() : user2.getUserId();}} catch (ArrayIndexOutOfBoundsException e) {// 如果出现数组下标越界的情况就在这里直接忽略这个异常继续下一次循环判断continue;}}// 3、检查所有主对角线 (左对角线从左上往右下)for (int r row - 4, c col - 4; r row c col; r, c) {try {if (board[r][c] chess board[r 1][c 1] chess board[r 2][c 2] chess board[r 3][c 3] chess board[r 4][c 4] chess) {// 构成 五子连珠! 胜负已分!log.info(主对角线 五子连珠! 胜负已分!);return chess 1 ? user1.getUserId() : user2.getUserId();}} catch (ArrayIndexOutOfBoundsException e) {// 如果出现数组下标越界的情况就在这里直接忽略这个异常继续下一次循环判断continue;}}// 4、检查所有副对角线 (右对角线, 从左下往右上)for (int r row - 4, c col 4; r row c col; r, c--) {try {if (board[r][c] chess board[r 1][c - 1] chess board[r 2][c - 2] chess board[r 3][c - 3] chess board[r 4][c - 4] chess) {// 构成 五子连珠! 胜负已分!log.info(副对角线 五子连珠! 胜负已分!);return chess 1 ? user1.getUserId() : user2.getUserId();}} catch (ArrayIndexOutOfBoundsException e) {// 如果出现数组下标越界的情况就在这里直接忽略这个异常继续下一次循环判断continue;}}// 胜负未分就直接返回 0 了return 0;} 如果经过上述逻辑判断还没返回对应的赢家Id那么就说明是胜负未分返回0. 五、前端代码的逻辑处理
1、处理游戏就绪响应 双方进入房间成功后就会建立一个房间页面的websocket连接其代码如下
//
// 初始化 websocket
//
let websocketUrl ws:// location.host /game;
let websocket new WebSocket(websocketUrl);websocket.onopen function () {console.log(连接游戏房间成功);
}websocket.close function () {console.log(和游戏服务器连接断开);
}websocket.onerror function () {console.log(和服务器的连接出现异常);
}websocket.onbeforeunload function () {websocket.close();
}//处理服务器返回的响应数据 (游戏就绪响应)
websocket.onmessage function (event) {console.log([handlerGameReady] event.data);let resp JSON.parse(event.data);if (!resp.ok) {alert(连接游戏失败! reason: resp.reason);// 如果出现连接失败的情况回到游戏大厅// location.assign(/game_hall.html);location.replace(/game_hall.html);return;}if (resp.message gameReady) {// 把后端返回的数据放进 gameInfo 对象中gameInfo.roomId resp.roomId;gameInfo.thisUserId resp.thisUserId;gameInfo.thatUserId resp.thatUserId;// 判断自己是不是先手gameInfo.isWhite (resp.whiteUser resp.thisUserId);// 初始化棋盘initGame();//设置显示区域的内容(轮到谁落子了)setScreenText(gameInfo.isWhite);} else if (resp.message repeatConnection) {console.log(检测到游戏多开!);alert(检测到游戏多开, 请使用其他账户进行登录!);// location.assign(/login.html);location.replace(/login.html);return;}
} 接收服务器的响应处理主要在 websocket.message() 上首先拿到后端返回来的响应将其转为 js 对象原本是JSON文本。 判断 resp.ok如果是false说明连接异常跳转到登录页面可能是还没登录就直接访问游戏房间页面。 然后判断resp.message如果是 gameReady说明该玩家加入房间成功进行判断先手设置我方和对方的userId初始化棋盘设置显示区域轮到谁落子了 resp.message如果是 repeatConnection说明玩家有多开行为那就给个提示弹窗返回到登录页面。
2、处理落子请求 前端发送的请求有用户Id、落子位置。 这里有一个点击事件点击对应位置就会落子然后给后端发送一个落子请求代码如下 这里也就会处理如果不是我落子你在棋盘上再怎么点也没有用只有到我落子了才能继续落子还有一种是游戏结束了我也不能继续在这个棋盘上落子了。 chess.onclick function (e) {if (over) {return;}if (!me) {return;}let x e.offsetX;let y e.offsetY;// 注意, 横坐标是列, 纵坐标是行let col Math.floor(x / 30);let row Math.floor(y / 30);// 客户端的棋盘状态只有两种主要是用来判断当前棋盘有没有棋子用来避免一个问题同一个位置重复落子的情况if (chessBoard[row][col] 0) {// 发送坐标给服务器, 服务器要返回结果send(row, col);}}function send(row, col) {let req {message: putChess,userId: gameInfo.thisUserId,row: row,col: col};websocket.send(JSON.stringify(req));}
3、处理落子响应 返回来的响应如果 resp.message ! putChess就说明响应数据有问题。 然后判断这个响应是不是自己落的子如果是自己落的子就绘制一个自己的棋子如果不是自己落的子那就说明是对方落的子。 落子之后要把对应的位置设为1说明该位置已经有棋子了不能再在这个位置落子。 后端返回的响应有用户Id、落子位置、输赢状态。落子后就要判断输赢了如果返回resp.winner 是我的 userId那就说明我赢了反之则是对方赢了然后改变对应的提示信息。还有一种情况就是0说明胜负未分。 胜负分出后就给页面新增一个按钮点击后返回到游戏大厅 // 之前 websocket.onmessage 主要是用来处理了游戏就绪响应在游戏就绪之后初始化完毕之后也就不再有这个 游戏就绪响应 了// 就在这个 initGame 内部修改 websocket.onmessage 方法~~让这个方法里面针 对落子响应 进行处理websocket.onmessage function (event) {console.log([handlerPutChess]: event.data);let resp JSON.parse(event.data);if (resp.message ! putChess) {console.log(响应类型错误);return;}// 先判定当前这个响应时否为自己逻的子还是对方落的子if (resp.userId gameInfo.thisUserId) {// 我自己落的子// 根据我自己棋子的颜色来绘制一个棋子oneStep(resp.col, resp.row, gameInfo.isWhite);} else if (resp.userId gameInfo.thatUserId) {// 我的对手落的子oneStep(resp.col, resp.row, !gameInfo.isWhite);} else {// 响应错误! userId 是有问题的console.log([handlerPutChess resp userId 错误);return;}// 给对应的位置设为 1方便后续逻辑判定当前位置是否已经有棋子了chessBoard[resp.row][resp.col] 1;// 交换双方的落子轮次me !me;setScreenText(me);// 判定游戏是否结束let screenDiv document.querySelector(#screen);if (resp.winner ! 0) {if (resp.winner gameInfo.thisUserId) {// alert(你赢了!);screenDiv.innerHTML 你赢了!;} else if (resp.winner gameInfo.thatUserId) {// alert(你输了);screenDiv.innerHTML 你输了!;} else {alert(winner 字段错误 resp.winner);}// 回到游戏大厅// location.assign(/game_hall.html);// 增加一个按钮让玩家点击之后再回到游戏大厅~let backButton document.createElement(button);backButton.innerHTML 回到游戏大厅backButton.className button; // 添加样式类backButton.onclick function() {location.replace(/game_hall.html);}let fatherDiv document.querySelector(.containerdiv);fatherDiv.appendChild(backButton);}} 六、梳理前后端交互流程 双方点击开始匹配按钮。 进入游戏房间 进入房间后建立 websocket 连接代码逻辑如 我落子后服务器接收到请求会给双方都发送响应。 前后端代码处理逻辑前后端就依次往下看代码这里就不展开了 七、代码以及线上云服务器的URL URLhttp://120.79.61.184:9090/login.html Gittespring-gobang · taotao/Studying JavaEE Advanced - 码云 - 开源中国 (gitee.com)