1.数据管理模块
数据管理模块只要负责对数据库中的数据进行统一的增删查改管理,任何其他模块需要修改数据都需要通过数据管理模块来完成。
1.1数据库设计
创建user表,用来表示用户信息以及积分信息。用户信息主要用来实现登录、注册、游戏对战数据管理等功能,积分信息用来实现匹配功能(初始积分默认为1000)。
1.1.1user表的创建
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL,
`password` varchar(64) NOT NULL,
`score` int DEFAULT NULL,
`total_count` int DEFAULT NULL,
`win_count` int DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`),
UNIQUE KEY `username_2` (`username`),
UNIQUE KEY `username_3` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb3
1.2user_table类
对应数据库中的表都需要进行管理,不同的表的结构不同所以需要很多的类来进行管理。当我们想要修改表时就可以通过实例化类来对表的内容进行操作。
1.2.1user_table类的实现
cpp
#ifndef __M_DB_H__
#define __M_DB_H__
#include "util.hpp"
#include <mutex>
#include <assert.h>
class user_table
{
public:
user_table(const std::string &host,
const std::string &user,
const std::string &password,
const std::string &db, unsigned int port = 3306)
{
_mysql = mysql_util::mysql_create(host, user, password, db, port);
assert(_mysql != NULL);
}
// 注册用户
bool insert(Json::Value &root)
{
if(root["username"].isNull()||root["password"].isNull())
{
DLOG("Please enter username or password");
return false;
}
#define INSERT "insert into user values(null,'%s',MD5('%s'),1000,0,0)"
char sql[4096] = {0};
sprintf(sql, INSERT, root["username"].asCString(), root["password"].asCString());
bool re = mysql_util::mysql_exec(_mysql, sql);
if (re == false)
{
DLOG("New user created error!!");
return false;
}
return true;
}
// 登陆验证,并返回用户信息
bool login(Json::Value &root)
{
#define LOGIN "select id,score,total_count,win_count from user where username='%s' and password=MD5('%s');"
char sql[4096] = {0};
sprintf(sql, LOGIN, root["username"].asCString(), root["password"].asCString());
MYSQL_RES *res = NULL;
{
std::unique_lock<std::mutex> lock(_mutex);
bool ret = mysql_util::mysql_exec(_mysql, sql);
if (ret == false)
{
DLOG("User login failed!!");
return false;
}
res = mysql_store_result(_mysql);
if (res == NULL)
{
DLOG("have no login user info!!");
return false;
}
}
int row_num = mysql_num_rows(res);
if (row_num != 1)
{
DLOG("user information is not unique!");
return false;
}
MYSQL_ROW row = mysql_fetch_row(res);
root["id"] = (Json::UInt64)std::stol(row[0]);
root["score"] = (Json::UInt64)std::stol(row[1]);
root["total_count"] = std::stoi(row[2]);
root["win_count"] = std::stoi(row[3]);
mysql_free_result(res);
return true;
}
// 通过名字查询用户信息
bool selete_by_name(std::string name, Json::Value &root)
{
#define USER_BY_NAME "select id,score,total_count,win_count from user where username='%s';"
char sql[4096] = {0};
sprintf(sql, USER_BY_NAME, name.c_str());
MYSQL_RES *res = NULL;
{
std::unique_lock<std::mutex> lock(_mutex);
bool ret = mysql_util::mysql_exec(_mysql, sql);
if (ret == false)
{
DLOG("User login failed!!");
return false;
}
res = mysql_store_result(_mysql);
if (res == NULL)
{
DLOG("have no login user info!!");
return false;
}
}
int row_num = mysql_num_rows(res);
if (row_num != 1)
{
DLOG("user information is not unique!");
return false;
}
MYSQL_ROW row = mysql_fetch_row(res);
root["id"] = (Json::UInt64)std::stol(row[0]);
root["score"] = (Json::UInt64)std::stol(row[1]);
root["total_count"] = std::stoi(row[2]);
root["win_count"] = std::stoi(row[3]);
mysql_free_result(res);
return true;
}
// 通过id查询用户信息
bool selete_by_id(uint64_t id, Json::Value &root)
{
#define USER_BY_ID "select username,score,total_count,win_count from user where id=%d;"
char sql[4096] = {0};
sprintf(sql, USER_BY_ID, id);
MYSQL_RES *res = NULL;
{
std::unique_lock<std::mutex> lock(_mutex);
bool ret = mysql_util::mysql_exec(_mysql, sql);
if (ret == false)
{
DLOG("User login failed!!");
return false;
}
res = mysql_store_result(_mysql);
if (res == NULL)
{
DLOG("have no login user info!!");
return false;
}
}
int row_num = mysql_num_rows(res);
if (row_num != 1)
{
DLOG("user information is not unique!");
return false;
}
MYSQL_ROW row = mysql_fetch_row(res);
root["id"] = (Json::UInt64)id;
root["username"] =row[0];
root["score"] = (Json::UInt64)std::stol(row[1]);
root["total_count"] = std::stoi(row[2]);
root["win_count"] = std::stoi(row[3]);
mysql_free_result(res);
return true;
}
// 胜利
bool win(uint64_t id)
{
#define USER_WIN "update user set score=score+30,total_count=total_count+1,win_count=win_count+1 where id=%d;"
char sql[4096] = {0};
sprintf(sql, USER_WIN, id);
bool ret = mysql_util::mysql_exec(_mysql, sql);
if (ret == false)
{
DLOG("update win user info failed!!");
return false;
}
return true;
}
// 失败
bool lose(uint64_t id)
{
#define USER_LOSE "update user set score=score-30,total_count=total_count+1 where id=%d;"
char sql[4096] = {0};
sprintf(sql, USER_LOSE, id);
bool ret = mysql_util::mysql_exec(_mysql, sql);
if (ret == false)
{
DLOG("update lose user info failed!!");
return false;
}
return true;
}
~user_table()
{
mysql_util::mysql_destroy(_mysql);
_mysql = NULL;
}
private:
MYSQL *_mysql;
std::mutex _mutex;
};
#endif
2.在线用户管理模块
对于在线的用户我要能够根据用户的信息找到客户端进行通信的Socket链接,还要能够判断用户是否在线,或者来判断其掉线,进而决定是否能执行某些操作。用户即可能在游戏大厅又可能在游戏房间中,所以就需要两个链表来管理它们。
2.1在线用户管理模块实现
cpp
#ifndef __M_ONLINE_H__
#define __M_ONLINE_H__
#include "util.hpp"
#include <mutex>
#include <unordered_map>
#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp>
typedef websocketpp::server<websocketpp::config::asio> websocket_server;
class online_manage
{
public:
// websocket链接建立成功之后才会进入游戏大厅&游戏房间在线管理
void enter_game_hall(uint64_t uid, websocket_server::connection_ptr &conn)
{
std::unique_lock<std::mutex> lock(_mutex);
_hall_user.insert(std::make_pair(uid,conn));
}
void enter_game_room(uint64_t uid, websocket_server::connection_ptr &conn)
{
std::unique_lock<std::mutex> lock(_mutex);
_room_user.insert(std::make_pair(uid,conn));
}
// 当websocket链接断开后才会移除游戏大厅&游戏房间的在线管理
void exit_game_hall(uint64_t uid)
{
std::unique_lock<std::mutex> lock(_mutex);
_hall_user.erase(uid);
}
void exit_game_room(uint64_t uid)
{
std::unique_lock<std::mutex> lock(_mutex);
_room_user.erase(uid);
}
// 判断当前用户是否在游戏大厅/游戏房间
bool is_in_game_hall(uint64_t uid)
{
std::unique_lock<std::mutex> lock(_mutex);
auto tl = _hall_user.find(uid);
if(tl==_hall_user.end())
{
return false;
}
return true;
}
bool is_in_game_room(uint64_t uid)
{
std::unique_lock<std::mutex> lock(_mutex);
auto tl = _room_user.find(uid);
if(tl==_room_user.end())
{
return false;
}
return true;
}
// 通过用户ID获取对应链接信息
websocket_server::connection_ptr get_conn_from_hall(uint64_t uid)
{
std::unique_lock<std::mutex> lock(_mutex);
auto tl = _hall_user.find(uid);
if(tl==_hall_user.end())
{
return websocket_server::connection_ptr();
}
return tl->second;
}
websocket_server::connection_ptr get_conn_from_room(uint64_t uid)
{
std::unique_lock<std::mutex> lock(_mutex);
auto tl = _room_user.find(uid);
if(tl==_room_user.end())
{
return websocket_server::connection_ptr();
}
return tl->second;
}
private:
std::mutex _mutex;
std::unordered_map<uint64_t, websocket_server::connection_ptr> _hall_user;
std::unordered_map<uint64_t, websocket_server::connection_ptr> _room_user;
};
#endif
3.游戏房间管理模块
首先,设计一个房间类,需要有能够表示自己的元素,也就是房间号。房间中要能处理下棋,还要处理聊天请求,其中为了保证文明聊天还需要保证能够检测敏感词。
3.1房间类
房间类中至少要包含:下棋、聊天、检测敏感词、返回房间号、返回房间状态、返回房间人数、添加黑白棋玩家、获取黑白棋id、退出房间、广播操作、判断是否胜利。
3.1.1房间类的实现
cpp
#include <iostream>
#include "db.hpp"
#include "online.hpp"
#define BOARD_ROW 15
#define BOARD_COL 15
#define CHESS_WHITE 1
#define CHESS_BLACK 2
typedef enum
{
GAME_START,
GAME_OVER
} room_statu;
class room
{
private:
uint64_t _room_id;
room_statu _statu;
uint64_t _white_id;
uint64_t _black_id;
int _player_count;
user_table *_tb_user;
online_manage *_online_user;
std::vector<std::vector<int>> _board;
private:
// 检测敏感词
bool checkSensitiveWord(const std::string &content)
{
// 自定义敏感词列表,按需增删
const std::vector<std::string> sensitiveList = {
"垃圾",
"菜逼",
"废物"};
// 遍历所有敏感词逐个匹配
for (const auto &word : sensitiveList)
{
// 字符串find找到返回下标,找不到返回std::string::npos
if (content.find(word) != std::string::npos)
{
return true;
}
}
return false;
}
bool five(int row, int col, int row_off, int col_off, int color)
{
int count = 1;
int search_row = row + row_off;
int search_col = col + col_off;
while (search_row >= 0 && search_row < BOARD_ROW && search_col >= 0 && search_col < BOARD_COL && _board[search_row][search_col] == color)
{
count++;
search_row += row_off;
search_col += col_off;
}
search_row = row - row_off;
search_col = col - col_off;
while (search_row >= 0 && search_row < BOARD_ROW && search_col >= 0 && search_col < BOARD_COL && _board[search_row][search_col] == color)
{
count++;
search_row -= row_off;
search_col -= col_off;
}
return (count >= 5);
}
uint64_t check_win(int row, int col, int 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_WHITE ? _white_id : _black_id;
}
return 0;
}
public:
room(uint64_t room_id, user_table *tb_user, online_manage *online_user)
: _room_id(room_id), _statu(GAME_START), _player_count(0), _tb_user(tb_user), _online_user(online_user), _board(BOARD_ROW, std::vector<int>(BOARD_COL, 0))
{
DLOG("%lu 房间创建成功", _room_id);
}
// 获取房间id/状态
uint64_t get_room_id() { return _room_id; }
room_statu statu() { return _statu; }
uint64_t get_player_count() { return _player_count; }
// 添加黑白棋id
void add_white_id(uint64_t uid)
{
_white_id = uid;
_player_count++;
}
void add_black_id(uint64_t uid)
{
_black_id = uid;
_player_count++;
}
// 获取黑白棋id
uint64_t get_white_id() { return _white_id; }
uint64_t get_black_id() { return _black_id; }
// 下棋动作
Json::Value handle_chess(Json::Value &req)
{
Json::Value resp = req;
// 判断双方是否都在线
int chess_row = req["row"].asInt();
int chess_col = req["col"].asInt();
uint64_t cur_id = req["uid"].asInt64();
if (_online_user->is_in_game_room(_white_id) == false)
{
resp["result"] = true;
resp["reason"] = "运气真好,对方掉线了!";
resp["winner"] = (Json::UInt64)_black_id;
return resp;
}
if (_online_user->is_in_game_room(_black_id) == false)
{
resp["result"] = true;
resp["reason"] = "运气真好,对方掉线了!";
resp["winner"] = (Json::UInt64)_white_id;
return resp;
}
// 判断下棋位置是否合理
if (_board[chess_row][chess_col] != 0)
{
resp["result"] = false;
resp["reason"] = "当前位置已经有其他棋子了,请重新选择!";
return resp;
}
int cur_color = cur_id == _white_id ? CHESS_WHITE : CHESS_BLACK;
_board[chess_row][chess_col] = cur_color;
// 判断是否有玩家胜利
uint64_t win_id = check_win(chess_row, chess_col, cur_color);
if (win_id != 0)
{
resp["reason"] = "五星连珠,大获全胜!";
}
resp["result"] = true;
resp["winner"] = (Json::UInt64)win_id;
return resp;
}
// 聊天动作
Json::Value handle_chat(Json::Value &req)
{
Json::Value resp = req;
std::string msg = req["message"].asString();
if (checkSensitiveWord(msg))
{
resp["result"] = false;
resp["reason"] = "发送信息中包含敏感词!";
return resp;
}
resp["result"] = true;
return resp;
}
// 用户退出
// 用户退出
void handle_exit(uint64_t uid)
{
Json::Value resp;
if (_statu == GAME_START)
{
_statu = GAME_OVER; // 标记游戏结束
// 计算胜者和败者
uint64_t winner_id = (uid == _white_id) ? _black_id : _white_id;
uint64_t loser_id = uid;
// 更新数据库积分
_tb_user->win(winner_id);
_tb_user->lose(loser_id);
DLOG("掉线处理: 赢家=%lu, 输家=%lu", winner_id, loser_id);
resp["optype"] = "put_chess";
resp["result"] = true;
resp["reason"] = "对方掉线,不战而胜!";
resp["room_id"] = (Json::UInt64)_room_id;
resp["uid"] = (Json::UInt64)uid;
resp["row"] = -1;
resp["col"] = -1;
resp["winner"] = (Json::UInt64)winner_id;
broadcast(resp);
}
_player_count--;
return;
}
// 总的处理函数,根据不同请求调用不同函数,将处理的数据广播给其他用户
void handle_request(Json::Value &req)
{
Json::Value resp;
// 判断请求的房间号跟当前房间是否匹配
uint64_t room_id = req["room_id"].asUInt64();
if (room_id != _room_id)
{
resp["optype"] = req["optype"].asString();
resp["result"] = false;
resp["reason"] = "房间号不匹配!";
broadcast(resp);
return;
}
// 根据不同请求调用不同函数
if (req["optype"].asString() == "put_chess")
{
resp = handle_chess(req);
// 判断游戏结束再更新数据库数据
if (resp["winner"].asInt64() != 0)
{
uint64_t winner_id = resp["winner"].asInt64();
uint64_t lose_id = winner_id == _white_id ? _black_id : _white_id;
_tb_user->win(winner_id);
_tb_user->lose(lose_id);
_statu = GAME_OVER;
}
}
else if (req["optype"].asString() == "chat")
{
resp = handle_chat(req);
}
else
{
resp["optype"] = req["optype"].asString();
resp["result"] = false;
resp["reason"] = "未知请求类型!";
}
broadcast(resp);
return;
}
// 广播
void broadcast(Json::Value &rsp)
{
// 把应答序列化
std::string body;
json_util::Serialization(rsp, body);
// 发送消息给所有用户
websocket_server::connection_ptr wconn = _online_user->get_conn_from_room(_white_id);
if (wconn.get() != nullptr)
{
wconn->send(body);
}
websocket_server::connection_ptr bconn = _online_user->get_conn_from_room(_black_id);
if (bconn.get() != nullptr)
{
bconn->send(body);
}
return;
}
~room()
{
DLOG("%lu 房间销毁成功", _room_id);
}
};
#endif
3.2房间管理类
服务器中会存在很多的房间,就需要实现对房间的管理。主要包含:创建房间、通过房间号查询房间信息、通过用户id查询房间信息、通过房间号删除房间、删除房间的用户(如果房间空了就删除房间)。
3.2.1房间管理类的实现
cpp
using room_ptr = std::shared_ptr<room>;
class room_manage
{
private:
user_table *_tb_user;
online_manage *_online_user;
uint64_t _next_rid;
std::mutex _mutex;
std::unordered_map<uint64_t, room_ptr> _rooms;
std::unordered_map<uint64_t, uint64_t> _users;
public:
room_manage(user_table *ut, online_manage *om)
: _next_rid(1), _tb_user(ut), _online_user(om)
{
DLOG("房间管理模块初始化成功");
}
~room_manage()
{
DLOG("房间管理模块即将销毁");
}
// 创建房间
room_ptr create_room(uint64_t uid1, uint64_t uid2)
{
// 判断两个用户是否在游戏大厅
if (_online_user->is_in_game_hall(uid1) == false)
{
DLOG("%lu 用户不在游戏大厅", uid1);
return room_ptr();
}
if (_online_user->is_in_game_hall(uid2) == false)
{
DLOG("%lu 用户不在游戏大厅", uid2);
return room_ptr();
}
// 创建房间,将用户信息添加到房间中
std::unique_lock<std::mutex> lock(_mutex);
room_ptr rp(new room(_next_rid, _tb_user, _online_user));
rp->add_white_id(uid1);
rp->add_black_id(uid2);
// 将房间信息管理起来
_rooms.insert(std::make_pair(_next_rid, rp));
_users.insert(std::make_pair(uid1, _next_rid));
_users.insert(std::make_pair(uid2, _next_rid));
// 返回房间信息
_next_rid++; // 创建新房间后自动增加下一个房间的房间号
return rp;
}
// 通过房间号查询房间信息
room_ptr get_room_by_rid(uint64_t rid)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _rooms.find(rid);
if (it == _rooms.end())
{
return room_ptr();
}
return it->second;
}
// 通过用户id查询房间信息
room_ptr get_room_by_uid(uint64_t uid)
{
std::unique_lock<std::mutex> lock(_mutex);
auto rit = _users.find(uid);
if (rit == _users.end())
{
return room_ptr();
}
uint64_t rid = rit->second;
auto it = _rooms.find(rid);
if (it == _rooms.end())
{
return room_ptr();
}
return it->second;
}
// 通过房间号删除房间
void remove_room(uint64_t rid)
{
std::unique_lock<std::mutex> lock(_mutex); // 添加锁
// 通过房间号获取房间信息
room_ptr rp = get_room_by_rid(rid);
if (rp.get() == nullptr)
{
return;
}
// 通过房间信息获取用户id
uint64_t uid1 = rp->get_white_id();
uint64_t uid2 = rp->get_black_id();
// 删除用户管理信息
_users.erase(uid1);
_users.erase(uid2);
// 删除房间管理信息
_rooms.erase(rid);
}
// 删除房间的用户,如果房间空了就删除房间
void remove_room_user(uint64_t uid)
{
// 通过用户id获取房间信息
room_ptr rp = get_room_by_uid(uid);
if (rp.get() == nullptr)
{
return;
}
uint64_t room_id = rp->get_room_id();
// 删除用户信息(不加锁,因为内部可能广播)
rp->handle_exit(uid);
// 加锁检查并删除房间
std::unique_lock<std::mutex> lock(_mutex);
if (rp->get_player_count() == 0)
{
// 删除用户映射
uint64_t uid1 = rp->get_white_id();
uint64_t uid2 = rp->get_black_id();
_users.erase(uid1);
_users.erase(uid2);
// 删除房间
_rooms.erase(room_id);
}
}
};
4.session模块
我们实现的在线五子棋对战项目还是在浏览器上来进行对战的,在进入游戏大厅之前都是使用http协议进行通信的。合同谈判又是无状态无连接的协议,使用为了保证维持用户的登录状态就需要session来保持登录状态。
4.1session类
session类需要包含:标识符(ssid)、用户id、登录状态、超时时间。包含的功能有:设置用户id、登录状态、超时时间,获取用户id、标识符、超时时间,还要能判断是否掉线。
4.1.1session类的实现
cpp
#include "room.hpp"
typedef enum
{
UNLOGIN,
ONLOGIN
} ss_statu;
class session
{
private:
uint64_t _ssid; // 标识符
uint64_t _uid; // 用户id
ss_statu _statu; // 登录状态
websocket_server::timer_ptr _tm; // 超时时间
public:
session(uint64_t sid) : _ssid(sid), _statu(UNLOGIN) { DLOG("session %p 被创建", this); }
~session() { DLOG("session %p 被销毁", this); }
void set_statu(ss_statu statu) { _statu = statu; }
void set_uid(uint64_t uid) { _uid = uid; }
uint64_t get_uid() { return _uid; }
uint64_t get_ssid() { return _ssid; }
bool is_login() { return _statu == ONLOGIN; }
void set_tm(const websocket_server::timer_ptr &tm) { _tm = tm; }
websocket_server::timer_ptr get_tm() { return _tm; }
};
4.2session管理类
房间管理类要包含:session分配器、互斥锁、session表、socket链接、WebSocket 服务端。要实现:创建session、添加session、通过ssid查询session信息、设置过期时间。
4.2.1session管理类的实现
cpp
#define SESSION_TIME 60000
#define SESSION_PERMANENT -1
using session_ptr = std::shared_ptr<session>;
class session_manage
{
private:
uint64_t _next_ssid;
std::mutex _mutex;
std::unordered_map<uint64_t, session_ptr> _session;
websocket_server *_server;
public:
session_manage(websocket_server *server) : _server(server), _next_ssid(1)
{
DLOG("session管理器创建成功");
}
~session_manage() { DLOG("session管理器即将销毁"); }
session_ptr create_session(uint64_t uid, ss_statu statu)
{
std::unique_lock<std::mutex> lock(_mutex);
session_ptr spr(new session(_next_ssid));
spr->set_statu(statu);
spr->set_uid(uid);
_session.insert(std::make_pair(_next_ssid,spr));
_next_ssid++;
return spr;
}
void append_session(session_ptr spr)
{
std::unique_lock<std::mutex> lock(_mutex);
_session.insert(std::make_pair(spr->get_ssid(),spr));
}
session_ptr get_session_by_ssid(uint64_t ssid)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _session.find(ssid);
if (it == _session.end())
{
DLOG("%lu 会话不存在", ssid);
return session_ptr();
}
return it->second;
}
void set_session_time(uint64_t ssid, int ms)
{
session_ptr spr = get_session_by_ssid(ssid);
if(spr.get() == nullptr)
{
return;
}
websocket_server::timer_ptr tp = spr->get_tm();
if(tp.get()==nullptr && ms == SESSION_PERMANENT )
{
//在session永久存在的情况下,使其永久存在
return;
}
else if(tp.get()==nullptr && ms != SESSION_PERMANENT)
{
//在session永久存在的情况下,设置过期时间
websocket_server::timer_ptr tmp_tp = _server->set_timer(ms,
std::bind(&session_manage::remove_session,this,ssid));
spr->set_tm(tmp_tp);
}
else if(tp.get()!=nullptr && ms == SESSION_PERMANENT)
{
//在session已经设置过期时间的情况下,设置其永久存在
tp->cancel();
spr->set_tm(websocket_server::timer_ptr());
_server->set_timer(0,std::bind(&session_manage::append_session,this,spr));
}
else if(tp.get()!=nullptr && ms != SESSION_PERMANENT)
{
//在session已经设置过期时间的情况下,重新设置其过期时间
tp->cancel();
spr->set_tm(websocket_server::timer_ptr());
_server->set_timer(0,std::bind(&session_manage::append_session,this,spr));
websocket_server::timer_ptr tmp_tp = _server->set_timer(ms,
std::bind(&session_manage::remove_session,this,ssid));
spr->set_tm(tmp_tp);
}
}
void remove_session(uint64_t ssid)
{
std::unique_lock<std::mutex> lock(_mutex);
_session.erase(ssid);
}
};
5.匹配模块
首先,五子棋对战的玩家匹配是根据自己的天梯分数进行匹配的,根据不同分段匹配到不同的玩家。所以需要很多的匹配队列,在这里我们的分段划分为:1.青铜、2.白银、3.黄金。
5.1匹配队列类
同一分段的匹配队列中同时又2个及其以上的人进行匹配时,出队这两个玩家,为他们创建房间,并将他们的信息放到房间中。
match_queue类需要包含:链表(_list)、互斥锁(_mutex)、条件变量(_cond)。包含的功能有:获取队列大小(size)、判断是否为空(empty)、等待条件变量(wait)、压入数据(push)、弹出数据(pop)、移除指定数据(remove)。
5.1.1匹配队列类的实现
cpp
#include "room.hpp"
#include <list>
template <class T>
class match_queue
{
private:
// 使用链表是因为匹配可以退出
std::list<T> _list;
// 加锁保证线程安全
std::mutex _mutex;
// 条件变量是队列的用户数量<2就阻塞
std::condition_variable _cond;
public:
match_queue() {}
~match_queue() {}
int size()
{
std::unique_lock<std::mutex> lock(_mutex);
return _list.size();
}
bool empty()
{
std::unique_lock<std::mutex> lock(_mutex);
return _list.empty();
}
void wait()
{
std::unique_lock<std::mutex> lock(_mutex);
_cond.wait(lock);
}
void push(const T &data)
{
std::unique_lock<std::mutex> lock(_mutex);
_list.push_back(data);
_cond.notify_all();
}
bool pop(T &data)
{
std::unique_lock<std::mutex> lock(_mutex);
if (_list.empty() == true)
{
return false;
}
data = _list.front();
_list.pop_front();
return true;
}
void remove(T &data)
{
std::unique_lock<std::mutex> lock(_mutex);
_list.remove(data);
}
};
5.2匹配队列管理类
match类需要包含:青铜匹配队列(_q_bronze)、白银匹配队列(_q_silver)、黄金匹配队列(_q_gold)、青铜队列线程(_th_bronze)、白银队列线程(_th_silver)、黄金队列线程(_th_gold)、用户表指针(_ut)、在线管理指针(_om)、房间管理指针(_rm)。
包含的功能有:处理匹配(handle_match)、线程入口(th_bronze_entry/th_silver_entry/th_gold_entry)、添加用户到匹配队列(add)、从匹配队列删除用户(del)。
5.2.1匹配队列管理类实现
cpp
class match
{
private:
// 青铜到黄金的匹配队列
match_queue<uint64_t> _q_bronze;
match_queue<uint64_t> _q_silver;
match_queue<uint64_t> _q_gold;
// 青铜到黄金的匹配队列对应的线程
std::thread _th_bronze;
std::thread _th_silver;
std::thread _th_gold;
user_table *_ut;
online_manage *_om;
room_manage *_rm;
private:
void handle_match(match_queue<uint64_t> &mq)
{
while (1)
{
// 等两个用户
while (mq.size() < 2)
{
mq.wait();
}
// 出队两个用户
uint64_t uid1, uid2;
bool ret = mq.pop(uid1);
if (ret == false)
{
continue;
}
ret = mq.pop(uid2);
if (ret == false)
{
this->add(uid1);
continue;
}
// 查看两个用户是否在线
websocket_server::connection_ptr conn1 = _om->get_conn_from_hall(uid1);
if (conn1.get() == nullptr)
{
this->add(uid2);
continue;
}
websocket_server::connection_ptr conn2 = _om->get_conn_from_hall(uid2);
if (conn2.get() == nullptr)
{
this->add(uid1);
continue;
}
// 创建房间
room_ptr rp = _rm->create_room(uid1, uid2);
if (rp.get() == nullptr)
{
DLOG("创建房间失败: %lu, %lu", uid1, uid2);
this->add(uid1);
this->add(uid2);
continue;
}
DLOG("匹配成功,创建房间: room_id=%lu, white=%lu, black=%lu",
rp->get_room_id(), uid1, uid2);
// 构建应答
Json::Value root;
root["optype"] = "match_success";
root["result"] = true;
std::string body;
json_util::Serialization(root, body);
conn1->send(body);
conn2->send(body);
}
}
// 线程入口
void th_bronze_entry() { handle_match(_q_bronze); }
void th_silver_entry() { handle_match(_q_silver); }
void th_gold_entry() { handle_match(_q_gold); }
public:
match(user_table *ut, online_manage *om, room_manage *rm)
: _ut(ut), _om(om), _rm(rm), _th_bronze(std::thread(&match::th_bronze_entry, this)), _th_silver(std::thread(&match::th_silver_entry, this)), _th_gold(std::thread(&match::th_gold_entry, this))
{
DLOG("游戏模块初始化完毕");
}
~match() {}
bool add(uint64_t uid)
{
Json::Value root;
bool ret = _ut->selete_by_id(uid, root);
if (ret == false)
{
DLOG("获取玩家%d失败", uid);
return false;
}
int score = root["score"].asInt();
if (score < 2000)
{
_q_bronze.push(uid);
}
else if (score >= 2000 && score < 3000)
{
_q_silver.push(uid);
}
else
{
_q_gold.push(uid);
}
return true;
}
bool del(uint64_t uid)
{
Json::Value root;
bool ret = _ut->selete_by_id(uid, root);
if (ret == false)
{
DLOG("获取玩家%d失败", uid);
return false;
}
int score = root["score"].asInt();
if (score < 2000)
{
_q_bronze.pop(uid);
}
else if (score >= 2000 && score < 3000)
{
_q_silver.pop(uid);
}
else
{
_q_gold.pop(uid);
}
return true;
}
};