mahjong_core/round/
mod.rs1pub use self::decide_dealer::{DecideDealerWinds, SetInitialWindsError};
2use crate::{
3 game::GameStyle, macros::derive_game_common, Game, GamePhase, Hands, PlayerId, TileId, Wind,
4 WINDS_ROUND_ORDER,
5};
6use serde::{Deserialize, Serialize};
7use strum_macros::EnumIter;
8use ts_rs::TS;
9
10mod decide_dealer;
11
12#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, TS)]
13#[ts(export)]
14pub struct RoundTileClaimed {
15 pub by: Option<PlayerId>,
16 pub from: PlayerId,
17 pub id: TileId,
18}
19
20pub type TileClaimed = Option<RoundTileClaimed>;
21
22derive_game_common! {
23#[derive(TS)]
24#[ts(export)]
25pub struct Round {
26 pub consecutive_same_seats: usize,
27 pub dealer_player_index: usize,
28 pub player_index: usize,
29 pub east_player_index: usize,
30 pub round_index: u32,
31 #[serde(skip)]
32 pub style: GameStyle,
33 pub tile_claimed: TileClaimed,
34 pub wall_tile_drawn: Option<TileId>,
35 pub wind: Wind,
36 pub initial_winds: Option<u8>,
37}}
38
39#[derive(Debug, EnumIter, Eq, PartialEq, Clone)]
40pub enum NextTurnError {
41 StuckWallTileNotDrawn,
42 StuckHandNotReady(PlayerId),
43}
44
45impl Round {
46 pub fn new(game_style: &GameStyle) -> Self {
47 Self {
49 consecutive_same_seats: 0,
50 dealer_player_index: 0,
51 player_index: 0,
52 round_index: 0,
53 style: game_style.clone(),
54 tile_claimed: None,
55 wall_tile_drawn: None,
56 wind: Wind::East,
57 east_player_index: 0,
58 initial_winds: None,
59 }
60 }
61
62 pub fn get_claimable_tile(&self, player_id: &PlayerId) -> Option<TileId> {
63 let tile_claimed = self.tile_claimed.clone()?;
64
65 if tile_claimed.by.is_some() || tile_claimed.from == *player_id {
66 return None;
67 }
68
69 Some(tile_claimed.id)
70 }
71
72 pub fn get_initial_winds_slice(&self) -> DecideDealerWinds {
73 DecideDealerWinds::from_number(self.initial_winds)
74 }
75
76 pub fn get_player_wind(&self, players: &[PlayerId], player_id: &PlayerId) -> Wind {
77 let player_index = players.iter().position(|p| p == player_id).unwrap();
78 let wind_index = (player_index + 4 - self.east_player_index) % 4;
79 WINDS_ROUND_ORDER[wind_index].clone()
80 }
81}
82
83impl Round {
84 pub fn next_turn(&mut self, hands: &Hands) -> Result<(), NextTurnError> {
85 if self.wall_tile_drawn.is_none() {
86 return Err(NextTurnError::StuckWallTileNotDrawn);
87 }
88
89 let expected_tiles = hands.get_style().tiles_after_claim() - 1;
90
91 for hand_player in hands.0.keys() {
92 let hand = hands.get(hand_player);
93 if hand.is_none() {
94 return Err(NextTurnError::StuckHandNotReady(hand_player.clone()));
95 }
96 let hand = hand.unwrap();
97 if hand.len() != expected_tiles {
98 return Err(NextTurnError::StuckHandNotReady(hand_player.clone()));
99 }
100 }
101
102 self.wall_tile_drawn = None;
103 self.tile_claimed = None;
104
105 self.player_index += 1;
106 if self.player_index == Game::get_players_num(&self.style) {
107 self.player_index = 0;
108 }
109
110 Ok(())
111 }
112
113 fn common_next_round(&mut self, phase: &mut GamePhase) {
114 let mut current_wind_index = WINDS_ROUND_ORDER
115 .iter()
116 .position(|r| r == &self.wind)
117 .unwrap();
118
119 self.consecutive_same_seats = 0;
120 self.dealer_player_index += 1;
121 if self.dealer_player_index == Game::get_players_num(&self.style) {
122 self.dealer_player_index = 0;
123 }
124
125 if self.dealer_player_index == self.east_player_index {
126 current_wind_index += 1;
127
128 if current_wind_index == WINDS_ROUND_ORDER.len() {
129 *phase = GamePhase::End;
130 return;
131 }
132
133 self.wind = WINDS_ROUND_ORDER.get(current_wind_index).unwrap().clone();
134 }
135
136 self.player_index = self.dealer_player_index;
137 }
138
139 pub fn move_after_win(&mut self, phase: &mut GamePhase, winner_player_index: usize) {
140 self.wall_tile_drawn = None;
141 self.tile_claimed = None;
142 self.round_index += 1;
143
144 let max_consecutive_same_seats = self.style.max_consecutive_same_seats();
145
146 if winner_player_index == self.dealer_player_index
147 && self.consecutive_same_seats < max_consecutive_same_seats
148 {
149 self.player_index = self.dealer_player_index;
150 self.consecutive_same_seats += 1;
151 return;
152 }
153
154 self.common_next_round(phase)
155 }
156
157 pub fn move_after_draw(&mut self, phase: &mut GamePhase) {
158 self.wall_tile_drawn = None;
159 self.tile_claimed = None;
160 self.round_index += 1;
161
162 let max_consecutive_same_seats = self.style.max_consecutive_same_seats();
163
164 if self.consecutive_same_seats < max_consecutive_same_seats {
165 self.player_index = self.dealer_player_index;
166 self.consecutive_same_seats += 1;
167 return;
168 }
169
170 self.common_next_round(phase)
171 }
172
173 pub fn set_initial_winds(
174 &mut self,
175 winds: Option<[Wind; 4]>,
176 ) -> Result<(), SetInitialWindsError> {
177 let winds = DecideDealerWinds::new(winds)?;
178 self.initial_winds = winds.to_number();
179 Ok(())
180 }
181}