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 #[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 if pass_collect.len() == 4 {
191 let pass_action = parse_pass(pass_collect.clone());
192 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 if new_action.action_type == ActionType::NewBid(0) {
203 continue;
204 }
205 game_replay.apply_action_mut(new_action);
206 }
207 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}