aboutsummaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'src/core')
-rw-r--r--src/core/card.cc52
-rw-r--r--src/core/card.h27
-rw-r--r--src/core/session.cc341
-rw-r--r--src/core/session.h58
-rw-r--r--src/core/user.h10
-rw-r--r--src/core/user_state.h19
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