web_lib/offscreen_game/
mod.rs

1use js_sys::Date;
2use mahjong_core::{
3    deck::DEFAULT_DECK, score::ScoringRule, Game, Hand, HandTile, PlayerId, Players, TileId,
4};
5use offscreen_player::{OffscreenPlayer, OffscreenPlayers};
6use selecting_hand::{SelectingHand, SelectingHandTile};
7use serde::{Deserialize, Serialize};
8use wasm_bindgen::prelude::wasm_bindgen;
9pub use wrappers::{ScoringRuleWasm, WindWasm};
10
11mod offscreen_player;
12mod round_validation;
13mod selecting_hand;
14mod wrappers;
15
16#[wasm_bindgen]
17pub struct ScoreResult {
18    rules: Vec<ScoringRule>,
19    pub score: u32,
20}
21
22#[wasm_bindgen]
23impl ScoreResult {
24    #[wasm_bindgen(getter)]
25    pub fn rules(&self) -> Vec<ScoringRuleWasm> {
26        self.rules.iter().map(|r| r.clone().into()).collect()
27    }
28}
29
30#[wasm_bindgen]
31#[derive(Serialize, Deserialize)]
32pub struct OffscreenGame {
33    pub date_created: u64,
34    game: Game,
35    players: OffscreenPlayers,
36}
37
38#[wasm_bindgen]
39impl OffscreenGame {
40    #[wasm_bindgen(constructor)]
41    #[allow(clippy::new_without_default)]
42    pub fn new() -> Self {
43        let mut game = Game::new(None);
44        game.start(false);
45
46        let players: OffscreenPlayers = [1, 2, 3, 4]
47            .iter()
48            .map(|num| {
49                let mut player = OffscreenPlayer {
50                    id: Players::new_player(),
51                    ..Default::default()
52                };
53                player.name = format!("Player {}", num);
54                (player.id.clone(), player)
55            })
56            .collect();
57
58        players.iter().for_each(|(player_id, _)| {
59            game.players.push(player_id.clone());
60        });
61
62        game.complete_players(false).unwrap();
63        let date_created = Date::now() as u64;
64
65        Self {
66            date_created,
67            game,
68            players,
69        }
70    }
71
72    #[wasm_bindgen(getter)]
73    pub fn players(&self) -> Vec<OffscreenPlayer> {
74        self.game
75            .players
76            .0
77            .iter()
78            .map(|p| self.players.get(p).unwrap().clone())
79            .collect()
80    }
81
82    pub fn set_player(&mut self, id: PlayerId, player: OffscreenPlayer) {
83        if self.players.contains_key(&id) {
84            self.players.insert(id, player);
85        }
86    }
87
88    pub fn update_player_score(&mut self, player_id: PlayerId, score: u32) {
89        self.game.score.0.insert(player_id, score);
90    }
91
92    pub fn get_player_score(&self, player_id: PlayerId) -> u32 {
93        if !self.game.score.0.contains_key(&player_id) {
94            return 0;
95        }
96        *self.game.score.0.get(&player_id).unwrap()
97    }
98
99    pub fn is_dealer(&self, player_id: PlayerId) -> bool {
100        Some(self.game.round.dealer_player_index)
101            == self.game.players.0.iter().position(|p| p == &player_id)
102    }
103
104    pub fn get_wind(&self, player_id: PlayerId) -> WindWasm {
105        let wind = self
106            .game
107            .round
108            .get_player_wind(&self.game.players.0, &player_id);
109
110        wind.into()
111    }
112
113    pub fn set_dealer(&mut self, player_id: PlayerId) {
114        if let Some(index) = self.game.players.0.iter().position(|p| p == &player_id) {
115            self.game.round.dealer_player_index = index;
116        }
117    }
118
119    #[wasm_bindgen(getter)]
120    pub fn available_tiles_for_round(&self) -> Vec<TileId> {
121        let tiles_ids: Vec<TileId> = DEFAULT_DECK
122            .0
123            .iter()
124            .enumerate()
125            .map(|(i, _)| i as TileId)
126            .filter(|i| {
127                !self.game.players.0.iter().any(|p| {
128                    self.game
129                        .table
130                        .hands
131                        .get(p)
132                        .unwrap()
133                        .list
134                        .iter()
135                        .any(|t| t.id == *i)
136                })
137            })
138            .collect();
139        let hand_list = tiles_ids
140            .iter()
141            .map(|id| HandTile {
142                concealed: true,
143                id: *id,
144                set_id: None,
145            })
146            .collect();
147        let mut hand = Hand::new(hand_list);
148        hand.sort_default();
149        hand.list.iter().map(|t| t.id).collect()
150    }
151
152    pub fn selecting_hand(&self, player_id: PlayerId) -> SelectingHand {
153        let mut hand = self.game.table.hands.get(&player_id).unwrap().clone();
154        hand.sort_default();
155        let tiles = hand
156            .list
157            .iter()
158            .map(|t| SelectingHandTile {
159                concealed: t.concealed,
160                id: t.id,
161                set_id: t.set_id.clone(),
162            })
163            .collect::<Vec<SelectingHandTile>>();
164
165        SelectingHand { tiles }
166    }
167
168    pub fn select_tile_for_round(&mut self, player_id: PlayerId, tile_id: TileId) {
169        let hand = self.game.table.hands.0.get_mut(&player_id).unwrap();
170        let has_tile = hand.list.iter().any(|t| t.id == tile_id);
171
172        if has_tile {
173            hand.list.retain(|t| t.id != tile_id);
174        } else {
175            let hand_tile = HandTile {
176                concealed: true,
177                id: tile_id,
178                set_id: None,
179            };
180
181            hand.list.push(hand_tile);
182        }
183    }
184
185    pub fn update_score(&mut self, player_id: PlayerId) -> ScoreResult {
186        let (rules, score) = self.game.calculate_hand_score(&player_id);
187
188        ScoreResult { rules, score }
189    }
190
191    pub fn set_wind(&mut self, player_id: PlayerId, wind: WindWasm) {
192        self.game.set_wind_for_player(&player_id, &wind.into());
193    }
194
195    pub fn serialize(&self) -> String {
196        serde_json::to_string(self).unwrap()
197    }
198
199    pub fn deserialize(data: String) -> Self {
200        serde_json::from_str(&data).unwrap()
201    }
202}