1pub use self::creation::GameNewOpts;
2pub use self::definition::{DrawTileResult, Game, GameId, GamePhase, GameStyle, GameVersion};
3use self::errors::DecideDealerError;
4pub use self::errors::{
5 BreakMeldError, CreateMeldError, DiscardTileError, DrawError, PassNullRoundError,
6};
7pub use self::players::{PlayerId, Players, PlayersVec};
8use crate::hand::KongTile;
9use crate::table::PositionTilesOpts;
10use crate::{
11 deck::DEFAULT_DECK,
12 hand::CanSayMahjongError,
13 meld::{
14 get_is_chow, get_is_kong, get_is_pung, get_tile_claimed_id_for_user, PlayerDiff,
15 PossibleMeld, SetCheckOpts,
16 },
17 round::{Round, RoundTileClaimed},
18 Hand, HandTile, TileId,
19};
20use crate::{Tile, Wind, WINDS_ROUND_ORDER};
21use rustc_hash::FxHashSet;
22use uuid::Uuid;
23
24mod charleston;
25mod creation;
26mod definition;
27mod errors;
28mod players;
29
30impl Game {
31 pub fn get_possible_melds_for_player(
34 &self,
35 player: &PlayerId,
36 check_for_mahjong: bool,
37 ) -> Vec<PossibleMeld> {
38 let mut melds: Vec<PossibleMeld> = vec![];
39
40 let (can_claim_tile, tile_claimed, player_hand) = self.get_can_claim_tile(player);
41
42 let hand = if can_claim_tile {
43 let mut hand = player_hand.unwrap().clone();
44 hand.push(HandTile {
45 concealed: true,
46 id: tile_claimed.unwrap(),
47 set_id: None,
48 });
49 hand
50 } else {
51 player_hand.unwrap().clone()
52 };
53
54 let mut round = self.round.clone();
55 if can_claim_tile {
56 round.tile_claimed = Some(RoundTileClaimed {
57 by: Some(player.clone()),
58 from: round.tile_claimed.unwrap().from,
59 id: tile_claimed.unwrap(),
60 });
61 }
62
63 let board_tile_player_diff =
64 self.get_board_tile_player_diff(Some(&round), Some(&hand), player);
65 let claimed_tile = get_tile_claimed_id_for_user(player, &round.tile_claimed);
66
67 let possible_melds =
68 hand.get_possible_melds(board_tile_player_diff, claimed_tile, check_for_mahjong);
69
70 for meld in possible_melds {
71 melds.push(PossibleMeld {
72 discard_tile: None,
73 is_concealed: meld.is_concealed,
74 is_mahjong: meld.is_mahjong,
75 is_upgrade: meld.is_upgrade,
76 player_id: player.clone(),
77 tiles: meld.tiles,
78 });
79 }
80
81 melds
82 }
83
84 pub fn get_can_claim_tile(&self, player: &PlayerId) -> (bool, Option<TileId>, Option<&Hand>) {
85 let tile_claimed = self.round.get_claimable_tile(player);
86 let player_hand = self.table.hands.0.get(player);
87
88 if player_hand.is_none() {
89 return (false, None, None);
90 }
91
92 let player_hand = player_hand.unwrap();
93 let can_claim_tile =
94 tile_claimed.is_some() && player_hand.len() < self.style.tiles_after_claim();
95
96 (can_claim_tile, tile_claimed, Some(player_hand))
97 }
98
99 pub fn get_possible_melds(&self, early_return: bool) -> Vec<PossibleMeld> {
100 let mut melds: Vec<PossibleMeld> = vec![];
101 let mut players = self.players.clone();
102
103 if early_return {
104 players.shuffle();
105 }
106
107 for player in &players.0 {
108 let mut player_melds = self.get_possible_melds_for_player(player, true);
109
110 if early_return && !player_melds.is_empty() {
111 return player_melds;
112 }
113
114 melds.append(&mut player_melds);
115 }
116
117 for player in &players.0 {
118 let mut player_melds = self.get_possible_melds_for_player(player, false);
119
120 if early_return && !player_melds.is_empty() {
121 return player_melds;
122 }
123
124 melds.append(&mut player_melds);
125 }
126
127 melds
128 }
129
130 pub fn get_possible_melds_by_discard(&self) -> Vec<PossibleMeld> {
131 let mut melds = self.get_possible_melds(false);
132
133 let player_index = self
134 .players
135 .iter()
136 .position(|p| self.table.hands.get(p).unwrap().len() == self.style.tiles_after_claim());
137
138 if player_index.is_none() {
139 return melds;
140 }
141
142 let player_index = player_index.unwrap();
143 let player_id = self.players.get(player_index).unwrap().clone();
144
145 let player_hand: Vec<HandTile> = self
146 .table
147 .hands
148 .get(&player_id)
149 .unwrap()
150 .list
151 .into_iter()
152 .filter(|t| t.set_id.is_none())
153 .collect();
154
155 player_hand.iter().for_each(|hand_tile| {
156 let mut game_copy = self.clone();
157
158 game_copy
159 .discard_tile_to_board(&hand_tile.id)
160 .unwrap_or_default();
161
162 let new_melds = game_copy.get_possible_melds(false);
163
164 new_melds
165 .iter()
166 .filter(|m| m.player_id != player_id)
167 .for_each(|meld| {
168 if !meld.tiles.iter().any(|t| t == &hand_tile.id) {
169 return;
170 }
171
172 melds.push(PossibleMeld {
173 discard_tile: Some(hand_tile.id),
174 is_concealed: meld.is_concealed,
175 is_mahjong: meld.is_mahjong,
176 is_upgrade: meld.is_upgrade,
177 player_id: meld.player_id.clone(),
178 tiles: meld.tiles.clone(),
179 });
180 });
181 });
182
183 melds
184 }
185
186 pub fn get_current_player(&self) -> Option<PlayerId> {
187 self.players.get(self.round.player_index).cloned()
188 }
189
190 pub fn get_board_tile_player_diff(
191 &self,
192 round: Option<&Round>,
193 hand: Option<&Hand>,
194 player_id: &PlayerId,
195 ) -> PlayerDiff {
196 let round = round.unwrap_or(&self.round);
197 let tile_claimed = round.tile_claimed.clone();
198
199 if let Some(tile_claimed) = tile_claimed {
200 tile_claimed.by?;
201
202 let hand = match hand {
203 Some(hand) => hand,
204 None => self.table.hands.0.get(player_id).unwrap(),
205 };
206 if !hand.list.iter().any(|h| h.id == tile_claimed.id) {
207 return None;
208 }
209
210 let player_index = self.players.iter().position(|p| p == player_id);
211 let other_player_index = self.players.iter().position(|p| *p == tile_claimed.from);
212
213 if player_index.is_none() || other_player_index.is_none() {
214 return None;
215 }
216
217 let player_index = player_index.unwrap();
218 let other_player_index = other_player_index.unwrap();
219 let raw_diff = player_index as i32 - other_player_index as i32;
220
221 return Some(if raw_diff == -3 { 1 } else { raw_diff });
222 }
223
224 None
225 }
226
227 pub fn get_dealer(&self) -> Option<&PlayerId> {
228 self.players.get(self.round.dealer_player_index)
229 }
230
231 pub fn get_player_wind(&self) -> Wind {
232 let current_player = self.get_current_player().unwrap();
233
234 self.round.get_player_wind(&self.players.0, ¤t_player)
235 }
236}
237
238impl Game {
239 pub fn set_players(&mut self, players: &Players) {
240 if players.len() != self.players.len() {
241 return;
242 }
243
244 let current_players = self.players.clone();
245
246 players.clone_into(&mut self.players);
247
248 for (index, player_id) in self.players.iter().enumerate() {
249 let current_player = current_players.get(index).unwrap();
250 let player_hand = self.table.hands.remove(current_player);
251 let score = self.score.remove(current_player);
252
253 self.table.hands.insert(player_id, player_hand);
254 self.score.insert(player_id, score);
255 }
256 }
257
258 pub fn say_mahjong(&mut self, player_id: &PlayerId) -> Result<(), CanSayMahjongError> {
259 let hand = self.table.hands.get(player_id).unwrap();
260
261 hand.can_say_mahjong()?;
262
263 self.calculate_hand_score(player_id);
264 let player_index = self.players.iter().position(|p| p == player_id);
265
266 if player_index.is_none() {
267 return Err(CanSayMahjongError::PlayerNotFound);
268 }
269
270 let player_index = player_index.unwrap();
271
272 self.round.move_after_win(&mut self.phase, player_index);
273
274 if self.phase != GamePhase::End {
275 self.phase = GamePhase::InitialShuffle;
276 }
277
278 Ok(())
279 }
280
281 pub fn pass_null_round(&mut self) -> Result<(), PassNullRoundError> {
282 if self.table.draw_wall.can_draw() {
283 return Err(PassNullRoundError::WallNotEmpty);
284 }
285
286 if self.round.tile_claimed.is_some() {
287 for hand in self.table.hands.0.values() {
288 if hand.can_drop_tile() {
289 return Err(PassNullRoundError::HandCanDropTile);
290 }
291
292 if hand.can_say_mahjong().is_ok() {
293 return Err(PassNullRoundError::HandCanSayMahjong);
294 }
295 }
296 }
297
298 self.round.move_after_draw(&mut self.phase);
299
300 if self.phase != GamePhase::End {
301 self.phase = GamePhase::InitialShuffle;
302 }
303
304 Ok(())
305 }
306
307 pub fn start(&mut self, shuffle_players: bool) {
308 if self.phase != GamePhase::Beginning {
309 return;
310 }
311
312 self.phase = GamePhase::WaitingPlayers;
313
314 self.complete_players(shuffle_players).unwrap_or_default();
315 }
316
317 pub fn decide_dealer(&mut self) -> Result<(), DecideDealerError> {
318 let mut new_players = self.players.clone();
319 let winds = self.round.get_initial_winds_slice();
320
321 winds.iter(|(player_index, player_wind)| {
322 let wind_index = WINDS_ROUND_ORDER
323 .iter()
324 .position(|w| w == player_wind)
325 .unwrap();
326
327 new_players.0[wind_index].clone_from(&self.players.0[player_index]);
328 });
329
330 self.round.dealer_player_index = 0;
331 self.round.east_player_index = 0;
332 self.round.player_index = 0;
333 self.round.tile_claimed = None;
334 self.players = new_players;
335
336 self.phase = GamePhase::InitialShuffle;
337
338 Ok(())
339 }
340
341 pub fn prepare_table(&mut self, with_dead_wall: bool) {
342 self.table = DEFAULT_DECK.create_table(&self.players);
343 self.table.draw_wall.position_tiles(Some(PositionTilesOpts {
344 shuffle: Some(true),
345 dead_wall: Some(with_dead_wall),
346 }));
347 self.phase = GamePhase::InitialDraw;
348 }
349
350 fn draw_tile_for_player(&mut self, player_id: &PlayerId) -> Result<(), DrawError> {
351 let player_wind = self.round.get_player_wind(&self.players.0, player_id);
352
353 loop {
354 let tile_id = self.table.draw_wall.pop_for_wind(&player_wind);
355
356 if tile_id.is_none() {
357 return Err(DrawError::NotEnoughTiles);
358 }
359
360 let tile_id = tile_id.unwrap();
361 let is_bonus = DEFAULT_DECK.0[tile_id].is_bonus();
362
363 if is_bonus {
364 let bonus_tiles = self.table.bonus_tiles.get_or_create(player_id);
365
366 bonus_tiles.push(tile_id);
367 continue;
368 }
369
370 let hand = self.table.hands.0.get_mut(player_id).unwrap();
371 hand.push(HandTile::from_id(tile_id));
372 return Ok(());
373 }
374 }
375
376 pub fn initial_draw(&mut self) -> Result<(), DrawError> {
377 let tiles_after_claim = self.style.tiles_after_claim();
378
379 for player_id in self.players.0.clone() {
380 'loop_label: loop {
381 let hand = self.table.hands.0.get(&player_id).unwrap();
382 if hand.len() == tiles_after_claim - 1 {
383 break 'loop_label;
384 }
385
386 self.draw_tile_for_player(&player_id)?;
387 }
388 }
389
390 self.phase = GamePhase::Playing;
391
392 Ok(())
393 }
394
395 pub fn draw_tile_from_wall(&mut self) -> DrawTileResult {
396 if self.table.draw_wall.is_empty() {
397 return DrawTileResult::WallExhausted;
398 }
399
400 if self.round.wall_tile_drawn.is_some() {
401 return DrawTileResult::AlreadyDrawn;
402 }
403
404 let player_wind = self.get_player_wind();
405 let tile_id = self.table.draw_wall.pop_for_wind(&player_wind);
406
407 if tile_id.is_none() {
408 return DrawTileResult::WallExhausted;
409 }
410
411 let tile_id = tile_id.unwrap();
412
413 let tile = &DEFAULT_DECK.0[tile_id];
414
415 if tile.is_bonus() {
416 let bonus_tiles = self
417 .table
418 .bonus_tiles
419 .get_or_create(&self.get_current_player().unwrap());
420
421 bonus_tiles.push(tile_id);
422
423 return DrawTileResult::Bonus(tile_id);
424 }
425
426 let wall_tile_drawn = Some(tile_id);
427 self.round.wall_tile_drawn = wall_tile_drawn;
428 let player_id = self.get_current_player().unwrap();
429
430 let hand = self.table.hands.0.get_mut(&player_id).unwrap();
431 hand.push(HandTile::from_id(tile_id));
432
433 DrawTileResult::Normal(tile_id)
434 }
435
436 pub fn discard_tile_to_board(&mut self, tile_id: &TileId) -> Result<(), DiscardTileError> {
437 let player_with_max_tiles = self
438 .players
439 .iter()
440 .find(|p| self.table.hands.get(p).unwrap().len() == self.style.tiles_after_claim());
441
442 if player_with_max_tiles.is_none() {
443 return Err(DiscardTileError::NoPlayerCanDiscard);
444 }
445
446 let player_id = player_with_max_tiles.unwrap().clone();
447 let player_hand = self.table.hands.0.get_mut(&player_id).unwrap();
448 let tiles_with_id = player_hand
449 .list
450 .iter()
451 .filter(|t| t.id == *tile_id)
452 .collect::<Vec<_>>();
453 let tile_index = player_hand
454 .list
455 .iter()
456 .position(|t| &t.id == tile_id && (tiles_with_id.len() == 1 || t.set_id.is_none()));
457
458 if tile_index.is_none() {
459 return Err(DiscardTileError::PlayerHasNoTile);
460 }
461
462 let tile_index = tile_index.unwrap();
463 let tile = player_hand.get(tile_index);
464
465 if !tile.concealed {
466 return Err(DiscardTileError::TileIsExposed);
467 }
468
469 if tile.set_id.is_some() {
470 return Err(DiscardTileError::TileIsPartOfMeld);
471 }
472
473 if let Some(tile_claimed) = self.round.tile_claimed.clone() {
474 if let Some(by) = tile_claimed.by {
475 if by == player_id
476 && tile.id != tile_claimed.id
477 && player_hand
478 .list
479 .iter()
480 .find(|t| t.id == tile_claimed.id)
481 .unwrap()
482 .set_id
483 .is_none()
484 {
485 return Err(DiscardTileError::ClaimedAnotherTile);
486 }
487 }
488 }
489
490 self.table.board.0.push(tile.id);
491
492 self.round.tile_claimed = Some(RoundTileClaimed {
493 from: player_id.clone(),
494 id: tile.id,
495 by: None,
496 });
497
498 player_hand.list.remove(tile_index);
499
500 Ok(())
501 }
502
503 pub fn create_meld(
504 &mut self,
505 player_id: &PlayerId,
506 tiles: &[TileId],
507 is_upgrade: bool,
508 is_concealed: bool,
509 ) -> Result<(), CreateMeldError> {
510 let tiles_set = tiles.iter().cloned().collect::<FxHashSet<TileId>>();
511 let hand = self.table.hands.get(player_id);
512 let sub_hand_tiles = hand
513 .unwrap()
514 .list
515 .iter()
516 .filter(|t| tiles_set.contains(&t.id))
517 .cloned()
518 .collect::<Vec<HandTile>>();
519
520 if !is_upgrade
521 && sub_hand_tiles
522 .iter()
523 .any(|t| t.set_id.is_some() || !t.concealed)
524 {
525 return Err(CreateMeldError::TileIsPartOfMeld);
526 }
527
528 let sub_hand = Hand::new(sub_hand_tiles);
529
530 let board_tile_player_diff =
531 self.get_board_tile_player_diff(None, Some(&sub_hand), player_id);
532
533 let opts_claimed_tile = get_tile_claimed_id_for_user(player_id, &self.round.tile_claimed);
534 let tiles_full: Vec<&Tile> = tiles.iter().map(|t| &DEFAULT_DECK.0[*t]).collect();
535
536 let opts = SetCheckOpts {
537 board_tile_player_diff,
538 claimed_tile: opts_claimed_tile,
539 sub_hand: &tiles_full,
540 };
541
542 let mut is_kong = false;
543
544 if get_is_pung(&opts) || get_is_chow(&opts) || {
545 is_kong = get_is_kong(&opts);
546 is_kong
547 } {
548 if (is_upgrade && !is_kong) || (is_concealed && opts_claimed_tile.is_some()) {
549 return Err(CreateMeldError::NotMeld);
550 }
551
552 let set_id = Uuid::new_v4().to_string();
553 let player_hand = self.table.hands.0.get_mut(player_id).unwrap();
554
555 for tile in tiles.iter() {
556 let tile = player_hand.list.iter().find(|t| t.id == *tile);
557
558 if tile.is_none() {
559 return Err(CreateMeldError::NotMeld);
560 }
561 }
562
563 player_hand
564 .list
565 .iter_mut()
566 .filter(|t| tiles.contains(&t.id))
567 .for_each(|tile| {
568 tile.concealed = is_concealed;
569 tile.set_id = Some(set_id.clone());
570 });
571
572 if is_kong {
573 let moved_tile = player_hand
574 .list
575 .iter()
576 .find(|t| t.set_id == Some(set_id.clone()))
577 .unwrap()
578 .clone();
579
580 self.draw_tile_for_player(player_id)
581 .map_err(|_| match self.pass_null_round() {
582 Ok(_) => CreateMeldError::EndRound,
583 Err(_) => CreateMeldError::NotMeld,
584 })?;
585
586 let next_player_hand = self.table.hands.0.get_mut(player_id).unwrap();
587
588 let position = next_player_hand
589 .list
590 .iter()
591 .position(|t| t.id == moved_tile.id)
592 .unwrap();
593 next_player_hand.list.remove(position);
594 next_player_hand.kong_tiles.insert(KongTile {
595 set_id: set_id.clone(),
596 concealed: is_concealed,
597 id: moved_tile.id,
598 });
599 }
600
601 return Ok(());
602 }
603
604 Err(CreateMeldError::NotMeld)
605 }
606
607 pub fn break_meld(
608 &mut self,
609 player_id: &PlayerId,
610 set_id: &String,
611 ) -> Result<(), BreakMeldError> {
612 let hand = self.table.hands.0.get_mut(player_id);
613
614 if hand.is_none() {
615 return Err(BreakMeldError::MissingHand);
616 }
617
618 let hand = hand.unwrap();
619
620 if hand.kong_tiles.iter().any(|t| t.set_id == set_id.clone()) {
621 return Err(BreakMeldError::MeldIsKong);
622 }
623
624 for hand_tile in hand.list.iter_mut() {
625 if hand_tile.set_id.is_some() && hand_tile.set_id.as_ref() == Some(set_id) {
626 if !hand_tile.concealed {
627 return Err(BreakMeldError::TileIsExposed);
628 }
629
630 hand_tile.set_id = None;
631 }
632 }
633
634 Ok(())
635 }
636
637 pub fn claim_tile(&mut self, player_id: &PlayerId) -> bool {
638 let player_hand = self.table.hands.0.get_mut(player_id);
639 if player_hand.is_none() {
640 return false;
641 }
642 let player_hand = player_hand.unwrap();
643
644 if player_hand.len() != self.style.tiles_after_claim() - 1
645 || self.round.tile_claimed.is_none()
646 || self.table.board.0.is_empty()
647 {
648 return false;
649 }
650
651 let tile = self.table.board.0.pop().unwrap();
652
653 let mut tile_claimed = self.round.tile_claimed.clone().unwrap();
654 tile_claimed.by = Some(player_id.clone());
655
656 self.round.tile_claimed = Some(tile_claimed);
657 self.round.player_index = self.players.iter().position(|p| p == player_id).unwrap();
658
659 player_hand.push(HandTile {
660 concealed: true,
661 id: tile,
662 set_id: None,
663 });
664
665 true
666 }
667
668 pub fn update_version(&mut self) {
669 self.version = Uuid::new_v4().to_string();
670 }
671
672 pub fn update_id(&mut self, id: Option<&str>) {
673 self.id = id.map_or_else(
674 || Uuid::new_v4().to_string(),
675 |inner_id| inner_id.to_string(),
676 );
677 }
678
679 pub fn complete_players(&mut self, shuffle_players: bool) -> Result<(), &'static str> {
680 if self.phase != GamePhase::WaitingPlayers {
681 return Err("Game is not waiting for players");
682 }
683
684 let players_num = Self::get_players_num(&self.style);
685
686 if self.players.len() != players_num {
687 return Err("Not enough players");
688 }
689
690 if shuffle_players {
691 self.players.shuffle();
692 }
693
694 for player_id in self.players.0.clone() {
695 self.table.hands.insert(player_id.clone(), Hand::default());
696 self.score.insert(player_id, 0);
697 }
698
699 self.phase = GamePhase::DecidingDealer;
700
701 Ok(())
702 }
703
704 pub fn set_wind_for_player(&mut self, player_id: &PlayerId, wind: &Wind) {
705 let current_player_with_wind = self
706 .players
707 .0
708 .iter()
709 .find(|p| self.round.get_player_wind(&self.players.0, p) == *wind)
710 .unwrap()
711 .clone();
712
713 if ¤t_player_with_wind == player_id {
714 return;
715 }
716
717 self.players.swap(player_id, ¤t_player_with_wind);
718 }
719}