mahjong_core/table/
draw_wall.rs

1use crate::{TileId, Wind, WINDS_ROUND_ORDER};
2use rand::seq::SliceRandom;
3use rand::thread_rng;
4use rustc_hash::FxHashMap;
5use serde::{Deserialize, Serialize};
6use std::{
7    fmt::{self, Display, Formatter},
8    str::FromStr,
9};
10use ts_rs::TS;
11
12pub enum DrawWallPlace {
13    Segment(Wind),
14    DeadWall,
15    Unordered,
16}
17
18impl Display for DrawWallPlace {
19    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
20        match self {
21            Self::Segment(wind) => write!(f, "Segment({})", wind),
22            Self::DeadWall => write!(f, "DeadWall"),
23            Self::Unordered => write!(f, "Unordered"),
24        }
25    }
26}
27
28impl FromStr for DrawWallPlace {
29    type Err = ();
30
31    fn from_str(s: &str) -> Result<Self, Self::Err> {
32        match s {
33            "Segment(東)" => Ok(Self::Segment(Wind::East)),
34            "Segment(南)" => Ok(Self::Segment(Wind::South)),
35            "Segment(西)" => Ok(Self::Segment(Wind::West)),
36            "Segment(北)" => Ok(Self::Segment(Wind::North)),
37            "DeadWall" => Ok(Self::DeadWall),
38            "Unordered" => Ok(Self::Unordered),
39            _ => Err(()),
40        }
41    }
42}
43
44#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, TS)]
45#[ts(export)]
46pub struct WallSegment(Vec<TileId>);
47
48#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, TS)]
49#[ts(export)]
50pub struct DrawWall {
51    segments: FxHashMap<Wind, WallSegment>,
52    dead_wall: WallSegment,
53    unordered: Vec<TileId>,
54}
55
56pub struct PositionTilesOpts {
57    pub shuffle: Option<bool>,
58    pub dead_wall: Option<bool>,
59}
60
61impl DrawWall {
62    pub fn new(tiles: Vec<TileId>) -> Self {
63        Self {
64            segments: FxHashMap::default(),
65            dead_wall: WallSegment::default(),
66            unordered: tiles,
67        }
68    }
69
70    pub fn new_full(tiles: Vec<(TileId, DrawWallPlace)>) -> Self {
71        let mut wall = Self::default();
72
73        for (tile, place) in tiles {
74            match place {
75                DrawWallPlace::Segment(wind) => {
76                    let segment = wall
77                        .segments
78                        .entry(wind)
79                        .or_insert_with(WallSegment::default);
80                    segment.0.push(tile);
81                }
82                DrawWallPlace::DeadWall => {
83                    wall.dead_wall.0.push(tile);
84                }
85                DrawWallPlace::Unordered => {
86                    wall.unordered.push(tile);
87                }
88            }
89        }
90
91        wall
92    }
93
94    pub fn can_draw(&self) -> bool {
95        for segment in self.segments.values() {
96            if !segment.0.is_empty() {
97                return true;
98            }
99        }
100
101        false
102    }
103
104    pub fn len(&self) -> usize {
105        self.segments
106            .values()
107            .map(|segment| segment.0.len())
108            .sum::<usize>()
109    }
110
111    pub fn is_empty(&self) -> bool {
112        self.len() == 0
113    }
114
115    pub fn iter_all<'a>(
116        &'a self,
117        vec: &'a mut Vec<(TileId, DrawWallPlace)>,
118    ) -> impl DoubleEndedIterator<Item = &'a (TileId, DrawWallPlace)> {
119        let mut wall_copy = self.clone();
120
121        for (wind, segment) in wall_copy.segments.iter_mut() {
122            for tile in segment.0.iter() {
123                vec.push((*tile, DrawWallPlace::Segment(wind.clone())));
124            }
125        }
126
127        let mut dead_wall = wall_copy.dead_wall.clone();
128        while let Some(tile) = dead_wall.0.pop() {
129            vec.push((tile, DrawWallPlace::DeadWall));
130        }
131
132        let mut unordered = wall_copy.unordered.clone();
133        while let Some(tile) = unordered.pop() {
134            vec.push((tile, DrawWallPlace::Unordered));
135        }
136
137        vec.iter()
138    }
139
140    pub fn get_next(&self, wind: &Wind) -> Option<&TileId> {
141        let mut wind_index = WINDS_ROUND_ORDER.iter().position(|w| w == wind).unwrap();
142        for _ in 0..WINDS_ROUND_ORDER.len() {
143            let current_wind = WINDS_ROUND_ORDER.get(wind_index).unwrap();
144            let segment = self.segments.get(current_wind);
145            if let Some(segment) = segment {
146                if !segment.0.is_empty() {
147                    return segment.0.last();
148                }
149            }
150            wind_index = (wind_index + 1) % WINDS_ROUND_ORDER.len();
151        }
152
153        None
154    }
155}
156
157impl DrawWall {
158    pub fn position_tiles(&mut self, opts: Option<PositionTilesOpts>) {
159        let mut use_dead_wall = false;
160        if let Some(opts) = opts {
161            if let Some(shuffle) = opts.shuffle {
162                if shuffle {
163                    self.unordered.shuffle(&mut thread_rng());
164                }
165            }
166            if let Some(dead_wall) = opts.dead_wall {
167                if dead_wall {
168                    use_dead_wall = true;
169                }
170            }
171        }
172
173        let mut current_wind_index = 0;
174        let remaining_tiles = if use_dead_wall { 14 } else { 0 };
175        while self.unordered.len() > remaining_tiles {
176            let tile = self.unordered.pop().unwrap();
177            let wind = WINDS_ROUND_ORDER.get(current_wind_index).unwrap().clone();
178
179            let segment = self.segments.entry(wind).or_default();
180
181            segment.0.push(tile);
182            current_wind_index = (current_wind_index + 1) % WINDS_ROUND_ORDER.len();
183        }
184
185        if use_dead_wall {
186            self.dead_wall.0.clone_from(&self.unordered);
187        }
188
189        self.unordered.sort();
190    }
191
192    pub fn pop_for_wind(&mut self, wind: &Wind) -> Option<TileId> {
193        let mut wind_index = WINDS_ROUND_ORDER.iter().position(|w| w == wind)?;
194
195        for _ in 0..WINDS_ROUND_ORDER.len() {
196            let loop_wind = WINDS_ROUND_ORDER.get(wind_index)?;
197            let segment = self.segments.get_mut(loop_wind)?;
198            if !segment.0.is_empty() {
199                return segment.0.pop();
200            }
201            wind_index = (wind_index + 1) % WINDS_ROUND_ORDER.len();
202        }
203
204        None
205    }
206
207    pub fn clear(&mut self) {
208        self.segments.clear();
209        self.dead_wall.0.clear();
210        self.unordered.clear();
211    }
212
213    pub fn replace_tail(&mut self, wind: &Wind, tile: &TileId) {
214        let mut wind_index = WINDS_ROUND_ORDER.iter().position(|w| w == wind).unwrap();
215        for _ in 0..WINDS_ROUND_ORDER.len() {
216            let current_wind = WINDS_ROUND_ORDER.get(wind_index).unwrap();
217            let segment = self.segments.get_mut(current_wind).unwrap();
218            if segment.0.is_empty() {
219                wind_index = (wind_index + 1) % WINDS_ROUND_ORDER.len();
220            } else {
221                segment.0.pop();
222                segment.0.push(*tile);
223                break;
224            }
225        }
226    }
227}