目录
在线用户管理模块
当用户登录/注册成功后,会跳转到大厅界面,或之后进入游戏房间,为了通过该用户的websocket连接传输消息(构建 用户id与websocket连接 的映射关系),需要有数据结构管理 大厅&房间用户
两个数据结构:
- 大厅用户管理
- 房间用户管理
对大厅/房间用户管理数据结构进行增删查
cpp
/*
-------------
online.hpp |在线用户管理模块|
在线用户管理
- 大厅用户
- 房间用户
-------------
*/
#pragma once
#include <iostream>
#include <mutex>
#include <unordered_map>
#include <websocketpp/server.hpp>
#include <websocketpp/config/asio.hpp>
using websocket_server = websocketpp::server<websocketpp::config::asio>;
class Online_Manager // 在线用户管理
{
public:
void EnterHall(int id, const websocket_server::connection_ptr &conn) // 进入游戏大厅(用户登录成功后的操作)
{
std::lock_guard<std::mutex> lock(_mutex);
// 当在线用户重复进入时更新连接
_hallUsers.insert_or_assign(id, conn); // C++17新接口,若值存在就覆盖
//_HallUsers.insert(std::make_pair(id, conn)); // 插入用户id和conn的映射
}
void ExitHall(int id) // 退出游戏大厅
{
std::lock_guard<std::mutex> lock(_mutex);
_hallUsers.erase(id); // 删除该映射
}
void EnterRoom(int id, const websocket_server::connection_ptr &conn) // 进入游戏房间
{
// [TRACE] 是否需要判断该用户在大厅?
std::lock_guard<std::mutex> lock(_mutex);
// 当房间用户重复进入时更新连接
_roomUsers.insert_or_assign(id, conn); // C++17接口:重复值则覆盖
//_RoomUsers.insert(std::make_pair(id, conn)); // 插入用户id和conn的映射
}
void ExitRoom(int id) // 退出游戏房间
{
std::lock_guard<std::mutex> lock(_mutex);
_roomUsers.erase(id); // 删除该映射
}
bool IsHall(int id) // 判断用户是否在大厅
{
std::lock_guard<std::mutex> lock(_mutex);
return _hallUsers.find(id) != _hallUsers.end();
}
bool IsRoom(int id) // 判断用户是否在房间
{
std::lock_guard<std::mutex> lock(_mutex);
return _roomUsers.find(id) != _roomUsers.end();
}
// 获取用户id所对应的websocket连接
websocket_server::connection_ptr GetConn_FromHall(int id)
{
std::lock_guard<std::mutex> lock(_mutex);
auto usr = _hallUsers.find(id);
if(usr == _hallUsers.end()) // 没找到,返回空对象
return websocket_server::connection_ptr();
else
return usr->second; // 返回conn
}
websocket_server::connection_ptr GetConn_FromRoom(int id)
{
std::lock_guard<std::mutex> lock(_mutex);
auto usr = _roomUsers.find(id);
if(usr == _roomUsers.end()) // 没找到,返回空对象
return websocket_server::connection_ptr();
else
return usr->second; // 返回conn
}
private:
std::mutex _mutex; // 在进行在线用户操作时加互斥锁
// unordered_map<用户id, websocket连接>
std::unordered_map<int, websocket_server::connection_ptr> _hallUsers; // 大厅用户
std::unordered_map<int, websocket_server::connection_ptr> _roomUsers; // 房间用户
};
游戏房间用户管理
协议定制
在设计游戏房间管理之前,需要统一一套服务端与客户端通信的JSON格式标准:
bash
# 游戏房间 JSON 协议
房间 WebSocket 使用 UTF-8 编码的 JSON 文本帧。所有请求必须是 JSON 对象,并包含:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `optype` | string | 操作类型 |
| `room_id` | integer | 房间编号,必须为正数 |
| `uid` | integer | 请求用户编号,必须为正数;服务端必须将其与认证会话中的用户编号比较 |
客户端传入的 `uid` 只用于协议完整性校验,不能作为身份认证依据。
## 通用响应
成功响应至少包含 `optype` 和 `result: true`。失败响应统一包含 `result: false`、数值 `code` 和可展示的 `reason`:
```json
{
"optype": "put_chess",
"result": false,
"code": 1003,
"reason": "该位置已有棋子"
}
```
未知或缺少 `optype` 时,`optype` 返回 `unknown`,错误码为 `1000`。
## 房间准备
`room_ready` 只允许作为服务端响应,不是客户端操作:
```json
{
"optype": "room_ready",
"result": true,
"room_id": 10001,
"uid": 10086,
"white_id": 10086,
"black_id": 10087
}
```
## 下棋
请求中的 `row` 和 `col` 为 `0` 到 `14` 的整数。白棋先手,之后由服务端严格校验回合、位置和棋局状态。
```json
{
"optype": "put_chess",
"room_id": 10001,
"uid": 10086,
"row": 7,
"col": 8
}
```
落子成功且未分胜负:
```json
{
"optype": "put_chess",
"result": true,
"room_id": 10001,
"uid": 10086,
"row": 7,
"col": 8,
"winner": 0
}
```
游戏结束时,`winner` 为获胜用户编号;对方掉线时 `row` 和 `col` 为 `-1`:
```json
{
"optype": "put_chess",
"result": true,
"room_id": 10001,
"uid": 10086,
"row": 7,
"col": 8,
"winner": 10086,
"reason": "五星连珠,游戏结束!"
}
```
## 聊天
请求和成功响应都必须包含非空 `message`。服务端应在广播前进行敏感词和长度校验。
```json
{
"optype": "chat",
"room_id": 10001,
"uid": 10086,
"message": "赶紧点"
}
```
成功响应:
```json
{
"optype": "chat",
"result": true,
"room_id": 10001,
"uid": 10086,
"message": "赶紧点"
}
```
## 退出房间
```json
{
"optype": "exit_room",
"room_id": 10001,
"uid": 10086
}
```
游戏中退出会把胜者写入 `winner`,并更新双方战绩:
```json
{
"optype": "exit_room",
"result": true,
"room_id": 10001,
"uid": 10086,
"winner": 10087,
"reason": "对方退出游戏,判定胜利"
}
```
## 错误码
| 范围 | 错误码 | 含义 |
| --- | ---: | --- |
| 通用 | 0 | 成功 |
| 通用 | 1 | 服务端内部错误 |
| 请求 | 1000 | 请求格式或操作类型无效 |
| 请求 | 1001 | 房间编号无效 |
| 请求 | 1002 | 用户无效或不属于房间 |
| 棋局 | 1003 | 位置已有棋子 |
| 棋局 | 1004 | 未轮到当前用户 |
| 棋局 | 1005 | 游戏未开始或已经结束 |
| 棋局 | 1006 | 对手不在线 |
| 棋局 | 1007 | 落子位置越界或格式错误 |
| 聊天 | 2001 | 消息为空、过长或包含敏感词 |
再根据这个标准,实现Protocol.hpp:
cpp
/*
-----------------------------------------------
Protocol.hpp Protocol.md
制定客户端与服务端websocket请求/响应的协议标准
-----------------------------------------------
*/
#pragma once
#include <json/json.h>
#include <string>
// Room.hpp 和网络层之间只传递 Json::Value。
// 本文件负责定义 JSON 协议,不负责判断棋局是否合法。
enum class RoomOp
{
ROOM_READY, // 进入房间
PUT_CHESS, // 下棋操作
CHAT, // 聊天操作
EXIT_ROOM // 退出房间操作
};
enum class RoomCode // 错误码
{
SUCCESS = 0, // 成功
INTERNAL_ERROR = 1, // 内部错误(例如代码)
INVALID_REQUEST = 1000, // 请求无效
INVALID_ROOM = 1001, // 房间无效
INVALID_USER = 1002, // 用户无效
POSITION_OCCUPIED = 1003, // 落子位置已有棋子
NOT_YOUR_TURN = 1004, // 未轮到当前玩家
GAME_OVER = 1005, // 游戏未开始
PLAYER_OFFLINE = 1006, // 玩家不在线
INVALID_POSITION = 1007, // 落子位置无效
INVALID_MESSAGE = 2001 // 消息无效
};
namespace Protocol
{
constexpr const char *FIELD_OPTYPE = "optype";
constexpr const char *FIELD_ROOM_ID = "room_id";
constexpr const char *FIELD_UID = "uid";
constexpr const char *FIELD_ROW = "row";
constexpr const char *FIELD_COL = "col";
constexpr const char *FIELD_MESSAGE = "message";
constexpr int BOARD_SIZE = 15;
inline const char *ToString(RoomOp op) // 将操作类型字段从枚举转为字符串
{
switch (op)
{
case RoomOp::ROOM_READY:
return "room_ready";
case RoomOp::PUT_CHESS:
return "put_chess";
case RoomOp::CHAT:
return "chat";
case RoomOp::EXIT_ROOM:
return "exit_room";
}
return "unknown";
}
inline bool ParseRoomOp(const Json::Value &request, RoomOp &op) // 通过request,提取op
{
if (!request.isObject() || !request.isMember(FIELD_OPTYPE) ||
!request[FIELD_OPTYPE].isString())
{
return false;
}
const std::string value = request[FIELD_OPTYPE].asString();
if (value == "room_ready")
op = RoomOp::ROOM_READY;
else if (value == "put_chess")
op = RoomOp::PUT_CHESS;
else if (value == "chat")
op = RoomOp::CHAT;
else if (value == "exit_room")
op = RoomOp::EXIT_ROOM;
else
return false;
return true;
}
inline int ToInt(RoomCode code) // 枚举转错误码
{
return static_cast<int>(code);
}
inline Json::Value MakeBaseResponse(RoomOp op, bool result) // 构建基础响应
{
Json::Value response(Json::objectValue);
response[FIELD_OPTYPE] = ToString(op);
response["result"] = result;
return response;
}
inline Json::Value MakeError(RoomOp op, RoomCode code, // 构建错误响应
const std::string &reason)
{
Json::Value response = MakeBaseResponse(op, false);
response["code"] = ToInt(code);
response["reason"] = reason;
return response;
}
inline Json::Value MakeError(RoomCode code, const std::string &reason) // 构建未知错误
{
Json::Value response(Json::objectValue);
response["optype"] = "unknown";
response["result"] = false;
response["code"] = ToInt(code);
response["reason"] = reason;
return response;
}
inline Json::Value MakeRoomReady(int room_id, int uid, // 进入房间的响应
int white_id, int black_id)
{
Json::Value response = MakeBaseResponse(RoomOp::ROOM_READY, true);
response[FIELD_ROOM_ID] = room_id;
response[FIELD_UID] = uid;
response["white_id"] = white_id;
response["black_id"] = black_id;
return response;
}
inline Json::Value MakeChessResult(int room_id, int uid, int row, int col, // 下棋成功响应
int winner = 0,
const std::string &reason = "")
{
Json::Value response = MakeBaseResponse(RoomOp::PUT_CHESS, true);
response[FIELD_ROOM_ID] = room_id;
response[FIELD_UID] = uid;
response[FIELD_ROW] = row;
response[FIELD_COL] = col;
response["winner"] = winner;
if (!reason.empty())
response["reason"] = reason;
return response;
}
inline Json::Value MakeChatResult(int room_id, int uid, // 发送消息成功响应
const std::string &message)
{
Json::Value response = MakeBaseResponse(RoomOp::CHAT, true);
response[FIELD_ROOM_ID] = room_id;
response[FIELD_UID] = uid;
response[FIELD_MESSAGE] = message;
return response;
}
inline Json::Value MakeExitResult(int room_id, int uid, int winner = 0, // 退出房间成功响应
const std::string &reason = "")
{
Json::Value response = MakeBaseResponse(RoomOp::EXIT_ROOM, true);
response[FIELD_ROOM_ID] = room_id;
response[FIELD_UID] = uid;
if (winner != 0)
response["winner"] = winner;
if (!reason.empty())
response["reason"] = reason;
return response;
}
// 统一读取请求字段,避免 Room 中到处重复 isMember/isInt 判断。
inline bool GetInt(const Json::Value &request, const char *name, int &value)
{
if (!request.isObject() || name == nullptr ||
!request.isMember(name) || !request[name].isInt())
return false;
value = request[name].asInt();
return true;
}
inline bool GetRoomId(const Json::Value &request, int &room_id)
{
return GetInt(request, FIELD_ROOM_ID, room_id) && room_id > 0;
}
inline bool GetUid(const Json::Value &request, int &uid)
{
return GetInt(request, FIELD_UID, uid) && uid > 0;
}
inline bool GetPosition(const Json::Value &request, int &row, int &col)
{
return GetInt(request, FIELD_ROW, row) &&
GetInt(request, FIELD_COL, col) &&
row >= 0 && row < BOARD_SIZE &&
col >= 0 && col < BOARD_SIZE;
}
inline bool GetString(const Json::Value &request, const char *name,
std::string &value)
{
if (!request.isObject() || name == nullptr ||
!request.isMember(name) || !request[name].isString())
{
return false;
}
value = request[name].asString();
return !value.empty();
}
} // namespace Protocol
构建响应都通过Protocol层完成,可以确保协议字段一致
房间设计
当用户进入游戏房间后,房间内的操作,就在此设计
cpp
/*
------------------
room.hpp romm.cpp |游戏房间管理模块|
一个房间为一个对象
实现对房间内的操作
------------------
*/
/*
*/
#pragma once
#include <vector>
#include "Util.hpp"
#include "Db.hpp"
#include "Online.hpp"
#include "Protocol/Protocol.hpp"
enum class RoomStatus // 房间状态
{
GAME_WAIT, // 游戏未开始
GAME_START // 游戏进行中
};
enum class Chess // 棋盘中一个位置的三种状态
{
VACANCY = 0, // 空
WHITE_CHESS, // 白棋
BLACK_CHESS // 黑棋
};
class Room
{
// 棋盘行列
int ROW = 15;
int COL = 15;
public:
// 房间初始0人、房间状态:未开始、棋盘初始化为0
Room(int room_id, User_Table *user, Online_Manager *online)
: _room_id(room_id), _statu(RoomStatus::GAME_WAIT), _player_cnt(0), _id_white(0), _id_black(0), _turn_uid(0), _user(user), _online(online), _board(ROW, std::vector<Chess>(COL, Chess::VACANCY))
{
LOG(LogLv::DEBUG, "创建房间 id:[%d]", _room_id);
}
~Room()
{
LOG(LogLv::DEBUG, "回收房间 id:[%d]", _room_id);
}
Json::Value Chess_Handle(Json::Value &request, int authenticated_uid); // 处理下棋操作,返回JSON格式的响应
Json::Value Chat_Handle(Json::Value &request, int authenticated_uid); // 处理聊天操作,返回JSON格式的响应
Json::Value Exit_Handle(Json::Value &request, int authenticated_uid); // 处理用户退出房间操作
// 总处理接口
void Handle_Request(Json::Value &request, int authenticated_uid); // 用于区分该请求属于那种类型的请求并交给对应处理接口
void Broadcast(Json::Value response); // 将信息广播给房间内玩家
bool AddUser(int id, Chess chess); // 为该房间添加黑/白棋用户
int GetId() { return _room_id; } // 获取房间号
RoomStatus GetStatu() { return _statu; } // 获取房间状态
uint16_t GetPlayerCnt() { return _player_cnt; } // 获取房间人数
int GetUser_White() { return _id_white; } // 获取白棋用户id
int GetUser_Black() { return _id_black; } // 获取黑棋用户id
private:
int Check_Win(int row, int col, Chess color); // 检查这一步棋是否胜利,返回胜利者id或0
bool Five(int row, int col, int row_off, int col_off, Chess color); // 判断该位置以特定偏移的格式是否有五子连珠
private:
int _room_id; // 房间码
RoomStatus _statu; // 房间状态
uint16_t _player_cnt; // 房间现在人数
int _id_white; // 执白棋的用户id
int _id_black; // 执黑棋的用户id
int _turn_uid; // 当前轮到的用户id
User_Table *_user; // 用户表操作指针
Online_Manager *_online; // 在线用户管理指针
std::vector<std::vector<Chess>> _board; // 棋盘
};
三种操作类型:下棋、聊天、退出房间
统一由Handle_Request分配,在处理完后,最后由Handle_Request执行广播
cpp
#include "Room.hpp"
Json::Value Room::Chess_Handle(Json::Value &request, int authenticated_uid) // 处理下棋操作,返回JSON格式的响应
{
if (_statu == RoomStatus::GAME_WAIT) // 游戏未开始
return Protocol::MakeError(RoomOp::PUT_CHESS, RoomCode::GAME_OVER, "游戏未开始或已经结束");
int chess_row = 0;
int chess_col = 0;
if (!Protocol::GetPosition(request, chess_row, chess_col))
return Protocol::MakeError(RoomOp::PUT_CHESS, RoomCode::INVALID_POSITION, "落子位置无效");
int self_id = authenticated_uid;
if (self_id != _id_white && self_id != _id_black)
return Protocol::MakeError(RoomOp::PUT_CHESS,
RoomCode::INVALID_USER,
"用户不属于当前房间");
int request_uid = 0;
if (!Protocol::GetUid(request, request_uid) || request_uid != authenticated_uid)
return Protocol::MakeError(RoomOp::PUT_CHESS, RoomCode::INVALID_USER, "请求用户身份无效");
if (self_id != _turn_uid)
return Protocol::MakeError(RoomOp::PUT_CHESS, RoomCode::NOT_YOUR_TURN, "还没有轮到您走棋");
int enermy_id = (self_id == _id_white ? _id_black : _id_white); // 敌人id
// 若对方掉线,则直接判赢
if (!(_online && _user)) // 如果_online或_user为空
return Protocol::MakeError(RoomCode::INTERNAL_ERROR, "在线用户或房间用户获取失败");
if (_online->IsRoom(enermy_id) == false)
{
return Protocol::MakeChessResult(_room_id,
authenticated_uid,
-1,
-1,
self_id,
"对方掉线,您已获胜");
}
// 获取走棋位置,判断当前走棋是否合理
if (_board[chess_row][chess_col] != Chess::VACANCY) // 该位置已经有棋
return Protocol::MakeError(RoomOp::PUT_CHESS,
RoomCode::POSITION_OCCUPIED,
"该位置已有棋子");
Chess self_color = (self_id == _id_white ? Chess::WHITE_CHESS : Chess::BLACK_CHESS);
_board[chess_row][chess_col] = self_color; // 下棋
// 判断是否有玩家胜利
int winner_id = Check_Win(chess_row, chess_col, self_color);
if(winner_id == 0) // 没有胜利者,就让对面下棋
{
_turn_uid = enermy_id;
}
// 若winner_id != 0,再设置reason,否则不设置
return Protocol::MakeChessResult(_room_id, self_id, chess_row, chess_col, winner_id,
winner_id == 0 ? "" : "获胜");
}
Json::Value Room::Chat_Handle(Json::Value &request, int authenticated_uid) // 处理聊天操作,返回JSON格式的响应
{
std::string msg;
if (!Protocol::GetString(request, Protocol::FIELD_MESSAGE, msg))
return Protocol::MakeError(RoomOp::CHAT, RoomCode::INVALID_MESSAGE, "消息不能为空");
int request_uid = 0;
if (!Protocol::GetUid(request, request_uid) || request_uid != authenticated_uid)
return Protocol::MakeError(RoomOp::CHAT, RoomCode::INVALID_USER, "请求用户身份无效");
if (msg.find("垃圾") != std::string::npos)
return Protocol::MakeError(RoomOp::CHAT, RoomCode::INVALID_MESSAGE, "消息包含敏感词");
Broadcast(Protocol::MakeChatResult(_room_id, authenticated_uid, msg));
return Protocol::MakeChatResult(_room_id, authenticated_uid, msg);
}
Json::Value Room::Exit_Handle(Json::Value &, int authenticated_uid)
{
if (authenticated_uid != _id_white && authenticated_uid != _id_black)
return Protocol::MakeError(RoomOp::EXIT_ROOM, RoomCode::INVALID_USER, "用户不属于当前房间");
const int opponent_id = authenticated_uid == _id_white ? _id_black : _id_white;
int winner_id = 0;
// 若游戏还未结束,直接判对方胜
if (_statu == RoomStatus::GAME_START && opponent_id != 0)
winner_id = opponent_id;
_turn_uid = 0;
_player_cnt--;
return Protocol::MakeExitResult(_room_id, authenticated_uid,
winner_id,
winner_id == 0 ? "玩家退出房间" : "对方退出游戏,判定胜利");
}
void Room::Handle_Request(Json::Value &request, int authenticated_uid) // 用于区分该请求属于那种类型的请求并交给对应处理接口
{
Json::Value response;
while (false)
{
RoomOp op;
// 判断该request的optype字段是否合法
if (!Protocol::ParseRoomOp(request, op))
{
response = Protocol::MakeError(RoomCode::INVALID_REQUEST, "无效的房间操作类型");
break;
}
// 判断请求房间号与当前房间号是否一致
int room_id = 0;
if (!Protocol::GetRoomId(request, room_id) || room_id != _room_id)
{
response = Protocol::MakeError(RoomCode::INVALID_ROOM, "请求房间号与该房间号不一致");
break;
}
int request_uid = 0;
if (!Protocol::GetUid(request, request_uid) || request_uid != authenticated_uid)
{
response = Protocol::MakeError(RoomCode::INVALID_USER, "请求用户身份无效");
break;
}
// 判断操作类型并转给对应处理接口
switch (op)
{
case RoomOp::PUT_CHESS: // 下棋操作
response = Chess_Handle(request, authenticated_uid);
break;
case RoomOp::CHAT:
response = Chat_Handle(request, authenticated_uid);
break;
case RoomOp::ROOM_READY:
response = Protocol::MakeError(RoomOp::ROOM_READY, RoomCode::INVALID_REQUEST, "room_ready只能作为服务器响应");
break;
case RoomOp::EXIT_ROOM:
response = Exit_Handle(request, authenticated_uid);
break;
default:
response = Protocol::MakeError(RoomCode::INVALID_REQUEST, "未知房间操作");
}
}
int winner_id;
if (Protocol::GetInt(request, "winner", winner_id) && winner_id != 0) // 胜负已分,更新数据库
{
int loser_id = winner_id == _id_black ? _id_white : _id_black;
// 更新数据库
_user->Win(winner_id);
_user->Lose(loser_id);
// 更新房间状态
_statu = RoomStatus::GAME_WAIT; // 游戏结束,房间重新变为等待
}
Broadcast(response); // 广播消息
}
bool Room::AddUser(int id, Chess chess) // 为该房间添加黑/白棋用户
{
if (chess == Chess::BLACK_CHESS) // 添加黑棋用户
{
if (_id_black != 0 && _id_black != id)
{
LOG(LogLv::ERROR, "该房间黑棋用户已被占");
return false;
}
if (_id_black == id)
return true;
_id_black = id;
_player_cnt++;
}
else if (chess == Chess::WHITE_CHESS) // 添加白棋用户
{
if (_id_white != 0 && _id_white != id)
{
LOG(LogLv::ERROR, "该房间白棋用户已被占");
return false;
}
if (_id_white == id)
return true;
_id_white = id;
_player_cnt++;
}
else // 传入的是Chess::VACANSY
return false;
if (_id_white != 0 && _id_black != 0)
{
_statu = RoomStatus::GAME_START;
_turn_uid = _id_white;
}
return true;
}
void Room::Broadcast(Json::Value response) // 广播消息
{
std::string body;
if (!Json_Util::Serialization(response, body)) // 序列化JSON
return;
const auto white = _online->GetConn_FromRoom(_id_white);
const auto black = _online->GetConn_FromRoom(_id_black);
if (white)
white->send(body);
if (black && black != white)
black->send(body);
}
int Room::Check_Win(int row, int col, Chess color) // 检查这一步棋是否胜利
{
if (Five(row, col, 0, 1, color) || // 横行
Five(row, col, 1, 0, color) || // 竖列
Five(row, col, 1, 1, color) || // 正斜
Five(row, col, -1, 1, color)) // 反斜
{
return (color == Chess::BLACK_CHESS) ? _id_black : _id_white;
}
// 没有胜利者
return 0;
}
bool Room::Five(int row, int col, int row_off, int col_off, Chess color) // 判断该位置以特定偏移的格式是否有五子连珠
{
int cnt = 1; // 当前连续相同颜色棋子的数量
// 先搜索一边
int search_row = row + row_off;
int search_col = col + col_off;
while (search_row >= 0 && search_col >= 0 &&
search_row < ROW && search_col < COL &&
_board[search_row][search_col] == color)
{
cnt++; // 同色棋子+1
// 继续偏移
search_row += row_off;
search_col += col_off;
}
// 一边搜索完再搜索另外一边
search_row = row - row_off;
search_col = col - col_off;
while (search_row >= 0 && search_col >= 0 &&
search_row < ROW && search_col < COL &&
_board[search_row][search_col] == color)
{
cnt++; // 同色棋子+1
// 继续偏移
search_row -= row_off;
search_col -= col_off;
}
return cnt >= 5;
}
上面所设计的只是单个房间,当房间增多时,需要将所有房间统一管理起来,因此还需要一个房间管理类
cpp
using room_ptr = std::shared_ptr<Room>;
class Room_Manager
{
public:
Room_Manager(User_Table *user, Online_Manager *online)
: _next_rid(1), _user(user), _online(online)
{
LOG(LogLv::DEBUG, "房间管理模块初始化成功");
}
~Room_Manager()
{
LOG(LogLv::DEBUG, "房间管理模块即将销毁");
}
room_ptr Create_Room(int uid1, int uid2); // 通过匹配成功的两个用户创建房间
room_ptr Get_Room_FromRid(int rid); // 通过房间id获取房间指针
room_ptr Get_Room_FromUid(int uid); // 通过用户id获取房间指针
void Remove_Room(int rid); // 通过房间id销毁房间
void Remove_RoomUser(int uid); // 删除房间中指定用户
private:
int _next_rid; // 下一个房间创建时的id
std::mutex _mutex; // 创建房间时保证线程安全
User_Table *_user; // 用户表操作指针
Online_Manager *_online; // 在线用户管理指针
std::unordered_map<int, room_ptr> _rooms; // 房间id和房间指针的映射
std::unordered_map<int, int> _roomid; // 用户id和房间id的映射
};
_rooms负责管理所有房间,而_roomid负责提供用户id到房间的关系
cpp
room_ptr Room_Manager::Create_Room(int uid1, int uid2) // 通过匹配成功的两个用户创建房间
{
// 校验两个用户是否在大厅
if (!_online->IsHall(uid1))
{
LOG(LogLv::ERROR, "用户[%d]不在大厅,创建房间失败", uid1);
return room_ptr();
}
else if (!_online->IsHall(uid2))
{
LOG(LogLv::ERROR, "用户[%d]不在大厅,创建房间失败", uid2);
return room_ptr();
}
std::lock_guard<std::mutex> lock(_mutex); // 加锁保证线程安全
// 创建房间
room_ptr room(new Room(_next_rid, _user, _online));
room->AddUser(uid1, Chess::BLACK_CHESS);
room->AddUser(uid2, Chess::WHITE_CHESS);
// 建立房间id与房间指针的映射
_rooms.insert(std::make_pair(_next_rid, room));
_roomid.insert(std::make_pair(uid1, _next_rid));
_roomid.insert(std::make_pair(uid2, _next_rid));
_next_rid++; // 更新下一个房间号
return room;
}
room_ptr Room_Manager::Get_Room_FromRid(int rid) // 通过房间id获取房间指针
{
std::lock_guard<std::mutex> lock(_mutex); // 加锁保证线程安全
auto roomit = _rooms.find(rid);
if (roomit == _rooms.end())
{
LOG(LogLv::ERROR, "房间id --> 房间指针 查找失败");
return room_ptr();
}
return roomit->second;
}
room_ptr Room_Manager::Get_Room_FromUid(int uid) // 通过用户id获取房间指针
{
std::lock_guard<std::mutex> lock(_mutex); // 加锁保证线程安全
// 1. 通过 用户id 获取 房间id
auto idit = _roomid.find(uid);
if (idit == _roomid.end())
{
LOG(LogLv::ERROR, "用户id --> 房间id 查找失败");
return room_ptr();
}
// 2. 通过 房间id 获取 房间指针
int roomid = idit->second;
auto roomit = _rooms.find(roomid);
if (roomit == _rooms.end())
{
LOG(LogLv::ERROR, "房间id --> 房间指针 查找失败");
return room_ptr();
}
return roomit->second;
}
void Room_Manager::Remove_Room(int rid) // 通过房间id销毁房间
{
// 通过 房间id 获取 房间指针
room_ptr room = Get_Room_FromRid(rid);
if (room == room_ptr())
{
LOG(LogLv::WARNING, "没有房间[%d],无需销毁", rid);
return;
}
// 通过房间指针,获取 黑棋 & 白棋 用户id
int id_black = room->GetUser_Black();
int id_white = room->GetUser_White();
// 删除对应 用户id -> 房间id 映射
_roomid.erase(id_black);
_roomid.erase(id_white);
// 删除对应 房间id -> 房间指针 映射
_rooms.erase(rid);
}
void Room_Manager::Remove_RoomUser(int uid) // 删除房间中指定用户
// 若房间没用户了,就销毁房间
{
// 通过 用户id 获取 房间指针
room_ptr room = Get_Room_FromUid(uid);
if (room == room_ptr())
{
LOG(LogLv::WARNING, "用户[%d]已离开房间,无需删除", uid);
return;
}
// 在房间内删除该玩家
room->Exit_Handle(Json::Value(), uid);
// 若房间中没有玩家了,就销毁房间
if(room->GetPlayerCnt() == 0)
Remove_Room(room->GetId());
}