diff options
Diffstat (limited to 'src/core')
| -rw-r--r-- | src/core/card.cc | 52 | ||||
| -rw-r--r-- | src/core/card.h | 27 | ||||
| -rw-r--r-- | src/core/session.cc | 341 | ||||
| -rw-r--r-- | src/core/session.h | 58 | ||||
| -rw-r--r-- | src/core/user.h | 10 | ||||
| -rw-r--r-- | src/core/user_state.h | 19 |
6 files changed, 507 insertions, 0 deletions
diff --git a/src/core/card.cc b/src/core/card.cc new file mode 100644 index 0000000..1a4c63d --- /dev/null +++ b/src/core/card.cc @@ -0,0 +1,52 @@ +#include "core/card.h" + +#include <sstream> +#include <fstream> + +#include "util/common.h" + +static std::map<std::string, Card> card_db; + +void init_card_db(){ + auto fs = std::ifstream{"./resource/mtgzhs"}; + std::string line; + while(std::getline(fs, line, '\n')) { + Card card; + card.name = line; + std::getline(fs, card.zhsname); + std::getline(fs, card.zhstext); + card_db[card.name] = card; + } +} + +Arc<Card> get_card(std::string name) { + auto card = make_shared<Card>(); + card->name = name; + card->image_url = "https://api.scryfall.com/cards/named?format=image&version=normal&exact=" + url_encode(name); + if (card_db.find(name) != card_db.end()) { + card->zhsname = card_db[name].zhsname; + card->zhstext = card_db[name].zhstext; + card->image_url = "https://api.scryfall.com/cards/named?format=image&version=normal&fuzzy=" + url_encode(card->zhsname); + str_replace(card->zhstext, "\001", "\n"); + } + return card; +} + +std::vector<Arc<Card>> parse_cardlist(const std::string &in_str) { + std::string str; + for (auto c : in_str) { + if (c != '\r') { + str += c; + } + } + + auto result = std::vector<Arc<Card>>{}; + auto ss = std::stringstream{str}; + + for (std::string line; std::getline(ss, line, '\n');) { + if (!line.empty()) { + result.push_back(get_card(line)); + } + } + return result; +}
\ No newline at end of file diff --git a/src/core/card.h b/src/core/card.h new file mode 100644 index 0000000..afd8198 --- /dev/null +++ b/src/core/card.h @@ -0,0 +1,27 @@ +#ifndef HIVE_MIND_CORE_CARD_H_ +#define HIVE_MIND_CORE_CARD_H_ + +#include <vector> +#include <map> + +#include "util/common.h" + +constexpr int PACK_NUM = 3; +constexpr int CARD_NUM_PER_PACK = 15; + +struct Card { + std::string name; + std::string text; + std::string zhsname; + std::string zhstext; + std::string image_url; +}; + +using CardPack = std::vector<Arc<Card>>; + +void init_card_db(); +Arc<Card> get_card(std::string name); + +std::vector<Arc<Card>> parse_cardlist(const std::string &s); + +#endif 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 diff --git a/src/core/session.h b/src/core/session.h new file mode 100644 index 0000000..2bb92a2 --- /dev/null +++ b/src/core/session.h @@ -0,0 +1,58 @@ +#ifndef HIVE_MIND_CORE_SESSION_H_ +#define HIVE_MIND_CORE_SESSION_H_ + +#include <chrono> +#include <map> +#include <mutex> +#include <set> +#include <vector> + +#include <mstch/mstch.hpp> + +#include "core/card.h" +#include "core/user.h" +#include "core/user_state.h" +#include "util/common.h" + +enum class SessionState { + pending, ongoing, finished +}; + +class Session { + public: + static Arc<Session> create_session(Arc<User> creator, std::vector<Arc<Card>> &card_list, int player_num); + static Arc<Session> get_session(const std::string &session_id); + + void add_user(Arc<User> user); + void kick_user(Arc<User> kicking_user, Arc<User> kicked_user); + void select_card(Arc<User> user, Arc<Card> card); + void start(Arc<User> starting_user); + mstch::map get_user_session_view(Arc<User> user); + std::string get_card_str(Arc<User> user); + int64_t get_user_timestamp(Arc<User> user); + const std::string &get_session_id() {return session_id;} + + private: + Arc<UserState> get_user_state(Arc<User> user); + Arc<UserState> get_creator(); + + void sanitize_state(); + bool need_new_pack(); + bool should_finish(); + + void validate_user(Arc<User> user); + void validate_user_is_new(Arc<User> user); + void validate_creator(Arc<User> user); + void validate_session_state(SessionState state); + + std::string session_id; + int player_num; + SessionState session_state = SessionState::pending; + std::vector<Arc<UserState>> users; + std::queue<Arc<Card>> remained_cards; + std::chrono::time_point<std::chrono::system_clock> time_created; + int round_num = 0; + std::mutex lock; +}; + +#endif diff --git a/src/core/user.h b/src/core/user.h new file mode 100644 index 0000000..1feaf2f --- /dev/null +++ b/src/core/user.h @@ -0,0 +1,10 @@ +#ifndef HIVE_MIND_CORE_USER_H_ +#define HIVE_MIND_CORE_USER_H_ + + +struct User { + std::string user_id; + std::string nick; +}; + +#endif diff --git a/src/core/user_state.h b/src/core/user_state.h new file mode 100644 index 0000000..c8020d8 --- /dev/null +++ b/src/core/user_state.h @@ -0,0 +1,19 @@ +#ifndef HIVE_MIND_CORE_USER_STATE_H_ +#define HIVE_MIND_CORE_USER_STATE_H_ + +#include <chrono> +#include <queue> +#include <set> + +#include "util/common.h" + +struct UserState { + bool is_creator = false; + Arc<User> user; + std::string error_msg; + std::queue<Arc<CardPack>> card_pack_queue; + std::vector<Arc<Card>> selected_card; + std::chrono::time_point<std::chrono::system_clock> time_last_updated; +}; + +#endif |
