#[derive(Debug, Clone, PartialEq, Eq)] pub struct SpinResult { pub session_id: String, pub winnings: u64, pub bet_amount: u64, pub balance: i64, pub reels: String, pub win_type: String, pub win_lines: Vec, pub win_cells: Vec, } /// Parse one server reply to `PLAY_START`. /// /// `Ok` carries the settled spin; `Err` carries a message that is safe to /// show to the player (server errors are reported without the `ERROR ` prefix). pub fn parse_spin_response(response: &str) -> Result { let response = response.trim(); if response.is_empty() { return Err("No response from server".to_string()); } if let Some(message) = response.strip_prefix("ERROR") { let message = message.trim(); return Err(if message.is_empty() { "Server error".to_string() } else { message.to_string() }); } let parts: Vec<&str> = response.split_whitespace().collect(); if parts.first() != Some(&"GAME_RESULT") { return Err(format!("Unexpected server response: {response}")); } if parts.len() < 7 { return Err("Malformed GAME_RESULT from server".to_string()); } let winnings = parts[2] .parse::() .map_err(|_| format!("Invalid winnings from server: {}", parts[2]))?; let bet_amount = parts[3] .parse::() .map_err(|_| format!("Invalid bet from server: {}", parts[3]))?; let balance = parts[4] .parse::() .map_err(|_| format!("Invalid balance from server: {}", parts[4]))?; if parts[5].chars().count() != 3 && parts[5].chars().count() != 15 { return Err(format!("Invalid reels from server: {}", parts[5])); } let win_lines = parts .get(7) .map(|raw| parse_indices(raw)) .unwrap_or_default(); let win_cells = parts .get(8) .map(|raw| parse_indices(raw)) .unwrap_or_default(); Ok(SpinResult { session_id: parts[1].to_string(), winnings, bet_amount, balance, reels: parts[5].to_string(), win_type: parts[6].to_string(), win_lines, win_cells, }) } fn parse_indices(raw: &str) -> Vec { raw.split(',') .filter_map(|value| value.trim().parse::().ok()) .collect() }