use crate::game::*; fn parse_move(mv: &str, game: &Game) -> Result { let mut chars = mv.chars(); let c = chars.next(); if c.is_none() { return Err(String::from("Empty string cannot be parsed.")); } let x; let fill_in_number; let dc = c.unwrap(); let mut sum_drops: u8 = 1; if (dc >= 'a') && (dc <= 'h') { // Optional drop number omitted, assume everything fill_in_number = true; x = dc; } else { if (dc < '1') || ('9' < dc) { return Err(String::from( "Moves must begin with a digit in 1-8 indicating the number of drops.", )); } fill_in_number = false; sum_drops = dc.to_digit(10).unwrap() as u8; let c = chars.next(); if c.is_none() { return Err(String::from("Moves must specify a stack position.")); } x = c.unwrap(); } if (x < 'a') || (x > 'h') { return Err(String::from( "Moves must specify a valid position on the game board.", )); } let x: u8 = x as u8 - 'a' as u8; let c = chars.next(); if c.is_none() { return Err(String::from("Moves must specify a stack position.")); } let y = c.unwrap(); if (y < '1') || (y > '8') { return Err(String::from( "Moves must specify a position on the game board.", )); } let y: u8 = (y.to_digit(10).unwrap() - 1) as u8; let c = chars.next(); if c.is_none() { return Err(String::from("Moves must specify a move direction.")); } let c = c.unwrap(); // This is so cool! Rust supports some very nice patterns, and // checks for uninitialised variables too! let dir; match c { '+' => dir = Direction::Up, '-' => dir = Direction::Down, '<' => dir = Direction::Left, '>' => dir = Direction::Right, _ => return Err(String::from("The valid directions are +,-,<, and >.")), } let mut drops: Vec = Vec::new(); for d in chars { if let Some(u) = d.to_digit(10) { if u > 9 { return Err(String::from( "No more than 8 pieces may be dropped on a given square.", )); } else { drops.push(u as u8) } } else { return Err(String::from( "A move must specify a number of pieces dropped for each square.", )); } } let pos = Position { x: x, y: y }; if fill_in_number { if let Some(stack) = game.query_square(&pos) { sum_drops = std::cmp::min(stack.len() as u8, game.get_size()); } else { return Err(String::from( "Moves must specify a valid position on the game board.", )); } } let sum = drops.iter().map(|&d| d as u32).sum::(); if sum == 0 { // Omitted all the drops, it's a total stack move drops.push(0); drops.push(sum_drops); } else if sum != sum_drops as u32 { return Err(format!( "The move called for {} stones, but {} were dropped.", sum_drops, sum )); } Ok(Action::Move(game.query_stone_owner(), pos, dir, drops)) } fn parse_place(pl: &str, game: &Game) -> Result { let mut chars = pl.chars(); let c = chars.next(); if c.is_none() { return Err(String::from("An action cannot be blank.")); } let stone_c = c.unwrap(); let stone; let x; if (stone_c == 'F') || (stone_c == 'C') || (stone_c == 'S') { match stone_c { 'C' => stone = Stone::Capstone, 'S' => stone = Stone::Standing, _ => stone = Stone::Flat, } let c = chars.next(); if c.is_none() { return Err(String::from( "Placements must specify a position on the game board.", )); } x = c.unwrap(); } else { stone = Stone::Flat; x = stone_c; } if (x < 'a') || (x > 'h') { return Err(if (x >= 'A') && (x <= 'Z') { format!("Unrecognised stone type '{}' in placement.", x) } else { String::from("Placements must specify a valid position on the game board.") }); } let x: u8 = x as u8 - 'a' as u8; let c = chars.next(); if c.is_none() { return Err(String::from( "Placements must specify a position on the game board.", )); } let y = c.unwrap(); if (y < '1') || (y > '8') { return Err(String::from( "Placements must specify a valid position on the game board.", )); } let y: u8 = (y.to_digit(10).unwrap() - 1) as u8; Ok(Action::Place( game.query_stone_owner(), Position { x: x, y: y }, stone, )) } pub fn parse_action(game: &Game, act: &str) -> Result { if act .chars() .any(|c| c == '+' || c == '-' || c == '>' || c == '<') { parse_move(act, game) } else { parse_place(act, game) } }