mahjong_core/table/
draw_wall.rs1use 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}