1use crate::{
5 deck::DEFAULT_DECK, meld::MeldType, Flower, Game, PlayerId, Season, Tile, FLOWERS_ORDER,
6 SEASONS_ORDER, WINDS_ROUND_ORDER,
7};
8use rustc_hash::{FxHashMap, FxHashSet};
9use serde::{Deserialize, Serialize};
10use strum_macros::EnumIter;
11use ts_rs::TS;
12
13pub type ScoreItem = u32;
14pub type ScoreMap = FxHashMap<PlayerId, ScoreItem>;
15
16#[derive(Clone, Debug, Serialize, Deserialize, TS)]
17pub struct Score(pub ScoreMap);
18
19impl Score {
21 pub fn get(&self, player_id: &PlayerId) -> Option<&ScoreItem> {
22 self.0.get(player_id)
23 }
24
25 pub fn iter(&self) -> impl Iterator<Item = (&PlayerId, &ScoreItem)> {
26 self.0.iter()
27 }
28}
29
30impl Score {
32 pub fn insert(&mut self, player_id: impl AsRef<str>, score: ScoreItem) {
33 self.0.insert(player_id.as_ref().to_string(), score);
34 }
35
36 pub fn remove(&mut self, player_id: &PlayerId) -> ScoreItem {
37 self.0.remove(player_id).unwrap()
38 }
39}
40
41impl Score {
42 pub fn new(players: &Vec<PlayerId>) -> Self {
43 let mut score = ScoreMap::default();
44
45 for player_id in players {
46 score.insert(player_id.clone(), 0);
47 }
48
49 Self(score)
50 }
51}
52
53#[derive(Clone, Debug, PartialEq, Eq, EnumIter)]
54pub enum ScoringRule {
55 AllFlowers,
56 AllInTriplets,
57 AllSeasons,
58 BasePoint, CommonHand,
60 GreatDragons,
61 LastWallTile,
62 NoFlowersSeasons,
63 SeatFlower,
64 SeatSeason,
65 SelfDraw,
66 Purity,
67}
68
69impl Game {
70 fn get_scoring_rules_points(scoring_rules: &Vec<ScoringRule>) -> u32 {
71 enum ScoringRulePoints {
72 Addition(u32),
73 Multiplier(u32),
74 }
75 let mut round_points = 0;
76 let mut points: Vec<ScoringRulePoints> = vec![];
77
78 for rule in scoring_rules {
79 points.push(match rule {
80 ScoringRule::AllFlowers => ScoringRulePoints::Addition(2),
81 ScoringRule::AllInTriplets => ScoringRulePoints::Addition(3),
82 ScoringRule::AllSeasons => ScoringRulePoints::Addition(2),
83 ScoringRule::BasePoint => ScoringRulePoints::Addition(1),
84 ScoringRule::CommonHand => ScoringRulePoints::Addition(1),
85 ScoringRule::GreatDragons => ScoringRulePoints::Addition(8),
86 ScoringRule::LastWallTile => ScoringRulePoints::Addition(1),
87 ScoringRule::NoFlowersSeasons => ScoringRulePoints::Addition(1),
88 ScoringRule::SeatFlower => ScoringRulePoints::Addition(1),
89 ScoringRule::SeatSeason => ScoringRulePoints::Addition(1),
90 ScoringRule::SelfDraw => ScoringRulePoints::Addition(1),
91 ScoringRule::Purity => ScoringRulePoints::Multiplier(6),
92 });
93 }
94
95 round_points += points
96 .iter()
97 .filter_map(|point| {
98 if let ScoringRulePoints::Addition(value) = point {
99 Some(*value)
100 } else {
101 None
102 }
103 })
104 .sum::<u32>();
105 let multiplier = points
106 .iter()
107 .filter_map(|point| {
108 if let ScoringRulePoints::Multiplier(value) = point {
109 Some(*value)
110 } else {
111 None
112 }
113 })
114 .product::<u32>();
115 round_points *= multiplier.max(1);
116
117 round_points
118 }
119
120 fn get_scoring_rules(&self, winner_player: &PlayerId) -> Vec<ScoringRule> {
121 let mut rules = Vec::new();
122 rules.push(ScoringRule::BasePoint);
123 let empty_bonus = vec![];
124 let winner_hand = self.table.hands.0.get(winner_player).unwrap();
125 let winner_melds = winner_hand.get_melds();
126 let melds_without_pair = winner_melds
127 .melds
128 .iter()
129 .filter(|meld| meld.meld_type != MeldType::Pair)
130 .collect::<Vec<_>>();
131
132 let winner_bonus = self
133 .table
134 .bonus_tiles
135 .0
136 .get(winner_player)
137 .unwrap_or(&empty_bonus);
138
139 if winner_melds.melds.iter().all(|meld| {
140 let tile = &DEFAULT_DECK.0[meld.tiles[0]];
141
142 matches!(tile, Tile::Suit(_))
143 }) {
144 rules.push(ScoringRule::Purity);
145 }
146
147 if melds_without_pair
148 .iter()
149 .all(|meld| meld.meld_type == MeldType::Chow)
150 {
151 rules.push(ScoringRule::CommonHand);
152 }
153
154 if melds_without_pair
155 .iter()
156 .all(|meld| meld.meld_type == MeldType::Pung || meld.meld_type == MeldType::Kong)
157 {
158 rules.push(ScoringRule::AllInTriplets);
159 }
160
161 if melds_without_pair
162 .iter()
163 .filter(|meld| {
164 if meld.meld_type == MeldType::Chow {
165 return false;
166 }
167
168 let tile = &DEFAULT_DECK.0[meld.tiles[0]];
169
170 matches!(tile, Tile::Dragon(_))
171 })
172 .count()
173 == 3
174 {
175 rules.push(ScoringRule::GreatDragons);
176 }
177
178 if self.table.draw_wall.is_empty() {
179 rules.push(ScoringRule::LastWallTile);
180 }
181
182 if self.round.tile_claimed.is_none() {
183 rules.push(ScoringRule::SelfDraw);
184 }
185
186 let mut flowers: FxHashSet<Flower> = FxHashSet::default();
187 let mut seasons: FxHashSet<Season> = FxHashSet::default();
188
189 for tile_id in winner_bonus {
190 let tile = &DEFAULT_DECK.0[*tile_id];
191 match tile {
192 Tile::Flower(flower) => {
193 flowers.insert(flower.value.clone());
194 }
195 Tile::Season(season) => {
196 seasons.insert(season.value.clone());
197 }
198 _ => {}
199 }
200 }
201
202 if flowers.is_empty() && seasons.is_empty() {
203 rules.push(ScoringRule::NoFlowersSeasons);
204 } else {
205 if flowers.len() == 4 {
206 rules.push(ScoringRule::AllFlowers);
207 }
208
209 if seasons.len() == 4 {
210 rules.push(ScoringRule::AllSeasons);
211 }
212
213 let player_wind = self.round.get_player_wind(&self.players.0, winner_player);
214 let has_seat_flower = flowers.iter().any(|flower| {
215 let flower_index = FLOWERS_ORDER.iter().position(|f| f == flower).unwrap();
216 WINDS_ROUND_ORDER[flower_index] == player_wind
217 });
218 let has_seat_season = seasons.iter().any(|season| {
219 let season_index = SEASONS_ORDER.iter().position(|s| s == season).unwrap();
220 WINDS_ROUND_ORDER[season_index] == player_wind
221 });
222
223 if has_seat_flower {
224 rules.push(ScoringRule::SeatFlower);
225 }
226
227 if has_seat_season {
228 rules.push(ScoringRule::SeatSeason);
229 }
230 }
231
232 rules
233 }
234}
235
236impl Game {
237 pub fn calculate_hand_score(&mut self, winner_player: &PlayerId) -> (Vec<ScoringRule>, u32) {
238 {
239 let score = &mut self.score;
240 let current_player_score = score.get(winner_player);
241 if current_player_score.is_none() {
242 return (vec![], 0);
243 }
244 }
245
246 let scoring_rules = self.get_scoring_rules(winner_player);
247 let round_points = Self::get_scoring_rules_points(&scoring_rules);
248
249 let current_player_score = self.score.get(winner_player).unwrap();
250
251 self.score
252 .insert(winner_player, current_player_score + round_points);
253
254 (scoring_rules, round_points)
255 }
256}