1#![deny(clippy::use_self, clippy::shadow_unrelated)]
2use std::fmt::{self, Display};
3use ts_rs::TS;
4
5use mahjong_core::{
6 deck::DeckContent, game::GameVersion, game_summary::GameSummary, hand::SetIdContent, Game,
7 GameId, Hand, Hands, PlayerId, TileId,
8};
9use rustc_hash::{FxHashMap, FxHashSet};
10use serde::{Deserialize, Serialize};
11pub use service_player::{ServicePlayer, ServicePlayerGame, ServicePlayerSummary};
12
13mod service_player;
14
15#[derive(Debug, Clone, Serialize, Deserialize, TS)]
16#[ts(export)]
17pub struct GameSettings {
18 pub ai_enabled: bool,
19 pub auto_sort_players: FxHashSet<PlayerId>,
20 pub auto_stop_claim_meld: FxHashSet<PlayerId>,
21 pub dead_wall: bool,
22 pub discard_wait_ms: Option<i32>,
23 pub fixed_settings: bool,
24 pub last_discard_time: i128,
25}
26
27impl Default for GameSettings {
28 fn default() -> Self {
29 Self {
30 ai_enabled: true,
31 auto_sort_players: FxHashSet::default(),
32 auto_stop_claim_meld: FxHashSet::default(),
33 dead_wall: false,
34 discard_wait_ms: Some(1000),
35 fixed_settings: false,
36 last_discard_time: 0,
37 }
38 }
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize, TS)]
42#[ts(export)]
43pub struct ServiceGame {
44 pub created_at: i64,
45 pub game: Game,
46 pub players: FxHashMap<PlayerId, ServicePlayer>,
47 pub settings: GameSettings,
48 pub updated_at: i64,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize, TS)]
52#[ts(export)]
53pub struct GameSettingsSummary {
54 pub ai_enabled: bool,
55 pub auto_sort: bool,
56 pub auto_stop_claim_meld: bool,
57 pub dead_wall: bool,
58 pub discard_wait_ms: Option<i32>,
59 pub fixed_settings: bool,
60 pub last_discard_time: String,
61}
62
63impl GameSettingsSummary {
64 pub fn from_game_settings(settings: &GameSettings, player_id: &PlayerId) -> Self {
65 Self {
66 ai_enabled: settings.ai_enabled,
67 auto_sort: settings.auto_sort_players.iter().any(|p| p == player_id),
68 auto_stop_claim_meld: settings.auto_stop_claim_meld.iter().any(|p| p == player_id),
69 dead_wall: settings.dead_wall,
70 discard_wait_ms: settings.discard_wait_ms,
71 fixed_settings: settings.fixed_settings,
72 last_discard_time: settings.last_discard_time.to_string(),
73 }
74 }
75
76 pub fn to_game_settings(&self, player_id: &PlayerId, settings: &GameSettings) -> GameSettings {
77 let mut new_settings = settings.clone();
78
79 if self.auto_sort {
80 new_settings.auto_sort_players.insert(player_id.clone());
81 } else {
82 new_settings.auto_sort_players.remove(player_id);
83 }
84
85 if self.auto_stop_claim_meld {
86 new_settings.auto_stop_claim_meld.insert(player_id.clone());
87 } else {
88 new_settings.auto_stop_claim_meld.remove(player_id);
89 }
90
91 new_settings.ai_enabled = self.ai_enabled;
92 new_settings.discard_wait_ms = self.discard_wait_ms;
93 new_settings.fixed_settings = self.fixed_settings;
94 new_settings.last_discard_time = self.last_discard_time.parse().unwrap_or(0);
95
96 new_settings
97 }
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize, TS)]
101#[ts(export)]
102pub struct ServiceGameSummary {
103 pub game_summary: GameSummary,
104 pub players: FxHashMap<PlayerId, ServicePlayerSummary>,
105 pub settings: GameSettingsSummary,
106}
107
108impl ServiceGame {
109 pub fn get_ai_players(&self) -> FxHashSet<PlayerId> {
110 self.players
111 .iter()
112 .filter(|(_, player)| player.is_ai)
113 .map(|(id, _)| id.clone())
114 .collect::<FxHashSet<PlayerId>>()
115 }
116}
117
118impl ServiceGameSummary {
119 pub fn from_service_game(game: &ServiceGame, player_id: &PlayerId) -> Option<Self> {
120 let game_summary = GameSummary::from_game(&game.game, player_id);
121
122 game_summary.as_ref()?;
123
124 let game_summary = game_summary.unwrap();
125
126 let players: FxHashMap<PlayerId, ServicePlayerSummary> = game
127 .players
128 .clone()
129 .into_iter()
130 .map(|(id, player)| {
131 (
132 id,
133 ServicePlayerSummary {
134 id: player.id,
135 name: player.name,
136 },
137 )
138 })
139 .collect();
140
141 Some(Self {
142 game_summary,
143 players,
144 settings: GameSettingsSummary::from_game_settings(&game.settings, player_id),
145 })
146 }
147
148 pub fn get_turn_player(&self) -> Option<ServicePlayerSummary> {
149 let player_id = self.game_summary.players.0[self.game_summary.round.player_index].clone();
150
151 self.players.get(&player_id).cloned()
152 }
153
154 pub fn get_dealer_player(&self) -> Option<ServicePlayerSummary> {
155 let player_id =
156 self.game_summary.players.0[self.game_summary.round.dealer_player_index].clone();
157
158 self.players.get(&player_id).cloned()
159 }
160}
161
162#[allow(clippy::large_enum_variant)]
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub enum SocketMessage {
165 GameUpdate(ServiceGame),
166 GameSummaryUpdate(ServiceGameSummary),
167 ListRooms,
168 Name(String),
169 PlayerLeft,
170 PlayerJoined,
171}
172
173#[derive(Serialize, Deserialize, TS)]
174#[ts(export)]
175pub struct WebSocketQuery {
176 pub game_id: GameId,
177 pub player_id: Option<PlayerId>,
178 pub token: String,
179}
180
181pub type AdminGetGamesResponse = Vec<ServicePlayerGame>;
182
183#[derive(Deserialize, Serialize, TS)]
184#[ts(export)]
185#[serde(tag = "type")]
186pub enum Queries {
187 UserBreakMeld {
188 game_id: GameId,
189 player_id: PlayerId,
190 set_id: SetIdContent,
191 },
192 UserCreateGame {
193 ai_player_names: Option<Vec<String>>,
194 auto_sort_own: Option<bool>,
195 dead_wall: Option<bool>,
196 player_id: PlayerId,
197 },
198 UserCreateMeld {
199 game_id: GameId,
200 is_concealed: bool,
201 is_upgrade: bool,
202 player_id: PlayerId,
203 tiles: FxHashSet<TileId>,
204 },
205 UserDiscardTile {
206 game_id: GameId,
207 tile_id: TileId,
208 },
209 UserDrawTile {
210 game_id: GameId,
211 game_version: GameVersion,
212 player_id: PlayerId,
213 },
214 UserGetDashboard,
215 UserMovePlayer {
216 game_id: GameId,
217 player_id: PlayerId,
218 },
219}
220
221#[derive(Deserialize, Serialize, TS)]
222#[ts(export)]
223pub struct UserGetDashboardResponse {
224 pub auth_info: AuthInfoSummary,
225 pub player: DashboardPlayer,
226 pub player_games: Vec<DashboardGame>,
227 pub player_total_score: i32,
228}
229
230#[derive(Deserialize, Serialize, TS)]
231#[ts(export)]
232#[serde(tag = "type")]
233pub enum QueriesResponses {
234 UserBreakMeld { game: ServiceGameSummary },
235 UserCreateGame { game: ServiceGameSummary },
236 UserCreateMeld { game: ServiceGameSummary },
237 UserDiscardTile { game: ServiceGameSummary },
238 UserDrawTile { game: ServiceGameSummary },
239 UserGetDashboard { dashboard: UserGetDashboardResponse },
240 UserMovePlayer { game: ServiceGameSummary },
241}
242
243pub type AdminPostDrawTileResponse = Hand;
244
245#[derive(Debug, Clone, Serialize, Deserialize, TS)]
246#[ts(export)]
247pub struct AdminPostCreateMeldRequest {
248 pub is_concealed: bool,
249 pub is_upgrade: bool,
250 pub player_id: String,
251 pub tiles: FxHashSet<TileId>,
252}
253pub type AdminPostCreateMeldResponse = Hand;
254
255#[derive(Debug, Clone, Serialize, Deserialize)]
256pub struct AdminPostBreakMeldRequest {
257 pub player_id: String,
258 pub set_id: SetIdContent,
259}
260pub type AdminPostBreakMeldResponse = Hand;
261
262#[derive(Debug, Clone, Serialize, Deserialize, TS)]
263#[ts(export)]
264pub struct AdminPostDiscardTileRequest {
265 pub tile_id: TileId,
266}
267pub type AdminPostDiscardTileResponse = ServiceGame;
268
269pub type AdminPostMovePlayerRequest = ();
270
271#[derive(Deserialize, Serialize, TS)]
272#[ts(export)]
273pub struct AdminPostMovePlayerResponse(pub ServiceGame);
274
275pub type AdminPostSortHandsRequest = ();
276
277#[derive(Deserialize, Serialize, TS)]
278#[ts(export)]
279pub struct AdminPostSortHandsResponse(pub Hands);
280
281#[derive(Debug, Clone, Serialize, Deserialize, TS)]
282#[ts(export)]
283pub struct AdminPostClaimTileRequest {
284 pub player_id: PlayerId,
285}
286#[derive(Debug, Clone, Serialize, Deserialize, TS)]
287#[ts(export)]
288pub struct AdminPostClaimTileResponse(pub ServiceGame);
289
290#[derive(Deserialize, Serialize, TS)]
291#[ts(export)]
292pub struct UserLoadGameQuery {
293 pub player_id: PlayerId,
294}
295#[derive(Debug, Clone, Serialize, Deserialize, TS)]
296#[ts(export)]
297pub struct UserGetLoadGameResponse(pub ServiceGameSummary);
298
299#[derive(Deserialize, Serialize, TS)]
300#[ts(export)]
301pub struct UserPostSortHandRequest {
302 pub game_version: GameVersion,
303 pub player_id: PlayerId,
304 pub tiles: Option<Vec<TileId>>,
305}
306#[derive(Deserialize, Serialize, TS)]
307#[ts(export)]
308pub struct UserPostSortHandResponse(pub ServiceGameSummary);
309
310#[derive(Deserialize, Serialize, TS)]
311#[ts(export)]
312pub struct AdminPostSayMahjongRequest {
313 pub player_id: PlayerId,
314}
315pub type AdminPostSayMahjongResponse = ServiceGame;
316
317#[derive(Deserialize, Serialize, TS)]
318#[ts(export)]
319pub struct AdminPostAIContinueRequest {
320 pub draw: Option<bool>,
321}
322#[derive(Deserialize, Serialize)]
323pub struct AdminPostAIContinueResponse {
324 pub service_game: ServiceGame,
325 pub changed: bool,
326}
327
328#[derive(Deserialize, Serialize, TS)]
329#[ts(export)]
330pub struct UserPostAIContinueRequest {
331 pub player_id: PlayerId,
332}
333#[derive(Deserialize, Serialize, TS)]
334#[ts(export)]
335pub struct UserPostAIContinueResponse {
336 pub service_game_summary: ServiceGameSummary,
337 pub changed: bool,
338}
339
340#[derive(Deserialize, Serialize, TS)]
341#[ts(export)]
342pub struct UserPostClaimTileRequest {
343 pub player_id: PlayerId,
344}
345#[derive(Deserialize, Serialize, TS)]
346#[ts(export)]
347pub struct UserPostClaimTileResponse(pub ServiceGameSummary);
348
349#[derive(Deserialize, Serialize, TS)]
350#[ts(export)]
351pub struct UserPostSayMahjongRequest {
352 pub player_id: PlayerId,
353}
354#[derive(Deserialize, Serialize, TS)]
355#[ts(export)]
356pub struct UserPostSayMahjongResponse(pub ServiceGameSummary);
357
358#[derive(Deserialize, Serialize, TS)]
359#[ts(export)]
360pub struct UserPostSetGameSettingsRequest {
361 pub player_id: PlayerId,
362 pub settings: GameSettingsSummary,
363}
364#[derive(Deserialize, Serialize, TS)]
365#[ts(export)]
366pub struct UserPostSetGameSettingsResponse(pub ServiceGameSummary);
367
368#[derive(Deserialize, Serialize, TS)]
369#[ts(export)]
370pub struct UserPostJoinGameResponse(pub PlayerId);
371
372#[derive(Deserialize, Serialize, Debug, TS)]
373#[ts(export)]
374pub struct UserPostSetAuthRequest {
375 pub username: String,
376 pub password: String,
377}
378#[derive(Deserialize, Serialize, TS)]
379#[ts(export)]
380pub struct UserPostSetAuthResponse {
381 pub token: String,
382}
383
384#[derive(Deserialize, Serialize, Debug, TS)]
385#[ts(export)]
386pub struct UserPostSetAuthAnonRequest {
387 pub id_token: String,
388}
389#[derive(Deserialize, Serialize, TS)]
390#[ts(export)]
391pub struct UserPostSetAuthAnonResponse {
392 pub token: String,
393}
394
395#[derive(Deserialize, Serialize, TS)]
396#[ts(export)]
397pub struct UserPostPassRoundRequest {
398 pub player_id: PlayerId,
399}
400#[derive(Deserialize, Serialize, TS)]
401#[ts(export)]
402pub struct UserPostPassRoundResponse(pub ServiceGameSummary);
403
404#[derive(Deserialize, Serialize, TS)]
405#[ts(export)]
406pub struct UserGetInfoResponse {
407 pub name: String,
408 pub total_score: i32,
409}
410
411#[derive(Deserialize, Serialize, TS)]
412#[ts(export)]
413pub struct DashboardPlayer {
414 pub id: String,
415 pub name: String,
416 pub created_at: String,
417}
418
419#[derive(Deserialize, Serialize, TS)]
420#[ts(export)]
421pub struct DashboardGame {
422 pub id: String,
423 pub created_at: String,
424 pub updated_at: String,
425}
426
427#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, TS)]
428#[ts(export)]
429pub enum AuthProvider {
430 Anonymous,
431 Email,
432 Github,
433}
434
435#[derive(Deserialize, Serialize, Clone, TS)]
436#[ts(export)]
437pub struct AuthInfoSummary {
438 pub provider: AuthProvider,
439 pub username: Option<String>,
440}
441
442#[derive(Deserialize, Serialize, TS)]
443#[ts(export)]
444pub struct UserPatchInfoRequest {
445 pub name: String,
446}
447#[derive(Deserialize, Serialize, TS)]
448#[ts(export)]
449pub struct UserPatchInfoResponse {
450 pub name: String,
451 pub total_score: i32,
452}
453
454#[derive(Deserialize, Serialize, TS)]
455#[ts(export)]
456pub struct GetDeckResponse(pub DeckContent);
457
458impl Display for AuthProvider {
459 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
460 let result = match self {
461 Self::Anonymous => "anonymous".to_string(),
462 Self::Email => "email".to_string(),
463 Self::Github => "github".to_string(),
464 };
465
466 write!(f, "{}", result)
467 }
468}