marjapussi/game/
parse.rs

1use std::collections::HashMap;
2use std::io::{Error, ErrorKind};
3use std::iter::zip;
4
5use serde::Deserialize;
6use strum::IntoEnumIterator;
7
8use crate::game::cards::{Card, Suit, Value};
9use crate::game::gameevent::{ActionType, AnswerType, GameAction, QuestionType};
10use crate::game::gameinfo::GameFinishedInfo;
11use crate::game::player::PlaceAtTable;
12use crate::game::Game;
13
14pub fn parse_card(card: String) -> Result<Card, Error> {
15    if card.len() != 3 {
16        return Err(Error::new(ErrorKind::Other, "wrong card format"));
17    }
18    let suit_char = card.chars().next().unwrap();
19    let value_char = card.chars().last().unwrap();
20    let suits_str = "gesr".chars();
21    let values_str = "6789UOKZA".chars();
22
23    let mut suit: Option<Suit> = None;
24    let mut value: Option<Value> = None;
25
26    let suits = Suit::iter();
27    let values = Value::iter();
28
29    for (c, s) in zip(suits_str, suits) {
30        if c == suit_char {
31            suit = Some(s);
32        }
33    }
34
35    for (c, v) in zip(values_str, values) {
36        if c == value_char {
37            value = Some(v);
38        }
39    }
40
41    if value.is_none() || suit.is_none() {
42        return Err(Error::new(ErrorKind::Other, "Wrong card format"));
43    }
44
45    Ok(Card {
46        suit: suit.unwrap(),
47        value: value.unwrap(),
48    })
49}
50
51pub fn parse_cards(cards: Vec<String>) -> Vec<Card> {
52    cards.into_iter().map(|c| parse_card(c).unwrap()).collect()
53}
54
55#[derive(Debug, Deserialize, Clone)]
56pub struct LegacyGameFormat {
57    /// The id of the game found in '_id.$oid'
58    #[serde(rename = "_id")]
59    id: String,
60    name: String,
61    created: String,
62    started: String,
63    finished: String,
64    players: Vec<String>,
65    cards: HashMap<String, Vec<String>>,
66    game_value: i32,
67    actions: Vec<String>,
68    players_sup: HashMap<String, Vec<String>>,
69    players_points: HashMap<String, i32>,
70    tricks: Vec<Vec<String>>,
71    #[serde(default)]
72    schwarz_game: bool,
73}
74
75fn parse_action(action: String) -> Result<GameAction, Error> {
76    let err = Err(Error::new(
77        ErrorKind::Other,
78        format!("The action {} could not be parsed.", action),
79    ));
80
81    let parts: Vec<&str> = action.split(',').collect();
82    if parts.len() != 3 {
83        return err;
84    }
85    let player_seat = parts[0].parse::<u8>().unwrap();
86    let action_type = parts[1];
87    let action_value = parts[2];
88
89    let action_type: ActionType = match action_type {
90        "PROV" => {
91            let val = action_value.parse::<i32>().unwrap();
92            if val == 0 {
93                ActionType::StopBidding
94            } else {
95                ActionType::NewBid(val)
96            }
97        }
98        "PRMO" => ActionType::NewBid(action_value.parse::<i32>().unwrap()),
99        "TRCK" => {
100            let card = parse_card(action_value.to_string())?;
101            ActionType::CardPlayed(card)
102        }
103        "QUES" => parse_ques(action.clone()),
104        "ANSW" => parse_answ(action.clone()),
105        _ => return err,
106    };
107
108    Ok(GameAction {
109        action_type,
110        player: PlaceAtTable(player_seat),
111    })
112}
113
114fn parse_pass(actions: Vec<String>) -> GameAction {
115    let player_seat = &actions[0][0..1].parse::<u8>().unwrap();
116    let mut cards = vec![];
117    for action in actions {
118        let parts: Vec<&str> = action.split(',').collect();
119        let card = parse_card(parts[2].to_string()).unwrap();
120        cards.push(card);
121    }
122    cards.sort();
123    cards.reverse();
124    GameAction {
125        action_type: ActionType::Pass(cards),
126        player: PlaceAtTable(*player_seat),
127    }
128}
129
130fn parse_suit(suit: &str) -> Suit {
131    match suit {
132        "g" => Suit::Green,
133        "e" => Suit::Acorns,
134        "s" => Suit::Bells,
135        "r" => Suit::Red,
136        _ => Suit::Red,
137    }
138}
139
140fn parse_ques(action: String) -> ActionType {
141    let mut action_type = ActionType::Question(QuestionType::Yours);
142    if &action[7..9] == "my" {
143        let col = parse_suit(&action[9..10]);
144        action_type = ActionType::AnnounceTrump(col);
145    }
146    if &action[7..9] == "ou" {
147        let col = parse_suit(&action[9..10]);
148        action_type = ActionType::Question(QuestionType::YourHalf(col));
149    }
150    action_type
151}
152
153fn parse_answ(action: String) -> ActionType {
154    let mut action_type = ActionType::Answer(AnswerType::NoPair);
155    if &action[7..9] == "my" {
156        let col = parse_suit(&action[9..10]);
157        action_type = ActionType::Answer(AnswerType::YesPair(col));
158    }
159    if &action[7..9] == "no" {
160        let col = parse_suit(&action[9..10]);
161        action_type = ActionType::Answer(AnswerType::NoHalf(col));
162    }
163    if &action[7..9] == "ou" {
164        let col = parse_suit(&action[9..10]);
165        action_type = ActionType::Answer(AnswerType::YesHalf(col));
166    }
167    action_type
168}
169
170pub fn parse_legacy_format(game_data: LegacyGameFormat) -> Result<GameFinishedInfo, Error> {
171    let names: [String; 4] = game_data.players.clone().try_into().unwrap();
172
173    let cards = [
174        parse_cards(game_data.cards.get(&names[0]).unwrap().clone()),
175        parse_cards(game_data.cards.get(&names[1]).unwrap().clone()),
176        parse_cards(game_data.cards.get(&names[2]).unwrap().clone()),
177        parse_cards(game_data.cards.get(&names[3]).unwrap().clone()),
178    ];
179
180    let mut game_replay = Game::new(game_data.name, names, Some(cards));
181
182    for _ in 0..4 {
183        game_replay.apply_action_mut(game_replay.legal_actions[0].clone());
184    }
185
186    let mut pass_collect = vec![];
187    for action in game_data.actions {
188        //println!("{:?}", game_replay.legal_actions);
189        //println!("{}", action.clone());
190        if pass_collect.len() == 4 {
191            let pass_action = parse_pass(pass_collect.clone());
192            //println!("{:?}", pass_action);
193            game_replay.apply_action_mut(pass_action);
194            pass_collect = vec![];
195        }
196        if &action[2..6] == "PASS" || &action[2..6] == "PBCK" {
197            pass_collect.push(action.clone());
198            continue;
199        }
200        let new_action = parse_action(action.clone())?;
201        //println!("{:?}", new_action);
202        if new_action.action_type == ActionType::NewBid(0) {
203            continue;
204        }
205        game_replay.apply_action_mut(new_action);
206    }
207    //print!("{:#?}", game_replay.state.phase);
208    let mut game_db = GameFinishedInfo::from(game_replay);
209    game_db.set_times(game_data.created, game_data.started, game_data.finished);
210    Ok(game_db)
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216    use crate::game::points::Points;
217
218    #[test]
219    pub fn test_parse_card() {
220        assert_eq!(
221            parse_card(String::from("r-A")).unwrap(),
222            Card {
223                suit: Suit::Red,
224                value: Value::Ace,
225            }
226        );
227        assert_eq!(
228            parse_card(String::from("g-O")).unwrap(),
229            Card {
230                suit: Suit::Green,
231                value: Value::Ober,
232            }
233        );
234        assert_eq!(
235            parse_card(String::from("s-6")).unwrap(),
236            Card {
237                suit: Suit::Bells,
238                value: Value::Six,
239            }
240        );
241    }
242
243    #[test]
244    pub fn test_parse_python_game() {
245        let input: LegacyGameFormat = serde_json::from_str(
246            r#"
247        {
248           "_id": "ffffffffe1f3816d1dad798f",
249           "name":"NameOfGame",
250           "created":"2023-04-15 22:45:13",
251           "started":"2023-04-15 22:45:34",
252           "series_id":"",
253           "finished":"2023-04-15 22:51:55",
254           "players":[
255              "Player A",
256              "Player B",
257              "Player C",
258              "Player D"
259           ],
260           "cards":{
261              "Player A":[
262                 "r-6",
263                 "e-Z",
264                 "e-K",
265                 "e-7",
266                 "g-K",
267                 "g-O",
268                 "g-8",
269                 "g-7",
270                 "g-6"
271              ],
272              "Player B":[
273                 "r-A",
274                 "r-O",
275                 "r-U",
276                 "r-9",
277                 "s-6",
278                 "e-A",
279                 "e-O",
280                 "g-A",
281                 "g-9"
282              ],
283              "Player C":[
284                 "r-7",
285                 "s-A",
286                 "s-9",
287                 "s-8",
288                 "e-U",
289                 "e-9",
290                 "e-6",
291                 "g-Z",
292                 "g-U"
293              ],
294              "Player D":[
295                 "r-Z",
296                 "r-K",
297                 "r-8",
298                 "s-Z",
299                 "s-K",
300                 "s-O",
301                 "s-U",
302                 "s-7",
303                 "e-8"
304              ]
305           },
306           "passed_cards":{
307              "forth":[
308                 "g-9",
309                 "g-A",
310                 "e-O",
311                 "e-A"
312              ],
313              "back":[
314                 "s-7",
315                 "r-K",
316                 "e-8",
317                 "e-O"
318              ]
319           },
320           "tricks":[
321              [
322                 "e-A",
323                 "e-7",
324                 "e-8",
325                 "e-6"
326              ],
327              [
328                 "g-A",
329                 "g-6",
330                 "s-7",
331                 "g-U"
332              ],
333              [
334                 "g-9",
335                 "g-O",
336                 "s-6",
337                 "g-Z"
338              ],
339              [
340                 "r-A",
341                 "r-7",
342                 "r-8",
343                 "r-6"
344              ],
345              [
346                 "e-O",
347                 "e-9",
348                 "r-Z",
349                 "e-K"
350              ],
351              [
352                 "s-U",
353                 "g-7",
354                 "r-9",
355                 "s-8"
356              ],
357              [
358                 "r-K",
359                 "e-U",
360                 "s-K",
361                 "g-8"
362              ],
363              [
364                 "r-O",
365                 "s-9",
366                 "s-O",
367                 "g-K"
368              ],
369              [
370                 "r-U",
371                 "s-A",
372                 "s-Z",
373                 "e-Z"
374              ]
375           ],
376           "actions":[
377              "0,PROV,125",
378              "1,PROV,130",
379              "2,PROV,135",
380              "3,PROV,155",
381              "0,PROV,0",
382              "1,PROV,0",
383              "2,PROV,0",
384              "1,PASS,g-9",
385              "1,PASS,g-A",
386              "1,PASS,e-O",
387              "1,PASS,e-A",
388              "3,PBCK,s-7",
389              "3,PBCK,r-K",
390              "3,PBCK,e-8",
391              "3,PBCK,e-O",
392              "3,PRMO,0",
393              "3,TRCK,e-A",
394              "0,TRCK,e-7",
395              "1,TRCK,e-8",
396              "2,TRCK,e-6",
397              "3,TRCK,g-A",
398              "0,TRCK,g-6",
399              "1,TRCK,s-7",
400              "2,TRCK,g-U",
401              "3,QUES,mys",
402              "3,TRCK,g-9",
403              "0,TRCK,g-O",
404              "1,TRCK,s-6",
405              "2,TRCK,g-Z",
406              "1,QUES,myr",
407              "1,TRCK,r-A",
408              "2,TRCK,r-7",
409              "3,TRCK,r-8",
410              "0,TRCK,r-6",
411              "1,TRCK,e-O",
412              "2,TRCK,e-9",
413              "3,TRCK,r-Z",
414              "0,TRCK,e-K",
415              "3,TRCK,s-U",
416              "0,TRCK,g-7",
417              "1,TRCK,r-9",
418              "2,TRCK,s-8",
419              "1,TRCK,r-K",
420              "2,TRCK,e-U",
421              "3,TRCK,s-K",
422              "0,TRCK,g-8",
423              "1,QUES,you",
424              "3,ANSW,nmy",
425              "1,TRCK,r-O",
426              "2,TRCK,s-9",
427              "3,TRCK,s-O",
428              "0,TRCK,g-K",
429              "1,TRCK,r-U",
430              "2,TRCK,s-A",
431              "3,TRCK,s-Z",
432              "0,TRCK,e-Z"
433           ],
434           "playing_player":"Player D",
435           "game_value":155,
436           "players_points":{
437              "Player A":0,
438              "Player B":199,
439              "Player C":0,
440              "Player D":121
441           },
442           "players_sup":{
443              "Player A":[
444
445              ],
446              "Player B":[
447                 "r"
448              ],
449              "Player C":[
450
451              ],
452              "Player D":[
453                 "s"
454              ]
455           },
456           "schwarz_game":true
457        }
458      "#,
459        )
460        .unwrap();
461        let result = parse_legacy_format(input.clone()).unwrap();
462        assert_eq!(result.game_value, Points(input.game_value));
463    }
464}