summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Worthe <justin@worthe-it.co.za>2019-04-25 16:50:06 +0200
committerJustin Worthe <justin@worthe-it.co.za>2019-04-25 16:50:06 +0200
commit510767263a0060ad13b2488a9402b1d176ad65ef (patch)
tree8f5d1dc7bb290c01c54f30b4ebe2868d8ac80272
parent5c957c0c8e928cbe64eb8a84733d649fd32642ef (diff)
Test that match replay matches my simulation
-rw-r--r--src/command.rs4
-rw-r--r--src/game.rs47
-rw-r--r--src/json.rs3
-rw-r--r--src/lib.rs5
-rw-r--r--src/main.rs15
-rwxr-xr-xtests/import-replay.sh12
-rw-r--r--tests/official-runner-matching.rs142
-rwxr-xr-xtests/replays/import-replay.sh12
8 files changed, 190 insertions, 50 deletions
diff --git a/src/command.rs b/src/command.rs
index 99de608..a510120 100644
--- a/src/command.rs
+++ b/src/command.rs
@@ -3,8 +3,8 @@ use crate::geometry::Direction;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Command {
- Move(u32, u32),
- Dig(u32, u32),
+ Move(i8, i8),
+ Dig(i8, i8),
Shoot(Direction),
DoNothing,
}
diff --git a/src/game.rs b/src/game.rs
index 626a377..dad72cd 100644
--- a/src/game.rs
+++ b/src/game.rs
@@ -3,42 +3,42 @@ use crate::command::Command;
use crate::json;
pub struct GameBoard {
- players: [Player; 2],
- powerups: Vec<Powerup>,
- map: Map,
+ pub players: [Player; 2],
+ pub powerups: Vec<Powerup>,
+ pub map: Map,
}
-struct Player {
- active_worm: usize,
- worms: Vec<Worm>
+pub struct Player {
+ pub active_worm: usize,
+ pub worms: Vec<Worm>
}
-struct Worm {
- id: i32,
- health: i32,
- position: Point2d<i8>,
- weapon_damage: i32,
- weapon_range: u8
+pub struct Worm {
+ pub id: i32,
+ pub health: i32,
+ pub position: Point2d<i8>,
+ pub weapon_damage: i32,
+ pub weapon_range: u8
}
-enum Powerup {
+pub enum Powerup {
Health(Point2d<i8>, i32)
}
-struct Map {
- size: u8,
+pub struct Map {
+ pub size: u8,
/// This is 2d, each row is size long
- cells: Vec<CellType>
+ pub cells: Vec<CellType>
}
-enum CellType {
+pub enum CellType {
Air,
Dirt,
DeepSpace,
}
-enum SimulationOutcome {
+pub enum SimulationOutcome {
PlayerWon(usize),
Draw,
Continue,
@@ -98,6 +98,17 @@ impl GameBoard {
}
pub fn simulate(&mut self, moves: [Command; 2]) -> SimulationOutcome {
+ for player in &mut self.players {
+ player.active_worm = (player.active_worm + 1) % player.worms.len();
+ }
SimulationOutcome::Continue
}
}
+
+impl Player {
+ pub fn find_worm(&self, id: i32) -> Option<&Worm> {
+ self.worms
+ .iter()
+ .find(|w| w.id == id)
+ }
+}
diff --git a/src/json.rs b/src/json.rs
index 5a8267f..979252e 100644
--- a/src/json.rs
+++ b/src/json.rs
@@ -1,11 +1,12 @@
use std::error::Error;
use std::fs::File;
use std::io::prelude::*;
+use std::path::Path;
use serde::{Deserialize, Serialize};
use serde_json;
-pub fn read_state_from_json_file(filename: &str) -> Result<State, Box<Error>> {
+pub fn read_state_from_json_file(filename: &Path) -> Result<State, Box<Error>> {
let mut file = File::open(filename)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..c0a6cd6
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,5 @@
+pub mod command;
+pub mod json;
+pub mod geometry;
+pub mod game;
+pub mod strategy;
diff --git a/src/main.rs b/src/main.rs
index 34d0061..d6d9a4c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,14 +1,11 @@
use std::io::prelude::*;
use std::io::stdin;
+use std::path::Path;
-mod command;
-mod json;
-mod geometry;
-mod game;
-mod strategy;
-
-use command::Command;
-use strategy::choose_move;
+use steam_powered_wyrm::command::Command;
+use steam_powered_wyrm::strategy::choose_move;
+use steam_powered_wyrm::json;
+use steam_powered_wyrm::game;
fn main() {
let mut game_board = None;
@@ -16,7 +13,7 @@ fn main() {
let round_number = line.expect("Failed to read line from stdin: {}");
let command =
- match json::read_state_from_json_file(&format!("./rounds/{}/state.json", round_number)) {
+ match json::read_state_from_json_file(&Path::new(&format!("./rounds/{}/state.json", round_number))) {
Ok(json_state) => {
match &mut game_board {
None => {
diff --git a/tests/import-replay.sh b/tests/import-replay.sh
new file mode 100755
index 0000000..51a7b83
--- /dev/null
+++ b/tests/import-replay.sh
@@ -0,0 +1,12 @@
+#/bin/sh
+
+# $1: The match-log directory
+# Should be run from the tests directory.
+
+logname=$(basename $1)
+
+mkdir $logname
+cp $1/A*.csv replays/$logname/A-log.csv
+cp $1/B*.csv replays/$logname/B-log.csv
+cp $1/Round\ 001/A*/JsonMap.json replays/$logname/A-init.json
+cp $1/Round\ 001/B*/JsonMap.json replays/$logname/B-init.json
diff --git a/tests/official-runner-matching.rs b/tests/official-runner-matching.rs
index 7ef12b8..12b6010 100644
--- a/tests/official-runner-matching.rs
+++ b/tests/official-runner-matching.rs
@@ -1,13 +1,139 @@
+use steam_powered_wyrm::json;
+use steam_powered_wyrm::game::*;
+use steam_powered_wyrm::command::Command;
+use std::path::Path;
+use std::fs::File;
+use std::io::prelude::*;
#[test]
fn simulates_the_same_match() {
- //TODO
- //iterate over list of replays (FS?)
-
- //read initial state.json
- //iterate over moves
- //simulate move (both sides)
- //assert state is as expected
- //repeat with players swapped?
+ 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)));
+
+ // active worm id field in CSV refers to the worm of the
+ // move. The rest of the state refers to after the move
+ // has happened.
+ assert_eq!(player[3].parse::<i32>().unwrap(), game_board.players[0].worms[game_board.players[0].active_worm].id, "Active worm is incorrect for player 0");
+ assert_eq!(opponent[3].parse::<i32>().unwrap(), game_board.players[1].worms[game_board.players[1].active_worm].id, "Active worm is incorrect for player 1");
+
+ if round != 0 {
+ let player_move = read_move(&player);
+ let opponent_move = read_move(&opponent);
+ let _ = game_board.simulate([player_move, opponent_move]);
+ }
+
+ for player_index in 0..2 {
+ let csv_row = match player_index {
+ 0 => &player,
+ _ => &opponent
+ };
+ 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]) -> Command {
+ match csv_line[1].as_ref() {
+ "move" => {
+ let (x, y) = read_xy_pair(&csv_line[2]);
+ Command::Move(x, y)
+ },
+ "dig" => {
+ let (x, y) = read_xy_pair(&csv_line[2]);
+ Command::Dig(x, y)
+ },
+ "nothing" => {
+ Command::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::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)
}
diff --git a/tests/replays/import-replay.sh b/tests/replays/import-replay.sh
deleted file mode 100755
index 034efed..0000000
--- a/tests/replays/import-replay.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#/bin/sh
-
-# $1: The match-log directory
-# Should be run from the target directory
-
-logname=$(basename $1)
-
-mkdir $logname
-cp $1/A*.csv $logname/A-log.csv
-cp $1/B*.csv $logname/B-log.csv
-cp $1/Round\ 001/A*/JsonMap.json $logname/A-init.json
-cp $1/Round\ 001/B*/JsonMap.json $logname/B-init.json