#include "core/session.h"
#include
#include
static std::map> 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::create_session(Arc creator, std::vector> &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(PACK_NUM * CARD_NUM_PER_PACK * player_num)) {
throw std::runtime_error{"insufficient cards"};
}
auto session = make_shared();
session->player_num = player_num;
session->session_id = gen_random();
session->time_created = std::chrono::system_clock::now();
auto user_state = make_shared();
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::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) {
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) {
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) {
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) {
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(player_num)) {
throw std::runtime_error{"room is full"};
}
auto user_state = make_shared();
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 kicking_user, Arc 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 starting_user) {
ulock _l{this->lock};
validate_session_state(SessionState::pending);
validate_creator(starting_user);
if (users.size() != static_cast(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, Arc card) {
ulock _l{this->lock};
validate_session_state(SessionState::ongoing);
validate_user(user);
// find the user
Arc 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) {
ulock _l{this->lock};
validate_user(user);
auto chrono_stamp = get_user_state(user)->time_last_updated;
return std::chrono::duration_cast(chrono_stamp.time_since_epoch()).count();
}
Arc Session::get_user_state(Arc user) {
Arc 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 Session::get_creator() {
Arc 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) {
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 create_pack(std::queue> &cards) {
auto pack = make_shared();
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", "
");
str_replace(zhstext, "\r", "
");
card_info["zhstext"] = zhstext;
if (zhstext.empty()) {
card_info["stab"] = std::string("
");
}
return card_info;
}
mstch::map Session::get_user_session_view(Arc user) {
ulock _l{this->lock};
mstch::map view{
{"download", mstch::array{
mstch::map{{"sessionid", session_id}},
}}
};
mstch::array players{};
Arc 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;
}