|
@@ -0,0 +1,312 @@
|
|
|
|
+#ifndef __ROOM_HPP__
|
|
|
|
+#define __ROOM_HPP__
|
|
|
|
+#include "util.hpp"
|
|
|
|
+#include "db.hpp"
|
|
|
|
+#include "online.hpp"
|
|
|
|
+#include <vector>
|
|
|
|
+
|
|
|
|
+#define BOARD_ROW 15
|
|
|
|
+#define BOARD_COL 15
|
|
|
|
+#define CHESS_WHITE 1
|
|
|
|
+#define CHESS_BLACK 2
|
|
|
|
+
|
|
|
|
+typedef enum {
|
|
|
|
+ GAME_START,
|
|
|
|
+ GAME_OVER
|
|
|
|
+} room_status;
|
|
|
|
+
|
|
|
|
+/*游戏房间管理模块 -- 用于管理在游戏房间中产生的各种数据以及动作,同时也包括对多个游戏房间本身的管理*/
|
|
|
|
+/*游戏房间类*/
|
|
|
|
+class room {
|
|
|
|
+private:
|
|
|
|
+ /*check_win子函数,其中row/col表示下棋位置,row_off/col_off表示是否偏移*/
|
|
|
|
+ bool five_piece(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;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*判断是否有用户胜利并返回winner_id (0表示没有用户胜利,非0表示有)*/
|
|
|
|
+ uint64_t check_win(int chess_row, int chess_col, int cur_color) {
|
|
|
|
+ uint64_t winner_id = cur_color == CHESS_WHITE ? _white_user_id : _black_user_id;
|
|
|
|
+ // 横行方向:当前位置开始,行不变,列++/--
|
|
|
|
+ if(five_piece(chess_row, chess_col, 0, 1, cur_color)) return winner_id;
|
|
|
|
+ // 纵列方向:当前位置开始,行++/--,列不变
|
|
|
|
+ if(five_piece(chess_row, chess_col, 1, 0, cur_color)) return winner_id;
|
|
|
|
+ // 正斜方向:当前位置开始,行++列-- 以及 行--列++
|
|
|
|
+ if(five_piece(chess_row, chess_col, 1, -1, cur_color)) return winner_id;
|
|
|
|
+ // 反斜方向:当前位置开始,行++列++ 以及 行--列--
|
|
|
|
+ if(five_piece(chess_row, chess_col, 1, 1, cur_color)) return winner_id;
|
|
|
|
+ // 没有人获胜返回0
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*用户胜利或失败后更新用户数据库信息*/
|
|
|
|
+ void update_db_info(uint64_t winner_id, uint64_t loser_id) {
|
|
|
|
+ _tb_user->win(winner_id);
|
|
|
|
+ _tb_user->lose(loser_id);
|
|
|
|
+ }
|
|
|
|
+public:
|
|
|
|
+ room(uint64_t room_id, user_table *tb_user, online_manager *online_user)
|
|
|
|
+ : _room_id(room_id), _statu(GAME_START), _tb_user(tb_user), _online_user(online_user), _board(BOARD_ROW, std::vector<int>(BOARD_COL, 0))
|
|
|
|
+ {
|
|
|
|
+ LOG(DEBUG, "%d号房间创建成功", _room_id);
|
|
|
|
+ }
|
|
|
|
+ ~room() { LOG(DEBUG, "%d号房间已被销毁", _room_id); }
|
|
|
|
+
|
|
|
|
+ /*添加白棋用户*/
|
|
|
|
+ void add_white_user(uint64_t id) {
|
|
|
|
+ _white_user_id = id;
|
|
|
|
+ ++_player_count;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*添加黑棋用户*/
|
|
|
|
+ void add_black_user(uint64_t id) {
|
|
|
|
+ _black_user_id = id;
|
|
|
|
+ ++_player_count;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*处理玩家下棋动作并返回响应*/
|
|
|
|
+ Json::Value handler_chess(Json::Value &req) {
|
|
|
|
+ Json::Value resp = req;
|
|
|
|
+ // 判断白棋与黑棋用户是否在线,若一方不在线,另一方直接获胜
|
|
|
|
+ if(_online_user->is_in_game_room(_white_user_id) == false) {
|
|
|
|
+ resp["result"] = true;
|
|
|
|
+ resp["reason"] = "对方已掉线,游戏获胜"; // 在黑棋的视角,白棋是"对方"
|
|
|
|
+ resp["winner"] = (Json::UInt64)_black_user_id; // 白棋掉线,黑棋用户
|
|
|
|
+ }
|
|
|
|
+ if(_online_user->is_in_game_room(_black_user_id) == false) {
|
|
|
|
+ resp["result"] = true;
|
|
|
|
+ resp["reason"] = "对方已掉线,游戏胜利";
|
|
|
|
+ resp["winner"] = (Json::UInt64)_white_user_id;
|
|
|
|
+ }
|
|
|
|
+ // 获取下棋位置,判断位置是否合理并下棋
|
|
|
|
+ uint64_t cur_uid = req["uid"].asUInt64();
|
|
|
|
+ int chess_row = req["row"].asInt();
|
|
|
|
+ int chess_col = req["col"].asInt();
|
|
|
|
+ if(_board[chess_row][chess_col] != 0) {
|
|
|
|
+ resp["result"] = false;
|
|
|
|
+ resp["reason"] = "该位置已被占用";
|
|
|
|
+ return resp;
|
|
|
|
+ }
|
|
|
|
+ int cur_color = (cur_uid == _white_user_id ? CHESS_WHITE : CHESS_BLACK);
|
|
|
|
+ _board[chess_row][chess_col] = cur_color;
|
|
|
|
+ // 判断是否有玩家获胜(存在五星连珠的情况) 其中0表示没有玩家胜利,非0表示胜利的玩家id
|
|
|
|
+ uint64_t winner_id = check_win(chess_row, chess_col, cur_color);
|
|
|
|
+ resp["result"] = true;
|
|
|
|
+ resp["reason"] = "下棋成功";
|
|
|
|
+ resp["winner"] = (Json::UInt64)winner_id;
|
|
|
|
+ if(winner_id != 0) { resp["reason"] = "五星连珠,游戏胜利"; }
|
|
|
|
+ return resp;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*处理玩家聊天动作并返回响应*/
|
|
|
|
+ Json::Value handler_chat(Json::Value &req) {
|
|
|
|
+ Json::Value resp = req;
|
|
|
|
+ // 检查消息中是否包含敏感词
|
|
|
|
+ std::string msg = req["message"].asString();
|
|
|
|
+ size_t pos = msg.find("垃圾");
|
|
|
|
+ if(pos != std::string::npos) {
|
|
|
|
+ resp["result"] = false;
|
|
|
|
+ resp["reason"] = "消息中包含敏感词";
|
|
|
|
+ return resp;
|
|
|
|
+ }
|
|
|
|
+ resp["reslut"] = true;
|
|
|
|
+ return resp;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*处理玩家退出动作并返回响应*/
|
|
|
|
+ void handler_exit(uint64_t uid) {
|
|
|
|
+ // 如果玩家在下棋中,则对方直接获胜
|
|
|
|
+ if(_statu == GAME_START) {
|
|
|
|
+ Json::Value resp;
|
|
|
|
+ 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)(uid == _white_user_id ? _black_user_id : _white_user_id);
|
|
|
|
+ // 更新用户数据库信息与游戏房间的状态
|
|
|
|
+ uint64_t loser_id = uid;
|
|
|
|
+ uint64_t winner_id = loser_id == _white_user_id ? _black_user_id : _white_user_id;
|
|
|
|
+ update_db_info(winner_id, loser_id);
|
|
|
|
+ _statu = GAME_OVER;
|
|
|
|
+ // 将消息广播给房间其他玩家
|
|
|
|
+ broadcast(resp);
|
|
|
|
+ }
|
|
|
|
+ // 游戏结束正常退出直接更新玩家数量
|
|
|
|
+ --_player_count;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*总的动作处理函数,负责判断动作类型并调用对应的处理函数,得到处理响应后将其广播给房间中其他用户*/
|
|
|
|
+ /*注意:玩家退出动作属于玩家断开连接后调用的操作,不属于handler的一种*/
|
|
|
|
+ void handler(Json::Value &req) {
|
|
|
|
+ Json::Value resp;
|
|
|
|
+ // 判断房间号是否匹配
|
|
|
|
+ if(_room_id != req["room_id"].asUInt64()) {
|
|
|
|
+ resp["optype"] = req["optype"].asString();
|
|
|
|
+ resp["result"] = false;
|
|
|
|
+ resp["reason"] = "房间号不匹配";
|
|
|
|
+ broadcast(resp);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ // 根据请求类型调用不同的处理函数
|
|
|
|
+ std::string type = req["optype"].asString();
|
|
|
|
+ if(type == "put_chess") {
|
|
|
|
+ resp = handler_chess(req);
|
|
|
|
+ // 判断是否有玩家获胜,如果有则需要更新用户数据库信息与游戏房间的状态
|
|
|
|
+ if(resp["winner"].asUInt64() != 0) {
|
|
|
|
+ uint64_t winner_id = resp["winner"].asUInt64();
|
|
|
|
+ uint64_t loser_id = (winner_id == _white_user_id ? _black_user_id : _white_user_id);
|
|
|
|
+ update_db_info(winner_id, loser_id);
|
|
|
|
+ _statu = GAME_OVER;
|
|
|
|
+ }
|
|
|
|
+ } else if(type == "chat") {
|
|
|
|
+ resp = handler_chat(req);
|
|
|
|
+ } else {
|
|
|
|
+ resp["optype"] = req["optype"].asString();
|
|
|
|
+ resp["result"] = false;
|
|
|
|
+ resp["reason"] = "位置请求类型";
|
|
|
|
+ }
|
|
|
|
+ // 将消息广播给房间中的其他玩家
|
|
|
|
+ broadcast(resp);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*将动作响应广播给房间中的其他玩家*/
|
|
|
|
+ void broadcast(Json::Value &resp) {
|
|
|
|
+ // 将Json响应进行序列化
|
|
|
|
+ std::string body;
|
|
|
|
+ json_util::serialize(resp, body);
|
|
|
|
+ // 获取房间中的所有玩家的通信连接
|
|
|
|
+ wsserver_t::connection_ptr conn_white = _online_user->get_conn_from_room(_white_user_id);
|
|
|
|
+ wsserver_t::connection_ptr conn_black = _online_user->get_conn_from_room(_black_user_id);
|
|
|
|
+ // 如果玩家连接没有断开,则将消息广播给他
|
|
|
|
+ if(conn_white.get() != nullptr) {
|
|
|
|
+ conn_white->send(body);
|
|
|
|
+ }
|
|
|
|
+ if(conn_black.get() != nullptr) {
|
|
|
|
+ conn_black->send(body);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+public:
|
|
|
|
+ // 将部分成员变量设为public,供外部类访问
|
|
|
|
+ uint64_t _room_id; // 房间ID
|
|
|
|
+ room_status _statu; // 房间状态
|
|
|
|
+ int _player_count; // 玩家数量
|
|
|
|
+ uint64_t _white_user_id; // 白棋玩家ID
|
|
|
|
+ uint64_t _black_user_id; // 黑棋玩家ID
|
|
|
|
+private:
|
|
|
|
+ user_table *_tb_user; // 管理玩家数据的句柄
|
|
|
|
+ online_manager *_online_user; // 管理玩家在线状态的句柄
|
|
|
|
+ std::vector<std::vector<int>> _board; // 二维棋盘
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+/*管理房间数据的智能指针*/
|
|
|
|
+using room_ptr = std::shared_ptr<room>;
|
|
|
|
+
|
|
|
|
+/*游戏房间管理类*/
|
|
|
|
+class room_manager {
|
|
|
|
+public:
|
|
|
|
+ room_manager(user_table *tb_user, online_manager *online_user)
|
|
|
|
+ : _next_rid(1), _tb_user(tb_user), _online_user(online_user) {
|
|
|
|
+ LOG(DEBUG, "游戏房间管理模块初始化成功");
|
|
|
|
+ }
|
|
|
|
+ ~room_manager() { LOG(NORMAL, "游戏房间管理模块已被销毁"); }
|
|
|
|
+
|
|
|
|
+ /*为两个玩家创建房间,并返回房间信息*/
|
|
|
|
+ room_ptr create_room(uint64_t uid1, uint64_t uid2) {
|
|
|
|
+ // 判断两个玩家是否都处于游戏大厅中
|
|
|
|
+ if(_online_user->is_in_game_hall(uid1) == false || _online_user->is_in_game_hall(uid2) == false) {
|
|
|
|
+ LOG(DEBUG, "玩家不在游戏大厅中,匹配失败");
|
|
|
|
+ return room_ptr();
|
|
|
|
+ }
|
|
|
|
+ // 创建游戏房间,将用户信息添加到房间中
|
|
|
|
+ std::unique_lock<std::mutex> lock(_mutex);
|
|
|
|
+ room_ptr rp(new room(_next_rid, _tb_user, _online_user));
|
|
|
|
+ rp->add_white_user(uid1);
|
|
|
|
+ rp->add_black_user(uid2);
|
|
|
|
+ // 将游戏房间管理起来(建立房间id与房间信息以及玩家id与房间id的关联关系)
|
|
|
|
+ _rooms[_next_rid] = rp;
|
|
|
|
+ _users[uid1] = _next_rid;
|
|
|
|
+ _users[uid2] = _next_rid;
|
|
|
|
+ // 更新下一个房间的房间id
|
|
|
|
+ ++_next_rid;
|
|
|
|
+ // 返回房间信息
|
|
|
|
+ return rp;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*通过房间id获取房间信息*/
|
|
|
|
+ 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 _rooms[rid];
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*通过用户id获取房间信息*/
|
|
|
|
+ room_ptr get_room_by_uid(uint64_t uid) {
|
|
|
|
+ std::unique_lock<std::mutex> lock(_mutex);
|
|
|
|
+ // 获取房间id
|
|
|
|
+ auto it1 = _users.find(uid);
|
|
|
|
+ if(it1 == _users.end()) return room_ptr();
|
|
|
|
+ uint64_t rid = _users[uid];
|
|
|
|
+ // 获取房间信息(这里不能直接调用get_room_by_rid,会造成死锁)
|
|
|
|
+ auto it2 = _rooms.find(rid);
|
|
|
|
+ if(it2 == _rooms.end()) return room_ptr();
|
|
|
|
+ return _rooms[rid];
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*通过房间id销毁房间*/
|
|
|
|
+ void remove_room(uint64_t rid) {
|
|
|
|
+ // 通过房间id获取房间信息
|
|
|
|
+ room_ptr rp = get_room_by_rid(rid);
|
|
|
|
+ if(rp.get() == nullptr) return;
|
|
|
|
+ // 通过房间信息获取房间中的玩家
|
|
|
|
+ uint64_t white_user_id = rp->_white_user_id;
|
|
|
|
+ uint64_t black_user_id = rp->_black_user_id;
|
|
|
|
+ // 移除房间管理中的玩家信息
|
|
|
|
+ std::unique_lock<std::mutex> lock(_mutex);
|
|
|
|
+ _users.erase(white_user_id);
|
|
|
|
+ _users.erase(black_user_id);
|
|
|
|
+ // 移除房间管理信息 -- 移除房间对应的shared_ptr(room_ptr)
|
|
|
|
+ _rooms.erase(rid);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*删除房间中的指定用户,若房间中没有用户则销毁房间(用户断开websocket连接时调用)*/
|
|
|
|
+ void remove_room_user(uint64_t uid) {
|
|
|
|
+ // 通过玩家id获取房间信息
|
|
|
|
+ room_ptr rp = get_room_by_uid(uid);
|
|
|
|
+ if(rp.get() == nullptr) return;
|
|
|
|
+ // 玩家退出
|
|
|
|
+ rp->handler_exit(uid);
|
|
|
|
+ // 如果房间中没有玩家了,则移除房间
|
|
|
|
+ if(rp->_player_count == 0) remove_room(rp->_room_id);
|
|
|
|
+ }
|
|
|
|
+private:
|
|
|
|
+ uint64_t _next_rid; //房间ID分配计数器
|
|
|
|
+ std::mutex _mutex;
|
|
|
|
+ user_table *_tb_user; // 管理玩家数据的句柄
|
|
|
|
+ online_manager *_online_user; // 管理玩家在线状态的句柄
|
|
|
|
+ std::unordered_map<uint64_t, room_ptr> _rooms; // 建立房间id与房间信息的关联关系
|
|
|
|
+ std::unordered_map<uint64_t, uint64_t> _users; // 建立用户id与房间id的关联关系
|
|
|
|
+};
|
|
|
|
+#endif
|