1use crate::{
2 deck::DEFAULT_DECK,
3 game::GameStyle,
4 meld::{
5 get_is_chow, get_is_kong, get_is_pair, get_is_pung, MeldType, PlayerDiff, PossibleMeld,
6 SetCheckOpts,
7 },
8 PlayerId, Tile, TileId,
9};
10use rustc_hash::{FxHashMap, FxHashSet};
11use serde::{Deserialize, Serialize};
12use strum_macros::EnumIter;
13use ts_rs::TS;
14
15pub type SetIdContent = String;
16pub type SetId = Option<SetIdContent>;
17
18#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
19pub struct HandPossibleMeld {
20 pub is_mahjong: bool,
21 pub is_concealed: bool,
22 pub is_upgrade: bool,
23 pub tiles: Vec<TileId>,
24}
25
26impl From<PossibleMeld> for HandPossibleMeld {
27 fn from(meld: PossibleMeld) -> Self {
28 Self {
29 is_concealed: meld.is_concealed,
30 is_mahjong: meld.is_mahjong,
31 is_upgrade: meld.is_upgrade,
32 tiles: meld.tiles.clone(),
33 }
34 }
35}
36
37#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, TS)]
38#[ts(export)]
39pub struct KongTile {
40 pub concealed: bool,
41 pub id: TileId,
42 pub set_id: SetIdContent,
43}
44
45#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, TS)]
46#[ts(export)]
47pub struct HandTile {
48 pub concealed: bool,
49 pub id: TileId,
50 pub set_id: SetId,
51}
52
53impl HandTile {
54 pub fn from_id(id: TileId) -> Self {
55 Self {
56 id,
57 set_id: None,
58 concealed: true,
59 }
60 }
61 pub fn from_tile(tile: &Tile) -> Self {
62 Self {
63 id: tile.get_id(),
64 set_id: None,
65 concealed: true,
66 }
67 }
68}
69
70#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, TS)]
71pub struct Hand {
72 pub list: Vec<HandTile>,
73 pub kong_tiles: FxHashSet<KongTile>,
74 #[serde(skip)]
75 pub style: Option<GameStyle>,
76}
77
78#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, TS)]
79#[ts(export)]
80pub struct HandMeld {
81 pub meld_type: MeldType,
82 pub tiles: Vec<TileId>,
83}
84
85#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq, TS)]
86#[ts(export)]
87pub struct HandMelds {
88 pub melds: Vec<HandMeld>,
89 pub tiles_without_meld: usize,
90}
91
92impl Hand {
94 pub fn get(&self, index: usize) -> &HandTile {
95 self.list.get(index).unwrap()
96 }
97
98 pub fn len(&self) -> usize {
99 self.list.len()
100 }
101
102 pub fn is_empty(&self) -> bool {
103 self.list.is_empty()
104 }
105
106 pub fn push(&mut self, tile: HandTile) {
107 self.list.push(tile)
108 }
109}
110
111#[derive(Debug, EnumIter, Eq, PartialEq, Clone)]
112pub enum SortHandError {
113 NotSortedMissingTile,
114}
115
116#[derive(Debug, EnumIter, Eq, PartialEq, Clone)]
117pub enum CanSayMahjongError {
118 CantDrop,
119 NotPair,
120 PlayerNotFound,
121}
122
123impl Hand {
124 pub fn new(list: Vec<HandTile>) -> Self {
125 Self {
126 list,
127 style: None,
128 kong_tiles: FxHashSet::default(),
129 }
130 }
131
132 pub fn from_ref_vec(tiles: &[&HandTile]) -> Self {
133 Self {
134 kong_tiles: FxHashSet::default(),
135 list: tiles.iter().cloned().cloned().collect(),
136 style: None,
137 }
138 }
139
140 pub fn from_ids(tiles: &[TileId]) -> Self {
141 Self {
142 list: tiles.iter().cloned().map(HandTile::from_id).collect(),
143 kong_tiles: FxHashSet::default(),
144 style: None,
145 }
146 }
147
148 pub fn sort_default(&mut self) {
149 self.list.sort_by(|a, b| {
150 let tile_a = &DEFAULT_DECK.0.get(a.id);
151 let tile_b = &DEFAULT_DECK.0.get(b.id);
152
153 if tile_a.is_none() || tile_b.is_none() {
154 return std::cmp::Ordering::Equal;
155 }
156
157 tile_a.unwrap().cmp_custom(tile_b.unwrap())
158 });
159 }
160
161 pub fn sort_by_tiles(&mut self, tiles: &[TileId]) -> Result<(), SortHandError> {
163 let hand_copy = self
164 .list
165 .clone()
166 .iter()
167 .map(|t| t.id)
168 .collect::<Vec<TileId>>();
169 let hand_set = hand_copy.iter().collect::<FxHashSet<&TileId>>();
170
171 if tiles.iter().any(|t| !hand_set.contains(&t)) {
172 return Err(SortHandError::NotSortedMissingTile);
173 }
174
175 self.list.sort_by(|a, b| {
176 let tile_a = tiles.iter().position(|t| *t == a.id);
177 let tile_b = tiles.iter().position(|t| *t == b.id);
178
179 if tile_a.is_none() && tile_b.is_none() {
180 return std::cmp::Ordering::Equal;
181 }
182
183 if tile_a.is_none() {
184 return std::cmp::Ordering::Greater;
185 }
186
187 if tile_b.is_none() {
188 return std::cmp::Ordering::Less;
189 }
190
191 let (tile_a, tile_b) = (tile_a.unwrap(), tile_b.unwrap());
192
193 tile_a.cmp(&tile_b)
194 });
195
196 Ok(())
197 }
198
199 pub fn get_melds(&self) -> HandMelds {
200 let mut melds = HandMelds::default();
201 let sets_groups = self.get_sets_groups();
202
203 for (set, tiles) in sets_groups.iter() {
204 if set.is_none() {
205 melds.tiles_without_meld = tiles.len();
206 continue;
207 }
208
209 let meld_type = MeldType::from_tiles(tiles);
210
211 if meld_type.is_none() {
212 continue;
213 }
214
215 let meld_type = meld_type.unwrap();
216
217 melds.melds.push(HandMeld {
218 meld_type,
219 tiles: tiles.clone(),
220 });
221 }
222
223 melds
224 }
225
226 pub fn can_say_mahjong(&self) -> Result<(), CanSayMahjongError> {
227 if !self.can_drop_tile() {
228 return Err(CanSayMahjongError::CantDrop);
229 }
230
231 let tiles_without_meld: Vec<&Tile> = self
232 .list
233 .iter()
234 .filter(|t| t.set_id.is_none())
235 .map(|t| &DEFAULT_DECK.0[t.id])
236 .collect();
237
238 let is_pair = get_is_pair(&tiles_without_meld);
239
240 if !is_pair {
241 return Err(CanSayMahjongError::NotPair);
242 }
243
244 Ok(())
245 }
246
247 fn get_pungs_tiles(&self) -> Vec<(Tile, SetId)> {
248 let sets_ids: FxHashSet<SetIdContent> =
249 self.list.iter().filter_map(|t| t.set_id.clone()).collect();
250 let mut pungs: Vec<(Tile, SetId)> = vec![];
251 let existing_kongs = self
252 .kong_tiles
253 .iter()
254 .map(|t| t.set_id.clone())
255 .collect::<FxHashSet<SetIdContent>>();
256
257 for set_id in sets_ids {
258 let tiles: Vec<&Tile> = self
259 .list
260 .iter()
261 .filter(|t| t.set_id == Some(set_id.clone()))
262 .map(|t| &DEFAULT_DECK.0[t.id])
263 .collect();
264
265 let is_pung = get_is_pung(&SetCheckOpts {
266 board_tile_player_diff: PlayerDiff::default(),
267 claimed_tile: None,
268 sub_hand: &tiles,
269 });
270
271 if is_pung && !existing_kongs.contains(&set_id) {
272 pungs.push((tiles[0].clone(), Some(set_id)));
273 }
274 }
275
276 pungs
277 }
278
279 pub fn get_possible_melds(
280 &self,
281 board_tile_player_diff: PlayerDiff,
282 claimed_tile: Option<TileId>,
283 check_for_mahjong: bool,
284 ) -> Vec<HandPossibleMeld> {
285 let hand_filtered: Vec<&HandTile> =
286 self.list.iter().filter(|h| h.set_id.is_none()).collect();
287 let mut melds: Vec<HandPossibleMeld> = vec![];
288 let existing_pungs = self.get_pungs_tiles();
289
290 if check_for_mahjong {
291 if self.can_say_mahjong().is_ok() {
292 let tiles = self
293 .list
294 .iter()
295 .filter(|t| t.set_id.is_none())
296 .map(|t| t.id)
297 .collect();
298 let meld = HandPossibleMeld {
299 is_upgrade: false,
300 is_concealed: false,
301 is_mahjong: true,
302 tiles,
303 };
304
305 melds.push(meld);
306 }
307
308 return melds;
309 }
310
311 for first_tile_index in 0..hand_filtered.len() {
312 let first_tile = hand_filtered[first_tile_index].id;
313 let first_tile_full = &DEFAULT_DECK.0[first_tile];
314
315 for second_tile_index in (first_tile_index + 1)..hand_filtered.len() {
316 let second_tile = hand_filtered[second_tile_index].id;
317 let second_tile_full = &DEFAULT_DECK.0[second_tile];
318
319 if !first_tile_full.is_same_type(second_tile_full) {
320 continue;
321 }
322
323 for third_tile_index in (second_tile_index + 1)..hand_filtered.len() {
324 let third_tile = hand_filtered[third_tile_index].id;
325 let third_tile_full = &DEFAULT_DECK.0[third_tile];
326 if !first_tile_full.is_same_type(third_tile_full) {
327 continue;
328 }
329
330 let sub_hand = [first_tile_full, second_tile_full, third_tile_full];
331
332 let opts = SetCheckOpts {
333 board_tile_player_diff,
334 claimed_tile,
335 sub_hand: &sub_hand,
336 };
337
338 if get_is_pung(&opts) || get_is_chow(&opts) {
339 let is_concealed = claimed_tile.is_none();
340
341 let meld = HandPossibleMeld {
342 is_concealed,
343 is_mahjong: false,
344 is_upgrade: false,
345 tiles: vec![first_tile, second_tile, third_tile],
346 };
347 melds.push(meld);
348 }
349
350 for forth_tile in hand_filtered.iter().skip(third_tile_index + 1) {
351 let forth_tile_full = &DEFAULT_DECK.0[forth_tile.id];
352
353 let mut opts = opts.clone();
354 let sub_hand_inner = [
355 first_tile_full,
356 second_tile_full,
357 third_tile_full,
358 forth_tile_full,
359 ];
360 opts.sub_hand = &sub_hand_inner;
361
362 if get_is_kong(&opts) {
363 let is_concealed = claimed_tile.is_none();
364
365 let meld = HandPossibleMeld {
366 is_concealed,
367 is_mahjong: false,
368 is_upgrade: false,
369 tiles: vec![first_tile, second_tile, third_tile, forth_tile.id],
370 };
371 melds.push(meld);
372 }
373 }
374 }
375 }
376
377 for (concealed_pung_tile, set_id) in existing_pungs.iter() {
378 if first_tile_full.is_same_content(concealed_pung_tile) {
379 let is_concealed = claimed_tile.is_none();
380 let mut tiles: Vec<TileId> = self
381 .list
382 .iter()
383 .filter(|t| t.set_id == *set_id)
384 .map(|t| t.id)
385 .collect();
386 tiles.push(first_tile);
387
388 let meld = HandPossibleMeld {
389 is_mahjong: false,
390 is_upgrade: true,
391 is_concealed,
392 tiles,
393 };
394 melds.push(meld);
395 }
396 }
397 }
398
399 melds
400 }
401
402 pub fn get_has_tile(&self, tile_id: &TileId) -> bool {
403 self.list.iter().any(|t| t.id == *tile_id)
404 }
405
406 pub fn get_sets_groups(&self) -> FxHashMap<SetId, Vec<TileId>> {
407 let mut sets: FxHashMap<SetId, Vec<TileId>> = FxHashMap::default();
408
409 for tile in &self.list {
410 let set_id = tile.set_id.clone();
411
412 sets.entry(set_id.clone()).or_default().push(tile.id);
413 }
414
415 for kong_tile in &self.kong_tiles {
416 sets.entry(Some(kong_tile.set_id.clone()))
417 .or_default()
418 .push(kong_tile.id);
419 }
420
421 sets
422 }
423
424 pub fn can_drop_tile(&self) -> bool {
425 self.list.len()
426 == self
427 .style
428 .as_ref()
429 .unwrap_or(&GameStyle::HongKong)
430 .tiles_after_claim()
431 }
432}
433
434impl From<Hand> for Vec<TileId> {
435 fn from(hand: Hand) -> Self {
436 hand.list.iter().map(|t| t.id).collect()
437 }
438}
439
440pub type HandsMap = FxHashMap<PlayerId, Hand>;
441#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default, TS)]
442#[ts(export)]
443pub struct Hands(pub HandsMap);
444
445impl Hands {
447 pub fn get(&self, player: &PlayerId) -> Option<Hand> {
448 self.0.get(player).cloned()
449 }
450}
451
452impl Hands {
454 pub fn remove(&mut self, player: &PlayerId) -> Hand {
455 self.0.remove(player).unwrap()
456 }
457
458 pub fn insert(&mut self, player: impl AsRef<str>, hand: Hand) -> Option<Hand> {
459 self.0.insert(player.as_ref().to_string(), hand)
460 }
461}
462
463impl Hands {
464 pub fn get_player_hand_len(&self, player: &str) -> usize {
465 self.0.get(player).unwrap().len()
466 }
467
468 pub fn get_style(&self) -> GameStyle {
469 self.0
470 .values()
471 .next()
472 .cloned()
473 .unwrap()
474 .style
475 .unwrap_or_default()
476 }
477}
478
479impl Hands {
480 pub fn insert_ids(&mut self, player: &str, tiles: &[TileId]) -> &mut Self {
481 self.0.insert(player.to_string(), Hand::from_ids(tiles));
482 self
483 }
484
485 pub fn sort_player_hand(&mut self, player: &PlayerId) {
486 let mut hand = self.0.get(player).unwrap().clone();
487 hand.sort_default();
488 self.0.insert(player.clone(), hand);
489 }
490}