类似竞技问答游戏:用户随机匹配一名对手,双方同时开始答题,直到双方都完成答题,对局结束。基本的逻辑就是这样,如果有其他需求,可以在其基础上进行扩展
明确了这一点,下面介绍开发思路。为每个用户拟定四种在线状态,分别是:待匹配、匹配中、游戏中、游戏结束。下面是流程图,用户的流程是被规则约束的,状态也随流程而变化
对流程再补充如下:
用户进入匹配大厅(具体效果如何由客户端体现),将用户的状态设置为待匹配
用户开始匹配,将用户的状态设置为匹配中,系统搜索其他同样处于匹配中的用户,在这个过程中,用户可以取消匹配,返回匹配大厅,此时用户状态重新设置为待匹配。匹配成功,保存匹配信息,将用户状态设置为游戏中
根据已保存的匹配信息,用户可以获得对手的信息。答题是时,每次用户分数更新,也会向对手推送更新后的分数
用户完成答题,则等待对手也完成答题。双方都完成答题,用户状态设置为游戏结束,展示对局结果
详细设计
针对概要设计提出的思路,我们需要思考以下几个问题:
如何保持客户端与服务器的连接?
如何设计客户端与服务端的消息交互?
如何保存以及改变用户状态?
如何匹配用户?
下面我们一个一个来解决
1. 如何保持用户与服务器的连接?以往我们使用 Http 请求服务器,并获取响应信息。然而 Http 有个缺陷,就是通信只能由客户端发起,无法做到服务端主动向客户端推送信息。根据概要设计我们知道,服务端需要向客户端推送对手的实时分数,因此这里不适合使用 Http,而选择了 WebSocket。WebSocket 最大的特点就是服务端可以主动向客户端推送信息,客户端也可以主动向服务端发送信息,是真正的双向平等对话
有关 SpringBoot 集成 WebSocket 可参考这篇博客:https://blog.csdn.net/qq_35387940/article/details/93483678
2. 如何设计客户端与服务端的消息交互?按照匹配机制要求,把消息划分为 ADD_USER(用户加入)、MATCH_USER(匹配对手)、CANCEL_MATCH(取消匹配)、PLAY_GAME(游戏开始)、GAME_OVER(游戏结束)
public enum MessageTypeEnum { /** * 用户加入 */ ADD_USER, /** * 匹配对手 */ MATCH_USER, /** * 取消匹配 */ CANCEL_MATCH, /** * 游戏开始 */ PLAY_GAME, /** * 游戏结束 */ GAME_OVER, }使用 WebSocket 客户端可以向服务端发送消息,服务端也能向客户端发送消息。把消息按照需求划分成不同的类型,客户端发送某一类型的消息,服务端接收后判断,并按照类型分别处理,最后返回向客户端推送处理结果。区别客户端 WebSocket 连接的是从客户端传来的 userId,用 HashMap 保存
@Component @Slf4j @ServerEndpoint(value = "/game/match/{userId}") public class ChatWebsocket { private Session session; private String userId; static QuestionSev questionSev; static MatchCacheUtil matchCacheUtil; static Lock lock = new ReentrantLock(); static Condition matchCond = lock.newCondition(); @Autowired public void setMatchCacheUtil(MatchCacheUtil matchCacheUtil) { ChatWebsocket.matchCacheUtil = matchCacheUtil; } @Autowired public void setQuestionSev(QuestionSev questionSev) { ChatWebsocket.questionSev = questionSev; } @OnOpen public void onOpen(@PathParam("userId") String userId, Session session) { log.info("ChatWebsocket open 有新连接加入 userId: {}", userId); this.userId = userId; this.session = session; matchCacheUtil.addClient(userId, this); log.info("ChatWebsocket open 连接建立完成 userId: {}", userId); } @OnError public void onError(Session session, Throwable error) { log.error("ChatWebsocket onError 发生了错误 userId: {}, errorMessage: {}", userId, error.getMessage()); matchCacheUtil.removeClinet(userId); matchCacheUtil.removeUserOnlineStatus(userId); matchCacheUtil.removeUserFromRoom(userId); matchCacheUtil.removeUserMatchInfo(userId); log.info("ChatWebsocket onError 连接断开完成 userId: {}", userId); } @OnClose public void onClose() { log.info("ChatWebsocket onClose 连接断开 userId: {}", userId); matchCacheUtil.removeClinet(userId); matchCacheUtil.removeUserOnlineStatus(userId); matchCacheUtil.removeUserFromRoom(userId); matchCacheUtil.removeUserMatchInfo(userId); log.info("ChatWebsocket onClose 连接断开完成 userId: {}", userId); } @OnMessage public void onMessage(String message, Session session) { log.info("ChatWebsocket onMessage userId: {}, 来自客户端的消息 message: {}", userId, message); JSONObject jsonObject = JSON.parseObject(message); MessageTypeEnum type = jsonObject.getObject("type", MessageTypeEnum.class); log.info("ChatWebsocket onMessage userId: {}, 来自客户端的消息类型 type: {}", userId, type); if (type == MessageTypeEnum.ADD_USER) { addUser(jsonObject); } else if (type == MessageTypeEnum.MATCH_USER) { matchUser(jsonObject); } else if (type == MessageTypeEnum.CANCEL_MATCH) { cancelMatch(jsonObject); } else if (type == MessageTypeEnum.PLAY_GAME) { toPlay(jsonObject); } else if (type == MessageTypeEnum.GAME_OVER) { gameover(jsonObject); } else { throw new GameServerException(GameServerError.WEBSOCKET_ADD_USER_FAILED); } log.info("ChatWebsocket onMessage userId: {} 消息接收结束", userId); } /** * 群发消息 */ private void sendMessageAll(MessageReply<?> messageReply) { log.info("ChatWebsocket sendMessageAll 消息群发开始 userId: {}, messageReply: {}", userId, JSON.toJSONString(messageReply)); Set<String> receivers = messageReply.getChatMessage().getReceivers(); for (String receiver : receivers) { ChatWebsocket client = matchCacheUtil.getClient(receiver); client.session.getAsyncRemote().sendText(JSON.toJSONString(messageReply)); } log.info("ChatWebsocket sendMessageAll 消息群发结束 userId: {}", userId); } // 出于减少篇幅的目的,业务处理方法暂不贴出... } 3. 如何保存以及改变用户状态?