summaryrefslogtreecommitdiff
path: root/2019-worms/tests/official-runner-matching.rs
diff options
context:
space:
mode:
Diffstat (limited to '2019-worms/tests/official-runner-matching.rs')
-rw-r--r--2019-worms/tests/official-runner-matching.rs238
1 files changed, 238 insertions, 0 deletions
diff --git a/2019-worms/tests/official-runner-matching.rs b/2019-worms/tests/official-runner-matching.rs
new file mode 100644
index 0000000..1b62088
--- /dev/null
+++ b/2019-worms/tests/official-runner-matching.rs
@@ -0,0 +1,238 @@
+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::<usize>()
+ .expect(&format!("Invalid player input on round {}", round))
+ );
+ assert_eq!(
+ round + 1,
+ opponent[0]
+ .parse::<usize>()
+ .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::<i32>().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::<i32>().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::<i8>().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::<i8>().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::<i32>().unwrap() <= 0,
+ "Worm is not actually dead"
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+fn read_file_lines(path: &Path, skip: usize) -> Vec<String> {
+ 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<String> {
+ 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::<i32>().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::<String>();
+ let x = char_iter
+ .by_ref()
+ .take_while(|c| *c != ' ')
+ .collect::<String>()
+ .trim()
+ .parse::<i8>()
+ .unwrap();
+ let y = char_iter
+ .by_ref()
+ .take_while(|c| *c != ' ')
+ .collect::<String>()
+ .trim()
+ .parse::<i8>()
+ .unwrap();
+ (x, y)
+}