1#[cfg(feature = "server")]
2use std::collections::HashMap;
3
4use serde::{Deserialize, Serialize};
5
6#[cfg(feature = "server")]
7use crate::dto::ScoreDTO;
8use crate::dto::{CardDTO, PeggingDTO, PendingDTO, PhaseDTO, PlayerDTO, PlayerStateDTO, PlaysDTO};
9
10#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
12pub struct UserGameDTO {
13 pub name: String,
15
16 pub phase: PhaseDTO,
18
19 pub pending: PendingDTO,
21
22 pub dealer: Option<PlayerDTO>,
24
25 pub user_state: PlayerStateDTO,
27
28 pub opponent_state: PlayerStateDTO,
30
31 pub crib: Vec<CardDTO>,
33
34 pub starter_cut: Option<CardDTO>,
36
37 pub plays: Option<PlaysDTO>,
39
40 pub pegging: PeggingDTO,
43
44 pub winner: Option<PlayerDTO>,
46}
47
48#[cfg(feature = "server")]
49impl UserGameDTO {
50 fn new(name: &str, phase: PhaseDTO) -> Self {
51 Self {
52 name: String::from(name),
53 phase,
54 pending: PendingDTO::Nobody,
55 dealer: None,
56 user_state: PlayerStateDTO::default(),
57 opponent_state: PlayerStateDTO::default(),
58 crib: Vec::default(),
59 starter_cut: None,
60 plays: None,
61 pegging: PeggingDTO::default(),
62 winner: None,
63 }
64 }
65
66 fn with_pending(mut self, pending: PendingDTO) -> Self {
67 self.pending = pending;
68 self
69 }
70
71 fn with_cuts_for_deal(
72 mut self,
73 user_cut: Option<CardDTO>,
74 opponent_cut: Option<CardDTO>,
75 ) -> Self {
76 self.user_state.cut = user_cut;
77 self.opponent_state.cut = opponent_cut;
78 self
79 }
80
81 fn with_hands(mut self, user_hand: Vec<CardDTO>, opponent_hand: Vec<CardDTO>) -> Self {
82 self.user_state.hand = user_hand;
83 self.opponent_state.hand = opponent_hand;
84 self
85 }
86
87 fn with_scores(mut self, user_score: ScoreDTO, opponent_score: ScoreDTO) -> Self {
88 self.user_state.score = user_score;
89 self.opponent_state.score = opponent_score;
90 self
91 }
92
93 fn with_dealer(mut self, dealer: Option<PlayerDTO>) -> Self {
94 self.dealer = dealer;
95 self
96 }
97
98 fn with_crib_and_starter_cut(
99 mut self,
100 cards: Vec<CardDTO>,
101 starter_cut: Option<CardDTO>,
102 ) -> Self {
103 self.crib = cards;
104 self.starter_cut = starter_cut;
105 self
106 }
107
108 fn with_plays(mut self, plays: PlaysDTO) -> Self {
109 self.plays = Some(plays);
110 self
111 }
112
113 fn with_pegging(mut self, pegging: PeggingDTO) -> Self {
114 self.pegging = pegging;
115 self
116 }
117
118 fn with_winner(mut self, winner: PlayerDTO) -> Self {
119 self.winner = Some(winner);
120 self
121 }
122}
123
124#[cfg(feature = "server")]
125mod server_only {
126 use server::domain::{
127 Finished, Game, HasCrib, HasCutsForDeal, HasHands, HasPegging, HasPending, HasPlayState,
128 HasRoles, HasScoreboard, HasStarterCut, PLAYER0, PLAYER1, Phase, Play, Player, Roles,
129 ScoreKind, UserId,
130 };
131
132 use super::*;
133 use crate::dto::{CardIdDTO, PeggingKindDTO, PlayActionDTO};
134
135 fn players(game: &Game, user_id: UserId) -> (Player, Player) {
136 let is_host = game.host() == user_id;
137 if is_host {
138 (PLAYER0, PLAYER1)
139 } else {
140 (PLAYER1, PLAYER0)
141 }
142 }
143
144 fn pending<T: HasPending>(s: &T, p: Player) -> PendingDTO {
145 PendingDTO::new(p, s.pending())
146 }
147
148 fn cut_for_deal<T: HasCutsForDeal>(s: &T, p: Player) -> Option<CardDTO> {
149 s.cut_for_deal(p).map(CardDTO::face_up)
150 }
151
152 fn starter_cut<T: HasStarterCut>(s: &T) -> Option<CardDTO> {
153 Some(CardDTO::face_up(s.starter_cut()))
154 }
155
156 fn dealer_from_roles(
157 roles: &Roles,
158 player_dto_map: &HashMap<Player, PlayerDTO>,
159 ) -> Option<PlayerDTO> {
160 player_dto_map.get(&roles.dealer().player()).cloned()
161 }
162
163 fn dealer_from_maybe_roles(
164 roles: Option<Roles>,
165 player_dto_map: &HashMap<Player, PlayerDTO>,
166 ) -> Option<PlayerDTO> {
167 roles.and_then(|roles| dealer_from_roles(&roles, player_dto_map))
168 }
169
170 fn dealer<T: HasRoles>(
171 s: &T,
172 player_dto_map: &HashMap<Player, PlayerDTO>,
173 ) -> Option<PlayerDTO> {
174 dealer_from_roles(s.roles(), player_dto_map)
175 }
176
177 fn hand_down<T: HasHands>(s: &T, p: Player) -> Vec<CardDTO> {
178 s.hand(p).as_ref().iter().map(CardDTO::face_down).collect()
179 }
180
181 fn hand_up<T: HasHands>(s: &T, p: Player) -> Vec<CardDTO> {
182 let hand = s.hand(p).clone().sorted();
183 hand.into_iter().map(CardDTO::face_up).collect()
184 }
185
186 fn score<T: HasScoreboard>(s: &T, p: Player) -> ScoreDTO {
187 ScoreDTO::from(&s.scoreboard().position(p))
188 }
189
190 fn crib_down<T: HasCrib>(s: &T) -> Vec<CardDTO> {
191 s.crib().as_ref().iter().map(CardDTO::face_down).collect()
192 }
193
194 fn crib_up<T: HasCrib>(s: &T) -> Vec<CardDTO> {
195 let crib = s.crib().clone().sorted();
196 crib.into_iter().map(CardDTO::face_up).collect()
197 }
198
199 fn plays<T: HasPlayState>(s: &T, player_dto_map: &HashMap<Player, PlayerDTO>) -> PlaysDTO {
200 let play_state = s.play_state();
201
202 let (legal_plays, next_action) = {
203 if play_state.is_finished() {
204 (vec![], PlayActionDTO::ScorePone)
205 } else {
206 let next_to_play = play_state.next_to_play();
207 let next_to_play_dto = player_dto_map
208 .get(&next_to_play)
209 .expect("next player must be defined");
210
211 let legal_plays = play_state.legal_plays(next_to_play);
212 let legal_play_cids = legal_plays
213 .iter()
214 .map(|card| CardIdDTO::from(card.cid()))
215 .collect::<Vec<_>>();
216
217 let can_play = !legal_plays.is_empty();
218 if can_play {
219 if next_to_play_dto == &PlayerDTO::User {
220 (legal_play_cids, PlayActionDTO::Play(PlayerDTO::User))
221 } else {
222 (vec![], PlayActionDTO::Play(PlayerDTO::Opponent))
223 }
224 } else {
225 (vec![], PlayActionDTO::Go(*next_to_play_dto))
226 }
227 }
228 };
229
230 let plays_to_dto = |plays: &Vec<Play>| {
231 plays
232 .iter()
233 .map(|play| {
234 (
235 *player_dto_map.get(&play.player()).expect("valid player"),
236 CardDTO::face_up(&play.card()),
237 )
238 })
239 .collect::<Vec<_>>()
240 };
241
242 let current = plays_to_dto(play_state.current_plays());
243 let previous = plays_to_dto(play_state.previous_plays());
244 let running_total = play_state.running_total().value() as u8;
245
246 PlaysDTO {
247 next_action,
248 legal_plays,
249 current,
250 previous,
251 running_total,
252 }
253 }
254
255 fn pegging<T: HasPegging>(s: &T, player_dto_map: &HashMap<Player, PlayerDTO>) -> PeggingDTO {
256 if let Some(pegging) = s.pegging() {
257 let recipient = *player_dto_map
258 .get(pegging.recipient())
259 .expect("valid player");
260
261 let score_items = pegging.score_sheet().items();
262
263 score_items
264 .iter()
265 .fold(PeggingDTO::new(recipient), |mut acc, item| {
266 let kind = score_kind_to_dto(item.kind());
267
268 let points = item.points().value();
269 let cids = item.cards().iter().map(|c| c.cid()).collect::<Vec<_>>();
270 let entry = acc.breakdown.entry(kind).or_default();
271 entry.points += points;
272 entry.breakdown.push(cids);
273
274 acc
275 })
276 } else {
277 PeggingDTO::default()
278 }
279 }
280
281 fn score_kind_to_dto(kind: ScoreKind) -> PeggingKindDTO {
282 match kind {
283 ScoreKind::Fifteen => PeggingKindDTO::Fifteens,
284 ScoreKind::Pair | ScoreKind::Triplet | ScoreKind::Quadruplet => PeggingKindDTO::Pairs,
285 ScoreKind::Run => PeggingKindDTO::Runs,
286 ScoreKind::Flush => PeggingKindDTO::Flush,
287 ScoreKind::LastCard => PeggingKindDTO::LastCard,
288 ScoreKind::ThirtyOne => PeggingKindDTO::ThirtyOne,
289 ScoreKind::HisHeels => PeggingKindDTO::HisHeels,
290 ScoreKind::Nobs => PeggingKindDTO::Nobs,
291 }
292 }
293
294 fn winner(finished: &Finished, player_dto_map: &HashMap<Player, PlayerDTO>) -> PlayerDTO {
295 *player_dto_map
296 .get(&finished.winner())
297 .expect("valid player")
298 }
299
300 impl From<(UserId, &Game)> for UserGameDTO {
301 fn from((user_id, game): (UserId, &Game)) -> Self {
302 let (me, them) = players(game, user_id);
303 let player_dto_map: HashMap<Player, PlayerDTO> =
304 [(me, PlayerDTO::User), (them, PlayerDTO::Opponent)]
305 .into_iter()
306 .collect();
307 let name = game.name();
308
309 match &game.phase() {
310 Phase::Starting(state) if game.guest().is_none() => {
311 Self::new(name, PhaseDTO::InLobby).with_pending(PendingDTO::Opponent)
312 }
313
314 Phase::Starting(state) => Self::new(name, PhaseDTO::CuttingForDeal)
315 .with_pending(pending(state, me))
316 .with_cuts_for_deal(cut_for_deal(state, me), cut_for_deal(state, them))
317 .with_dealer(dealer_from_maybe_roles(state.roles(), &player_dto_map)),
318
319 Phase::Discarding(state) => UserGameDTO::new(name, PhaseDTO::Discarding)
320 .with_scores(score(state, me), score(state, them))
321 .with_dealer(dealer(state, &player_dto_map))
322 .with_pending(pending(state, me))
323 .with_hands(hand_up(state, me), hand_down(state, them))
324 .with_crib_and_starter_cut(crib_down(state), None),
325
326 Phase::Playing(state) => Self::new(name, PhaseDTO::Playing)
327 .with_scores(score(state, me), score(state, them))
328 .with_dealer(dealer(state, &player_dto_map))
329 .with_pending(pending(state, me))
330 .with_hands(hand_up(state, me), hand_down(state, them))
331 .with_crib_and_starter_cut(crib_down(state), starter_cut(state))
332 .with_plays(plays(state, &player_dto_map))
333 .with_pegging(pegging(state, &player_dto_map)),
334
335 Phase::ScoringPone(state) => {
336 let they_are_pone = state.pone().player() == them;
337 UserGameDTO::new(name, PhaseDTO::ScoringPone)
338 .with_scores(score(state, me), score(state, them))
339 .with_dealer(dealer(state, &player_dto_map))
340 .with_pending(pending(state, me))
341 .with_hands(
342 hand_up(state, me),
343 if they_are_pone {
344 hand_up(state, them)
345 } else {
346 hand_down(state, them)
347 },
348 )
349 .with_crib_and_starter_cut(crib_down(state), starter_cut(state))
350 .with_pegging(pegging(state, &player_dto_map))
351 }
352
353 Phase::ScoringDealer(state) => UserGameDTO::new(name, PhaseDTO::ScoringDealer)
354 .with_scores(score(state, me), score(state, them))
355 .with_dealer(dealer(state, &player_dto_map))
356 .with_pending(pending(state, me))
357 .with_hands(hand_up(state, me), hand_up(state, them))
358 .with_crib_and_starter_cut(crib_down(state), starter_cut(state))
359 .with_pegging(pegging(state, &player_dto_map)),
360
361 Phase::ScoringCrib(state) => UserGameDTO::new(name, PhaseDTO::ScoringCrib)
362 .with_scores(score(state, me), score(state, them))
363 .with_dealer(dealer(state, &player_dto_map))
364 .with_pending(pending(state, me))
365 .with_hands(hand_up(state, me), hand_up(state, them))
366 .with_crib_and_starter_cut(crib_up(state), starter_cut(state))
367 .with_pegging(pegging(state, &player_dto_map)),
368
369 Phase::Finished(state) => UserGameDTO::new(name, PhaseDTO::Finished)
370 .with_scores(score(state, me), score(state, them))
371 .with_dealer(dealer(state, &player_dto_map))
372 .with_hands(hand_up(state, me), hand_up(state, them))
373 .with_crib_and_starter_cut(crib_up(state), starter_cut(state))
374 .with_winner(winner(state, &player_dto_map)),
375 }
376 }
377 }
378}