aboutsummaryrefslogtreecommitdiff
path: root/src/core/session.cc
diff options
context:
space:
mode:
authorMistivia <i@mistivia.com>2025-01-15 19:12:12 +0800
committerMistivia <i@mistivia.com>2025-01-15 19:12:46 +0800
commit4da382953c830a61ae24ccbe95c4241db8788269 (patch)
tree31f6d184273274e2660516d1ac1146b5661f20b6 /src/core/session.cc
restore
Diffstat (limited to 'src/core/session.cc')
-rw-r--r--src/core/session.cc341
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