Cavoke  1.1.0
A Platform for creating and hosting multiplayer turn-based board games
Loading...
Searching...
No Matches
game_session.cpp
1#include "game_session.h"
2
3namespace cavoke::server::model {
4using namespace drogon::orm;
5
6game_session_error::game_session_error(std::string message)
7 : cavoke_base_exception(std::move(message),
8 InvalidClientInput,
9 "cavoke/sessions") {
10}
11
17void GameSessionAccessObject::add_user(const std::string &user_id,
18 std::optional<int> player_id) {
19 auto session_snapshot = get_snapshot();
20 if (get_actual_status().getValueOfStatus() != NOT_STARTED) {
21 throw game_session_error("session has already started");
22 }
23
24 if (0 != default_mp_players.count(
25 Criteria(drogon_model::cavoke_orm::Players::Cols::_session_id,
26 CompareOperator::EQ, id) &&
27 Criteria(drogon_model::cavoke_orm::Players::Cols::_user_id,
28 CompareOperator::EQ, user_id))) {
29 return;
30 }
31 // get player id for user
32 int pos;
33 if (player_id.has_value()) {
34 pos = player_id.value();
35 // check role's validity
36 if (!(0 <= pos && pos < m_game_config.players_num)) {
37 throw game_session_error("no such role " + std::to_string(pos));
38 }
39 // NOTE: same player_ids are not possible thanks to a constraint in sql
40 // schema
41 } else {
42 std::set<int> possible_positions;
43 for (int candidate_pos = 0; candidate_pos < m_game_config.players_num;
44 ++candidate_pos) {
45 possible_positions.insert(candidate_pos);
46 }
47 for (int occupied_pos : get_occupied_positions()) {
48 possible_positions.erase(occupied_pos);
49 }
50 if (possible_positions.empty()) {
51 throw game_session_error("maximum number of players reached");
52 }
53 pos = *possible_positions.begin();
54 }
55 try {
56 drogon_model::cavoke_orm::Players new_player;
57 new_player.setUserId(user_id);
58 new_player.setSessionId(id);
59 new_player.setScoreToNull();
60 new_player.setPlayerId(pos);
61 new_player.setPlayerstate("");
62 default_mp_players.insert(new_player);
63 LOG_DEBUG << "Added: " << user_id << " with player_id=" << pos << " to "
64 << id;
65 } catch (const DrogonDbException &) {
66 throw game_session_error("position is already occupied");
67 }
68}
69
74int GameSessionAccessObject::get_player_id(const std::string &user_id) const {
75 try {
76 return default_mp_players
77 .findOne(
78 Criteria(drogon_model::cavoke_orm::Players::Cols::_session_id,
79 CompareOperator::EQ, id) &&
80 Criteria(drogon_model::cavoke_orm::Players::Cols::_user_id,
81 CompareOperator::EQ, user_id))
82 .getValueOfPlayerId();
83 } catch (const UnexpectedRows &) {
84 throw game_session_error("user not in session");
85 }
86}
87
92std::string GameSessionAccessObject::get_user_id(int player_id) const {
93 try {
94 return default_mp_players
95 .findOne(
96 Criteria(drogon_model::cavoke_orm::Players::Cols::_session_id,
97 CompareOperator::EQ, id) &&
98 Criteria(drogon_model::cavoke_orm::Players::Cols::_player_id,
99 CompareOperator::EQ, player_id))
100 .getValueOfUserId();
101 } catch (const UnexpectedRows &) {
102 throw game_session_error("user not in session");
103 }
104}
105
108GameSessionAccessObject::get_session_info() const {
109 auto session_snapshot = get_snapshot();
110 return make_session_info(session_snapshot, get_actual_status(),
111 get_players());
112}
113
114std::vector<int> GameSessionAccessObject::get_occupied_positions() const {
115 std::vector<int> result;
116 for (const auto &player : default_mp_players.findBy(
117 Criteria(drogon_model::cavoke_orm::Players::Cols::_session_id,
118 CompareOperator::EQ, id))) {
119 // cppcheck-suppress useStlAlgorithm
120 result.push_back(player.getValueOfPlayerId());
121 }
122 return result;
123}
124
125// std::optional<json> &GameSessionAccessObject::get_game_settings() const {
126// auto session = get_snapshot();
127// auto settings_ptr = session.getGameSettings();
128// return *settings_ptr;
129// }
130
131std::vector<GameSessionAccessObject::PlayerInfo>
132GameSessionAccessObject::get_players() const {
133 auto players = default_mp_players.findBy(
134 Criteria(drogon_model::cavoke_orm::Players::Cols::_session_id,
135 CompareOperator::EQ, id));
136 auto users_in_session_result = drogon::app().getDbClient()->execSqlSync(
137 "select u.*, player_id from players join users u on players.user_id = "
138 "u.id and players.session_id = $1",
139 id);
140 std::map<int, drogon_model::cavoke_orm::Users> users;
141 for (auto &r : users_in_session_result) {
142 int player_id = r["player_id"].as<int>();
143 users[player_id] = drogon_model::cavoke_orm::Users(r);
144 }
145 std::vector<PlayerInfo> result;
146 std::transform(
147 players.begin(), players.end(), std::back_inserter(result),
148 [&users](const drogon_model::cavoke_orm::Players &player) {
149 int player_id = player.getValueOfPlayerId();
150 return PlayerInfo{UserInfo::from_user(users[player_id]), player_id};
151 });
152 return result;
153}
154
155void GameSessionAccessObject::start(const json &game_settings) {
156 auto session = default_mp_sessions.findOne(
157 Criteria(drogon_model::cavoke_orm::Sessions::Cols::_id,
158 CompareOperator::EQ, id));
159 // FIXME: not atomic, transactions perhaps or some other blocking
160 // sql-mechanism?
161 if (get_actual_status().getValueOfStatus() != NOT_STARTED) {
162 throw game_session_error("session has already started");
163 }
164 session.setGameSettings(game_settings.dump());
165 set_status(RUNNING);
166 default_mp_sessions.update(session);
167}
168
169bool GameSessionAccessObject::is_player(const std::string &user_id) const {
170 try {
171 [[maybe_unused]] int tmp = get_player_id(user_id);
172 return true;
173 } catch (const game_session_error &) {
174 return false;
175 }
176}
177
178std::string GameSessionAccessObject::get_host() const {
179 return get_snapshot().getValueOfHostId();
180}
181
182bool GameSessionAccessObject::is_host(const std::string &user_id) const {
183 return get_host() == user_id;
184}
185
186void GameSessionAccessObject::update_status(bool is_terminal) {
187 set_status(is_terminal ? FINISHED : RUNNING);
188}
189
190drogon_model::cavoke_orm::Sessions GameSessionAccessObject::get_snapshot()
191 const {
192 return default_mp_sessions.findOne(
193 Criteria(drogon_model::cavoke_orm::Sessions::Cols::_id,
194 CompareOperator::EQ, id));
195}
196drogon_model::cavoke_orm::Sessions GameSessionAccessObject::get_snapshot(
197 const std::string &session_id) {
198 auto mp_sessions = MAPPER_FOR(drogon_model::cavoke_orm::Sessions);
199 return mp_sessions.findOne(
200 Criteria(drogon_model::cavoke_orm::Sessions::Cols::_id,
201 drogon::orm::CompareOperator::EQ, session_id));
202}
203
205GameSessionAccessObject::make_session_info(
206 const drogon_model::cavoke_orm::Sessions &session,
207 const drogon_model::cavoke_orm::Statuses &status,
208 std::vector<PlayerInfo> players) {
209 return {session.getValueOfId(), session.getValueOfGameId(),
210 session.getValueOfHostId(),
211 static_cast<SessionStatus>(status.getValueOfStatus()),
212 std::move(players)};
213}
214
215void GameSessionAccessObject::remove_user(const std::string &user_id) {
216 if (get_actual_status().getValueOfStatus() != NOT_STARTED) {
217 throw game_session_error("session has already started");
218 }
219
220 default_mp_players.deleteBy(
221 Criteria(drogon_model::cavoke_orm::Players::Cols::_user_id,
222 CompareOperator::EQ, user_id));
223}
224
225void GameSessionAccessObject::set_role(const std::string &user_id,
226 int new_role) {
227 // check role's validity
228 if (!(0 <= new_role && new_role < m_game_config.players_num)) {
229 throw game_session_error("no such role " + std::to_string(new_role));
230 }
231 try {
232 // INFO: does not differentiate the following cases: user not in this
233 // session, user in this session changes his role to his current one
234 default_mp_players.updateBy(
235 {drogon_model::cavoke_orm::Players::Cols::_player_id},
236 Criteria(drogon_model::cavoke_orm::Players::Cols::_user_id,
237 CompareOperator::EQ, user_id),
238 new_role);
239 } catch (const std::exception &err) {
240 throw game_session_error("could not update role for player");
241 }
242}
243
244void GameSessionAccessObject::delete_session() {
245 default_mp_sessions.deleteBy(
246 Criteria(drogon_model::cavoke_orm::Sessions::Cols::_id,
247 CompareOperator::EQ, id));
248}
249
250void GameSessionAccessObject::leave_session(const std::string &user_id) {
251 drogon::app().getDbClient()->execSqlSync(
252 "select leave_session($1::uuid, $2::varchar);", id, user_id);
253}
254
255drogon_model::cavoke_orm::Statuses GameSessionAccessObject::get_actual_status()
256 const {
257 return default_mp_statuses
258 .orderBy(drogon_model::cavoke::Statuses::Cols::_saved_on,
259 SortOrder::DESC)
260 .findBy(Criteria(drogon_model::cavoke_orm::Statuses::Cols::_session_id,
261 CompareOperator::EQ, id))[0];
262}
263
264void GameSessionAccessObject::set_status(SessionStatus status) {
265 drogon_model::cavoke::Statuses status_record;
266 status_record.setSessionId(id);
267 status_record.setStatus(status);
268 default_mp_statuses.insert(status_record);
269}
270
271} // namespace cavoke::server::model
Serializable representation of session for client.
Definition: game_session.h:97
exception for errors thrown during actions with sessions
Definition: game_session.h:23