summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Worthe <justin@worthe-it.co.za>2019-04-22 21:50:00 +0200
committerJustin Worthe <justin@worthe-it.co.za>2019-04-22 21:50:00 +0200
commit88430f31c73f469086b68f2b77d1e1ba5f9178e7 (patch)
tree292a0aceba92e4d0c38679ed919b9b463c82152b
parent3e54b01003aa9d27de8f4ca13c9240fe785ec0e1 (diff)
More minimal game state
I'd prefer to start with just the state that I need, and progressively readd the bits that I've skipped as I find I need them, or as the competition evolves.
-rw-r--r--src/command.rs41
-rw-r--r--src/game.rs61
-rw-r--r--src/geometry.rs2
-rw-r--r--src/geometry/direction.rs56
-rw-r--r--src/json.rs35
-rw-r--r--src/main.rs284
-rw-r--r--src/strategy.rs6
7 files changed, 135 insertions, 350 deletions
diff --git a/src/command.rs b/src/command.rs
index 06dd400..99de608 100644
--- a/src/command.rs
+++ b/src/command.rs
@@ -1,4 +1,5 @@
use std::fmt;
+use crate::geometry::Direction;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Command {
@@ -19,43 +20,3 @@ impl fmt::Display for Command {
}
}
}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum Direction {
- North,
- NorthEast,
- East,
- SouthEast,
- South,
- SouthWest,
- West,
- NorthWest,
-}
-
-impl fmt::Display for Direction {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use Direction::*;
- let s = match self {
- North => "N",
- NorthEast => "NE",
- East => "E",
- SouthEast => "SE",
- South => "S",
- SouthWest => "SW",
- West => "W",
- NorthWest => "NW",
- };
- f.write_str(s)
- }
-}
-
-impl Direction {
- pub fn is_diagonal(&self) -> bool {
- use Direction::*;
-
- match self {
- NorthEast | SouthEast | SouthWest | NorthWest => true,
- _ => false,
- }
- }
-}
diff --git a/src/game.rs b/src/game.rs
index 492eaf3..70960aa 100644
--- a/src/game.rs
+++ b/src/game.rs
@@ -1,6 +1,7 @@
use crate::geometry::*;
+use crate::json;
-struct GameBoard {
+pub struct GameBoard {
player: Player,
opponent: Player,
powerups: Vec<Powerup>,
@@ -13,24 +14,14 @@ struct Player {
}
struct Worm {
- id: i32,
health: i32,
- position: Point2d<u8>,
- digging_range: u32,
- movement_range: u32,
- // This is unnecessary for now, but necessary later. I know
- // for sure for the first round that all the worms will do the
- // same damage and there isn't any way to change it.
- weapon: Option<Weapon>,
-}
-
-struct Weapon {
- damage: u32,
- range: u32,
+ position: Point2d<i8>,
+ weapon_damage: i32,
+ weapon_range: u8
}
enum Powerup {
- Health(Point2d<u8>, i32)
+ Health(Point2d<i8>, i32)
}
struct Map {
@@ -44,3 +35,43 @@ enum CellType {
Dirt,
DeepSpace,
}
+
+
+impl GameBoard {
+ pub fn new(json: json::State) -> GameBoard {
+ let commando_damage = json.my_player.worms[0].weapon.damage;
+ let commando_range = json.my_player.worms[0].weapon.range;
+
+ GameBoard {
+ player: Player {
+ active_worm: json.active_worm_index().unwrap(),
+ worms: json.my_player.worms.iter().map(|w| Worm {
+ health: w.health,
+ position: Point2d::new(w.position.x, w.position.y),
+ weapon_damage: commando_damage,
+ weapon_range: commando_range
+ }).collect()
+ },
+ opponent: Player {
+ active_worm: 0,
+ worms: json.opponents.iter().flat_map(|o| &o.worms).map(|w| Worm {
+ health: w.health,
+ position: Point2d::new(w.position.x, w.position.y),
+ weapon_damage: commando_damage,
+ weapon_range: commando_range
+ }).collect()
+ },
+ powerups: json.map.iter().flatten().filter_map(|c| {
+ c.powerup.clone().map(|p| Powerup::Health(Point2d::new(c.x, c.y), p.value))
+ }).collect(),
+ map: Map {
+ size: json.map_size,
+ cells: json.map.iter().flatten().map(|c| match c.cell_type {
+ json::CellType::Air => CellType::Air,
+ json::CellType::Dirt => CellType::Dirt,
+ json::CellType::DeepSpace => CellType::DeepSpace,
+ }).collect()
+ }
+ }
+ }
+}
diff --git a/src/geometry.rs b/src/geometry.rs
index 9e541ef..dc8b096 100644
--- a/src/geometry.rs
+++ b/src/geometry.rs
@@ -4,3 +4,5 @@ mod point;
pub use self::point::*;
mod rect;
pub use self::rect::*;
+mod direction;
+pub use self::direction::*;
diff --git a/src/geometry/direction.rs b/src/geometry/direction.rs
new file mode 100644
index 0000000..84fe785
--- /dev/null
+++ b/src/geometry/direction.rs
@@ -0,0 +1,56 @@
+use std::fmt;
+use crate::geometry::vec::Vec2d;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum Direction {
+ North,
+ NorthEast,
+ East,
+ SouthEast,
+ South,
+ SouthWest,
+ West,
+ NorthWest,
+}
+
+impl fmt::Display for Direction {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use Direction::*;
+ let s = match self {
+ North => "N",
+ NorthEast => "NE",
+ East => "E",
+ SouthEast => "SE",
+ South => "S",
+ SouthWest => "SW",
+ West => "W",
+ NorthWest => "NW",
+ };
+ f.write_str(s)
+ }
+}
+
+impl Direction {
+ pub fn is_diagonal(&self) -> bool {
+ use Direction::*;
+
+ match self {
+ NorthEast | SouthEast | SouthWest | NorthWest => true,
+ _ => false,
+ }
+ }
+
+ pub fn as_vec(&self) -> Vec2d<i8> {
+ use Direction::*;
+ match self {
+ North => Vec2d::new(0, -1),
+ NorthEast => Vec2d::new(1, -1),
+ East => Vec2d::new(1, 0),
+ SouthEast => Vec2d::new(1, 1),
+ South => Vec2d::new(0, 1),
+ SouthWest => Vec2d::new(-1, 1),
+ West => Vec2d::new(-1, 0),
+ NorthWest => Vec2d::new(-1, -1),
+ }
+ }
+}
diff --git a/src/json.rs b/src/json.rs
index 3046b7c..5a8267f 100644
--- a/src/json.rs
+++ b/src/json.rs
@@ -19,7 +19,7 @@ pub fn read_state_from_json_file(filename: &str) -> Result<State, Box<Error>> {
pub struct State {
pub current_round: u32,
pub max_rounds: u32,
- pub map_size: u32,
+ pub map_size: u8,
pub current_worm_id: i32,
pub consecutive_do_nothing_count: u32,
pub my_player: Player,
@@ -68,8 +68,8 @@ pub struct OpponentWorm {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Cell {
- pub x: u32,
- pub y: u32,
+ pub x: i8,
+ pub y: i8,
#[serde(rename = "type")]
pub cell_type: CellType,
pub occupier: Option<CellWorm>,
@@ -126,51 +126,46 @@ pub enum PowerupType {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Position {
- pub x: u32,
- pub y: u32,
+ pub x: i8,
+ pub y: i8,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Weapon {
- pub damage: u32,
- pub range: u32,
+ pub damage: i32,
+ pub range: u8,
}
impl State {
- pub fn active_worm(&self) -> Option<&PlayerWorm> {
+ pub fn active_worm_index(&self) -> Option<usize> {
self.my_player
.worms
.iter()
- .find(|w| w.id == self.current_worm_id)
- }
-
- pub fn cell_at(&self, pos: &Position) -> Option<&Cell> {
- self.map
- .iter()
- .flatten()
- .find(|c| c.x == pos.x && c.y == pos.y)
+ .position(|w| w.id == self.current_worm_id)
}
}
impl Position {
- pub fn west(&self, distance: u32) -> Option<Position> {
+ pub fn west(&self, distance: i8) -> Option<Position> {
self.x
.checked_sub(distance)
+ .filter(|&x| x >= 0)
.map(|x| Position { x, y: self.y })
}
- pub fn east(&self, distance: u32, max: u32) -> Option<Position> {
+ pub fn east(&self, distance: i8, max: i8) -> Option<Position> {
self.x
.checked_add(distance)
.filter(|&x| x < max)
.map(|x| Position { x, y: self.y })
}
- pub fn north(&self, distance: u32) -> Option<Position> {
+ pub fn north(&self, distance: i8) -> Option<Position> {
self.y
.checked_sub(distance)
+ .filter(|&y| y >= 0)
.map(|y| Position { x: self.x, y })
}
- pub fn south(&self, distance: u32, max: u32) -> Option<Position> {
+ pub fn south(&self, distance: i8, max: i8) -> Option<Position> {
self.y
.checked_add(distance)
.filter(|&y| y < max)
diff --git a/src/main.rs b/src/main.rs
index cba96f5..216915c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,16 +7,21 @@ mod command;
mod json;
mod geometry;
mod game;
+mod strategy;
-use command::*;
-use json::*;
+use command::Command;
+use strategy::choose_move;
fn main() {
for line in stdin().lock().lines() {
let round_number = line.expect("Failed to read line from stdin: {}");
+
let command =
- match read_state_from_json_file(&format!("./rounds/{}/state.json", round_number)) {
- Ok(state) => choose_command(state),
+ match json::read_state_from_json_file(&format!("./rounds/{}/state.json", round_number)) {
+ Ok(json_state) => {
+ let game_board = game::GameBoard::new(json_state);
+ choose_move(&game_board)
+ },
Err(e) => {
eprintln!("WARN: State file could not be parsed: {}", e);
Command::DoNothing
@@ -25,274 +30,3 @@ fn main() {
println!("C;{};{}", round_number, command);
}
}
-
-fn choose_command(state: State) -> Command {
- match state.active_worm() {
- Some(worm) => {
- if let Some(direction) = find_worm_in_firing_distance(&state, worm) {
- Command::Shoot(direction)
- } else {
- let choices = valid_adjacent_positions(&state, &worm.position);
- let choice = choices
- .choose(&mut rand::thread_rng())
- .expect("No valid directions to move in");
- let chosen_cell = state.cell_at(&choice);
-
- match chosen_cell.map(|c| &c.cell_type) {
- Some(CellType::Air) => Command::Move(choice.x, choice.y),
- Some(CellType::Dirt) => Command::Dig(choice.x, choice.y),
- Some(CellType::DeepSpace) | None => Command::DoNothing,
- }
- }
- }
- None => {
- eprintln!("WARN: The active worm did not appear in the state file");
- Command::DoNothing
- }
- }
-}
-
-fn find_worm_in_firing_distance(state: &State, worm: &PlayerWorm) -> Option<Direction> {
- let directions: [(Direction, Box<dyn Fn(&Position, u32) -> Option<Position>>); 8] = [
- (Direction::West, Box::new(|p, d| p.west(d))),
- (Direction::NorthWest, Box::new(|p, d| p.north(d).and_then(|p| p.west(d)))),
- (Direction::North, Box::new(|p, d| p.north(d))),
- (Direction::NorthEast, Box::new(|p, d| p.north(d).and_then(|p| p.east(d, state.map_size)))),
- (Direction::East, Box::new(|p, d| p.east(d, state.map_size))),
- (Direction::SouthEast, Box::new(|p, d| p.south(d, state.map_size).and_then(|p| p.east(d, state.map_size)))),
- (Direction::South, Box::new(|p, d| p.south(d, state.map_size))),
- (Direction::SouthWest, Box::new(|p, d| p.south(d, state.map_size).and_then(|p| p.west(d)))),
- ];
-
- for (dir, dir_fn) in &directions {
- let range = adjust_range_for_diagonals(dir, worm.weapon.range);
-
- for distance in 1..=range {
- let target = dir_fn(&worm.position, distance);
- match target.and_then(|t| state.cell_at(&t)) {
- Some(Cell {
- occupier: Some(CellWorm::OpponentWorm { .. }),
- ..
- }) => return Some(*dir),
- Some(Cell {
- cell_type: CellType::Air,
- ..
- }) => continue,
- _ => break,
- }
- }
- }
- None
-}
-
-fn adjust_range_for_diagonals(dir: &Direction, straight_range: u32) -> u32 {
- if dir.is_diagonal() {
- ((straight_range as f32 + 1.) / 2f32.sqrt()).floor() as u32
- } else {
- straight_range
- }
-}
-
-fn valid_adjacent_positions(state: &State, pos: &Position) -> Vec<Position> {
- let choices = [
- pos.west(1),
- pos.west(1).and_then(|p| p.north(1)),
- pos.north(1),
- pos.north(1).and_then(|p| p.east(1, state.map_size)),
- pos.east(1, state.map_size),
- pos.east(1, state.map_size)
- .and_then(|p| p.south(1, state.map_size)),
- pos.south(1, state.map_size),
- pos.south(1, state.map_size).and_then(|p| p.west(1)),
- ];
- choices.iter().flatten().cloned().collect()
-}
-
-#[cfg(test)]
-mod test {
- use super::*;
-
- #[test]
- fn adjacent_positions_give_valid_positions() {
- let dummy_state = State {
- current_round: 0,
- max_rounds: 0,
- map_size: 3,
- current_worm_id: 0,
- consecutive_do_nothing_count: 0,
- my_player: Player {
- id: 0,
- score: 0,
- health: 0,
- worms: Vec::new(),
- },
- opponents: Vec::new(),
- map: Vec::new(),
- };
-
- assert_eq!(
- 3,
- valid_adjacent_positions(&dummy_state, &Position { x: 0, y: 0 }).len()
- );
- assert_eq!(
- 5,
- valid_adjacent_positions(&dummy_state, &Position { x: 1, y: 0 }).len()
- );
- assert_eq!(
- 3,
- valid_adjacent_positions(&dummy_state, &Position { x: 2, y: 0 }).len()
- );
- assert_eq!(
- 5,
- valid_adjacent_positions(&dummy_state, &Position { x: 0, y: 1 }).len()
- );
- assert_eq!(
- 8,
- valid_adjacent_positions(&dummy_state, &Position { x: 1, y: 1 }).len()
- );
- assert_eq!(
- 5,
- valid_adjacent_positions(&dummy_state, &Position { x: 2, y: 1 }).len()
- );
- assert_eq!(
- 3,
- valid_adjacent_positions(&dummy_state, &Position { x: 0, y: 2 }).len()
- );
- assert_eq!(
- 5,
- valid_adjacent_positions(&dummy_state, &Position { x: 1, y: 2 }).len()
- );
- assert_eq!(
- 3,
- valid_adjacent_positions(&dummy_state, &Position { x: 2, y: 2 }).len()
- );
- }
-
- #[test]
- fn range_adjustment_matches_examples() {
- assert_eq!(1, adjust_range_for_diagonals(&Direction::East, 1));
- assert_eq!(2, adjust_range_for_diagonals(&Direction::East, 2));
- assert_eq!(3, adjust_range_for_diagonals(&Direction::East, 3));
- assert_eq!(4, adjust_range_for_diagonals(&Direction::East, 4));
-
- assert_eq!(1, adjust_range_for_diagonals(&Direction::SouthEast, 1));
- assert_eq!(2, adjust_range_for_diagonals(&Direction::SouthEast, 2));
- assert_eq!(2, adjust_range_for_diagonals(&Direction::SouthEast, 3));
- assert_eq!(3, adjust_range_for_diagonals(&Direction::SouthEast, 4));
- }
-
- mod find_worm_in_firing_distance {
- use super::super::*;
-
- fn worm_shooting_dummy_state() -> (State, PlayerWorm) {
- let dummy_state = State {
- current_round: 0,
- max_rounds: 0,
- map_size: 5,
- current_worm_id: 0,
- consecutive_do_nothing_count: 0,
- my_player: Player {
- id: 0,
- score: 0,
- health: 0,
- worms: Vec::new(),
- },
- opponents: Vec::new(),
- map: vec![Vec::new()],
- };
- let active_worm = PlayerWorm {
- id: 0,
- health: 100,
- position: Position { x: 2, y: 2 },
- digging_range: 1,
- movement_range: 1,
- weapon: Weapon {
- range: 3,
- damage: 1,
- },
- };
-
- (dummy_state, active_worm)
- }
-
- #[test]
- fn finds_a_worm_that_can_be_shot() {
- let (mut dummy_state, active_worm) = worm_shooting_dummy_state();
- dummy_state.map[0].push(Cell {
- x: 3,
- y: 2,
- cell_type: CellType::Air,
- occupier: None,
- powerup: None,
- });
- dummy_state.map[0].push(Cell {
- x: 4,
- y: 2,
- cell_type: CellType::Air,
- occupier: Some(CellWorm::OpponentWorm {
- id: 0,
- player_id: 1,
- health: 0,
- position: Position { x: 4, y: 2 },
- digging_range: 1,
- movement_range: 1,
- }),
- powerup: None,
- });
-
- let firing_dir = find_worm_in_firing_distance(&dummy_state, &active_worm);
- assert_eq!(Some(Direction::East), firing_dir);
- }
-
- #[test]
- fn worm_cant_shoot_through_dirt() {
- let (mut dummy_state, active_worm) = worm_shooting_dummy_state();
- dummy_state.map[0].push(Cell {
- x: 3,
- y: 2,
- cell_type: CellType::Dirt,
- occupier: None,
- powerup: None,
- });
- dummy_state.map[0].push(Cell {
- x: 4,
- y: 2,
- cell_type: CellType::Air,
- occupier: Some(CellWorm::OpponentWorm {
- id: 0,
- player_id: 1,
- health: 0,
- position: Position { x: 4, y: 2 },
- digging_range: 1,
- movement_range: 1,
- }),
- powerup: None,
- });
-
- let firing_dir = find_worm_in_firing_distance(&dummy_state, &active_worm);
- assert_eq!(None, firing_dir);
- }
-
- #[test]
- fn identifies_lack_of_worms_to_shoot() {
- let (mut dummy_state, active_worm) = worm_shooting_dummy_state();
- dummy_state.map[0].push(Cell {
- x: 3,
- y: 2,
- cell_type: CellType::Air,
- occupier: None,
- powerup: None,
- });
- dummy_state.map[0].push(Cell {
- x: 4,
- y: 2,
- cell_type: CellType::Air,
- occupier: None,
- powerup: None,
- });
-
- let firing_dir = find_worm_in_firing_distance(&dummy_state, &active_worm);
- assert_eq!(None, firing_dir);
- }
- }
-}
diff --git a/src/strategy.rs b/src/strategy.rs
new file mode 100644
index 0000000..6ee0dce
--- /dev/null
+++ b/src/strategy.rs
@@ -0,0 +1,6 @@
+use crate::command::Command;
+use crate::game::GameBoard;
+
+pub fn choose_move(state: &GameBoard) -> Command {
+ Command::DoNothing
+}