diff options
Diffstat (limited to 'src/core/session.cc')
| -rw-r--r-- | src/core/session.cc | 341 |
1 files changed, 341 insertions, 0 deletions
diff --git a/src/core/session.cc b/src/core/session.cc new file mode 100644 index 0000000..2b10e9f --- /dev/null +++ b/src/core/session.cc @@ -0,0 +1,341 @@ +#include "core/session.h" + +#include <exception> +#include <random> + +static std::map<std::string, Arc<Session>> session_map; +static std::mutex session_map_lock; + +std::string to_string(SessionState state) { + switch(state) { + case SessionState::pending: return "未开始"; + case SessionState::ongoing: return "进行中"; + default: return "已经结束"; + } + return ""; +}; + +Arc<Session> Session::create_session(Arc<User> creator, std::vector<Arc<Card>> &card_list, int player_num) { + ulock _l{session_map_lock}; + + if(player_num < 1) { + throw std::runtime_error{"play num must > 1"}; + } + if (card_list.size() < static_cast<size_t>(PACK_NUM * CARD_NUM_PER_PACK * player_num)) { + throw std::runtime_error{"insufficient cards"}; + } + + auto session = make_shared<Session>(); + session->player_num = player_num; + session->session_id = gen_random(); + session->time_created = std::chrono::system_clock::now(); + + auto user_state = make_shared<UserState>(); + user_state->is_creator = true; + user_state->user = creator; + user_state->user->nick = "房主"; + user_state->time_last_updated = std::chrono::system_clock::now(); + session->users.push_back(user_state); + std::random_device rd; + std::mt19937 g(rd()); + std::shuffle(card_list.begin(), card_list.end(), g); + for (auto &card : card_list){ + session->remained_cards.push(card); + } + + session_map[session->session_id] = session; + return session; +} + +Arc<Session> Session::get_session(const std::string &session_id) { + ulock _l{session_map_lock}; + + if (session_map.find(session_id) == session_map.end()) { + throw std::runtime_error{"cannot find session"}; + } + return session_map[session_id]; +} + +void Session::validate_user(Arc<User> user) { + for (auto &user_state : this->users) { + if (user_state->user->user_id == user->user_id) { + return; + } + } + throw std::runtime_error{"user do not exist"}; +} + +void Session::validate_user_is_new(Arc<User> user) { + for (auto &user_state : this->users) { + if (user_state->user->user_id == user->user_id) { + throw std::runtime_error{"user already exist"}; + } + } + return; +} + +void Session::validate_creator(Arc<User> user) { + for (auto &user_state : this->users) { + if (user_state->user->user_id == user->user_id && user_state->is_creator) { + return; + } + } + throw std::runtime_error{"not creator"}; +} + +void Session::validate_session_state(SessionState state) { + if (this->session_state != state) { + throw std::runtime_error{"session is not valid"}; + } + return; +} + +void Session::add_user(Arc<User> user) { + ulock _l{this->lock}; + validate_session_state(SessionState::pending); + validate_user_is_new(user); + if (user->nick.empty()) { + throw std::runtime_error{"invalid nick"}; + } + if (users.size() >= static_cast<size_t>(player_num)) { + throw std::runtime_error{"room is full"}; + } + + auto user_state = make_shared<UserState>(); + user_state->user = user; + user_state->time_last_updated = std::chrono::system_clock::now(); + users.push_back(user_state); + + get_creator()->time_last_updated = std::chrono::system_clock::now(); +} + +void Session::kick_user(Arc<User> kicking_user, Arc<User> kicked_user) { + ulock _l{this->lock}; + validate_session_state(SessionState::pending); + validate_creator(kicking_user); + validate_user(kicked_user); + + for (auto it = users.begin(); it < users.end(); it++) { + if ((*it)->user->user_id == kicked_user->user_id && !(*it)->is_creator) { + users.erase(it); + } + } +} + +void Session::start(Arc<User> starting_user) { + ulock _l{this->lock}; + validate_session_state(SessionState::pending); + validate_creator(starting_user); + + if (users.size() != static_cast<size_t>(player_num)) { + throw std::runtime_error{"player num wrong"}; + } + + this->session_state = SessionState::ongoing; + for (auto &user_state : users) { + user_state->time_last_updated = std::chrono::system_clock::now(); + } + sanitize_state(); +} + +void Session::select_card(Arc<User> user, Arc<Card> card) { + ulock _l{this->lock}; + validate_session_state(SessionState::ongoing); + validate_user(user); + + // find the user + Arc<UserState> user_state; + size_t user_idx; + for (size_t i = 0; i < users.size(); i++) { + if (users[i]->user->user_id == user->user_id) { + user_state = users[i]; + user_idx = i; + } + } + if (user_state->card_pack_queue.empty()) { + throw std::runtime_error{"no card to select"}; + } + // find the card + auto pack = user_state->card_pack_queue.front(); + for (auto it = pack->begin(); it < pack->end(); it++) { + if ((*it)->name == card->name) { + // get the card and transfer pack to next player + user_state->selected_card.push_back(card); + pack->erase(it); + if (!pack->empty()) { + users[(user_idx + 1) % player_num]->card_pack_queue.push(pack); + if (users[(user_idx + 1) % player_num]->card_pack_queue.size() == 1) { + users[(user_idx + 1) % player_num]->time_last_updated = std::chrono::system_clock::now(); + } + } + user_state->card_pack_queue.pop(); + sanitize_state(); + return; + } + } + throw std::runtime_error{"invalid card"}; +} + +int64_t Session::get_user_timestamp(Arc<User> user) { + ulock _l{this->lock}; + validate_user(user); + + auto chrono_stamp = get_user_state(user)->time_last_updated; + + return std::chrono::duration_cast<std::chrono::milliseconds>(chrono_stamp.time_since_epoch()).count(); +} + +Arc<UserState> Session::get_user_state(Arc<User> user) { + Arc<UserState> cur_user; + for (auto &user_state : this->users) { + if (user_state->user->user_id == user->user_id) { + cur_user = user_state; + } + } + return cur_user; +} + +Arc<UserState> Session::get_creator() { + Arc<UserState> cur_user; + for (auto &user_state : this->users) { + if (user_state->is_creator) { + cur_user = user_state; + } + } + return cur_user; +} + + +std::string Session::get_card_str(Arc<User> user) { + ulock _l{this->lock}; + validate_user(user); + + auto cur_user = get_user_state(user); + std::string card_str; + for (auto &card : cur_user->selected_card) { + card_str += card->name; + card_str += "\n"; + } + return card_str; +} + +bool Session::need_new_pack() { + for (auto &user_state : users) { + if (!user_state->card_pack_queue.empty()) { + return false; + } + } + round_num++; + if (round_num > PACK_NUM) { + return false; + } + return true; +} + +bool Session::should_finish() { + for (auto &user_state : users) { + if (!user_state->card_pack_queue.empty()) { + return false; + } + } + if (round_num >= PACK_NUM) { + return true; + } + return false; +} + +Arc<CardPack> create_pack(std::queue<Arc<Card>> &cards) { + auto pack = make_shared<CardPack>(); + for (int i = 0; i < CARD_NUM_PER_PACK; i++) { + pack->push_back(cards.front()); + cards.pop(); + } + return pack; +} + +void Session::sanitize_state() { + if (need_new_pack()) { + for (auto &user_state : users) { + user_state->card_pack_queue.push(create_pack(remained_cards)); + user_state->time_last_updated = std::chrono::system_clock::now(); + } + } + if (should_finish()) { + for (auto &user_state : users) { + user_state->time_last_updated = std::chrono::system_clock::now(); + } + session_state = SessionState::finished; + } +} + +mstch::map card_view(Card &card, std::string &session_id) { + auto card_info = mstch::map{ + {"sessionid", session_id}, + {"name", html_encode(card.name)}, + {"imgurl", card.image_url}, + {"nameurl", url_encode(card.name)}}; + std::string zhstext; + if (!card.zhsname.empty()) { + zhstext = "【" + card.zhsname + "】"; + } + if (!card.zhstext.empty()) { + zhstext += "\r"; + zhstext += card.zhstext; + } + zhstext = html_encode(zhstext); + str_replace(zhstext, "\n", "<br>"); + str_replace(zhstext, "\r", "</p><p>"); + card_info["zhstext"] = zhstext; + if (zhstext.empty()) { + card_info["stab"] = std::string("<div></div>"); + } + return card_info; +} + +mstch::map Session::get_user_session_view(Arc<User> user) { + ulock _l{this->lock}; + mstch::map view{ + {"download", mstch::array{ + mstch::map{{"sessionid", session_id}}, + }} + }; + mstch::array players{}; + Arc<UserState> cur_user; + for (auto &user_state : this->users) { + if (user_state->user->user_id == user->user_id) { + cur_user = user_state; + if (user_state->is_creator && session_state == SessionState::pending) { + view["start"] = mstch::array{mstch::map{{"sessionid", session_id}}}; + } + } + players.push_back(mstch::map{ + {"nick", user_state->user->nick}, + {"packnum", std::to_string(user_state->card_pack_queue.size())}}); + } + view["players"] = players; + view["state"] = to_string(session_state); + if (cur_user == nullptr && session_state == SessionState::pending) { + view["join"] = mstch::array{mstch::map{{"sessionid", session_id}}}; + } + if (cur_user == nullptr) { + return view; + } + + mstch::array cardstopick{}; + mstch::array cardspicked{}; + if (!cur_user->card_pack_queue.empty()) { + auto pack = cur_user->card_pack_queue.front(); + for (auto &card : *pack) { + cardstopick.push_back(card_view(*card, session_id)); + } + } + if (!cur_user->selected_card.empty()) { + for (auto &card : cur_user->selected_card) { + cardspicked.push_back(card_view(*card, session_id)); + } + } + view["cardstopick"] = cardstopick; + view["cardspicked"] = cardspicked; + + return view; +}
\ No newline at end of file |
