#ifndef __ROOM_HPP__ #define __ROOM_HPP__ #include "util.hpp" #include "db.hpp" #include "online.hpp" #include #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(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> _board; // 二维棋盘 }; /*管理房间数据的智能指针*/ using room_ptr = std::shared_ptr; /*游戏房间管理类*/ 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 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 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 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 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 _rooms; // 建立房间id与房间信息的关联关系 std::unordered_map _users; // 建立用户id与房间id的关联关系 }; #endif