当前位置: 首页 > news >正文

项目实战--网页五子棋(对战功能)(7)

1. webSocket交互约定

上期我们完成了游戏房间的实体类以及前端界面,这期我们实现玩家对战功能,这里我们同样通过webSocket实现玩家和玩家之间的交互,连接建立成功时,约定服务器发送一些游戏的初始信息给客户端:

{
    message: 'gameReady', //游戏消息类别,游戏就绪
    ok: true, 
    errMsg: '',//错误信息
    roomId: '', //游戏房间id
    userId1: 1, //当前客户端玩家
    userId2: 2, //对手玩家
    isBlack: true //当前玩家是否拿黑子(先手)
}

约定玩家落子的请求和响应:

落子请求:

{
    message: 'putChess', 
    userId: ,
    row: , //落子坐标行
    col:  //落子坐标列
}

落子响应:

{
    message: 'putChess', 
    userid: ,
    row: , //落子坐标行
    col:  //落子坐标列
    winnerId: //是否分出胜负,-1表示未分出胜负,否则表示获胜玩家id
}

2. 建立webSocket连接

2.1 客户端代码

我们在script.js文件中增加webSocket连接相关的代码


let webSocket = new WebSocket('ws://127.0.0.1:8080/game');
webSocket.onopen = function() {
    console.log("连接game成功");
}
webSocket.onclose = function() {
    console.log("连接关闭请重新登陆");
    location.href = "/login.html";
}
webSocket.onerror = function() {
    console.log("error");
}

//页面关闭时释放webSocket
window.onbeforeunload = function() {
    webSocket.close();
}

//处理服务器发送的消息
webSocket.onmessage = function(e) {
    //连接成功处理返回响应
    let resp = JSON.parse(e.data);
    if(resp.message != "gameReady" || !resp.ok) {
        alert("进入游戏房间失败: " + resp.errMsg);
        location.href = "/hall.html";
        return;
    }
    resp.roomId = e.roomId;
    resp.userId1 = e.userId1;
    resp.userId2 = e.userId2;
    resp.isBlack = e.isBlack;

    //初始化棋盘
    initGame();
    //提示先手玩家落子
    setScreenText(resp.isBlack);
}
2.2 服务器代码

 创建webSocket

package org.ting.j20250110_gobang.websocket;

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;
@Component
public class GameWebSocket extends TextWebSocketHandler {
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {

    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {

    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {

    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {

    }
}

注册webSocket:

package org.ting.j20250110_gobang.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
import org.ting.j20250110_gobang.websocket.GameWebSocket;
import org.ting.j20250110_gobang.websocket.MatchWebSocket;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Autowired
    MatchWebSocket matchWebSocket;
    @Autowired
    GameWebSocket gameWebSocket;
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(matchWebSocket, "/findMatch") //注意路径和前端对应
                //添加拦截器获取到session,方便获取session中的用户信息
                .addInterceptors(new HttpSessionHandshakeInterceptor());

        registry.addHandler(gameWebSocket, "/game")
                //添加拦截器获取到session,方便获取session中的用户信息
                .addInterceptors(new HttpSessionHandshakeInterceptor());
    }
}

3. 创建数据交互实体类

package org.ting.j20250110_gobang.game;

public class GameReadyResponse {
    private String message; //游戏消息类别,游戏就绪
    private boolean ok;
    private String errMsg;//错误信息
    private String roomId; //游戏房间id
    private int userId1; //当前客户端玩家的id
    private int userId2;//对手id
    private boolean isBlack; //当前玩家是否拿黑子(先手)
    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 getErrMsg() {
        return errMsg;
    }

    public void setErrMsg(String errMsg) {
        this.errMsg = errMsg;
    }

    public String getRoomId() {
        return roomId;
    }

    public void setRoomId(String roomId) {
        this.roomId = roomId;
    }

    public Integer getUserId1() {
        return userId1;
    }

    public void setUserId1(Integer userId1) {
        this.userId1 = userId1;
    }

    public Integer getUserId2() {
        return userId2;
    }

    public void setUserId2(Integer userId2) {
        this.userId2 = userId2;
    }

    public boolean isBlack() {
        return isBlack;
    }

    public void setBlack(boolean black) {
        isBlack = black;
    }
}
package org.ting.j20250110_gobang.game;

public class GameRequest {
    private int userId;

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public int getRow() {
        return row;
    }

    public void setRow(int row) {
        this.row = row;
    }

    public int getCol() {
        return col;
    }

    public void setCol(int col) {
        this.col = col;
    }

    private int row; //落子坐标行
    private int col;  //落子坐标列
}
package org.ting.j20250110_gobang.game;

public class GameResponse {

    private String message;
    private int userid;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public int getUserid() {
        return userid;
    }

    public void setUserid(int userid) {
        this.userid = userid;
    }

    public int getRow() {
        return row;
    }

    public void setRow(int row) {
        this.row = row;
    }

    public int getCol() {
        return col;
    }

    public void setCol(int col) {
        this.col = col;
    }

    public int getWinnerId() {
        return winnerId;
    }

    public void setWinnerId(int winnerId) {
        this.winnerId = winnerId;
    }

    private int row; //落子坐标行
    private int col;  //落子坐标列
    private int winnerId; //是否分出胜负,-1表示未分出胜负,否则表示获胜玩家id

}

4. 维护用户在线状态

前面我们在OnlineUserManager中使用了一个哈希表来维护用户的在线状态,但是当用户匹配成功后会进入游戏房间页面,从游戏大厅页面进入游戏房间页面,这个操作会使得我们在游戏大厅页面的webSocket连接断开,连接断开时,根据我们之前的代码来看,是会把玩家从哈希表中移除的,也就是把玩家下线,那么这个时候如果有人使用这个账号是可以在另一个地方登录成功的,就会造成两个账号同时在线的情况,为了避免这种情况我们在OnlineUserManager中再添加另一个哈希表用来维护在游戏房间中的用户:

package org.ting.j20250110_gobang.game;

import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketSession;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class OnlineUserManager {
    //使用ConcurrentHashMap保证线程安全
    //维护大厅中在线用户
    private Map<Integer, WebSocketSession> onlineUser = new ConcurrentHashMap<>();
    //维护游戏房间中的用户
    private Map<Integer, WebSocketSession> inRoomUser = new ConcurrentHashMap<>();
    public void enterGameRoom(int userId, WebSocketSession session) {
        //用户进入游戏房间
        inRoomUser.put(userId, session);
    }
    public void exitGameRoom(int userId) {
        //用户退出游戏房间
        inRoomUser.remove(userId);
    }
    public WebSocketSession getFromRoom(int userId) {
        //获取用户的websocket会话
        return inRoomUser.get(userId);
    }
    public void enterGameHall(int userId, WebSocketSession session) {
        //用户上线
        onlineUser.put(userId, session);
    }
    public void exitGameHall(int userId) {
        //用户下线
        onlineUser.remove(userId);
    }

    public WebSocketSession getFromHall(int userId) {
        //获取用户的websocket会话
        return onlineUser.get(userId);
    }
}

注意要更改之前玩家上线时代码的判断逻辑,还需要检测用户是否在游戏房间中:

相关文章:

  • 九、Spring Boot:自动配置原理
  • 线上JVM OOM问题,如何排查和解决?
  • Vscode通过Roo Cline接入Deepseek
  • git和gitee在idea中的使用
  • 有关Java中的集合(2):Map<T>(底层源码分析)
  • JavaSE语法笔记
  • 【Springer上传手稿记录】《Signal, Image and Video Processing》
  • JavaScript---数组内置方法与日期内置方法汇总
  • SP导入智能材质球
  • C语言学习笔记-初阶(23)函数详解
  • Tomcat 乱码问题彻底解决
  • 快速调用DeepSeek API: 硅基流动 X 华为云 X ChatBox(2025/2/5)
  • Linux上构建RPM包指南
  • 力扣27.移除元素(双指针)
  • go前后端开源项目go-admin,本地启动
  • 在Linux上使用APT安装Sniffnet的详细步骤
  • 哈希表和STL —— unorderde_set/unordered_map【复习笔记】
  • 深入理解 JavaScript 中的 call、apply 和 bind
  • 《C++深拷贝与浅拷贝:内存安全的拷贝构造函数实践》
  • 【AI认知】大语言生成模型和推理模型的技术差异和应用区别
  • 中巡组在行动丨①震慑:这些地区有官员落马
  • 筑牢安全防线、提升应急避难能力水平,5项国家标准发布
  • 中国女足将于5月17日至6月2日赴美国集训并参加邀请赛
  • 福建宁德市长张永宁拟任设区市党委正职,曾获评全国优秀县委书记
  • 山西省委常委李金科添新职
  • 《新时代的中国国家安全》白皮书(全文)