|
3 | 3 | #include <stdexcept>
|
4 | 4 | #include <string>
|
5 | 5 | #include <unordered_map>
|
| 6 | +#include <utility> |
| 7 | +#include <variant> |
6 | 8 |
|
7 | 9 | #include "absl/status/statusor.h"
|
8 | 10 | #include "absl/strings/str_split.h"
|
9 | 11 | #include "cpp/cards/golf/golf.h"
|
| 12 | +#include "cpp/golf_service/api.h" |
10 | 13 | #include "cpp/golf_service/game_state_mapper.h"
|
11 | 14 | #include "mongoose.h"
|
12 | 15 |
|
13 |
| -using std::string; |
14 |
| - |
15 |
| -typedef struct Args { |
16 |
| - string username; |
17 |
| - string gameId; |
18 |
| - int players; |
19 |
| - golf::Position position; |
20 |
| - struct mg_connection *c; |
21 |
| -} Args; |
22 |
| - |
23 |
| -typedef struct COMMAND { |
24 |
| - const char *name; |
25 |
| - string (*command)(Args); |
26 |
| -} API; |
27 |
| - |
28 | 16 | using golf::GameStateMapper;
|
| 17 | +using golf_service::DiscardDrawRequest; |
| 18 | +using golf_service::GolfServiceRequest; |
| 19 | +using golf_service::JoinGameRequest; |
| 20 | +using golf_service::KnockRequest; |
| 21 | +using golf_service::NewGameRequest; |
| 22 | +using golf_service::PeekRequest; |
| 23 | +using golf_service::readDiscardDrawRequest; |
| 24 | +using golf_service::readJoinGameRequest; |
| 25 | +using golf_service::readKnockRequest; |
| 26 | +using golf_service::readNewGameRequest; |
| 27 | +using golf_service::readPeekRequest; |
| 28 | +using golf_service::readRegisterUserRequest; |
| 29 | +using golf_service::readSwapForDiscardRequest; |
| 30 | +using golf_service::readSwapForDrawRequest; |
| 31 | +using golf_service::RegisterUserRequest; |
| 32 | +using golf_service::SwapForDiscardRequest; |
| 33 | +using golf_service::SwapForDrawRequest; |
| 34 | +using std::string; |
29 | 35 |
|
30 | 36 | std::mutex m;
|
31 | 37 | std::unordered_map<std::string, mg_connection *> connectionsByUser;
|
32 | 38 | golf::GameManager gm;
|
33 | 39 | GameStateMapper gsm;
|
34 | 40 |
|
35 |
| -static void registerUser(const Args &args) { |
| 41 | +template <typename T> |
| 42 | +static bool validRequestType(const GolfServiceRequest &serviceRequest, struct mg_connection *c) { |
| 43 | + if (std::holds_alternative<T>(serviceRequest)) { |
| 44 | + return true; |
| 45 | + } |
| 46 | + |
| 47 | + string output("error|invalid request"); |
| 48 | + mg_ws_send(c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT); |
| 49 | + return false; |
| 50 | +} |
| 51 | + |
| 52 | +static void registerUser(const GolfServiceRequest &serviceRequest, struct mg_connection *c) { |
| 53 | + if (!validRequestType<RegisterUserRequest>(serviceRequest, c)) { |
| 54 | + return; |
| 55 | + } |
| 56 | + |
| 57 | + const RegisterUserRequest registerUserRequest = std::get<RegisterUserRequest>(serviceRequest); |
36 | 58 | // don't allow re-registration yet
|
37 | 59 | for (auto i = connectionsByUser.begin(); i != connectionsByUser.end(); i++) {
|
38 |
| - if (connectionsByUser.at(i->first) == args.c) { |
| 60 | + if (connectionsByUser.at(i->first) == c) { |
39 | 61 | string output("error|already registered");
|
40 |
| - mg_ws_send(args.c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT); |
| 62 | + mg_ws_send(c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT); |
41 | 63 | return;
|
42 | 64 | }
|
43 | 65 | }
|
44 | 66 |
|
45 |
| - auto res = gm.registerUser(args.username); |
| 67 | + auto res = gm.registerUser(registerUserRequest.username); |
46 | 68 | if (!res.ok()) {
|
47 | 69 | string output("error|");
|
48 | 70 | output.append(res.status().message());
|
49 |
| - mg_ws_send(args.c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT); |
| 71 | + mg_ws_send(c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT); |
50 | 72 | return;
|
51 | 73 | }
|
52 | 74 |
|
53 | 75 | string user = *res;
|
54 |
| - connectionsByUser.insert({user, args.c}); |
| 76 | + connectionsByUser.insert({user, c}); |
55 | 77 | string output(R"({"inGame":false,"username":")" + user + "\"}");
|
56 |
| - mg_ws_send(args.c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT); |
| 78 | + mg_ws_send(c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT); |
57 | 79 | }
|
58 | 80 |
|
59 |
| -static bool usernameMismatch(const Args &args) { |
60 |
| - if (connectionsByUser.find(args.username) == connectionsByUser.end() || |
61 |
| - connectionsByUser.at(args.username) != args.c) { |
| 81 | +static bool usernameMismatch(const string &username, struct mg_connection *c) { |
| 82 | + if (connectionsByUser.find(username) == connectionsByUser.end() || |
| 83 | + connectionsByUser.at(username) != c) { |
62 | 84 | string output("error|username mismatch");
|
63 |
| - mg_ws_send(args.c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT); |
| 85 | + mg_ws_send(c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT); |
64 | 86 | return true;
|
65 | 87 | }
|
66 | 88 | return false;
|
67 | 89 | }
|
68 | 90 |
|
69 | 91 | static void handleGameManagerResult(const absl::StatusOr<golf::GameStatePtr> &res,
|
70 |
| - const Args &args) { |
| 92 | + struct mg_connection *c) { |
71 | 93 | if (!res.ok()) {
|
72 | 94 | string output("error|");
|
73 | 95 | output.append(res.status().message());
|
74 |
| - mg_ws_send(args.c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT); |
| 96 | + mg_ws_send(c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT); |
75 | 97 | return;
|
76 | 98 | }
|
77 | 99 |
|
78 | 100 | const auto &gameStatePtr = *res;
|
79 |
| - for (auto &user : gm.getUsersByGameId(args.gameId)) { |
| 101 | + for (auto &user : gm.getUsersByGameId(gameStatePtr->getGameId())) { |
80 | 102 | auto stateForUser = GameStateMapper::gameStateJson(gameStatePtr, user);
|
81 |
| - auto c = connectionsByUser.at(user); |
82 |
| - mg_ws_send(c, stateForUser.c_str(), stateForUser.size(), WEBSOCKET_OP_TEXT); |
| 103 | + auto userConnection = connectionsByUser.at(user); |
| 104 | + mg_ws_send(userConnection, stateForUser.c_str(), stateForUser.size(), WEBSOCKET_OP_TEXT); |
83 | 105 | }
|
84 | 106 | }
|
85 | 107 |
|
86 |
| -static void newGame(const Args &args) { |
87 |
| - if (usernameMismatch(args)) { |
| 108 | +static void newGame(const GolfServiceRequest &serviceRequest, struct mg_connection *c) { |
| 109 | + if (!validRequestType<NewGameRequest>(serviceRequest, c)) { |
88 | 110 | return;
|
89 | 111 | }
|
90 | 112 |
|
91 |
| - auto res = gm.newGame(args.username, args.players); |
92 |
| - string output; |
93 |
| - if (!res.ok()) { |
94 |
| - output.append("error|"); |
95 |
| - output.append(res.status().message()); |
96 |
| - } else { |
97 |
| - output.append(GameStateMapper::gameStateJson(*res, args.username)); |
| 113 | + auto newGameRequest = std::get<NewGameRequest>(serviceRequest); |
| 114 | + if (usernameMismatch(newGameRequest.username, c)) { |
| 115 | + return; |
98 | 116 | }
|
99 |
| - mg_ws_send(args.c, output.c_str(), output.size(), WEBSOCKET_OP_TEXT); |
| 117 | + |
| 118 | + auto res = gm.newGame(newGameRequest.username, newGameRequest.players); |
| 119 | + handleGameManagerResult(res, c); |
100 | 120 | }
|
101 | 121 |
|
102 |
| -static void joinGame(const Args &args) { |
103 |
| - if (usernameMismatch(args)) { |
| 122 | +static void joinGame(const GolfServiceRequest &serviceRequest, struct mg_connection *c) { |
| 123 | + if (!validRequestType<JoinGameRequest>(serviceRequest, c)) { |
104 | 124 | return;
|
105 | 125 | }
|
106 |
| - auto res = gm.joinGame(args.gameId, args.username); |
107 |
| - handleGameManagerResult(res, args); |
108 |
| -} |
109 | 126 |
|
110 |
| -static void peekAtDrawPile(const Args &args) { |
111 |
| - if (usernameMismatch(args)) { |
| 127 | + auto joinGameRequest = std::get<JoinGameRequest>(serviceRequest); |
| 128 | + if (usernameMismatch(joinGameRequest.username, c)) { |
112 | 129 | return;
|
113 | 130 | }
|
114 |
| - auto res = gm.peekAtDrawPile(args.username); |
115 |
| - handleGameManagerResult(res, args); |
| 131 | + auto res = gm.joinGame(joinGameRequest.gameId, joinGameRequest.username); |
| 132 | + handleGameManagerResult(res, c); |
116 | 133 | }
|
117 | 134 |
|
118 |
| -static void discardFromDrawPile(const Args &args) { |
119 |
| - if (usernameMismatch(args)) { |
| 135 | +static void peekAtDrawPile(const GolfServiceRequest &serviceRequest, struct mg_connection *c) { |
| 136 | + if (!validRequestType<PeekRequest>(serviceRequest, c)) { |
120 | 137 | return;
|
121 | 138 | }
|
122 |
| - auto res = gm.swapDrawForDiscardPile(args.username); |
123 |
| - handleGameManagerResult(res, args); |
| 139 | + |
| 140 | + auto peekRequest = std::get<PeekRequest>(serviceRequest); |
| 141 | + if (usernameMismatch(peekRequest.username, c)) { |
| 142 | + return; |
| 143 | + } |
| 144 | + auto res = gm.peekAtDrawPile(peekRequest.username); |
| 145 | + handleGameManagerResult(res, c); |
124 | 146 | }
|
125 | 147 |
|
126 |
| -static void swapForDrawPile(const Args &args) { |
127 |
| - if (usernameMismatch(args)) { |
| 148 | +static void discardFromDrawPile(const GolfServiceRequest &serviceRequest, struct mg_connection *c) { |
| 149 | + if (!validRequestType<DiscardDrawRequest>(serviceRequest, c)) { |
| 150 | + return; |
| 151 | + } |
| 152 | + |
| 153 | + auto discardDrawRequest = std::get<DiscardDrawRequest>(serviceRequest); |
| 154 | + if (usernameMismatch(discardDrawRequest.username, c)) { |
128 | 155 | return;
|
129 | 156 | }
|
130 |
| - auto res = gm.swapForDrawPile(args.username, args.position); |
131 |
| - handleGameManagerResult(res, args); |
| 157 | + auto res = gm.swapDrawForDiscardPile(discardDrawRequest.username); |
| 158 | + handleGameManagerResult(res, c); |
132 | 159 | }
|
133 | 160 |
|
134 |
| -static void swapForDiscardPile(const Args &args) { |
135 |
| - if (usernameMismatch(args)) { |
| 161 | +static void swapForDrawPile(const GolfServiceRequest &serviceRequest, struct mg_connection *c) { |
| 162 | + if (!validRequestType<SwapForDrawRequest>(serviceRequest, c)) { |
136 | 163 | return;
|
137 | 164 | }
|
138 |
| - auto res = gm.swapForDiscardPile(args.username, args.position); |
139 |
| - handleGameManagerResult(res, args); |
| 165 | + |
| 166 | + auto swapForDrawRequest = std::get<SwapForDrawRequest>(serviceRequest); |
| 167 | + if (usernameMismatch(swapForDrawRequest.username, c)) { |
| 168 | + return; |
| 169 | + } |
| 170 | + auto res = gm.swapForDrawPile(swapForDrawRequest.username, swapForDrawRequest.position); |
| 171 | + handleGameManagerResult(res, c); |
140 | 172 | }
|
141 | 173 |
|
142 |
| -static void knock(const Args &args) { |
143 |
| - if (usernameMismatch(args)) { |
| 174 | +static void swapForDiscardPile(const GolfServiceRequest &serviceRequest, struct mg_connection *c) { |
| 175 | + if (!validRequestType<SwapForDiscardRequest>(serviceRequest, c)) { |
| 176 | + return; |
| 177 | + } |
| 178 | + |
| 179 | + auto swapForDiscardRequest = std::get<SwapForDiscardRequest>(serviceRequest); |
| 180 | + if (usernameMismatch(swapForDiscardRequest.username, c)) { |
144 | 181 | return;
|
145 | 182 | }
|
146 |
| - auto res = gm.knock(args.username); |
147 |
| - handleGameManagerResult(res, args); |
| 183 | + auto res = gm.swapForDiscardPile(swapForDiscardRequest.username, swapForDiscardRequest.position); |
| 184 | + handleGameManagerResult(res, c); |
148 | 185 | }
|
149 | 186 |
|
150 |
| -const std::unordered_map<string, void (*)(const Args &)> handlers{ |
151 |
| - {"register", registerUser}, |
152 |
| - {"new", newGame}, |
153 |
| - {"join", joinGame}, |
154 |
| - {"peek", peekAtDrawPile}, |
155 |
| - {"discardDraw", discardFromDrawPile}, |
156 |
| - {"swapDraw", swapForDrawPile}, |
157 |
| - {"swapDiscard", swapForDiscardPile}, |
158 |
| - {"knock", knock}, |
159 |
| -}; |
| 187 | +static void knock(const GolfServiceRequest &serviceRequest, struct mg_connection *c) { |
| 188 | + if (!validRequestType<KnockRequest>(serviceRequest, c)) { |
| 189 | + return; |
| 190 | + } |
160 | 191 |
|
161 |
| -static absl::StatusOr<Args> parseArgs(std::vector<string> &parts, struct mg_connection *c) { |
162 |
| - if (parts.size() != 5) { |
163 |
| - return absl::InvalidArgumentError("args -> <user>|<game>|<numPlayers>|<pos>"); |
164 |
| - } |
165 |
| - string username = parts[1]; |
166 |
| - string gameId = parts[2]; |
167 |
| - int numberOfPlayers; |
168 |
| - try { |
169 |
| - numberOfPlayers = std::stoi(parts[3]); |
170 |
| - } catch (std::invalid_argument const &ex) { |
171 |
| - return absl::InvalidArgumentError(ex.what()); |
172 |
| - } catch (std::out_of_range const &ex) { |
173 |
| - return absl::InvalidArgumentError(ex.what()); |
174 |
| - } |
175 |
| - golf::Position position; |
176 |
| - if (parts[4] == "tl") { |
177 |
| - position = golf::Position::TopLeft; |
178 |
| - } else if (parts[4] == "tr") { |
179 |
| - position = golf::Position::TopRight; |
180 |
| - } else if (parts[4] == "bl") { |
181 |
| - position = golf::Position::BottomLeft; |
182 |
| - } else if (parts[4] == "br") { |
183 |
| - position = golf::Position::BottomRight; |
184 |
| - } else { |
185 |
| - return absl::InvalidArgumentError("invalid position. must be in (tl, tr, bl, br)"); |
186 |
| - } |
187 |
| - |
188 |
| - return Args{username, gameId, numberOfPlayers, position, c}; |
| 192 | + auto knockRequest = std::get<KnockRequest>(serviceRequest); |
| 193 | + if (usernameMismatch(knockRequest.username, c)) { |
| 194 | + return; |
| 195 | + } |
| 196 | + auto res = gm.knock(knockRequest.username); |
| 197 | + handleGameManagerResult(res, c); |
189 | 198 | }
|
190 | 199 |
|
| 200 | +typedef std::function<void(const GolfServiceRequest &, struct mg_connection *)> handler; |
| 201 | +typedef std::function<absl::StatusOr<GolfServiceRequest>(std::vector<string>)> argReader; |
| 202 | + |
| 203 | +// make this map<string, pair<handler, parser>> |
| 204 | +const std::unordered_map<string, std::pair<handler, argReader>> handlers{ |
| 205 | + {"register", {registerUser, readRegisterUserRequest}}, |
| 206 | + {"new", {newGame, readNewGameRequest}}, |
| 207 | + {"join", {joinGame, readJoinGameRequest}}, |
| 208 | + {"peek", {peekAtDrawPile, readPeekRequest}}, |
| 209 | + {"discardDraw", {discardFromDrawPile, readDiscardDrawRequest}}, |
| 210 | + {"swapDraw", {swapForDrawPile, readSwapForDrawRequest}}, |
| 211 | + {"swapDiscard", {swapForDiscardPile, readSwapForDiscardRequest}}, |
| 212 | + {"knock", {knock, readKnockRequest}}, |
| 213 | +}; |
| 214 | + |
191 | 215 | static void handleMessage(struct mg_ws_message *wm, struct mg_connection *c) {
|
192 | 216 | std::scoped_lock lock(m);
|
193 | 217 |
|
194 | 218 | std::vector<string> commandParts =
|
195 | 219 | absl::StrSplit(string(wm->data.ptr), '|', absl::SkipWhitespace());
|
196 |
| - |
197 |
| - if (commandParts.size() < 2) { |
| 220 | + if (commandParts.empty()) { |
198 | 221 | string response = "error|arg count";
|
199 | 222 | mg_ws_send(c, response.c_str(), response.size(), WEBSOCKET_OP_TEXT);
|
200 | 223 | }
|
201 | 224 |
|
202 |
| - auto res = parseArgs(commandParts, c); |
203 |
| - if (!res.ok()) { |
204 |
| - std::string response = "error|"; |
205 |
| - response.append(res.status().message()); |
| 225 | + auto command = handlers.find(commandParts[0]); |
| 226 | + if (command == handlers.end()) { |
| 227 | + std::string response = "error|bad_command"; |
206 | 228 | mg_ws_send(c, response.c_str(), response.size(), WEBSOCKET_OP_TEXT);
|
207 | 229 | return;
|
208 | 230 | }
|
209 | 231 |
|
210 |
| - Args args = *res; |
211 |
| - |
212 |
| - auto cmdIter = handlers.find(commandParts[0]); |
213 |
| - if (cmdIter == handlers.end()) { |
214 |
| - std::string response = "error|bad_command"; |
| 232 | + auto res = command->second.second(commandParts); |
| 233 | + if (!res.ok()) { |
| 234 | + std::string response = "error|"; |
| 235 | + response.append(res.status().message()); |
215 | 236 | mg_ws_send(c, response.c_str(), response.size(), WEBSOCKET_OP_TEXT);
|
216 | 237 | return;
|
217 | 238 | }
|
218 | 239 |
|
219 |
| - cmdIter->second(args); |
| 240 | + GolfServiceRequest req = *res; |
| 241 | + |
| 242 | + command->second.first(req, c); |
220 | 243 | }
|
221 | 244 |
|
222 | 245 | static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
|
0 commit comments