网页版Java(Spring/Spring Boot/Spring MVC)五子棋项目(四)对战模块

网页版Java(Spring/Spring Boot/Spring MVC)五子棋项目(四)对战模块

  • 一、约定前后端交互接口
    • 1. 建立连接 的接口
    • 2. 针对落子的请求和响应 的接口
  • 二、实现前端页面
    • 游戏大厅页面
    • 游戏大厅里的 java script
  • 三、实现后端
    • 一. 建立各种请求和相应的类
      • 1. 客户端连接到游戏房间后, 服务器返回的响应.
        • GameReadyResponse
      • 2. 落子请求
        • GameRequest
      • 3. 落子响应
        • GameResponse
    • 二. 实现用户管理类 和 房间管理类
      • OnlineUserManager
      • Room
      • RoomManager
    • 三. 实现GameAPI
      • 1. afterConnectionEstablished
        • 1. 先获取到用户的身份信息. (从 HttpSession 里拿到当前用户的对象)
        • 2. 判定当前用户是否已经进入房间. (拿着房间管理器进行查询)
        • 3. 判定当前是不是多开 (该用户是不是已经在其他地方进入游戏了)
        • 4. 设置当前玩家上线!
        • 5. 把两个玩家加入到游戏房间中.
        • 6. 此处如果又有玩家尝试连接同一个房间, 就提示报错.
      • 2. afterConnectionClosed
        • 断开连接

一、约定前后端交互接口

1. 建立连接 的接口

在这里插入图片描述

2. 针对落子的请求和响应 的接口

在这里插入图片描述

二、实现前端页面

在这里插入图片描述
在这里插入图片描述

游戏大厅页面

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>游戏房间</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/game_room.css">
</head>
<body><div class="nav">五子棋对战</div><div class="container"><div><!-- 棋盘区域, 需要基于 canvas 进行实现 --><canvas id="chess" width="450px" height="450px"></canvas><!-- 显示区域 --><div id="screen"> 等待玩家连接中... </div></div></div><script src="js/script.js"></script>
</body>
</html>

游戏大厅里的 java script

<script>let gameInfo = {roomId: null,thisUserId: null,thatUserId: null,isWhite: true,}//// 设定界面显示相关操作//function setScreenText(me) {let screen = document.querySelector('#screen');if (me) {screen.innerHTML = "轮到你落子了!";} else {screen.innerHTML = "轮到对方落子了!";}}//// 初始化 websocket//// 此处写的路径要写作 /game, 不要写作 /game/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("和服务器的连接出现异常!");}window.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");return;}if (resp.message == 'gameReady') {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') {alert("检测到游戏多开! 请使用其他账号登录!");location.assign("/login.html");}}//// 初始化一局游戏//function initGame() {// 是我下还是对方下. 根据服务器分配的先后手情况决定let me = gameInfo.isWhite;// 游戏是否结束let over = false;let chessBoard = [];//初始化chessBord数组(表示棋盘的数组)for (let i = 0; i < 15; i++) {chessBoard[i] = [];for (let j = 0; j < 15; j++) {chessBoard[i][j] = 0;}}let chess = document.querySelector('#chess');let context = chess.getContext('2d');context.strokeStyle = "#BFBFBF";// 背景图片let logo = new Image();logo.src = "image/sky.jpeg";logo.onload = function () {context.drawImage(logo, 0, 0, 450, 450);initChessBoard();}// 绘制棋盘网格function initChessBoard() {for (let i = 0; i < 15; i++) {context.moveTo(15 + i * 30, 15);context.lineTo(15 + i * 30, 430);context.stroke();context.moveTo(15, 15 + i * 30);context.lineTo(435, 15 + i * 30);context.stroke();}}// 绘制一个棋子, me 为 truefunction oneStep(i, j, isWhite) {context.beginPath();context.arc(15 + i * 30, 15 + j * 30, 13, 0, 2 * Math.PI);context.closePath();var gradient = context.createRadialGradient(15 + i * 30 + 2, 15 + j * 30 - 2, 13, 15 + i * 30 + 2, 15 + j * 30 - 2, 0);if (!isWhite) {gradient.addColorStop(0, "#0A0A0A");gradient.addColorStop(1, "#636766");} else {gradient.addColorStop(0, "#D1D1D1");gradient.addColorStop(1, "#F9F9F9");}context.fillStyle = gradient;context.fill();}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);// 留到浏览器收到落子响应的时候再处理(收到响应再来画棋子)// oneStep(col, row, gameInfo.isWhite);// chessBoard[row][col] = 1;}}function send(row, col) {let req = {message: 'putChess',userId: gameInfo.thisUserId,row: row,col: col};websocket.send(JSON.stringify(req));}// 之前 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 backBtn = document.createElement('button');backBtn.innerHTML = '回到大厅';backBtn.onclick = function() {location.replace('/game_hall.html');}let fatherDiv = document.querySelector('.container>div');fatherDiv.appendChild(backBtn);}}}
</script>

三、实现后端

一. 建立各种请求和相应的类

1. 客户端连接到游戏房间后, 服务器返回的响应.

GameReadyResponse

package com.example.java_gobang.game;// 客户端连接到游戏房间后, 服务器返回的响应.
public class GameReadyResponse {private String message;private boolean ok;private String reason;private String roomId;private int thisUserId;private int thatUserId;private int whiteUser;public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public boolean isOk() {return ok;}public void setOk(boolean ok) {this.ok = ok;}public String getReason() {return reason;}public void setReason(String reason) {this.reason = reason;}public String getRoomId() {return roomId;}public void setRoomId(String roomId) {this.roomId = roomId;}public int getThisUserId() {return thisUserId;}public void setThisUserId(int thisUserId) {this.thisUserId = thisUserId;}public int getThatUserId() {return thatUserId;}public void setThatUserId(int thatUserId) {this.thatUserId = thatUserId;}public int getWhiteUser() {return whiteUser;}public void setWhiteUser(int whiteUser) {this.whiteUser = whiteUser;}
}

2. 落子请求

GameRequest

3. 落子响应

GameResponse

二. 实现用户管理类 和 房间管理类

OnlineUserManager

package com.example.java_gobang.game;import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketSession;import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;@Component
public class OnlineUserManager {// 这个哈希表就用来表示当前用户在游戏大厅在线状态.private ConcurrentHashMap<Integer, WebSocketSession> gameHall = new ConcurrentHashMap<>();// 这个哈希表就用来表示当前用户在游戏房间的在线状态.private ConcurrentHashMap<Integer, WebSocketSession> gameRoom = new ConcurrentHashMap<>();public void enterGameHall(int userId, WebSocketSession webSocketSession) {gameHall.put(userId, webSocketSession);}public void exitGameHall(int userId) {gameHall.remove(userId);}public WebSocketSession getFromGameHall(int userId) {return gameHall.get(userId);}public void enterGameRoom(int userId, WebSocketSession webSocketSession) {gameRoom.put(userId, webSocketSession);}public void exitGameRoom(int userId) {gameRoom.remove(userId);}public WebSocketSession getFromGameRoom(int userId) {return gameRoom.get(userId);}
}

Room

package com.example.java_gobang.game;import com.example.java_gobang.JavaGobangApplication;
import com.example.java_gobang.model.User;
import com.example.java_gobang.model.UserMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;import java.io.IOException;
import java.util.UUID;// 这个类就表示一个游戏房间
public class Room {// 使用字符串类型来表示, 方便生成唯一值.private String roomId;private User user1;private User user2;// 先手方的玩家 idprivate int whiteUser;private static final int MAX_ROW = 15;private static final int MAX_COL = 15;// 这个二维数组用来表示棋盘// 约定:// 1) 使用 0 表示当前位置未落子. 初始化好的 int 二维数组, 就相当于是 全 0// 2) 使用 1 表示 user1 的落子位置// 3) 使用 2 表示 user2 的落子位置private int[][] board = new int[MAX_ROW][MAX_COL];// 创建 ObjectMapper 用来转换 JSONprivate ObjectMapper objectMapper = new ObjectMapper();// 引入 OnlineUserManager// @Autowiredprivate OnlineUserManager onlineUserManager;// 引入 RoomManager, 用于房间销毁// @Autowiredprivate RoomManager roomManager;private UserMapper userMapper;// 通过这个方法来处理一次落子操作.// 要做的事情: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) {// 在客户端已经针对重复落子进行过判定了. 此处为了程序更加稳健, 在服务器再判定一次.System.out.println("当前位置 (" + row + ", " + col + ") 已经有子了!");return;}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());System.out.println("玩家1 掉线!");}if (session2 == null) {// 玩家2 已经下线. 直接认为玩家1 获胜!response.setWinner(user1.getUserId());System.out.println("玩家2 掉线!");}// 把响应构造成 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) {// 胜负已分System.out.println("游戏结束! 房间即将销毁! roomId=" + 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());}}private void printBoard() {// 打印出棋盘System.out.println("[打印棋盘信息] " + 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("=====================================================================");}// 使用这个方法来判定当前落子是否分出胜负.// 约定如果玩家1 获胜, 就返回玩家1 的 userId// 如果玩家2 获胜, 就返回玩家2 的 userId// 如果胜负未分, 就返回 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) {// 构成了五子连珠! 胜负已分!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) {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) {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) {return chess == 1 ? user1.getUserId() : user2.getUserId();}} catch (ArrayIndexOutOfBoundsException e) {continue;}}// 胜负未分, 就直接返回 0 了.return 0;}public int getWhiteUser() {return whiteUser;}public void setWhiteUser(int whiteUser) {this.whiteUser = whiteUser;}public String getRoomId() {return roomId;}public void setRoomId(String roomId) {this.roomId = roomId;}public User getUser1() {return user1;}public void setUser1(User user1) {this.user1 = user1;}public User getUser2() {return user2;}public void setUser2(User user2) {this.user2 = user2;}public Room() {// 构造 Room 的时候生成一个唯一的字符串表示房间 id.// 使用 UUID 来作为房间 idroomId = UUID.randomUUID().toString();// 通过入口类中记录的 context 来手动获取到前面的 RoomManager 和 OnlineUserManageronlineUserManager = JavaGobangApplication.context.getBean(OnlineUserManager.class);roomManager = JavaGobangApplication.context.getBean(RoomManager.class);userMapper = JavaGobangApplication.context.getBean(UserMapper.class);}public static void main(String[] args) {Room room = new Room();System.out.println(room.roomId);}
}

RoomManager

package com.example.java_gobang.game;import org.springframework.stereotype.Component;import java.util.concurrent.ConcurrentHashMap;// 房间管理器类.
// 这个类也希望有唯一实例.
@Component
public class RoomManager {private ConcurrentHashMap<String, Room> rooms = new ConcurrentHashMap<>();private ConcurrentHashMap<Integer, String> userIdToRoomId = new ConcurrentHashMap<>();public void add(Room room, int userId1, int userId2) {rooms.put(room.getRoomId(), room);userIdToRoomId.put(userId1, room.getRoomId());userIdToRoomId.put(userId2, room.getRoomId());}public void remove(String roomId, int userId1, int userId2) {rooms.remove(roomId);userIdToRoomId.remove(userId1);userIdToRoomId.remove(userId2);}public Room getRoomByRoomId(String roomId) {return rooms.get(roomId);}public Room getRoomByUserId(int userId) {String roomId = userIdToRoomId.get(userId);if (roomId == null) {// userId -> roomId 映射关系不存在, 直接返回 nullreturn null;}return rooms.get(roomId);}
}

三. 实现GameAPI

package com.example.java_gobang.api;import com.example.java_gobang.game.*;
import com.example.java_gobang.model.User;
import com.example.java_gobang.model.UserMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;import javax.annotation.Resource;
import java.io.IOException;@Component
public class GameAPI extends TextWebSocketHandler {private ObjectMapper objectMapper = new ObjectMapper();@Autowiredprivate RoomManager roomManager;@Autowiredprivate OnlineUserManager onlineUserManager;@Resourceprivate UserMapper userMapper;@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {GameReadyResponse resp = new GameReadyResponse();// 1. 先获取到用户的身份信息. (从 HttpSession 里拿到当前用户的对象)User user = (User) session.getAttributes().get("user");if (user == null) {resp.setOk(false);resp.setReason("用户尚未登录!");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));return;}// 2. 判定当前用户是否已经进入房间. (拿着房间管理器进行查询)Room room = roomManager.getRoomByUserId(user.getUserId());if (room == null) {// 如果为 null, 当前没有找到对应的房间. 该玩家还没有匹配到.resp.setOk(false);resp.setReason("用户尚未匹配到!");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));return;}// 3. 判定当前是不是多开 (该用户是不是已经在其他地方进入游戏了)//    前面准备了一个 OnlineUserManagerif (onlineUserManager.getFromGameHall(user.getUserId()) != null|| onlineUserManager.getFromGameRoom(user.getUserId()) != null) {// 如果一个账号, 一边是在游戏大厅, 一边是在游戏房间, 也视为多开~~resp.setOk(true);resp.setReason("禁止多开游戏页面");resp.setMessage("repeatConnection");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));return;}// 4. 设置当前玩家上线!onlineUserManager.enterGameRoom(user.getUserId(), session);// 5. 把两个玩家加入到游戏房间中.//    前面的创建房间/匹配过程, 是在 game_hall.html 页面中完成的.//    因此前面匹配到对手之后, 需要经过页面跳转, 来到 game_room.html 才算正式进入游戏房间(才算玩家准备就绪)//    当前这个逻辑是在 game_room.html 页面加载的时候进行的.//    执行到当前逻辑, 说明玩家已经页面跳转成功了!!//    页面跳转, 其实是个大活~~ (很有可能出现 "失败" 的情况的)synchronized (room) {if (room.getUser1() == null) {// 第一个玩家还尚未加入房间.// 就把当前连上 websocket 的玩家作为 user1, 加入到房间中.room.setUser1(user);// 把先连入房间的玩家作为先手方.room.setWhiteUser(user.getUserId());System.out.println("玩家 " + user.getUsername() + " 已经准备就绪! 作为玩家1");return;}if (room.getUser2() == null) {// 如果进入到这个逻辑, 说明玩家1 已经加入房间, 现在要给当前玩家作为玩家2 了room.setUser2(user);System.out.println("玩家 " + user.getUsername() + " 已经准备就绪! 作为玩家2");// 当两个玩家都加入成功之后, 就要让服务器, 给这两个玩家都返回 websocket 的响应数据.// 通知这两个玩家说, 游戏双方都已经准备好了.// 通知玩家1noticeGameReady(room, room.getUser1(), room.getUser2());// 通知玩家2noticeGameReady(room, room.getUser2(), room.getUser1());return;}}// 6. 此处如果又有玩家尝试连接同一个房间, 就提示报错.//    这种情况理论上是不存在的, 为了让程序更加的健壮, 还是做一个判定和提示.resp.setOk(false);resp.setReason("当前房间已满, 您不能加入房间");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));}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)));}@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {// 1. 先从 session 里拿到当前用户的身份信息User user = (User) session.getAttributes().get("user");if (user == null) {System.out.println("[handleTextMessage] 当前玩家尚未登录! ");return;}// 2. 根据玩家 id 获取到房间对象Room room = roomManager.getRoomByUserId(user.getUserId());// 3. 通过 room 对象来处理这次具体的请求room.putChess(message.getPayload());}@Overridepublic 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());}System.out.println("当前用户 " + user.getUsername() + " 游戏房间连接异常!");// 通知对手获胜了noticeThatUserWin(user);}@Overridepublic 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());}System.out.println("当前用户 " + user.getUsername() + " 离开游戏房间!");// 通知对手获胜了noticeThatUserWin(user);}private void noticeThatUserWin(User user) throws IOException {// 1. 根据当前玩家, 找到玩家所在的房间Room room = roomManager.getRoomByUserId(user.getUserId());if (room == null) {// 这个情况意味着房间已经被释放了, 也就没有 "对手" 了System.out.println("当前房间已经释放, 无需通知对手!");return;}// 2. 根据房间找到对手User thatUser = (user == room.getUser1()) ? room.getUser2() : room.getUser1();// 3. 找到对手的在线状态WebSocketSession webSocketSession = onlineUserManager.getFromGameRoom(thatUser.getUserId());if (webSocketSession == null) {// 这就意味着对手也掉线了!System.out.println("对手也已经掉线了, 无需通知!");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());}
}

1. afterConnectionEstablished

1. 先获取到用户的身份信息. (从 HttpSession 里拿到当前用户的对象)

2. 判定当前用户是否已经进入房间. (拿着房间管理器进行查询)

3. 判定当前是不是多开 (该用户是不是已经在其他地方进入游戏了)

4. 设置当前玩家上线!

5. 把两个玩家加入到游戏房间中.

6. 此处如果又有玩家尝试连接同一个房间, 就提示报错.

    @Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {GameReadyResponse resp = new GameReadyResponse();// 1. 先获取到用户的身份信息. (从 HttpSession 里拿到当前用户的对象)User user = (User) session.getAttributes().get("user");if (user == null) {resp.setOk(false);resp.setReason("用户尚未登录!");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));return;}// 2. 判定当前用户是否已经进入房间. (拿着房间管理器进行查询)Room room = roomManager.getRoomByUserId(user.getUserId());if (room == null) {// 如果为 null, 当前没有找到对应的房间. 该玩家还没有匹配到.resp.setOk(false);resp.setReason("用户尚未匹配到!");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));return;}// 3. 判定当前是不是多开 (该用户是不是已经在其他地方进入游戏了)//    前面准备了一个 OnlineUserManagerif (onlineUserManager.getFromGameHall(user.getUserId()) != null|| onlineUserManager.getFromGameRoom(user.getUserId()) != null) {// 如果一个账号, 一边是在游戏大厅, 一边是在游戏房间, 也视为多开~~resp.setOk(true);resp.setReason("禁止多开游戏页面");resp.setMessage("repeatConnection");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));return;}// 4. 设置当前玩家上线!onlineUserManager.enterGameRoom(user.getUserId(), session);// 5. 把两个玩家加入到游戏房间中.//    前面的创建房间/匹配过程, 是在 game_hall.html 页面中完成的.//    因此前面匹配到对手之后, 需要经过页面跳转, 来到 game_room.html 才算正式进入游戏房间(才算玩家准备就绪)//    当前这个逻辑是在 game_room.html 页面加载的时候进行的.//    执行到当前逻辑, 说明玩家已经页面跳转成功了!!//    页面跳转, 其实是个大活~~ (很有可能出现 "失败" 的情况的)synchronized (room) {if (room.getUser1() == null) {// 第一个玩家还尚未加入房间.// 就把当前连上 websocket 的玩家作为 user1, 加入到房间中.room.setUser1(user);// 把先连入房间的玩家作为先手方.room.setWhiteUser(user.getUserId());System.out.println("玩家 " + user.getUsername() + " 已经准备就绪! 作为玩家1");return;}if (room.getUser2() == null) {// 如果进入到这个逻辑, 说明玩家1 已经加入房间, 现在要给当前玩家作为玩家2 了room.setUser2(user);System.out.println("玩家 " + user.getUsername() + " 已经准备就绪! 作为玩家2");// 当两个玩家都加入成功之后, 就要让服务器, 给这两个玩家都返回 websocket 的响应数据.// 通知这两个玩家说, 游戏双方都已经准备好了.// 通知玩家1noticeGameReady(room, room.getUser1(), room.getUser2());// 通知玩家2noticeGameReady(room, room.getUser2(), room.getUser1());return;}}// 6. 此处如果又有玩家尝试连接同一个房间, 就提示报错.//    这种情况理论上是不存在的, 为了让程序更加的健壮, 还是做一个判定和提示.resp.setOk(false);resp.setReason("当前房间已满, 您不能加入房间");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));}

2. afterConnectionClosed

断开连接

    @Overridepublic 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());}System.out.println("当前用户 " + user.getUsername() + " 离开游戏房间!");// 通知对手获胜了noticeThatUserWin(user);}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/87668.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

品牌宣传与媒体传播是声誉管理的主要方式之一

企业声誉是现如今影响品牌信任度、客户忠诚度的重要因素&#xff0c;也被视为企业的一种无形资&#xff0c;更影响着企业未来的发展。因此&#xff0c;企业声誉管理也日渐成为企业管理的重要课题之一&#xff0c;尤其在品牌营销管理领域。 什么是声誉管理&#xff1f;声誉管理有…

鸿蒙边缘计算网关正式开售

IDO-IPC3528鸿蒙边缘计算网关基于RK3568研发设计&#xff0c;采用22nm先进工艺制程&#xff0c;四核A55 CPU&#xff0c;主频高达2.0GHz&#xff0c;支持高达8GB高速LPDDR4&#xff0c;1T算力NPU&#xff0c;4K H.265/H264硬解码&#xff1b;视频输出接口HDMI2.0&#xff0c;双…

ArcGISPro随机森林自动化调参分类预测模型展示

更改ArcGISPro的python环境变量请参考文章 ArcGISPro中如何使用机器学习脚本_Z_W_H_的博客-CSDN博客 脚本文件如下 点击运行 结果展示 负类预测概率 正类预测概率 二值化概率 文件夹&#xff08;模型验证结果&#xff09; 数据集数据库 ROC曲线 由于个人数据量太少所以…

GrapeCity Documents for Imaging Crack

GrapeCity Documents for Imaging Crack 增加了对双面打印的支持。 GcExcel.NET支持PrintOutOptions类中的Duplex枚举&#xff0c;以启用/禁用页面上的双面打印。 枚举中有四个选项&#xff0c;用户可以相应地使用它们来打印工作簿&#xff1a; 双面打印。Default表示打印机的默…

【力扣每日一题】2023.8.13 合并两个有序数组

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目给我们两个升序数组&#xff0c;让我们合并它们&#xff0c;要求合并之后仍然是升序&#xff0c;并且这个合并操作是在数组1原地修改…

基于Googlenet深度学习网络的人员行为动作识别matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 1. 原理 1.1 深度学习与卷积神经网络&#xff08;CNN&#xff09; 1.2 GoogLeNet 2. 实现过程 2.1 数据预处理 2.2 构建网络模型 2.3 数据输入与训练 2.4 模型评估与调优 3. 应用领域…

Exploiting Proximity-Aware Tasks for Embodied Social Navigation 论文阅读

论文信息 题目&#xff1a;Exploiting Proximity-Aware Tasks for Embodied Social Navigation 作者&#xff1a;Enrico Cancelli&#xff0c; Tommaso Campari 来源&#xff1a;arXiv 时间&#xff1a;2023 Abstract 学习如何在封闭且空间受限的室内环境中在人类之间导航&a…

Azure DevOps基于 Net6.0 的 WPF 程序如何进行持续集成、持续编译

正文 1&#xff0c; Azure DevOps 创建项目 Project name&#xff1a;”NetCore_WPF_Sample“ Visibility&#xff1a;”Private“&#xff08;根据实际项目需求&#xff09; Version control&#xff1a;”Git“ Work item process&#xff1a;”Agile“ 点击 ”Create“…

ADM2587E在RS485和RS422接口的应用(ADM2587E电路原理图和程序开发)

最近做一个项目使用到ADM2587E&#xff0c;为了解决公司历史遗留的问题&#xff08;ADM2587E芯片发烫&#xff0c;容易烧毁&#xff0c;485设备只能手拉手连接三四个&#xff0c;就通信不正常现象&#xff09;&#xff0c;认真阅读了Datasheet和官网LayOut的一些设计文档&#…

加盐加密算法

MD5加密加盐加密项目密码升级 MD5加密 MD5一系列公式进行复杂数学运算&#xff1b;特点&#xff1a;&#xff08;用途校验和、计算hash值方式、加密&#xff09; 1&#xff1a;定长&#xff1b;无论原始数据多长&#xff1b;算出的结果都是4或者8字节的版本。 2&#xff1a;冲…

GPT内功心法:搜索思维到GPT思维的转换

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

由浅入深学习Tapable

文章目录 由浅入深学习TapableTapable是什么Tapable的Hook分类同步和异步的 使用Sync*同步类型钩子基本使用bailLoopWaterfall Async*异步类型钩子ParallelSeries 由浅入深学习Tapable webpack有两个非常重要的类&#xff1a;Compiler和Compilation。他们通过注入插件的方式&a…

MySQL group by后取每个分组中最新一条数据

一、需求 MySQL group by后取每个分组中最新一条数据 二、实现 1&#xff09;方案1&#xff1a;使用min()和max()方法 1、group by后取每个分组中最新一条数据 SELECT MAX(test_id) FROM test GROUP BY test_user_id; 2、group by后取每个分组中第一条插入的数据 SELECT…

【不支持发行版本 5】错误解决

说明&#xff1a;启动项目报下面的错误&#xff0c;不支持发行版本 5 解决&#xff1a;在pom文件中添加下面这两行配置&#xff0c;修改成你自己安装的jdk版本 <properties><maven.compiler.source>11</maven.compiler.source><maven.compiler.target&g…

大华智慧园区综合管理平台文件上传漏洞复现(HW0day)

0x01 产品简介 “大华智慧园区综合管理平台”是一款综合管理平台&#xff0c;具备园区运营、资源调配和智能服务等功能。平台意在协助优化园区资源分配&#xff0c;满足多元化的管理需求&#xff0c;同时通过提供智能服务&#xff0c;增强使用体验。 0x02 漏洞概述 大华智慧园…

基于灰狼优化(GWO)、帝国竞争算法(ICA)和粒子群优化(PSO)对梯度下降法训练的神经网络的权值进行了改进(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

解决监督学习,深度学习报错:AttributeError: ‘xxx‘ object has no attribute ‘module‘!!!!

哈喽小伙伴们大家好呀&#xff0c;很长时间没有更新啦&#xff0c;最近在研究一个问题&#xff0c;就是AttributeError: xxx object has no attribute module 今天终于是解决了&#xff0c;所以来记录分享一下&#xff1a; 我这里出现的问题是&#xff1a; 因为我的数据比较大…

深度学习之用PyTorch实现逻辑回归

0.1 学习视频源于&#xff1a;b站&#xff1a;刘二大人《PyTorch深度学习实践》 0.2 本章内容为自主学习总结内容&#xff0c;若有错误欢迎指正&#xff01; 代码&#xff08;类比线性回归&#xff09;&#xff1a; # 调用库 import torch import torch.nn.functional as F#…

Amazon CloudFront 部署小指南(五)- 使用 Amazon 边缘技术优化游戏内资源更新发布...

内容简介 游戏内资源包括玩家的装备/弹药/材料等素材&#xff0c;对游戏内资源的发布和更新是游戏运营商的一个常规业务流程&#xff0c;使用频率会十分高&#xff0c;所以游戏运营商希望该流程可以做到简化和可控。针对这个需求&#xff0c;我们设计了 3 个架构&#xff0c;面…

【Django】招聘面试管理01 创建项目运行项目

文章目录 前言一、创建项目二、运行项目三、访问后台管理页面四、配置项总结 前言 跟着视频学一学&#xff0c;记录一下。 一、创建项目 照着步骤创建虚拟环境&#xff0c;安装Django等依赖包&#xff0c;创建项目&#xff1a;【Django学习】01 项目创建、结构及命令 > d…