use steam_powered_wyrm::command::{Action, Command}; use steam_powered_wyrm::constants::*; use steam_powered_wyrm::game::*; use steam_powered_wyrm::geometry::*; use steam_powered_wyrm::json; use std::fs::File; use std::io::prelude::*; use std::path::Path; #[test] fn simulates_the_same_match() { let replays = Path::new("tests/replays/"); for replay in replays.read_dir().expect("read_dir failed") { let replay = replay.expect("error on replay").path(); let mut game_board = GameBoard::new( json::read_state_from_json_file(&replay.join(Path::new("A-init.json"))) .expect("Failed to read initial state"), ); let player_csv = read_file_lines(&replay.join(Path::new("A-log.csv")), 1); let opponent_csv = read_file_lines(&replay.join(Path::new("B-log.csv")), 1); for round in 0..player_csv.len() { println!("Testing round {}", round); let player = split_csv(&player_csv[round]); let opponent = split_csv(&opponent_csv[round]); assert_eq!( round + 1, player[0] .parse::() .expect(&format!("Invalid player input on round {}", round)) ); assert_eq!( round + 1, opponent[0] .parse::() .expect(&format!("Invalid opponent input on round {}", round)) ); if round != 0 { let player_move = read_move( &player, game_board.players[0].worms[game_board.players[0].active_worm].id, ); let opponent_move = read_move( &opponent, game_board.players[1].worms[game_board.players[1].active_worm].id, ); let _ = game_board.simulate([player_move, opponent_move]); if player[1] == "invalid" { game_board.players[0].moves_score -= INVALID_COMMAND_SCORE_PENALTY; } if opponent[1] == "invalid" { game_board.players[1].moves_score -= INVALID_COMMAND_SCORE_PENALTY; } } for player_index in 0..2 { let csv_row = match player_index { 0 => &player, _ => &opponent, }; assert_eq!( csv_row[4].parse::().unwrap(), game_board.players[player_index].score(), "Score is incorrect for player {}, Row: {:?}", player_index, csv_row ); for worm_index in 0..3 { let worm_id = worm_index as i32 + 1; match game_board.players[player_index].find_worm(worm_id) { Some(worm) => { assert_eq!( csv_row[6 + worm_index * 3].parse::().unwrap(), worm.health, "Worm health is incorrect for worm {} on player {}, Row: {:?}", worm_id, player_index, csv_row ); assert_eq!( csv_row[7 + worm_index * 3].parse::().unwrap(), worm.position.x, "Worm x is incorrect for worm {} on player {}, Row: {:?}", worm_id, player_index, csv_row ); assert_eq!( csv_row[8 + worm_index * 3].parse::().unwrap(), worm.position.y, "Worm y is incorrect for worm {} on player {}, Row: {:?}", worm_id, player_index, csv_row ); } None => { // If the worms don't appear in my state, they should be dead assert!( csv_row[6 + worm_index * 3].parse::().unwrap() <= 0, "Worm is not actually dead" ); } } } } } } } fn read_file_lines(path: &Path, skip: usize) -> Vec { let mut file = File::open(path).unwrap(); let mut contents = String::new(); file.read_to_string(&mut contents).unwrap(); contents .split("\n") .skip(skip) .map(String::from) .filter(|s| !s.is_empty()) .collect() } fn split_csv(input: &str) -> Vec { let mut result = Vec::new(); let mut next = Vec::new(); let mut quoted = false; for c in input.chars() { match c { '"' => { quoted = !quoted; } ',' if !quoted => { result.push(next.iter().collect()); next = Vec::new(); } c => { next.push(c); } } } result.push(next.iter().collect()); result } fn read_move(csv_line: &[String], expected_worm_id: i32) -> Command { let worm_id = csv_line[3].parse::().unwrap(); let select = if worm_id == expected_worm_id { None } else { Some(worm_id) }; match csv_line[1].as_ref() { "move" => { let (x, y) = read_xy_pair(&csv_line[2]); Command { worm: select, action: Action::Move(Point2d::new(x, y)), } } "dig" => { let (x, y) = read_xy_pair(&csv_line[2]); Command { worm: select, action: Action::Dig(Point2d::new(x, y)), } } "banana" => { let (x, y) = read_xy_pair(&csv_line[2]); Command { worm: select, action: Action::Bomb(Point2d::new(x, y)), } } "snowball" => { let (x, y) = read_xy_pair(&csv_line[2]); Command { worm: select, action: Action::Snowball(Point2d::new(x, y)), } } "nothing" | "invalid" => Command { worm: select, action: Action::DoNothing, }, "shoot" => { use steam_powered_wyrm::geometry::Direction::*; let dir = match csv_line[2].as_ref() { "shoot N" => North, "shoot NE" => NorthEast, "shoot E" => East, "shoot SE" => SouthEast, "shoot S" => South, "shoot SW" => SouthWest, "shoot W" => West, "shoot NW" => NorthWest, _ => panic!("Unknown shoot direction: {}", csv_line[2]), }; Command { worm: select, action: Action::Shoot(dir), } } x => { panic!("Unknown command {}", x); } } } fn read_xy_pair(input: &str) -> (i8, i8) { let mut char_iter = input.chars(); let _ = char_iter .by_ref() .take_while(|c| *c != ' ') .collect::(); let x = char_iter .by_ref() .take_while(|c| *c != ' ') .collect::() .trim() .parse::() .unwrap(); let y = char_iter .by_ref() .take_while(|c| *c != ' ') .collect::() .trim() .parse::() .unwrap(); (x, y) }