From 27682d0ab246af8d0375853fdea44c38de2c2db4 Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Sat, 13 May 2017 16:02:59 +0200 Subject: Initial commit --- .gitignore | 2 + Cargo.lock | 30 +++++++++++++++ Cargo.toml | 8 ++++ bot.json | 8 ++++ readme.md | 0 src/main.rs | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 167 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 bot.json create mode 100644 readme.md create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54466f5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target + diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c499a6e --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,30 @@ +[root] +name = "100k_worthebot_battleships" +version = "0.1.0" +dependencies = [ + "json 0.11.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "json" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum json 0.11.6 (registry+https://github.com/rust-lang/crates.io-index)" = "27600e8bb3b71bcc6213fb36b66b8dce60adc17a624257687ef5d1d4facafba7" +"checksum libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)" = "babb8281da88cba992fa1f4ddec7d63ed96280a1a53ec9b919fd37b53d71e502" +"checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6b43272 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "100k_worthebot_battleships" +version = "0.1.0" +authors = ["Justin Worthe "] + +[dependencies] +rand = "0.3" +json = "0.11.6" \ No newline at end of file diff --git a/bot.json b/bot.json new file mode 100644 index 0000000..04c2127 --- /dev/null +++ b/bot.json @@ -0,0 +1,8 @@ +{ + "Author":"Justin Worthe", + "Email":"justin.worthe@gmail.com", + "NickName" :"Admiral Worthebot", + "BotType": 8, + "ProjectLocation" : "", + "RunFile" : "target\\release\\100k_worthebot_battleships" +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..e69de29 diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..7c904d6 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,119 @@ +extern crate json; +extern crate rand; + +use std::env; +use std::path::Path; +use std::path::PathBuf; +use std::io::prelude::*; +use std::fs::File; +use std::fmt; + +use rand::distributions::{IndependentSample, Range}; + +const COMMAND_FILE: &'static str = "command.txt"; +const PLACE_FILE: &'static str = "place.txt"; +const STATE_FILE: &'static str = "state.json"; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum Orientation { + North, + East, + South, + West, +} + +impl fmt::Display for Orientation { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str( + match self { + &Orientation::North => "North", + &Orientation::East => "East", + &Orientation::South => "South", + &Orientation::West => "West" + } + ) + } +} + +#[derive(Clone, PartialEq, Eq, Debug)] +enum Action { + PlaceShips(Vec<(String, u32, u32, Orientation)>), + Shoot(u32, u32) +} + +impl fmt::Display for Action { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + &Action::Shoot(x, y) => writeln!(f, "1,{},{}", x, y), + &Action::PlaceShips(ref ships) => ships.iter().map(|&(ref ship_type, x, y, orientation)| { + writeln!(f, "{} {} {} {}", ship_type, x, y, orientation) + }).fold(Ok(()), |acc, next| acc.and(next)) + } + } +} + +fn main() { + let working_dir = env::args() + .nth(2) + .map(|pwd| PathBuf::from(pwd)) + .expect("Requires game state folder to be passed as the second parameter"); + + // The state file is read here. Chances are, you'll want to read + // more values out of it than the sample bot currently does. + let state = read_file(&working_dir).expect("Failed to read state.json"); + + let is_place_phase = state["Phase"] == 1; + let map_dimension = state["MapDimension"].as_u32().expect("Could not read map dimension from the state"); + + let action = if is_place_phase { + place_ships() + } + else { + shoot_randomly(map_dimension) + }; + + + write_action(&working_dir, is_place_phase, action).expect("Failed to write action to file"); +} + +fn read_file(working_dir: &Path) -> Result { + let state_path = working_dir.join(STATE_FILE); + let mut file = File::open(state_path.as_path()).map_err(|e| e.to_string())?; + let mut content = String::new(); + file.read_to_string(&mut content).map_err(|e| e.to_string())?; + json::parse(content.as_ref()).map_err(|e| e.to_string()) +} + +fn place_ships() -> Action { + let ships = vec!( + (String::from("Battleship"), 1, 0, Orientation::North), + (String::from("Carrier"), 3, 1, Orientation::East), + (String::from("Cruiser"), 4, 2, Orientation::North), + (String::from("Destroyer"), 7, 3, Orientation::North), + (String::from("Submarine"), 1, 8, Orientation::East) + ); + + Action::PlaceShips(ships) +} + +fn shoot_randomly(map_dimension: u32) -> Action { + let mut rng = rand::thread_rng(); + let between = Range::new(0, map_dimension); + let x = between.ind_sample(&mut rng); + let y = between.ind_sample(&mut rng); + Action::Shoot(x, y) +} + +fn write_action(working_dir: &Path, is_place_phase: bool, action: Action) -> Result<(), String> { + let filename = if is_place_phase { + PLACE_FILE + } + else { + COMMAND_FILE + }; + + let full_filename = working_dir.join(filename); + let mut file = File::create(full_filename.as_path()).map_err(|e| e.to_string())?; + writeln!(file, "{}", action).map_err(|e| e.to_string())?; + Ok(()) +} -- cgit v1.2.3 From 36b72bfef7b7b8dea94546d11704ec529091bce1 Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Sat, 13 May 2017 19:19:06 +0200 Subject: Split into smaller portions --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/actions.rs | 22 +++++++++++ src/files.rs | 32 ++++++++++++++++ src/lib.rs | 55 +++++++++++++++++++++++++++ src/main.rs | 118 +++++---------------------------------------------------- src/math.rs | 49 ++++++++++++++++++++++++ src/ships.rs | 38 +++++++++++++++++++ 8 files changed, 207 insertions(+), 111 deletions(-) create mode 100644 src/actions.rs create mode 100644 src/files.rs create mode 100644 src/lib.rs create mode 100644 src/math.rs create mode 100644 src/ships.rs diff --git a/Cargo.lock b/Cargo.lock index c499a6e..652adca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,5 @@ [root] -name = "100k_worthebot_battleships" +name = "worthebot_battleships" version = "0.1.0" dependencies = [ "json 0.11.6 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 6b43272..c90a18f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "100k_worthebot_battleships" +name = "worthebot_battleships" version = "0.1.0" authors = ["Justin Worthe "] diff --git a/src/actions.rs b/src/actions.rs new file mode 100644 index 0000000..a7f61b5 --- /dev/null +++ b/src/actions.rs @@ -0,0 +1,22 @@ +use math::*; +use ships::*; + +use std::fmt; + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum Action { + PlaceShips(Vec<(Ship, Point, Orientation)>), + Shoot(Point) +} + +impl fmt::Display for Action { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + &Action::Shoot(p) => writeln!(f, "1,{},{}", p.x, p.y), + &Action::PlaceShips(ref ships) => ships.iter().map(|&(ref ship_type, p, orientation)| { + writeln!(f, "{} {} {} {}", ship_type, p.x, p.y, orientation) + }).fold(Ok(()), |acc, next| acc.and(next)) + } + } +} + diff --git a/src/files.rs b/src/files.rs new file mode 100644 index 0000000..87d295f --- /dev/null +++ b/src/files.rs @@ -0,0 +1,32 @@ +use json; +use std::io::prelude::*; +use std::fs::File; +use std::path::PathBuf; + +use actions::*; + +const COMMAND_FILE: &'static str = "command.txt"; +const PLACE_FILE: &'static str = "place.txt"; +const STATE_FILE: &'static str = "state.json"; + +pub fn read_file(working_dir: &PathBuf) -> Result { + let state_path = working_dir.join(STATE_FILE); + let mut file = File::open(state_path.as_path()).map_err(|e| e.to_string())?; + let mut content = String::new(); + file.read_to_string(&mut content).map_err(|e| e.to_string())?; + json::parse(content.as_ref()).map_err(|e| e.to_string()) +} + +pub fn write_action(working_dir: &PathBuf, is_place_phase: bool, action: Action) -> Result<(), String> { + let filename = if is_place_phase { + PLACE_FILE + } + else { + COMMAND_FILE + }; + + let full_filename = working_dir.join(filename); + let mut file = File::create(full_filename.as_path()).map_err(|e| e.to_string())?; + writeln!(file, "{}", action).map_err(|e| e.to_string())?; + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..5ea9ecc --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,55 @@ +extern crate json; +extern crate rand; + +mod actions; +mod math; +mod files; +mod ships; + +use actions::*; +use math::*; +use files::*; +use ships::*; + +use std::path::PathBuf; + +pub fn write_move(working_dir: PathBuf) -> Result<(), String> { + let state = read_file(&working_dir)?; + + let is_place_phase = state["Phase"] == 1; + let map_dimension = state["MapDimension"] + .as_u16() + .ok_or("Did not find the map dimension in the state json file")?; + + let action = if is_place_phase { + place_ships() + } + else { + shoot_randomly(map_dimension) + }; + + write_action(&working_dir, is_place_phase, action) + .map_err(|e| format!("Failed to write action to file. Error: {}", e))?; + + Ok(()) +} + + +fn place_ships() -> Action { + let ships = vec!( + (Ship::Battleship, Point::new(1, 0), Orientation::North), + (Ship::Carrier, Point::new(3, 1), Orientation::East), + (Ship::Cruiser, Point::new(4, 2), Orientation::North), + (Ship::Destroyer, Point::new(7, 3), Orientation::North), + (Ship::Submarine, Point::new(1, 8), Orientation::East) + ); + + Action::PlaceShips(ships) +} + +fn shoot_randomly(map_dimension: u16) -> Action { + Action::Shoot(random_point(map_dimension)) +} + + + diff --git a/src/main.rs b/src/main.rs index 7c904d6..ee0ba59 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,119 +1,19 @@ -extern crate json; -extern crate rand; +extern crate worthebot_battleships; +use worthebot_battleships as bot; use std::env; -use std::path::Path; use std::path::PathBuf; -use std::io::prelude::*; -use std::fs::File; -use std::fmt; -use rand::distributions::{IndependentSample, Range}; - -const COMMAND_FILE: &'static str = "command.txt"; -const PLACE_FILE: &'static str = "place.txt"; -const STATE_FILE: &'static str = "state.json"; - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum Orientation { - North, - East, - South, - West, -} - -impl fmt::Display for Orientation { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str( - match self { - &Orientation::North => "North", - &Orientation::East => "East", - &Orientation::South => "South", - &Orientation::West => "West" - } - ) - } -} - -#[derive(Clone, PartialEq, Eq, Debug)] -enum Action { - PlaceShips(Vec<(String, u32, u32, Orientation)>), - Shoot(u32, u32) -} - -impl fmt::Display for Action { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - &Action::Shoot(x, y) => writeln!(f, "1,{},{}", x, y), - &Action::PlaceShips(ref ships) => ships.iter().map(|&(ref ship_type, x, y, orientation)| { - writeln!(f, "{} {} {} {}", ship_type, x, y, orientation) - }).fold(Ok(()), |acc, next| acc.and(next)) - } - } -} - -fn main() { +fn main() { let working_dir = env::args() .nth(2) - .map(|pwd| PathBuf::from(pwd)) - .expect("Requires game state folder to be passed as the second parameter"); + .map(|x| PathBuf::from(x)) + .ok_or(String::from("Requires game state folder to be passed as the second parameter")); - // The state file is read here. Chances are, you'll want to read - // more values out of it than the sample bot currently does. - let state = read_file(&working_dir).expect("Failed to read state.json"); - - let is_place_phase = state["Phase"] == 1; - let map_dimension = state["MapDimension"].as_u32().expect("Could not read map dimension from the state"); - - let action = if is_place_phase { - place_ships() - } - else { - shoot_randomly(map_dimension) - }; - - - write_action(&working_dir, is_place_phase, action).expect("Failed to write action to file"); -} - -fn read_file(working_dir: &Path) -> Result { - let state_path = working_dir.join(STATE_FILE); - let mut file = File::open(state_path.as_path()).map_err(|e| e.to_string())?; - let mut content = String::new(); - file.read_to_string(&mut content).map_err(|e| e.to_string())?; - json::parse(content.as_ref()).map_err(|e| e.to_string()) -} - -fn place_ships() -> Action { - let ships = vec!( - (String::from("Battleship"), 1, 0, Orientation::North), - (String::from("Carrier"), 3, 1, Orientation::East), - (String::from("Cruiser"), 4, 2, Orientation::North), - (String::from("Destroyer"), 7, 3, Orientation::North), - (String::from("Submarine"), 1, 8, Orientation::East) - ); - - Action::PlaceShips(ships) -} - -fn shoot_randomly(map_dimension: u32) -> Action { - let mut rng = rand::thread_rng(); - let between = Range::new(0, map_dimension); - let x = between.ind_sample(&mut rng); - let y = between.ind_sample(&mut rng); - Action::Shoot(x, y) -} + let result = working_dir.and_then(|working_dir| bot::write_move(working_dir)); -fn write_action(working_dir: &Path, is_place_phase: bool, action: Action) -> Result<(), String> { - let filename = if is_place_phase { - PLACE_FILE + match result { + Ok(()) => println!("Bot terminated successfully"), + Err(e) => println!("Error in bot execution: {}", e) } - else { - COMMAND_FILE - }; - - let full_filename = working_dir.join(filename); - let mut file = File::create(full_filename.as_path()).map_err(|e| e.to_string())?; - writeln!(file, "{}", action).map_err(|e| e.to_string())?; - Ok(()) } diff --git a/src/math.rs b/src/math.rs new file mode 100644 index 0000000..665d4d0 --- /dev/null +++ b/src/math.rs @@ -0,0 +1,49 @@ +use std::fmt; +use rand; +use rand::distributions::{IndependentSample, Range}; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Orientation { + North, + East, + South, + West, +} + +impl fmt::Display for Orientation { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use Orientation::*; + + f.write_str( + match self { + &North => "North", + &East => "East", + &South => "South", + &West => "West" + } + ) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct Point { + pub x: u16, + pub y: u16 +} + +impl Point { + pub fn new(x: u16, y: u16) -> Point { + Point { + x: x, + y: y + } + } +} + +pub fn random_point(map_dimension: u16) -> Point { + let mut rng = rand::thread_rng(); + let between = Range::new(0, map_dimension); + let x = between.ind_sample(&mut rng); + let y = between.ind_sample(&mut rng); + Point::new(x, y) +} diff --git a/src/ships.rs b/src/ships.rs new file mode 100644 index 0000000..8058d4a --- /dev/null +++ b/src/ships.rs @@ -0,0 +1,38 @@ +use std::fmt; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Ship { + Battleship, + Carrier, + Cruiser, + Destroyer, + Submarine +} + +impl fmt::Display for Ship { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use Ship::*; + + f.write_str( + match self { + &Battleship => "Battleship", + &Carrier => "Carrier", + &Cruiser => "Cruiser", + &Destroyer => "Destroyer", + &Submarine => "Submarine" + } + ) + } +} + +impl Ship { + pub fn length(&self) -> u16 { + match self { + &Battleship => 4, + &Carrier => 5, + &Cruiser => 3, + &Destroyer => 2, + &Submarine => 3 + } + } +} -- cgit v1.2.3 From 872529bc9a13b1923047a7f9308abaa40eb63d3c Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Sat, 13 May 2017 21:01:14 +0200 Subject: Random placement --- bot.json | 2 +- src/actions.rs | 52 +++++++++++++++++++++++++++++++++++++++++++++++--- src/files.rs | 5 ++++- src/lib.rs | 30 +++++++---------------------- src/math.rs | 58 +++++++++++++++++++++++++++++++++++++++++++------------- src/placement.rs | 24 +++++++++++++++++++++++ src/ships.rs | 2 ++ src/shooting.rs | 6 ++++++ 8 files changed, 138 insertions(+), 41 deletions(-) create mode 100644 src/placement.rs create mode 100644 src/shooting.rs diff --git a/bot.json b/bot.json index 04c2127..eca72f5 100644 --- a/bot.json +++ b/bot.json @@ -4,5 +4,5 @@ "NickName" :"Admiral Worthebot", "BotType": 8, "ProjectLocation" : "", - "RunFile" : "target\\release\\100k_worthebot_battleships" + "RunFile" : "target\\release\\worthebot_battleships" } diff --git a/src/actions.rs b/src/actions.rs index a7f61b5..1b6400d 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -3,9 +3,11 @@ use ships::*; use std::fmt; +use std::collections::HashSet; + #[derive(Clone, PartialEq, Eq, Debug)] pub enum Action { - PlaceShips(Vec<(Ship, Point, Orientation)>), + PlaceShips(Vec), Shoot(Point) } @@ -13,10 +15,54 @@ impl fmt::Display for Action { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { &Action::Shoot(p) => writeln!(f, "1,{},{}", p.x, p.y), - &Action::PlaceShips(ref ships) => ships.iter().map(|&(ref ship_type, p, orientation)| { - writeln!(f, "{} {} {} {}", ship_type, p.x, p.y, orientation) + &Action::PlaceShips(ref ships) => ships.iter().map(|ref ship| { + writeln!(f, "{} {} {} {}", ship.ship_type, ship.point.x, ship.point.y, ship.direction) }).fold(Ok(()), |acc, next| acc.and(next)) } } } +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct ShipPlacement { + ship_type: Ship, + point: Point, + direction: Direction +} + +impl ShipPlacement { + pub fn new(ship_type: Ship, point: Point, direction: Direction) -> ShipPlacement { + ShipPlacement { + ship_type: ship_type, + point: point, + direction: direction + } + } + + pub fn valid(&self, map_size: u16) -> bool { + let start = self.point; + let end = start.move_point(self.direction, self.ship_type.length(), map_size); + start.x < map_size && start.y < map_size && end.is_some() + } + pub fn valid_placements(placements: &Vec, map_size: u16) -> bool { + let mut occupied = HashSet::new(); + let mut individuals_valid = true; + let mut no_overlaps = true; + + for placement in placements { + individuals_valid = individuals_valid && placement.valid(map_size); + + for i in 0..placement.ship_type.length() { + match placement.point.move_point(placement.direction, i, map_size) { + Some(block) => { + no_overlaps = no_overlaps && !occupied.contains(&block); + occupied.insert(block); + }, + None => { + //invalid case here is handled above + } + } + } + } + individuals_valid && no_overlaps + } +} diff --git a/src/files.rs b/src/files.rs index 87d295f..99dca8c 100644 --- a/src/files.rs +++ b/src/files.rs @@ -27,6 +27,9 @@ pub fn write_action(working_dir: &PathBuf, is_place_phase: bool, action: Action) let full_filename = working_dir.join(filename); let mut file = File::create(full_filename.as_path()).map_err(|e| e.to_string())?; - writeln!(file, "{}", action).map_err(|e| e.to_string())?; + write!(file, "{}", action).map_err(|e| e.to_string())?; + + println!("Making move: {}", action); + Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 5ea9ecc..f3c1a00 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,11 +5,14 @@ mod actions; mod math; mod files; mod ships; +mod placement; +mod shooting; -use actions::*; use math::*; use files::*; use ships::*; +use placement::*; +use shooting::*; use std::path::PathBuf; @@ -17,15 +20,15 @@ pub fn write_move(working_dir: PathBuf) -> Result<(), String> { let state = read_file(&working_dir)?; let is_place_phase = state["Phase"] == 1; - let map_dimension = state["MapDimension"] + let map_size = state["MapDimension"] .as_u16() .ok_or("Did not find the map dimension in the state json file")?; let action = if is_place_phase { - place_ships() + place_ships_randomly(map_size) } else { - shoot_randomly(map_dimension) + shoot_randomly(map_size) }; write_action(&working_dir, is_place_phase, action) @@ -34,22 +37,3 @@ pub fn write_move(working_dir: PathBuf) -> Result<(), String> { Ok(()) } - -fn place_ships() -> Action { - let ships = vec!( - (Ship::Battleship, Point::new(1, 0), Orientation::North), - (Ship::Carrier, Point::new(3, 1), Orientation::East), - (Ship::Cruiser, Point::new(4, 2), Orientation::North), - (Ship::Destroyer, Point::new(7, 3), Orientation::North), - (Ship::Submarine, Point::new(1, 8), Orientation::East) - ); - - Action::PlaceShips(ships) -} - -fn shoot_randomly(map_dimension: u16) -> Action { - Action::Shoot(random_point(map_dimension)) -} - - - diff --git a/src/math.rs b/src/math.rs index 665d4d0..8e81348 100644 --- a/src/math.rs +++ b/src/math.rs @@ -2,18 +2,18 @@ use std::fmt; use rand; use rand::distributions::{IndependentSample, Range}; -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum Orientation { +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub enum Direction { North, East, South, West, } -impl fmt::Display for Orientation { +use Direction::*; + +impl fmt::Display for Direction { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use Orientation::*; - f.write_str( match self { &North => "North", @@ -25,7 +25,22 @@ impl fmt::Display for Orientation { } } -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +impl Direction { + pub fn random() -> Direction { + let mut rng = rand::thread_rng(); + let between = Range::new(0, 4); + let dir = between.ind_sample(&mut rng); + match dir { + 0 => North, + 1 => East, + 2 => South, + 3 => West, + _ => panic!("Invalid number generated by random number generator") + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub struct Point { pub x: u16, pub y: u16 @@ -38,12 +53,29 @@ impl Point { y: y } } -} -pub fn random_point(map_dimension: u16) -> Point { - let mut rng = rand::thread_rng(); - let between = Range::new(0, map_dimension); - let x = between.ind_sample(&mut rng); - let y = between.ind_sample(&mut rng); - Point::new(x, y) + pub fn random(map_size: u16) -> Point { + let mut rng = rand::thread_rng(); + let between = Range::new(0, map_size); + let x = between.ind_sample(&mut rng); + let y = between.ind_sample(&mut rng); + Point::new(x, y) + } + + + pub fn move_point(&self, direction: Direction, distance: u16, map_size: u16) -> Option { + let x = self.x; + let y = self.y; + + match direction { + South if y < distance => None, + West if x < distance => None, + North if y + distance >= map_size => None, + East if x + distance >= map_size => None, + South => Some(Point::new(x, y-distance)), + West => Some(Point::new(x-distance, y)), + North => Some(Point::new(x, y+distance)), + East => Some(Point::new(x+distance, y)) + } + } } diff --git a/src/placement.rs b/src/placement.rs new file mode 100644 index 0000000..faa340e --- /dev/null +++ b/src/placement.rs @@ -0,0 +1,24 @@ +use actions::*; +use math::*; +use ships::*; + +pub fn place_ships_randomly(map_size: u16) -> Action { + let mut current_placement: Vec; + + while { + current_placement = create_random_placement(map_size); + !ShipPlacement::valid_placements(¤t_placement, map_size) + } {} + + Action::PlaceShips(current_placement) +} + +fn create_random_placement(map_size: u16) -> Vec { + vec!( + ShipPlacement::new(Ship::Battleship, Point::random(map_size), Direction::random()), + ShipPlacement::new(Ship::Carrier, Point::random(map_size), Direction::random()), + ShipPlacement::new(Ship::Cruiser, Point::random(map_size), Direction::random()), + ShipPlacement::new(Ship::Destroyer, Point::random(map_size), Direction::random()), + ShipPlacement::new(Ship::Submarine, Point::random(map_size), Direction::random()) + ) +} diff --git a/src/ships.rs b/src/ships.rs index 8058d4a..e21b20e 100644 --- a/src/ships.rs +++ b/src/ships.rs @@ -27,6 +27,8 @@ impl fmt::Display for Ship { impl Ship { pub fn length(&self) -> u16 { + use Ship::*; + match self { &Battleship => 4, &Carrier => 5, diff --git a/src/shooting.rs b/src/shooting.rs new file mode 100644 index 0000000..14f2b81 --- /dev/null +++ b/src/shooting.rs @@ -0,0 +1,6 @@ +use actions::*; +use math::*; + +pub fn shoot_randomly(map_size: u16) -> Action { + Action::Shoot(Point::random(map_size)) +} -- cgit v1.2.3 From 05ccf5572b39fe254c7b0464cc78a1b623f732c7 Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Sat, 13 May 2017 21:43:27 +0200 Subject: Added checking if state is in use before shooting --- src/lib.rs | 15 ++++++----- src/shooting.rs | 14 +++++++++-- src/state.rs | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 src/state.rs diff --git a/src/lib.rs b/src/lib.rs index f3c1a00..fab9e36 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,28 +7,31 @@ mod files; mod ships; mod placement; mod shooting; +mod state; use math::*; use files::*; use ships::*; use placement::*; use shooting::*; +use state::*; use std::path::PathBuf; pub fn write_move(working_dir: PathBuf) -> Result<(), String> { - let state = read_file(&working_dir)?; + let state_json = read_file(&working_dir)?; - let is_place_phase = state["Phase"] == 1; - let map_size = state["MapDimension"] - .as_u16() - .ok_or("Did not find the map dimension in the state json file")?; + println!("{}", state_json); + + let is_place_phase = state_json["Phase"] == 1; + let map_size = State::map_size_from_json(&state_json)?; let action = if is_place_phase { place_ships_randomly(map_size) } else { - shoot_randomly(map_size) + let state = State::new(&state_json)?; + shoot_randomly(&state) }; write_action(&working_dir, is_place_phase, action) diff --git a/src/shooting.rs b/src/shooting.rs index 14f2b81..c2ca56e 100644 --- a/src/shooting.rs +++ b/src/shooting.rs @@ -1,6 +1,16 @@ use actions::*; use math::*; +use state::*; -pub fn shoot_randomly(map_size: u16) -> Action { - Action::Shoot(Point::random(map_size)) +pub fn shoot_randomly(state: &State) -> Action { + let mut shot: Point; + + while { + shot = Point::random(state.map_size); + let ref target = state.opponent_map.cells[shot.x as usize][shot.y as usize]; + target.damaged || target.missed + } {} + + + Action::Shoot(shot) } diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..df66ec9 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,78 @@ +use json; + +pub struct State { + pub map_size: u16, + pub opponent_map: OpponentMap +} + +impl State { + pub fn new(json: &json::JsonValue) -> Result { + let map_size = State::map_size_from_json(&json)?; + let ref opponent_map_json = json["OpponentMap"]; + let opponent_map = OpponentMap::new(&opponent_map_json, map_size)?; + + Ok(State { + map_size: map_size, + opponent_map: opponent_map + }) + } + + pub fn map_size_from_json(json: &json::JsonValue) -> Result { + json["MapDimension"] + .as_u16() + .ok_or(String::from("Did not find the map dimension in the state json file")) + } +} + +pub struct OpponentMap { + pub cells: Vec> +} + +impl OpponentMap { + fn new(json: &json::JsonValue, map_size: u16) -> Result { + let mut cells = Vec::with_capacity(map_size as usize); + for _ in 0..map_size { + let mut row = Vec::with_capacity(map_size as usize); + for _ in 0..map_size { + row.push(Cell::new()); + } + cells.push(row); + } + + for json_cell in json["Cells"].members() { + let x = json_cell["X"] + .as_u16() + .ok_or(String::from("Failed to read X value of opponent map cell in json file"))?; + let y = json_cell["Y"] + .as_u16() + .ok_or(String::from("Failed to read Y value of opponent map cell in json file"))?; + let damaged = json_cell["Damaged"] + .as_bool() + .ok_or(String::from("Failed to read Damaged value of opponent map cell in json file"))?; + let missed = json_cell["Missed"] + .as_bool() + .ok_or(String::from("Failed to read Missed value of opponent map cell in json file"))?; + + cells[x as usize][y as usize].damaged = damaged; + cells[x as usize][y as usize].missed = missed; + } + + Ok(OpponentMap { + cells: cells + }) + } +} + +pub struct Cell { + pub damaged: bool, + pub missed: bool +} + +impl Cell { + fn new() -> Cell { + Cell { + damaged: false, + missed: false + } + } +} -- cgit v1.2.3 From 25a551316e27f4cc52c160d099db9cc3673b3421 Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Sun, 14 May 2017 17:10:26 +0200 Subject: Added documentation of json state types --- readme.md | 0 readme.org | 1447 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/files.rs | 3 +- src/lib.rs | 2 +- 4 files changed, 1450 insertions(+), 2 deletions(-) delete mode 100644 readme.md create mode 100644 readme.org diff --git a/readme.md b/readme.md deleted file mode 100644 index e69de29..0000000 diff --git a/readme.org b/readme.org new file mode 100644 index 0000000..ff3e319 --- /dev/null +++ b/readme.org @@ -0,0 +1,1447 @@ +* State.json + +#+BEGIN_EXAMPLE +State = {PlayerMap, OpponentMap, GameVersion=String, GameLevel=u16, Round=u16, MapDimension=u16, Phase=u16, Player1Map=null, Player2Map=null} + +PlayerMap = {Cells=[PlayerCell], Owner, MapWidth=u16, MapHeight=u16} + +PlayerCell = {Occupied=bool, Hit=bool, X=u16, Y=u16} + +Owner = {FailedFirstPhaseCommands=u16, Name=String, Ships=[PlayerShip], Points=u16, Killed=bool, IsWinner=bool, ShotsFired=u16, ShotsHit=u16, ShipsRemaining=u16, Key=String} + +PlayerShip = {Cells=[PlayerCell], Destroyed=bool, Placed=bool, ShipType=String, Weapons=[PlayerWeapon], } + +PlayerWeapon = {WeaponType=String} + +OpponentMap = {Ships=[OpponentShip], Alive=bool, Points=u16, Name=String, Cells=[OpponentCell]} + +OpponentShip = {Destroyed=bool, ShipType=String} + +OpponentCell = {Damaged=bool, Missed=bool, X=u16, Y=u16} + +#+END_EXAMPLE + +* State.json example + +#+BEGIN_SRC json +{ + "PlayerMap": { + "Cells": [ + { + "Occupied": false, + "Hit": true, + "X": 0, + "Y": 0 + }, + { + "Occupied": false, + "Hit": false, + "X": 0, + "Y": 1 + }, + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 2 + }, + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 3 + }, + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 4 + }, + { + "Occupied": false, + "Hit": false, + "X": 0, + "Y": 5 + }, + { + "Occupied": true, + "Hit": true, + "X": 0, + "Y": 6 + }, + { + "Occupied": true, + "Hit": true, + + "X": 0, + "Y": 7 + }, + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 8 + }, + { + "Occupied": false, + "Hit": true, + "X": 0, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 1, + "Y": 0 + }, + { + "Occupied": false, + "Hit": false, + "X": 1, + "Y": 1 + }, + { + "Occupied": false, + "Hit": false, + "X": 1, + "Y": 2 + }, + { + "Occupied": false, + "Hit": true, + "X": 1, + "Y": 3 + }, + { + "Occupied": false, + "Hit": false, + "X": 1, + "Y": 4 + }, + { + "Occupied": false, + "Hit": false, + "X": 1, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 1, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 1, + "Y": 7 + }, + { + "Occupied": true, + "Hit": false, + "X": 1, + "Y": 8 + }, + { + "Occupied": false, + "Hit": true, + "X": 1, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 2, + "Y": 0 + }, + { + "Occupied": false, + "Hit": false, + "X": 2, + "Y": 1 + }, + { + "Occupied": false, + "Hit": false, + "X": 2, + "Y": 2 + }, + { + "Occupied": false, + "Hit": false, + "X": 2, + "Y": 3 + }, + { + "Occupied": false, + "Hit": true, + "X": 2, + "Y": 4 + }, + { + "Occupied": false, + "Hit": true, + "X": 2, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 2, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 2, + "Y": 7 + }, + { + "Occupied": true, + "Hit": true, + "X": 2, + "Y": 8 + }, + { + "Occupied": false, + "Hit": false, + "X": 2, + "Y": 9 + }, + { + "Occupied": false, + "Hit": true, + "X": 3, + "Y": 0 + }, + { + "Occupied": false, + "Hit": true, + "X": 3, + "Y": 1 + }, + { + "Occupied": false, + "Hit": true, + "X": 3, + "Y": 2 + }, + { + "Occupied": false, + "Hit": false, + "X": 3, + "Y": 3 + }, + { + "Occupied": false, + "Hit": false, + "X": 3, + "Y": 4 + }, + { + "Occupied": false, + "Hit": false, + "X": 3, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 3, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 3, + "Y": 7 + }, + { + "Occupied": true, + "Hit": false, + "X": 3, + "Y": 8 + }, + { + "Occupied": false, + "Hit": false, + "X": 3, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 4, + "Y": 0 + }, + { + "Occupied": false, + "Hit": true, + "X": 4, + "Y": 1 + }, + { + "Occupied": false, + "Hit": true, + "X": 4, + "Y": 2 + }, + { + "Occupied": false, + "Hit": false, + "X": 4, + "Y": 3 + }, + { + "Occupied": false, + "Hit": false, + "X": 4, + "Y": 4 + }, + { + "Occupied": false, + "Hit": false, + "X": 4, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 4, + "Y": 6 + }, + { + "Occupied": false, + "Hit": true, + "X": 4, + "Y": 7 + }, + { + "Occupied": true, + "Hit": false, + "X": 4, + "Y": 8 + }, + { + "Occupied": false, + "Hit": true, + "X": 4, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 0 + }, + { + "Occupied": true, + "Hit": true, + "X": 5, + "Y": 1 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 2 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 3 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 4 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 7 + }, + { + "Occupied": true, + "Hit": false, + "X": 5, + "Y": 8 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 6, + "Y": 0 + }, + { + "Occupied": true, + "Hit": false, + "X": 6, + "Y": 1 + }, + { + "Occupied": false, + "Hit": false, + "X": 6, + "Y": 2 + }, + { + "Occupied": false, + "Hit": true, + "X": 6, + "Y": 3 + }, + { + "Occupied": false, + "Hit": false, + "X": 6, + "Y": 4 + }, + { + "Occupied": false, + "Hit": true, + "X": 6, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 6, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 6, + "Y": 7 + }, + { + "Occupied": false, + "Hit": true, + "X": 6, + "Y": 8 + }, + { + "Occupied": false, + "Hit": false, + "X": 6, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 7, + "Y": 0 + }, + { + "Occupied": true, + "Hit": true, + "X": 7, + "Y": 1 + }, + { + "Occupied": false, + "Hit": false, + "X": 7, + "Y": 2 + }, + { + "Occupied": false, + "Hit": false, + "X": 7, + "Y": 3 + }, + { + "Occupied": false, + "Hit": false, + "X": 7, + "Y": 4 + }, + { + "Occupied": true, + "Hit": false, + "X": 7, + "Y": 5 + }, + { + "Occupied": true, + "Hit": false, + "X": 7, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 7, + "Y": 7 + }, + { + "Occupied": false, + "Hit": false, + "X": 7, + "Y": 8 + }, + { + "Occupied": false, + "Hit": false, + "X": 7, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 8, + "Y": 0 + }, + { + "Occupied": true, + "Hit": false, + "X": 8, + "Y": 1 + }, + { + "Occupied": false, + "Hit": false, + "X": 8, + "Y": 2 + }, + { + "Occupied": false, + "Hit": true, + "X": 8, + "Y": 3 + }, + { + "Occupied": false, + "Hit": true, + "X": 8, + "Y": 4 + }, + { + "Occupied": false, + "Hit": false, + "X": 8, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 8, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 8, + "Y": 7 + }, + { + "Occupied": false, + "Hit": false, + "X": 8, + "Y": 8 + }, + { + "Occupied": false, + "Hit": true, + "X": 8, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 0 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 1 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 2 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 3 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 4 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 7 + }, + { + "Occupied": false, + "Hit": true, + "X": 9, + "Y": 8 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 9 + } + ], + "Owner": { + "FailedFirstPhaseCommands": 0, + "Name": "Admiral Worthebot", + "Ships": [ + { + "Cells": [ + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 2 + }, + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 3 + }, + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 4 + } + ], + "Destroyed": false, + "Placed": true, + "ShipType": "Submarine", + "Weapons": [ + { + "WeaponType": "SingleShot" + } + ] + }, + { + "Cells": [ + { + "Occupied": true, + "Hit": false, + "X": 7, + "Y": 6 + }, + { + "Occupied": true, + "Hit": false, + "X": 7, + "Y": 5 + } + ], + "Destroyed": false, + "Placed": true, + "ShipType": "Destroyer", + "Weapons": [ + { + "WeaponType": "SingleShot" + } + ] + }, + { + "Cells": [ + { + "Occupied": true, + "Hit": true, + "X": 5, + "Y": 1 + }, + { + "Occupied": true, + "Hit": false, + "X": 6, + "Y": 1 + }, + { + "Occupied": true, + "Hit": true, + "X": 7, + "Y": 1 + }, + { + "Occupied": true, + "Hit": false, + "X": 8, + "Y": 1 + } + ], + "Destroyed": false, + "Placed": true, + "ShipType": "Battleship", + "Weapons": [ + { + "WeaponType": "SingleShot" + } + ] + }, + { + "Cells": [ + { + "Occupied": true, + "Hit": false, + "X": 5, + "Y": 8 + }, + { + "Occupied": true, + "Hit": false, + "X": 4, + "Y": 8 + }, + { + "Occupied": true, + "Hit": false, + "X": 3, + "Y": 8 + }, + { + "Occupied": true, + "Hit": true, + "X": 2, + "Y": 8 + }, + { + "Occupied": true, + "Hit": false, + "X": 1, + "Y": 8 + } + ], + "Destroyed": false, + "Placed": true, + "ShipType": "Carrier", + "Weapons": [ + { + "WeaponType": "SingleShot" + } + ] + }, + { + "Cells": [ + { + "Occupied": true, + "Hit": true, + "X": 0, + "Y": 6 + }, + { + "Occupied": true, + "Hit": true, + "X": 0, + "Y": 7 + }, + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 8 + } + ], + "Destroyed": false, + "Placed": true, + "ShipType": "Cruiser", + "Weapons": [ + { + "WeaponType": "SingleShot" + } + ] + } + ], + "Points": 280, + "Killed": false, + "IsWinner": false, + "ShotsFired": 86, + "ShotsHit": 16, + "ShipsRemaining": 5, + "Key": "B" + }, + "MapWidth": 10, + "MapHeight": 10 + }, + "OpponentMap": { + "Ships": [ + { + "Destroyed": false, + "ShipType": "Submarine" + }, + { + "Destroyed": true, + "ShipType": "Destroyer" + }, + { + "Destroyed": true, + "ShipType": "Battleship" + }, + { + "Destroyed": true, + "ShipType": "Carrier" + }, + { + "Destroyed": true, + "ShipType": "Cruiser" + } + ], + "Alive": true, + "Points": 50, + "Name": "John", + "Cells": [ + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 0 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 1 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 2 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 4 + }, + { + "Damaged": false, + "Missed": false, + "X": 0, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 7 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 8 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 9 + }, + { + "Damaged": true, + "Missed": false, + "X": 1, + "Y": 0 + }, + { + "Damaged": true, + "Missed": false, + "X": 1, + "Y": 1 + }, + { + "Damaged": true, + "Missed": false, + "X": 1, + "Y": 2 + }, + { + "Damaged": true, + "Missed": false, + "X": 1, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 1, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 1, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 1, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 1, + "Y": 7 + }, + { + "Damaged": true, + "Missed": false, + "X": 1, + "Y": 8 + }, + { + "Damaged": false, + "Missed": true, + "X": 1, + "Y": 9 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 0 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 1 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 2 + }, + { + "Damaged": false, + "Missed": false, + "X": 2, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 7 + }, + { + "Damaged": false, + "Missed": false, + "X": 2, + "Y": 8 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 9 + }, + { + "Damaged": false, + "Missed": false, + "X": 3, + "Y": 0 + }, + { + "Damaged": true, + "Missed": false, + "X": 3, + "Y": 1 + }, + { + "Damaged": false, + "Missed": false, + "X": 3, + "Y": 2 + }, + { + "Damaged": false, + "Missed": false, + "X": 3, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 3, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 3, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 3, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 3, + "Y": 7 + }, + { + "Damaged": true, + "Missed": false, + "X": 3, + "Y": 8 + }, + { + "Damaged": false, + "Missed": false, + "X": 3, + "Y": 9 + }, + { + "Damaged": false, + "Missed": false, + "X": 4, + "Y": 0 + }, + { + "Damaged": true, + "Missed": false, + "X": 4, + "Y": 1 + }, + { + "Damaged": true, + "Missed": false, + "X": 4, + "Y": 2 + }, + { + "Damaged": true, + "Missed": false, + "X": 4, + "Y": 3 + }, + { + "Damaged": true, + "Missed": false, + "X": 4, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 4, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 4, + "Y": 6 + }, + { + "Damaged": false, + "Missed": false, + "X": 4, + "Y": 7 + }, + { + "Damaged": false, + "Missed": true, + "X": 4, + "Y": 8 + }, + { + "Damaged": false, + "Missed": true, + "X": 4, + "Y": 9 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 0 + }, + { + "Damaged": true, + "Missed": false, + "X": 5, + "Y": 1 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 2 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 7 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 8 + }, + { + "Damaged": false, + "Missed": false, + "X": 5, + "Y": 9 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 0 + }, + { + "Damaged": true, + "Missed": false, + "X": 6, + "Y": 1 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 2 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 7 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 8 + }, + { + "Damaged": false, + "Missed": false, + "X": 6, + "Y": 9 + }, + { + "Damaged": false, + "Missed": false, + "X": 7, + "Y": 0 + }, + { + "Damaged": true, + "Missed": false, + "X": 7, + "Y": 1 + }, + { + "Damaged": false, + "Missed": true, + "X": 7, + "Y": 2 + }, + { + "Damaged": true, + "Missed": false, + "X": 7, + "Y": 3 + }, + { + "Damaged": true, + "Missed": false, + "X": 7, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 7, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 7, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 7, + "Y": 7 + }, + { + "Damaged": false, + "Missed": true, + "X": 7, + "Y": 8 + }, + { + "Damaged": false, + "Missed": true, + "X": 7, + "Y": 9 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 0 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 1 + }, + { + "Damaged": false, + "Missed": false, + "X": 8, + "Y": 2 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 7 + }, + { + "Damaged": false, + "Missed": false, + "X": 8, + "Y": 8 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 9 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 0 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 1 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 2 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 7 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 8 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 9 + } + ] + }, + "GameVersion": "1.0.0", + "GameLevel": 1, + "Round": 87, + "MapDimension": 10, + "Phase": 2, + "Player1Map": null, + "Player2Map": null +} +#+END_SRC diff --git a/src/files.rs b/src/files.rs index 99dca8c..7f3daed 100644 --- a/src/files.rs +++ b/src/files.rs @@ -5,9 +5,10 @@ use std::path::PathBuf; use actions::*; +const STATE_FILE: &'static str = "state.json"; + const COMMAND_FILE: &'static str = "command.txt"; const PLACE_FILE: &'static str = "place.txt"; -const STATE_FILE: &'static str = "state.json"; pub fn read_file(working_dir: &PathBuf) -> Result { let state_path = working_dir.join(STATE_FILE); diff --git a/src/lib.rs b/src/lib.rs index fab9e36..5dda02f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,7 @@ use std::path::PathBuf; pub fn write_move(working_dir: PathBuf) -> Result<(), String> { let state_json = read_file(&working_dir)?; - println!("{}", state_json); + println!("\n\n{}\n\n", state_json.pretty(2)); let is_place_phase = state_json["Phase"] == 1; let map_size = State::map_size_from_json(&state_json)?; -- cgit v1.2.3 From 10c8ceb168e86a58e38086691ddd519bac63ff03 Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Sun, 14 May 2017 21:49:48 +0200 Subject: Added model for knowledge of the game's state Will be useful to track deductions that have already been made. --- .gitignore | 1 + Cargo.lock | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 5 ++- src/actions.rs | 4 +-- src/files.rs | 21 +++++++++++++ src/knowledge.rs | 54 +++++++++++++++++++++++++++++++++ src/lib.rs | 25 +++++++++++++-- src/math.rs | 4 +-- src/ships.rs | 19 +++++++++++- src/state.rs | 30 ++++++++++++++++-- 10 files changed, 244 insertions(+), 11 deletions(-) create mode 100644 src/knowledge.rs diff --git a/.gitignore b/.gitignore index 54466f5..a41bb2d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target +/knowledge-state.json diff --git a/Cargo.lock b/Cargo.lock index 652adca..96ea518 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,8 +4,21 @@ version = "0.1.0" dependencies = [ "json 0.11.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "dtoa" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "itoa" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "json" version = "0.11.6" @@ -16,6 +29,16 @@ name = "libc" version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "num-traits" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "quote" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rand" version = "0.3.15" @@ -24,7 +47,76 @@ dependencies = [ "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "serde" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde_derive" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive_internals 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive_internals" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", + "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_json" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synom" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-xid" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [metadata] +"checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90" +"checksum itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2f404fbc66fd9aac13e998248505e7ecb2ad8e44ab6388684c5fb11c6c251c" "checksum json 0.11.6 (registry+https://github.com/rust-lang/crates.io-index)" = "27600e8bb3b71bcc6213fb36b66b8dce60adc17a624257687ef5d1d4facafba7" "checksum libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)" = "babb8281da88cba992fa1f4ddec7d63ed96280a1a53ec9b919fd37b53d71e502" +"checksum num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "e1cbfa3781f3fe73dc05321bed52a06d2d491eaa764c52335cf4399f046ece99" +"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" "checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d" +"checksum serde 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "991ef6be409a3b7a46cb9ee701d86156ce851825c65dbee7f16dbd5c4e7e2d47" +"checksum serde_derive 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9fd81eef9f0b4ec341b11095335b6a4b28ed85581b12dd27585dee1529df35e0" +"checksum serde_derive_internals 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "021c338d22c7e30f957a6ab7e388cb6098499dda9fd4ba1661ee074ca7a180d1" +"checksum serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "48b04779552e92037212c3615370f6bd57a40ebba7f20e554ff9f55e41a69a7b" +"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" +"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" diff --git a/Cargo.toml b/Cargo.toml index c90a18f..cdebbe8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,7 @@ authors = ["Justin Worthe "] [dependencies] rand = "0.3" -json = "0.11.6" \ No newline at end of file +json = "0.11.6" +serde = "1.0.4" +serde_json = "1.0.2" +serde_derive = "1.0.4" \ No newline at end of file diff --git a/src/actions.rs b/src/actions.rs index 1b6400d..9009099 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -5,7 +5,7 @@ use std::fmt; use std::collections::HashSet; -#[derive(Clone, PartialEq, Eq, Debug)] +#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] pub enum Action { PlaceShips(Vec), Shoot(Point) @@ -22,7 +22,7 @@ impl fmt::Display for Action { } } -#[derive(Clone, PartialEq, Eq, Debug)] +#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] pub struct ShipPlacement { ship_type: Ship, point: Point, diff --git a/src/files.rs b/src/files.rs index 7f3daed..0810a4e 100644 --- a/src/files.rs +++ b/src/files.rs @@ -1,15 +1,21 @@ use json; +use serde_json; + use std::io::prelude::*; use std::fs::File; use std::path::PathBuf; use actions::*; +use knowledge::*; const STATE_FILE: &'static str = "state.json"; const COMMAND_FILE: &'static str = "command.txt"; const PLACE_FILE: &'static str = "place.txt"; +const KNOWLEDGE_FILE: &'static str = "knowledge-state.json"; + + pub fn read_file(working_dir: &PathBuf) -> Result { let state_path = working_dir.join(STATE_FILE); let mut file = File::open(state_path.as_path()).map_err(|e| e.to_string())?; @@ -34,3 +40,18 @@ pub fn write_action(working_dir: &PathBuf, is_place_phase: bool, action: Action) Ok(()) } + +pub fn read_knowledge() -> Result { + let mut file = File::open(KNOWLEDGE_FILE).map_err(|e| e.to_string())?; + let mut content = String::new(); + file.read_to_string(&mut content).map_err(|e| e.to_string())?; + serde_json::from_str(content.as_ref()).map_err(|e| e.to_string()) +} + +pub fn write_knowledge(knowledge: &Knowledge) -> Result<(), String> { + let json = serde_json::to_string(knowledge).map_err(|e| e.to_string())?; + let mut file = File::create(KNOWLEDGE_FILE).map_err(|e| e.to_string())?; + write!(file, "{}", json).map_err(|e| e.to_string())?; + + Ok(()) +} diff --git a/src/knowledge.rs b/src/knowledge.rs new file mode 100644 index 0000000..3b0c61a --- /dev/null +++ b/src/knowledge.rs @@ -0,0 +1,54 @@ +use actions::*; +use ships::*; +use std::collections::HashMap; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct Knowledge { + pub last_action: Option, + pub opponent_map: OpponentMapKnowledge +} + +impl Knowledge { + pub fn new() -> Knowledge { + Knowledge { + last_action: None, + opponent_map: OpponentMapKnowledge::new() + } + } + + pub fn with_action(&self, action: Action) -> Knowledge { + Knowledge { + last_action: Some(action), + opponent_map: self.opponent_map.clone() + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct OpponentMapKnowledge { + pub ships: HashMap, + pub cells: Vec> +} + +impl OpponentMapKnowledge { + pub fn new() -> OpponentMapKnowledge { + OpponentMapKnowledge { + ships: HashMap::new(), + cells: Vec::new() + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct OpponentShipKnowledge { + pub destroyed: bool +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct KnowledgeCell { + pub shot_attempted: bool, + pub possible_ship_uses: HashMap, + pub known_ship: Option +} + + diff --git a/src/lib.rs b/src/lib.rs index 5dda02f..81dd845 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,9 @@ extern crate json; extern crate rand; +extern crate serde; +extern crate serde_json; +#[macro_use] +extern crate serde_derive; mod actions; mod math; @@ -8,6 +12,7 @@ mod ships; mod placement; mod shooting; mod state; +mod knowledge; use math::*; use files::*; @@ -15,17 +20,23 @@ use ships::*; use placement::*; use shooting::*; use state::*; +use knowledge::*; use std::path::PathBuf; pub fn write_move(working_dir: PathBuf) -> Result<(), String> { let state_json = read_file(&working_dir)?; - println!("\n\n{}\n\n", state_json.pretty(2)); - let is_place_phase = state_json["Phase"] == 1; let map_size = State::map_size_from_json(&state_json)?; + let starting_knowledge = if is_place_phase { + Knowledge::new() + } + else { + read_knowledge()? + }; + let action = if is_place_phase { place_ships_randomly(map_size) } @@ -34,8 +45,18 @@ pub fn write_move(working_dir: PathBuf) -> Result<(), String> { shoot_randomly(&state) }; + let ending_knowledge = starting_knowledge + .with_action(action.clone()); + + write_knowledge(&ending_knowledge) + .map_err(|e| format!("Failed to write knowledge to file. Error: {}", e))?; + write_action(&working_dir, is_place_phase, action) .map_err(|e| format!("Failed to write action to file. Error: {}", e))?; + + println!("Input state:\n{}\n\n", state_json); + println!("Existing knowledge:\n{}\n\n", serde_json::to_string(&starting_knowledge).unwrap_or(String::from(""))); + println!("End of turn knowledge:\n{}\n\n", serde_json::to_string(&ending_knowledge).unwrap_or(String::from(""))); Ok(()) } diff --git a/src/math.rs b/src/math.rs index 8e81348..3d8a976 100644 --- a/src/math.rs +++ b/src/math.rs @@ -2,7 +2,7 @@ use std::fmt; use rand; use rand::distributions::{IndependentSample, Range}; -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] pub enum Direction { North, East, @@ -40,7 +40,7 @@ impl Direction { } } -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] pub struct Point { pub x: u16, pub y: u16 diff --git a/src/ships.rs b/src/ships.rs index e21b20e..344f9ed 100644 --- a/src/ships.rs +++ b/src/ships.rs @@ -1,6 +1,7 @@ use std::fmt; +use std::str; -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] pub enum Ship { Battleship, Carrier, @@ -25,6 +26,22 @@ impl fmt::Display for Ship { } } +impl str::FromStr for Ship { + type Err = String; + fn from_str(s: &str) -> Result { + use Ship::*; + + match s { + "Battleship" => Ok(Battleship), + "Carrier" => Ok(Carrier), + "Cruiser" => Ok(Cruiser), + "Destroyer" => Ok(Destroyer), + "Submarine" => Ok(Submarine), + _ => Err(String::from("ship type is not known")) + } + } +} + impl Ship { pub fn length(&self) -> u16 { use Ship::*; diff --git a/src/state.rs b/src/state.rs index df66ec9..8c176e1 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,4 +1,6 @@ use json; +use std::collections::HashMap; +use ships::*; pub struct State { pub map_size: u16, @@ -25,7 +27,8 @@ impl State { } pub struct OpponentMap { - pub cells: Vec> + pub cells: Vec>, + pub ships: HashMap, } impl OpponentMap { @@ -38,7 +41,7 @@ impl OpponentMap { } cells.push(row); } - + for json_cell in json["Cells"].members() { let x = json_cell["X"] .as_u16() @@ -56,13 +59,34 @@ impl OpponentMap { cells[x as usize][y as usize].damaged = damaged; cells[x as usize][y as usize].missed = missed; } + + let mut ships = HashMap::new(); + for json_ship in json["Ships"].members() { + let ship_type_string = json_ship["ShipType"] + .as_str() + .ok_or(String::from("Failed to read ShipType value of opponent map ship in json file"))?; + let ship_type = ship_type_string.parse::()?; + + let destroyed = json_ship["Destroyed"] + .as_bool() + .ok_or(String::from("Failed to read Destroyed value of opponent map ship in json file"))?; + ships.insert(ship_type, OpponentShip { + destroyed: destroyed + }); + } + Ok(OpponentMap { - cells: cells + cells: cells, + ships: ships }) } } +pub struct OpponentShip { + pub destroyed: bool +} + pub struct Cell { pub damaged: bool, pub missed: bool -- cgit v1.2.3 From 7dd0fe43fe7e72e5f56a8a61bbaec3a78399e6c8 Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Sat, 20 May 2017 18:01:43 +0200 Subject: Moved ship placement knowledge out to be one per ship --- src/actions.rs | 4 +- src/knowledge.rs | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++----- src/lib.rs | 40 ++++++++------- src/math.rs | 70 +++++++++++++++++++++----- src/placement.rs | 1 - src/ships.rs | 12 +++++ 6 files changed, 228 insertions(+), 46 deletions(-) diff --git a/src/actions.rs b/src/actions.rs index 9009099..2291de6 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -40,7 +40,7 @@ impl ShipPlacement { pub fn valid(&self, map_size: u16) -> bool { let start = self.point; - let end = start.move_point(self.direction, self.ship_type.length(), map_size); + let end = start.move_point(self.direction, self.ship_type.length() as i32, map_size); start.x < map_size && start.y < map_size && end.is_some() } pub fn valid_placements(placements: &Vec, map_size: u16) -> bool { @@ -51,7 +51,7 @@ impl ShipPlacement { for placement in placements { individuals_valid = individuals_valid && placement.valid(map_size); - for i in 0..placement.ship_type.length() { + for i in 0..placement.ship_type.length() as i32 { match placement.point.move_point(placement.direction, i, map_size) { Some(block) => { no_overlaps = no_overlaps && !occupied.contains(&block); diff --git a/src/knowledge.rs b/src/knowledge.rs index 3b0c61a..20c982f 100644 --- a/src/knowledge.rs +++ b/src/knowledge.rs @@ -1,27 +1,42 @@ use actions::*; use ships::*; +use state::*; +use math::*; + use std::collections::HashMap; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct Knowledge { - pub last_action: Option, + pub last_action: Action, pub opponent_map: OpponentMapKnowledge } impl Knowledge { - pub fn new() -> Knowledge { + pub fn new(map_size: u16, action: Action) -> Knowledge { Knowledge { - last_action: None, - opponent_map: OpponentMapKnowledge::new() + last_action: action, + opponent_map: OpponentMapKnowledge::new(map_size) } } pub fn with_action(&self, action: Action) -> Knowledge { Knowledge { - last_action: Some(action), + last_action: action, opponent_map: self.opponent_map.clone() } } + + pub fn resolve_last_action(&self, state: &State) -> Knowledge { + let mut new_knowledge = self.clone(); + match self.last_action { + Action::PlaceShips(_) => {}, + Action::Shoot(p) => { + new_knowledge.opponent_map.update_from_shot(p, &state); + } + }; + + new_knowledge + } } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -31,24 +46,132 @@ pub struct OpponentMapKnowledge { } impl OpponentMapKnowledge { - pub fn new() -> OpponentMapKnowledge { + pub fn new(map_size: u16) -> OpponentMapKnowledge { + let mut cells = Vec::with_capacity(map_size as usize); + for x in 0..map_size { + cells.push(Vec::with_capacity(map_size as usize)); + for y in 0..map_size { + cells[x as usize].push(KnowledgeCell::new(x, y)); + } + } + + let ships = Ship::all_types().iter() + .map(|s| (s.clone(), OpponentShipKnowledge::new(s.clone(), map_size))) + .collect::>(); + OpponentMapKnowledge { - ships: HashMap::new(), - cells: Vec::new() + ships: ships, + cells: cells } } + + fn update_from_shot(&mut self, p: Point, state: &State) { + let ref shot_cell = state.opponent_map.cells[p.x as usize][p.y as usize]; + let sunk_ship = self.ships.iter() + .filter(|&(_, x)| !x.destroyed) + .filter(|&(s, _)| state.opponent_map.ships.get(s).map(|x| x.destroyed) == Some(true)) + .map(|(s, _)| s.clone()) + .next(); //only one ship can be sunk at a time + + match sunk_ship { + None => {}, + Some(ship) => { + match self.ships.get_mut(&ship) { + Some(ref mut ship_knowledge) => {ship_knowledge.destroyed = true}, + None => {} + } + } + } + + let ref mut knowledge_cell = self.cells[p.x as usize][p.y as usize]; + if shot_cell.missed { + knowledge_cell.missed = true; + // knowledge_cell.possible_ship_uses.clear(); + } + else { + knowledge_cell.hit = true; + knowledge_cell.known_ship = sunk_ship; + // knowledge_cell.reduce_possibilities_to_known_ship(); + } + + } } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct OpponentShipKnowledge { - pub destroyed: bool + pub ship: Ship, + pub destroyed: bool, + pub possible_placements: Vec +} + +impl OpponentShipKnowledge { + pub fn new(ship: Ship, map_size: u16) -> OpponentShipKnowledge { + OpponentShipKnowledge { + ship: ship, + destroyed: false, + possible_placements: PossibleShipPlacement::enumerate(ship, map_size) + } + } } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct KnowledgeCell { - pub shot_attempted: bool, - pub possible_ship_uses: HashMap, - pub known_ship: Option + pub missed: bool, + pub hit: bool, + pub known_ship: Option, + pub position: Point +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct PossibleShipPlacement { + pub ship: Ship, + pub direction: Direction, + pub position: Point +} + +impl PossibleShipPlacement { + pub fn enumerate(ship: Ship, map_size: u16) -> Vec { + (0..(map_size-ship.length()+1)).flat_map(move |par| { + (0..map_size).flat_map(move |per| { + vec!( + PossibleShipPlacement { + ship: ship, + direction: Direction::East, + position: Point::new(par, per) + }, + PossibleShipPlacement { + ship: ship, + direction: Direction::South, + position: Point::new(per, par) + } + ) + }) + }).collect() + } + + pub fn touches_point(&self, p: Point) -> bool { + p.check_for_ship_collision(self.position, self.direction, self.ship.length()) + } +} + +impl KnowledgeCell { + pub fn new(x: u16, y: u16) -> KnowledgeCell { + KnowledgeCell { + missed: false, + hit: false, + position: Point::new(x, y), + known_ship: None + } + } + + pub fn shot_attempted(&self) -> bool { + self.missed || self.hit + } + + pub fn unknown_hit(&self) -> bool { + self.hit && self.known_ship.is_none() + } + } diff --git a/src/lib.rs b/src/lib.rs index 81dd845..f84c172 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ mod shooting; mod state; mod knowledge; +use actions::*; use math::*; use files::*; use ships::*; @@ -30,34 +31,37 @@ pub fn write_move(working_dir: PathBuf) -> Result<(), String> { let is_place_phase = state_json["Phase"] == 1; let map_size = State::map_size_from_json(&state_json)?; - let starting_knowledge = if is_place_phase { - Knowledge::new() - } - else { - read_knowledge()? - }; - - let action = if is_place_phase { - place_ships_randomly(map_size) + let (action, knowledge) = if is_place_phase { + placement(map_size) } else { let state = State::new(&state_json)?; - shoot_randomly(&state) + shoot(&state)? }; - - let ending_knowledge = starting_knowledge - .with_action(action.clone()); - - write_knowledge(&ending_knowledge) + + write_knowledge(&knowledge) .map_err(|e| format!("Failed to write knowledge to file. Error: {}", e))?; write_action(&working_dir, is_place_phase, action) .map_err(|e| format!("Failed to write action to file. Error: {}", e))?; - println!("Input state:\n{}\n\n", state_json); - println!("Existing knowledge:\n{}\n\n", serde_json::to_string(&starting_knowledge).unwrap_or(String::from(""))); - println!("End of turn knowledge:\n{}\n\n", serde_json::to_string(&ending_knowledge).unwrap_or(String::from(""))); + println!("Knowledge:\n{}\n\n", serde_json::to_string(&knowledge).unwrap_or(String::from(""))); Ok(()) } +fn placement(map_size: u16) -> (Action, Knowledge) { + let action = place_ships_randomly(map_size); + let knowledge = Knowledge::new(map_size, action.clone()); + + (action, knowledge) +} + +fn shoot(state: &State) -> Result<(Action, Knowledge), String> { + let old_knowledge = read_knowledge()?; + let knowledge = old_knowledge.resolve_last_action(&state); + let action = shoot_randomly(&state); + + Ok((action.clone(), knowledge.with_action(action))) +} + diff --git a/src/math.rs b/src/math.rs index 3d8a976..2ef1013 100644 --- a/src/math.rs +++ b/src/math.rs @@ -63,19 +63,63 @@ impl Point { } - pub fn move_point(&self, direction: Direction, distance: u16, map_size: u16) -> Option { - let x = self.x; - let y = self.y; - - match direction { - South if y < distance => None, - West if x < distance => None, - North if y + distance >= map_size => None, - East if x + distance >= map_size => None, - South => Some(Point::new(x, y-distance)), - West => Some(Point::new(x-distance, y)), - North => Some(Point::new(x, y+distance)), - East => Some(Point::new(x+distance, y)) + pub fn move_point(&self, direction: Direction, distance: i32, map_size: u16) -> Option { + let x = self.x as i32 + match direction { + West => -distance, + East => distance, + _ => 0 + }; + let y = self.y as i32 + match direction { + South => -distance, + North => distance, + _ => 0 + }; + let max = map_size as i32; + + if x >= max || y >= max || x < 0 || y < 0 { + None + } + else { + Some(Point::new(x as u16, y as u16)) } } + + pub fn move_point_no_bounds_check(&self, direction: Direction, distance: i32) -> Point { + let x = self.x as i32 + match direction { + West => -distance, + East => distance, + _ => 0 + }; + let y = self.y as i32 + match direction { + South => -distance, + North => distance, + _ => 0 + }; + + Point::new(x as u16, y as u16) + } + + pub fn check_for_ship_collision(&self, ship_start: Point, direction: Direction, length: u16) -> bool { + let reverse = match direction { + West | South => true, + East | North => false + }; + + let same_cross = match direction { + East | West => self.y == ship_start.y, + North | South => self.x == ship_start.x + }; + + let (parr_self, parr_ship) = match direction { + East | West => (self.x, ship_start.x), + North | South => (self.y, ship_start.y) + }; + + let corrected_parr_ship = match reverse { + true => parr_ship - length + 1, + false => parr_ship + }; + + same_cross && parr_self >= corrected_parr_ship && parr_self < corrected_parr_ship + length + } } diff --git a/src/placement.rs b/src/placement.rs index faa340e..4740d76 100644 --- a/src/placement.rs +++ b/src/placement.rs @@ -9,7 +9,6 @@ pub fn place_ships_randomly(map_size: u16) -> Action { current_placement = create_random_placement(map_size); !ShipPlacement::valid_placements(¤t_placement, map_size) } {} - Action::PlaceShips(current_placement) } diff --git a/src/ships.rs b/src/ships.rs index 344f9ed..ef29fe9 100644 --- a/src/ships.rs +++ b/src/ships.rs @@ -54,4 +54,16 @@ impl Ship { &Submarine => 3 } } + + pub fn all_types() -> Vec { + use Ship::*; + + vec!( + Battleship, + Carrier, + Cruiser, + Destroyer, + Submarine + ) + } } -- cgit v1.2.3 From 973f7be695f7c3308d384e1ee30066547e4a07c7 Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Sat, 20 May 2017 20:58:18 +0200 Subject: Eliminated more possibilities when ships are sunk --- src/knowledge.rs | 112 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 81 insertions(+), 31 deletions(-) diff --git a/src/knowledge.rs b/src/knowledge.rs index 20c982f..01c098d 100644 --- a/src/knowledge.rs +++ b/src/knowledge.rs @@ -37,6 +37,8 @@ impl Knowledge { new_knowledge } + + } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -46,7 +48,7 @@ pub struct OpponentMapKnowledge { } impl OpponentMapKnowledge { - pub fn new(map_size: u16) -> OpponentMapKnowledge { + fn new(map_size: u16) -> OpponentMapKnowledge { let mut cells = Vec::with_capacity(map_size as usize); for x in 0..map_size { cells.push(Vec::with_capacity(map_size as usize)); @@ -66,6 +68,7 @@ impl OpponentMapKnowledge { } fn update_from_shot(&mut self, p: Point, state: &State) { + let cells_copy = self.cells.clone(); let ref shot_cell = state.opponent_map.cells[p.x as usize][p.y as usize]; let sunk_ship = self.ships.iter() .filter(|&(_, x)| !x.destroyed) @@ -73,27 +76,52 @@ impl OpponentMapKnowledge { .map(|(s, _)| s.clone()) .next(); //only one ship can be sunk at a time - match sunk_ship { - None => {}, - Some(ship) => { - match self.ships.get_mut(&ship) { - Some(ref mut ship_knowledge) => {ship_knowledge.destroyed = true}, - None => {} - } - } - } - - let ref mut knowledge_cell = self.cells[p.x as usize][p.y as usize]; + + sunk_ship + .and_then(|ship| self.ships.get_mut(&ship)) + .map(|ref mut ship_knowledge| ship_knowledge.destroyed = true); + if shot_cell.missed { - knowledge_cell.missed = true; - // knowledge_cell.possible_ship_uses.clear(); + self.cells[p.x as usize][p.y as usize].missed = true; + for knowledge in self.ships.values_mut() { + knowledge.possible_placements.retain(|x| !x.touches_point(p)); + } } else { - knowledge_cell.hit = true; - knowledge_cell.known_ship = sunk_ship; - // knowledge_cell.reduce_possibilities_to_known_ship(); + self.cells[p.x as usize][p.y as usize].hit = true; + self.cells[p.x as usize][p.y as usize].known_ship = sunk_ship; + + if sunk_ship.is_some() { + for knowledge in self.ships.values_mut() { + knowledge.possible_placements.retain(|x| { + (sunk_ship != Some(x.ship) && !x.touches_point(p)) || + (sunk_ship == Some(x.ship) && x.touches_point(p) && x.all_are_hits(&cells_copy)) + + }); + } + } } + self.derive_ship_positions(); + } + + fn derive_ship_positions(&mut self) { + for knowledge in self.ships.values() { + if knowledge.possible_placements.len() == 1 { + let ref true_placement = knowledge.possible_placements[0]; + for p in true_placement.points_on_ship() { + self.cells[p.x as usize][p.y as usize].known_ship = Some(true_placement.ship); + } + } + } + self.clear_impossible_placements(); + } + + fn clear_impossible_placements(&mut self) { + let ref cells = self.cells; + for knowledge in self.ships.values_mut() { + knowledge.possible_placements.retain(|x| x.all_could_be_hits(&cells)); + } } } @@ -105,7 +133,7 @@ pub struct OpponentShipKnowledge { } impl OpponentShipKnowledge { - pub fn new(ship: Ship, map_size: u16) -> OpponentShipKnowledge { + fn new(ship: Ship, map_size: u16) -> OpponentShipKnowledge { OpponentShipKnowledge { ship: ship, destroyed: false, @@ -114,14 +142,6 @@ impl OpponentShipKnowledge { } } -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct KnowledgeCell { - pub missed: bool, - pub hit: bool, - pub known_ship: Option, - pub position: Point -} - #[derive(Serialize, Deserialize, Clone, Debug)] pub struct PossibleShipPlacement { pub ship: Ship, @@ -130,7 +150,7 @@ pub struct PossibleShipPlacement { } impl PossibleShipPlacement { - pub fn enumerate(ship: Ship, map_size: u16) -> Vec { + fn enumerate(ship: Ship, map_size: u16) -> Vec { (0..(map_size-ship.length()+1)).flat_map(move |par| { (0..map_size).flat_map(move |per| { vec!( @@ -149,13 +169,43 @@ impl PossibleShipPlacement { }).collect() } - pub fn touches_point(&self, p: Point) -> bool { + fn touches_point(&self, p: Point) -> bool { p.check_for_ship_collision(self.position, self.direction, self.ship.length()) } + + fn points_on_ship(&self) -> Vec { + (0..self.ship.length() as i32).map(|i| { + self.position.move_point_no_bounds_check(self.direction, i) + }).collect() + } + + fn all_are_hits(&self, cells: &Vec>) -> bool { + self.points_on_ship() + .iter() + .fold(true, |acc, p| acc && cells[p.x as usize][p.y as usize].hit) + } + + fn all_could_be_hits(&self, cells: &Vec>) -> bool { + self.points_on_ship() + .iter() + .fold(true, |acc, p| { + let ref cell = cells[p.x as usize][p.y as usize]; + acc && !cell.missed && + cell.known_ship.map(|ship| ship == self.ship).unwrap_or(true) + }) + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct KnowledgeCell { + pub missed: bool, + pub hit: bool, + pub known_ship: Option, + pub position: Point } impl KnowledgeCell { - pub fn new(x: u16, y: u16) -> KnowledgeCell { + fn new(x: u16, y: u16) -> KnowledgeCell { KnowledgeCell { missed: false, hit: false, @@ -164,11 +214,11 @@ impl KnowledgeCell { } } - pub fn shot_attempted(&self) -> bool { + fn shot_attempted(&self) -> bool { self.missed || self.hit } - pub fn unknown_hit(&self) -> bool { + fn unknown_hit(&self) -> bool { self.hit && self.known_ship.is_none() } -- cgit v1.2.3 From 6b8c71c635539a8609f82aa6eb52ea56c2c41c0c Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Sat, 20 May 2017 22:20:47 +0200 Subject: Finished up efficient elimination of found ships --- src/knowledge.rs | 90 ++++++++++++++++++++++++++++++++++++++++++++++---------- src/lib.rs | 2 +- src/math.rs | 11 ++++++- src/shooting.rs | 40 ++++++++++++++++++++----- 4 files changed, 118 insertions(+), 25 deletions(-) diff --git a/src/knowledge.rs b/src/knowledge.rs index 01c098d..f009c10 100644 --- a/src/knowledge.rs +++ b/src/knowledge.rs @@ -8,21 +8,24 @@ use std::collections::HashMap; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct Knowledge { pub last_action: Action, - pub opponent_map: OpponentMapKnowledge + pub opponent_map: OpponentMapKnowledge, + pub map_size: u16 } impl Knowledge { pub fn new(map_size: u16, action: Action) -> Knowledge { Knowledge { last_action: action, - opponent_map: OpponentMapKnowledge::new(map_size) + opponent_map: OpponentMapKnowledge::new(map_size), + map_size: map_size } } pub fn with_action(&self, action: Action) -> Knowledge { Knowledge { last_action: action, - opponent_map: self.opponent_map.clone() + opponent_map: self.opponent_map.clone(), + map_size: self.map_size } } @@ -38,7 +41,39 @@ impl Knowledge { new_knowledge } - + pub fn has_unknown_hits(&self) -> bool { + self.opponent_map.cells.iter().fold(false, |acc, x| { + x.iter().fold(acc, |acc, y| acc || y.unknown_hit()) + }) + } + + pub fn get_best_adjacent_shots(&self) -> Vec { + let unknown_hits = self.opponent_map.cells_with_unknown_hits(); + let adjacent_cells = self.opponent_map.adjacent_unshot_cells(&unknown_hits); + + let possible_placements = self.opponent_map + .ships + .values() + .flat_map(|knowledge| knowledge.possible_placements.clone()); + + let mut max_score = 1; + let mut best_cells = Vec::new(); + + for placement in possible_placements { + for &cell in &adjacent_cells { + let score = placement.count_hit_cells(cell, &unknown_hits); + if score > max_score { + max_score = score; + best_cells = vec!(cell); + } + else if score == max_score { + best_cells.push(cell); + } + } + } + + best_cells + } } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -68,7 +103,6 @@ impl OpponentMapKnowledge { } fn update_from_shot(&mut self, p: Point, state: &State) { - let cells_copy = self.cells.clone(); let ref shot_cell = state.opponent_map.cells[p.x as usize][p.y as usize]; let sunk_ship = self.ships.iter() .filter(|&(_, x)| !x.destroyed) @@ -90,15 +124,16 @@ impl OpponentMapKnowledge { else { self.cells[p.x as usize][p.y as usize].hit = true; self.cells[p.x as usize][p.y as usize].known_ship = sunk_ship; + } - if sunk_ship.is_some() { - for knowledge in self.ships.values_mut() { - knowledge.possible_placements.retain(|x| { - (sunk_ship != Some(x.ship) && !x.touches_point(p)) || + let cells_copy = self.cells.clone(); + if sunk_ship.is_some() { + for knowledge in self.ships.values_mut() { + knowledge.possible_placements.retain(|x| { + (sunk_ship != Some(x.ship) && !x.touches_point(p)) || (sunk_ship == Some(x.ship) && x.touches_point(p) && x.all_are_hits(&cells_copy)) - }); - } + }); } } @@ -123,6 +158,21 @@ impl OpponentMapKnowledge { knowledge.possible_placements.retain(|x| x.all_could_be_hits(&cells)); } } + + fn cells_with_unknown_hits(&self) -> Vec { + self.cells.iter().flat_map(|x| { + x.iter().filter(|y| y.unknown_hit()).map(|y| y.position) + }).collect() + } + + fn adjacent_unshot_cells(&self, cells: &Vec) -> Vec { + self.cells.iter().flat_map(|x| { + x.iter() + .filter(|y| !y.shot_attempted()) + .map(|y| y.position) + .filter(|&y| cells.iter().any(|z| z.is_adjacent(y))) + }).collect() + } } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -161,7 +211,7 @@ impl PossibleShipPlacement { }, PossibleShipPlacement { ship: ship, - direction: Direction::South, + direction: Direction::North, position: Point::new(per, par) } ) @@ -169,11 +219,11 @@ impl PossibleShipPlacement { }).collect() } - fn touches_point(&self, p: Point) -> bool { + pub fn touches_point(&self, p: Point) -> bool { p.check_for_ship_collision(self.position, self.direction, self.ship.length()) } - fn points_on_ship(&self) -> Vec { + pub fn points_on_ship(&self) -> Vec { (0..self.ship.length() as i32).map(|i| { self.position.move_point_no_bounds_check(self.direction, i) }).collect() @@ -194,6 +244,14 @@ impl PossibleShipPlacement { cell.known_ship.map(|ship| ship == self.ship).unwrap_or(true) }) } + + fn count_hit_cells(&self, required: Point, wanted: &Vec) -> u16 { + if !self.touches_point(required) { + return 0; + } + + wanted.iter().filter(|&&x| self.touches_point(x)).count() as u16 + } } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -214,11 +272,11 @@ impl KnowledgeCell { } } - fn shot_attempted(&self) -> bool { + pub fn shot_attempted(&self) -> bool { self.missed || self.hit } - fn unknown_hit(&self) -> bool { + pub fn unknown_hit(&self) -> bool { self.hit && self.known_ship.is_none() } diff --git a/src/lib.rs b/src/lib.rs index f84c172..00eaf02 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,7 +60,7 @@ fn placement(map_size: u16) -> (Action, Knowledge) { fn shoot(state: &State) -> Result<(Action, Knowledge), String> { let old_knowledge = read_knowledge()?; let knowledge = old_knowledge.resolve_last_action(&state); - let action = shoot_randomly(&state); + let action = shoot_smartly(&knowledge); Ok((action.clone(), knowledge.with_action(action))) } diff --git a/src/math.rs b/src/math.rs index 2ef1013..ce9d885 100644 --- a/src/math.rs +++ b/src/math.rs @@ -116,10 +116,19 @@ impl Point { }; let corrected_parr_ship = match reverse { - true => parr_ship - length + 1, + true => 1 + parr_ship - length, false => parr_ship }; same_cross && parr_self >= corrected_parr_ship && parr_self < corrected_parr_ship + length } + + pub fn is_adjacent(&self, other: Point) -> bool { + let dx = if self.x > other.x {self.x - other.x} else {other.x - self.x}; + let dy = if self.y > other.y {self.y - other.y} else {other.y - self.y}; + + (dx == 0 && dy == 1) || + (dx == 1 && dy == 0) + + } } diff --git a/src/shooting.rs b/src/shooting.rs index c2ca56e..616ebf3 100644 --- a/src/shooting.rs +++ b/src/shooting.rs @@ -1,16 +1,42 @@ +use rand; +use rand::distributions::{IndependentSample, Range}; + use actions::*; use math::*; -use state::*; +use knowledge::*; + +pub fn shoot_smartly(knowledge: &Knowledge) -> Action { + let shot = if knowledge.has_unknown_hits() { + destroy_shoot(&knowledge) + } + else { + seek_shoot(&knowledge) + }; + + Action::Shoot(shot) +} -pub fn shoot_randomly(state: &State) -> Action { +fn seek_shoot(knowledge: &Knowledge) -> Point { let mut shot: Point; while { - shot = Point::random(state.map_size); - let ref target = state.opponent_map.cells[shot.x as usize][shot.y as usize]; - target.damaged || target.missed + shot = Point::random(knowledge.map_size); + let ref target = knowledge.opponent_map.cells[shot.x as usize][shot.y as usize]; + target.shot_attempted() } {} + shot +} - - Action::Shoot(shot) +fn destroy_shoot(knowledge: &Knowledge) -> Point { + let possibilities = knowledge.get_best_adjacent_shots(); + if possibilities.is_empty() { + seek_shoot(&knowledge) + } + else { + let mut rng = rand::thread_rng(); + let between = Range::new(0, possibilities.len()); + let i = between.ind_sample(&mut rng); + + possibilities[i as usize] + } } -- cgit v1.2.3 From b6a295a9eaf5fec16f72870baae84e6eadd1be33 Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Sat, 20 May 2017 22:46:00 +0200 Subject: Implemented lattice restricted battleship searching --- src/knowledge.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++++++---- src/math.rs | 20 ++++++++++++++++++++ src/shooting.rs | 20 ++++++++++++-------- 3 files changed, 81 insertions(+), 12 deletions(-) diff --git a/src/knowledge.rs b/src/knowledge.rs index f009c10..5e45f07 100644 --- a/src/knowledge.rs +++ b/src/knowledge.rs @@ -51,10 +51,7 @@ impl Knowledge { let unknown_hits = self.opponent_map.cells_with_unknown_hits(); let adjacent_cells = self.opponent_map.adjacent_unshot_cells(&unknown_hits); - let possible_placements = self.opponent_map - .ships - .values() - .flat_map(|knowledge| knowledge.possible_placements.clone()); + let possible_placements = self.opponent_map.possible_placements(); let mut max_score = 1; let mut best_cells = Vec::new(); @@ -74,6 +71,39 @@ impl Knowledge { best_cells } + + pub fn get_most_possibility_shots_on_lattice(&self) -> Vec { + let on_lattice = self.opponent_map.cells_on_lattice(self.lattice_size()); + let possible_placements = self.opponent_map.possible_placements(); + + let mut max_possibilities = 1; + let mut best_cells = Vec::new(); + + for cell in on_lattice { + let possibilities = possible_placements.iter() + .filter(|placement| placement.touches_point(cell)).count(); + if possibilities > max_possibilities { + max_possibilities = possibilities; + best_cells = vec!(cell); + } + else if possibilities == max_possibilities { + best_cells.push(cell); + } + } + + best_cells + } + + fn lattice_size(&self) -> u16 { + let any_long_ships = self.opponent_map.ships.iter() + .any(|(ship, knowledge)| ship.length() >= 4 && !knowledge.destroyed); + if any_long_ships { + 4 + } + else { + 2 + } + } } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -173,6 +203,21 @@ impl OpponentMapKnowledge { .filter(|&y| cells.iter().any(|z| z.is_adjacent(y))) }).collect() } + + fn cells_on_lattice(&self, lattice_size: u16) -> Vec { + self.cells.iter().flat_map(|x| { + x.iter() + .filter(|y| !y.shot_attempted() && y.position.is_on_lattice(lattice_size)) + .map(|y| y.position) + }).collect() + } + + fn possible_placements(&self) -> Vec { + self.ships + .values() + .flat_map(|knowledge| knowledge.possible_placements.clone()) + .collect() + } } #[derive(Serialize, Deserialize, Clone, Debug)] diff --git a/src/math.rs b/src/math.rs index ce9d885..da06622 100644 --- a/src/math.rs +++ b/src/math.rs @@ -131,4 +131,24 @@ impl Point { (dx == 1 && dy == 0) } + + pub fn is_on_lattice(&self, lattice_size: u16) -> bool { + (self.x + self.y) % lattice_size == 0 + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn point_on_lattice_works() { + assert_eq!(true, Point::new(0,0).is_on_lattice(4)); + assert_eq!(true, Point::new(4,0).is_on_lattice(4)); + assert_eq!(true, Point::new(0,4).is_on_lattice(4)); + assert_eq!(true, Point::new(4,4).is_on_lattice(4)); + assert_eq!(true, Point::new(1,3).is_on_lattice(4)); + assert_eq!(true, Point::new(3,1).is_on_lattice(4)); + } } diff --git a/src/shooting.rs b/src/shooting.rs index 616ebf3..4d87d86 100644 --- a/src/shooting.rs +++ b/src/shooting.rs @@ -17,14 +17,18 @@ pub fn shoot_smartly(knowledge: &Knowledge) -> Action { } fn seek_shoot(knowledge: &Knowledge) -> Point { - let mut shot: Point; - - while { - shot = Point::random(knowledge.map_size); - let ref target = knowledge.opponent_map.cells[shot.x as usize][shot.y as usize]; - target.shot_attempted() - } {} - shot + let possibilities = knowledge.get_most_possibility_shots_on_lattice(); + if possibilities.is_empty() { + println!("All possible shots on the current lattice have been tried!"); + Point::new(0,0) + } + else { + let mut rng = rand::thread_rng(); + let between = Range::new(0, possibilities.len()); + let i = between.ind_sample(&mut rng); + + possibilities[i as usize] + } } fn destroy_shoot(knowledge: &Knowledge) -> Point { -- cgit v1.2.3 From 2da686b462c0053e408e6814d7d57d00b55a2245 Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Wed, 24 May 2017 18:30:15 +0200 Subject: Changed bot extension for windows --- bot.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot.json b/bot.json index eca72f5..6245b49 100644 --- a/bot.json +++ b/bot.json @@ -4,5 +4,5 @@ "NickName" :"Admiral Worthebot", "BotType": 8, "ProjectLocation" : "", - "RunFile" : "target\\release\\worthebot_battleships" + "RunFile" : "target\\release\\worthebot_battleships.exe" } -- cgit v1.2.3 From fe1c30d52f643843004230f804cfd55f356d0d6d Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Sat, 27 May 2017 18:59:36 +0200 Subject: Added some tests for math --- src/math.rs | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 2 deletions(-) diff --git a/src/math.rs b/src/math.rs index da06622..f0e323c 100644 --- a/src/math.rs +++ b/src/math.rs @@ -141,14 +141,102 @@ impl Point { #[cfg(test)] mod tests { use super::*; + + #[test] + fn ship_collision_check_works_west() { + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,5), West, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(6,5), West, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(7,5), West, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(8,5), West, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(9,5), West, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(10,5), West, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(4,5), West, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(6,4), West, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(6,6), West, 5)); + } #[test] - fn point_on_lattice_works() { + fn ship_collision_check_works_east() { + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,5), East, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(4,5), East, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(3,5), East, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(2,5), East, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(1,5), East, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(0,5), East, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(6,5), East, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(4,4), East, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(4,6), East, 5)); + } + + #[test] + fn ship_collision_check_works_north() { + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,5), North, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,4), North, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,3), North, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,2), North, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,1), North, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(5,0), North, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(5,6), North, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(4,4), North, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(6,4), North, 5)); + } + + #[test] + fn ship_collision_check_works_south() { + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,5), South, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,6), South, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,7), South, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,8), South, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,9), South, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(5,10), South, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(5,4), South, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(4,6), South, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(6,6), South, 5)); + } + + #[test] + fn adjacency_check_works() { + assert_eq!(true, Point::new(5,5).is_adjacent(Point::new(4,5))); + assert_eq!(true, Point::new(5,5).is_adjacent(Point::new(6,5))); + assert_eq!(true, Point::new(5,5).is_adjacent(Point::new(5,4))); + assert_eq!(true, Point::new(5,5).is_adjacent(Point::new(5,6))); + + assert_eq!(false, Point::new(5,5).is_adjacent(Point::new(4,4))); + assert_eq!(false, Point::new(5,5).is_adjacent(Point::new(6,6))); + assert_eq!(false, Point::new(5,5).is_adjacent(Point::new(6,4))); + assert_eq!(false, Point::new(5,5).is_adjacent(Point::new(4,6))); + assert_eq!(false, Point::new(5,5).is_adjacent(Point::new(5,5))); + + assert_eq!(false, Point::new(5,5).is_adjacent(Point::new(10,5))); + + } + + #[test] + fn point_on_4_lattice_works() { assert_eq!(true, Point::new(0,0).is_on_lattice(4)); assert_eq!(true, Point::new(4,0).is_on_lattice(4)); assert_eq!(true, Point::new(0,4).is_on_lattice(4)); assert_eq!(true, Point::new(4,4).is_on_lattice(4)); assert_eq!(true, Point::new(1,3).is_on_lattice(4)); - assert_eq!(true, Point::new(3,1).is_on_lattice(4)); + assert_eq!(true, Point::new(3,1).is_on_lattice(4)); + + assert_eq!(false, Point::new(0,1).is_on_lattice(4)); + assert_eq!(false, Point::new(0,2).is_on_lattice(4)); + assert_eq!(false, Point::new(0,3).is_on_lattice(4)); + assert_eq!(false, Point::new(1,0).is_on_lattice(4)); + assert_eq!(false, Point::new(2,0).is_on_lattice(4)); + assert_eq!(false, Point::new(3,0).is_on_lattice(4)); + } + + #[test] + fn point_on_2_lattice_works() { + assert_eq!(true, Point::new(0,0).is_on_lattice(2)); + assert_eq!(true, Point::new(2,0).is_on_lattice(2)); + assert_eq!(true, Point::new(0,2).is_on_lattice(2)); + assert_eq!(true, Point::new(2,2).is_on_lattice(2)); + assert_eq!(true, Point::new(1,1).is_on_lattice(2)); + + assert_eq!(false, Point::new(0,1).is_on_lattice(2)); + assert_eq!(false, Point::new(1,0).is_on_lattice(2)); } } -- cgit v1.2.3 From f9fc98d0091ec72d639f1fdb4cce1ef25f7eea05 Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Sun, 28 May 2017 17:51:48 +0200 Subject: Additional maths tests --- src/math.rs | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/src/math.rs b/src/math.rs index f0e323c..a93d03e 100644 --- a/src/math.rs +++ b/src/math.rs @@ -40,6 +40,18 @@ impl Direction { } } +#[cfg(test)] +mod direction_tests { + use super::*; + + #[test] + fn random_direction_does_not_panic() { + for _ in 0..10000 { + Direction::random(); + } + } +} + #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] pub struct Point { pub x: u16, @@ -139,9 +151,75 @@ impl Point { #[cfg(test)] -mod tests { +mod point_tests { use super::*; + #[test] + fn random_point_is_in_correct_range() { + for _ in 0..10000 { + let point = Point::random(15); + assert!(point.x < 15); + assert!(point.y < 15); + } + } + + + #[test] + fn move_point_works_west() { + assert_eq!(Some(Point::new(3,5)), Point::new(5,5).move_point(West, 2, 10)); + assert_eq!(Some(Point::new(7,5)), Point::new(5,5).move_point(West, -2, 10)); + assert_eq!(None, Point::new(5,5).move_point(West, 6, 10)); + assert_eq!(None, Point::new(5,5).move_point(West, -5, 10)); + } + + #[test] + fn move_point_works_east() { + assert_eq!(Some(Point::new(7,5)), Point::new(5,5).move_point(East, 2, 10)); + assert_eq!(Some(Point::new(3,5)), Point::new(5,5).move_point(East, -2, 10)); + assert_eq!(None, Point::new(5,5).move_point(East, 5, 10)); + assert_eq!(None, Point::new(5,5).move_point(East, -6, 10)); + } + + #[test] + fn move_point_works_south() { + assert_eq!(Some(Point::new(5,3)), Point::new(5,5).move_point(South, 2, 10)); + assert_eq!(Some(Point::new(5,7)), Point::new(5,5).move_point(South, -2, 10)); + assert_eq!(None, Point::new(5,5).move_point(South, 6, 10)); + assert_eq!(None, Point::new(5,5).move_point(South, -5, 10)); + } + + #[test] + fn move_point_works_north() { + assert_eq!(Some(Point::new(5,7)), Point::new(5,5).move_point(North, 2, 10)); + assert_eq!(Some(Point::new(5,3)), Point::new(5,5).move_point(North, -2, 10)); + assert_eq!(None, Point::new(5,5).move_point(North, 5, 10)); + assert_eq!(None, Point::new(5,5).move_point(North, -6, 10)); + } + + #[test] + fn unrestricted_move_point_works_west() { + assert_eq!(Point::new(3,5), Point::new(5,5).move_point_no_bounds_check(West, 2)); + assert_eq!(Point::new(7,5), Point::new(5,5).move_point_no_bounds_check(West, -2)); + } + + #[test] + fn unrestricted_move_point_works_east() { + assert_eq!(Point::new(7,5), Point::new(5,5).move_point_no_bounds_check(East, 2)); + assert_eq!(Point::new(3,5), Point::new(5,5).move_point_no_bounds_check(East, -2)); + } + + #[test] + fn unrestricted_move_point_works_south() { + assert_eq!(Point::new(5,3), Point::new(5,5).move_point_no_bounds_check(South, 2)); + assert_eq!(Point::new(5,7), Point::new(5,5).move_point_no_bounds_check(South, -2)); + } + + #[test] + fn unrestricted_move_point_works_north() { + assert_eq!(Point::new(5,7), Point::new(5,5).move_point_no_bounds_check(North, 2)); + assert_eq!(Point::new(5,3), Point::new(5,5).move_point_no_bounds_check(North, -2)); + } + #[test] fn ship_collision_check_works_west() { assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,5), West, 5)); -- cgit v1.2.3 From 29181fce4797b6e4833ab56d1fa7ff9fa865965b Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Tue, 30 May 2017 21:28:32 +0200 Subject: Added readme to project --- notes.org | 1447 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ readme.org | 1447 ------------------------------------------------------------ readme.txt | 20 + 3 files changed, 1467 insertions(+), 1447 deletions(-) create mode 100644 notes.org delete mode 100644 readme.org create mode 100644 readme.txt diff --git a/notes.org b/notes.org new file mode 100644 index 0000000..ff3e319 --- /dev/null +++ b/notes.org @@ -0,0 +1,1447 @@ +* State.json + +#+BEGIN_EXAMPLE +State = {PlayerMap, OpponentMap, GameVersion=String, GameLevel=u16, Round=u16, MapDimension=u16, Phase=u16, Player1Map=null, Player2Map=null} + +PlayerMap = {Cells=[PlayerCell], Owner, MapWidth=u16, MapHeight=u16} + +PlayerCell = {Occupied=bool, Hit=bool, X=u16, Y=u16} + +Owner = {FailedFirstPhaseCommands=u16, Name=String, Ships=[PlayerShip], Points=u16, Killed=bool, IsWinner=bool, ShotsFired=u16, ShotsHit=u16, ShipsRemaining=u16, Key=String} + +PlayerShip = {Cells=[PlayerCell], Destroyed=bool, Placed=bool, ShipType=String, Weapons=[PlayerWeapon], } + +PlayerWeapon = {WeaponType=String} + +OpponentMap = {Ships=[OpponentShip], Alive=bool, Points=u16, Name=String, Cells=[OpponentCell]} + +OpponentShip = {Destroyed=bool, ShipType=String} + +OpponentCell = {Damaged=bool, Missed=bool, X=u16, Y=u16} + +#+END_EXAMPLE + +* State.json example + +#+BEGIN_SRC json +{ + "PlayerMap": { + "Cells": [ + { + "Occupied": false, + "Hit": true, + "X": 0, + "Y": 0 + }, + { + "Occupied": false, + "Hit": false, + "X": 0, + "Y": 1 + }, + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 2 + }, + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 3 + }, + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 4 + }, + { + "Occupied": false, + "Hit": false, + "X": 0, + "Y": 5 + }, + { + "Occupied": true, + "Hit": true, + "X": 0, + "Y": 6 + }, + { + "Occupied": true, + "Hit": true, + + "X": 0, + "Y": 7 + }, + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 8 + }, + { + "Occupied": false, + "Hit": true, + "X": 0, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 1, + "Y": 0 + }, + { + "Occupied": false, + "Hit": false, + "X": 1, + "Y": 1 + }, + { + "Occupied": false, + "Hit": false, + "X": 1, + "Y": 2 + }, + { + "Occupied": false, + "Hit": true, + "X": 1, + "Y": 3 + }, + { + "Occupied": false, + "Hit": false, + "X": 1, + "Y": 4 + }, + { + "Occupied": false, + "Hit": false, + "X": 1, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 1, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 1, + "Y": 7 + }, + { + "Occupied": true, + "Hit": false, + "X": 1, + "Y": 8 + }, + { + "Occupied": false, + "Hit": true, + "X": 1, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 2, + "Y": 0 + }, + { + "Occupied": false, + "Hit": false, + "X": 2, + "Y": 1 + }, + { + "Occupied": false, + "Hit": false, + "X": 2, + "Y": 2 + }, + { + "Occupied": false, + "Hit": false, + "X": 2, + "Y": 3 + }, + { + "Occupied": false, + "Hit": true, + "X": 2, + "Y": 4 + }, + { + "Occupied": false, + "Hit": true, + "X": 2, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 2, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 2, + "Y": 7 + }, + { + "Occupied": true, + "Hit": true, + "X": 2, + "Y": 8 + }, + { + "Occupied": false, + "Hit": false, + "X": 2, + "Y": 9 + }, + { + "Occupied": false, + "Hit": true, + "X": 3, + "Y": 0 + }, + { + "Occupied": false, + "Hit": true, + "X": 3, + "Y": 1 + }, + { + "Occupied": false, + "Hit": true, + "X": 3, + "Y": 2 + }, + { + "Occupied": false, + "Hit": false, + "X": 3, + "Y": 3 + }, + { + "Occupied": false, + "Hit": false, + "X": 3, + "Y": 4 + }, + { + "Occupied": false, + "Hit": false, + "X": 3, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 3, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 3, + "Y": 7 + }, + { + "Occupied": true, + "Hit": false, + "X": 3, + "Y": 8 + }, + { + "Occupied": false, + "Hit": false, + "X": 3, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 4, + "Y": 0 + }, + { + "Occupied": false, + "Hit": true, + "X": 4, + "Y": 1 + }, + { + "Occupied": false, + "Hit": true, + "X": 4, + "Y": 2 + }, + { + "Occupied": false, + "Hit": false, + "X": 4, + "Y": 3 + }, + { + "Occupied": false, + "Hit": false, + "X": 4, + "Y": 4 + }, + { + "Occupied": false, + "Hit": false, + "X": 4, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 4, + "Y": 6 + }, + { + "Occupied": false, + "Hit": true, + "X": 4, + "Y": 7 + }, + { + "Occupied": true, + "Hit": false, + "X": 4, + "Y": 8 + }, + { + "Occupied": false, + "Hit": true, + "X": 4, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 0 + }, + { + "Occupied": true, + "Hit": true, + "X": 5, + "Y": 1 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 2 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 3 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 4 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 7 + }, + { + "Occupied": true, + "Hit": false, + "X": 5, + "Y": 8 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 6, + "Y": 0 + }, + { + "Occupied": true, + "Hit": false, + "X": 6, + "Y": 1 + }, + { + "Occupied": false, + "Hit": false, + "X": 6, + "Y": 2 + }, + { + "Occupied": false, + "Hit": true, + "X": 6, + "Y": 3 + }, + { + "Occupied": false, + "Hit": false, + "X": 6, + "Y": 4 + }, + { + "Occupied": false, + "Hit": true, + "X": 6, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 6, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 6, + "Y": 7 + }, + { + "Occupied": false, + "Hit": true, + "X": 6, + "Y": 8 + }, + { + "Occupied": false, + "Hit": false, + "X": 6, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 7, + "Y": 0 + }, + { + "Occupied": true, + "Hit": true, + "X": 7, + "Y": 1 + }, + { + "Occupied": false, + "Hit": false, + "X": 7, + "Y": 2 + }, + { + "Occupied": false, + "Hit": false, + "X": 7, + "Y": 3 + }, + { + "Occupied": false, + "Hit": false, + "X": 7, + "Y": 4 + }, + { + "Occupied": true, + "Hit": false, + "X": 7, + "Y": 5 + }, + { + "Occupied": true, + "Hit": false, + "X": 7, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 7, + "Y": 7 + }, + { + "Occupied": false, + "Hit": false, + "X": 7, + "Y": 8 + }, + { + "Occupied": false, + "Hit": false, + "X": 7, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 8, + "Y": 0 + }, + { + "Occupied": true, + "Hit": false, + "X": 8, + "Y": 1 + }, + { + "Occupied": false, + "Hit": false, + "X": 8, + "Y": 2 + }, + { + "Occupied": false, + "Hit": true, + "X": 8, + "Y": 3 + }, + { + "Occupied": false, + "Hit": true, + "X": 8, + "Y": 4 + }, + { + "Occupied": false, + "Hit": false, + "X": 8, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 8, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 8, + "Y": 7 + }, + { + "Occupied": false, + "Hit": false, + "X": 8, + "Y": 8 + }, + { + "Occupied": false, + "Hit": true, + "X": 8, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 0 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 1 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 2 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 3 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 4 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 7 + }, + { + "Occupied": false, + "Hit": true, + "X": 9, + "Y": 8 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 9 + } + ], + "Owner": { + "FailedFirstPhaseCommands": 0, + "Name": "Admiral Worthebot", + "Ships": [ + { + "Cells": [ + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 2 + }, + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 3 + }, + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 4 + } + ], + "Destroyed": false, + "Placed": true, + "ShipType": "Submarine", + "Weapons": [ + { + "WeaponType": "SingleShot" + } + ] + }, + { + "Cells": [ + { + "Occupied": true, + "Hit": false, + "X": 7, + "Y": 6 + }, + { + "Occupied": true, + "Hit": false, + "X": 7, + "Y": 5 + } + ], + "Destroyed": false, + "Placed": true, + "ShipType": "Destroyer", + "Weapons": [ + { + "WeaponType": "SingleShot" + } + ] + }, + { + "Cells": [ + { + "Occupied": true, + "Hit": true, + "X": 5, + "Y": 1 + }, + { + "Occupied": true, + "Hit": false, + "X": 6, + "Y": 1 + }, + { + "Occupied": true, + "Hit": true, + "X": 7, + "Y": 1 + }, + { + "Occupied": true, + "Hit": false, + "X": 8, + "Y": 1 + } + ], + "Destroyed": false, + "Placed": true, + "ShipType": "Battleship", + "Weapons": [ + { + "WeaponType": "SingleShot" + } + ] + }, + { + "Cells": [ + { + "Occupied": true, + "Hit": false, + "X": 5, + "Y": 8 + }, + { + "Occupied": true, + "Hit": false, + "X": 4, + "Y": 8 + }, + { + "Occupied": true, + "Hit": false, + "X": 3, + "Y": 8 + }, + { + "Occupied": true, + "Hit": true, + "X": 2, + "Y": 8 + }, + { + "Occupied": true, + "Hit": false, + "X": 1, + "Y": 8 + } + ], + "Destroyed": false, + "Placed": true, + "ShipType": "Carrier", + "Weapons": [ + { + "WeaponType": "SingleShot" + } + ] + }, + { + "Cells": [ + { + "Occupied": true, + "Hit": true, + "X": 0, + "Y": 6 + }, + { + "Occupied": true, + "Hit": true, + "X": 0, + "Y": 7 + }, + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 8 + } + ], + "Destroyed": false, + "Placed": true, + "ShipType": "Cruiser", + "Weapons": [ + { + "WeaponType": "SingleShot" + } + ] + } + ], + "Points": 280, + "Killed": false, + "IsWinner": false, + "ShotsFired": 86, + "ShotsHit": 16, + "ShipsRemaining": 5, + "Key": "B" + }, + "MapWidth": 10, + "MapHeight": 10 + }, + "OpponentMap": { + "Ships": [ + { + "Destroyed": false, + "ShipType": "Submarine" + }, + { + "Destroyed": true, + "ShipType": "Destroyer" + }, + { + "Destroyed": true, + "ShipType": "Battleship" + }, + { + "Destroyed": true, + "ShipType": "Carrier" + }, + { + "Destroyed": true, + "ShipType": "Cruiser" + } + ], + "Alive": true, + "Points": 50, + "Name": "John", + "Cells": [ + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 0 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 1 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 2 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 4 + }, + { + "Damaged": false, + "Missed": false, + "X": 0, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 7 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 8 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 9 + }, + { + "Damaged": true, + "Missed": false, + "X": 1, + "Y": 0 + }, + { + "Damaged": true, + "Missed": false, + "X": 1, + "Y": 1 + }, + { + "Damaged": true, + "Missed": false, + "X": 1, + "Y": 2 + }, + { + "Damaged": true, + "Missed": false, + "X": 1, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 1, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 1, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 1, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 1, + "Y": 7 + }, + { + "Damaged": true, + "Missed": false, + "X": 1, + "Y": 8 + }, + { + "Damaged": false, + "Missed": true, + "X": 1, + "Y": 9 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 0 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 1 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 2 + }, + { + "Damaged": false, + "Missed": false, + "X": 2, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 7 + }, + { + "Damaged": false, + "Missed": false, + "X": 2, + "Y": 8 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 9 + }, + { + "Damaged": false, + "Missed": false, + "X": 3, + "Y": 0 + }, + { + "Damaged": true, + "Missed": false, + "X": 3, + "Y": 1 + }, + { + "Damaged": false, + "Missed": false, + "X": 3, + "Y": 2 + }, + { + "Damaged": false, + "Missed": false, + "X": 3, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 3, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 3, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 3, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 3, + "Y": 7 + }, + { + "Damaged": true, + "Missed": false, + "X": 3, + "Y": 8 + }, + { + "Damaged": false, + "Missed": false, + "X": 3, + "Y": 9 + }, + { + "Damaged": false, + "Missed": false, + "X": 4, + "Y": 0 + }, + { + "Damaged": true, + "Missed": false, + "X": 4, + "Y": 1 + }, + { + "Damaged": true, + "Missed": false, + "X": 4, + "Y": 2 + }, + { + "Damaged": true, + "Missed": false, + "X": 4, + "Y": 3 + }, + { + "Damaged": true, + "Missed": false, + "X": 4, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 4, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 4, + "Y": 6 + }, + { + "Damaged": false, + "Missed": false, + "X": 4, + "Y": 7 + }, + { + "Damaged": false, + "Missed": true, + "X": 4, + "Y": 8 + }, + { + "Damaged": false, + "Missed": true, + "X": 4, + "Y": 9 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 0 + }, + { + "Damaged": true, + "Missed": false, + "X": 5, + "Y": 1 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 2 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 7 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 8 + }, + { + "Damaged": false, + "Missed": false, + "X": 5, + "Y": 9 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 0 + }, + { + "Damaged": true, + "Missed": false, + "X": 6, + "Y": 1 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 2 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 7 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 8 + }, + { + "Damaged": false, + "Missed": false, + "X": 6, + "Y": 9 + }, + { + "Damaged": false, + "Missed": false, + "X": 7, + "Y": 0 + }, + { + "Damaged": true, + "Missed": false, + "X": 7, + "Y": 1 + }, + { + "Damaged": false, + "Missed": true, + "X": 7, + "Y": 2 + }, + { + "Damaged": true, + "Missed": false, + "X": 7, + "Y": 3 + }, + { + "Damaged": true, + "Missed": false, + "X": 7, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 7, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 7, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 7, + "Y": 7 + }, + { + "Damaged": false, + "Missed": true, + "X": 7, + "Y": 8 + }, + { + "Damaged": false, + "Missed": true, + "X": 7, + "Y": 9 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 0 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 1 + }, + { + "Damaged": false, + "Missed": false, + "X": 8, + "Y": 2 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 7 + }, + { + "Damaged": false, + "Missed": false, + "X": 8, + "Y": 8 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 9 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 0 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 1 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 2 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 7 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 8 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 9 + } + ] + }, + "GameVersion": "1.0.0", + "GameLevel": 1, + "Round": 87, + "MapDimension": 10, + "Phase": 2, + "Player1Map": null, + "Player2Map": null +} +#+END_SRC diff --git a/readme.org b/readme.org deleted file mode 100644 index ff3e319..0000000 --- a/readme.org +++ /dev/null @@ -1,1447 +0,0 @@ -* State.json - -#+BEGIN_EXAMPLE -State = {PlayerMap, OpponentMap, GameVersion=String, GameLevel=u16, Round=u16, MapDimension=u16, Phase=u16, Player1Map=null, Player2Map=null} - -PlayerMap = {Cells=[PlayerCell], Owner, MapWidth=u16, MapHeight=u16} - -PlayerCell = {Occupied=bool, Hit=bool, X=u16, Y=u16} - -Owner = {FailedFirstPhaseCommands=u16, Name=String, Ships=[PlayerShip], Points=u16, Killed=bool, IsWinner=bool, ShotsFired=u16, ShotsHit=u16, ShipsRemaining=u16, Key=String} - -PlayerShip = {Cells=[PlayerCell], Destroyed=bool, Placed=bool, ShipType=String, Weapons=[PlayerWeapon], } - -PlayerWeapon = {WeaponType=String} - -OpponentMap = {Ships=[OpponentShip], Alive=bool, Points=u16, Name=String, Cells=[OpponentCell]} - -OpponentShip = {Destroyed=bool, ShipType=String} - -OpponentCell = {Damaged=bool, Missed=bool, X=u16, Y=u16} - -#+END_EXAMPLE - -* State.json example - -#+BEGIN_SRC json -{ - "PlayerMap": { - "Cells": [ - { - "Occupied": false, - "Hit": true, - "X": 0, - "Y": 0 - }, - { - "Occupied": false, - "Hit": false, - "X": 0, - "Y": 1 - }, - { - "Occupied": true, - "Hit": false, - "X": 0, - "Y": 2 - }, - { - "Occupied": true, - "Hit": false, - "X": 0, - "Y": 3 - }, - { - "Occupied": true, - "Hit": false, - "X": 0, - "Y": 4 - }, - { - "Occupied": false, - "Hit": false, - "X": 0, - "Y": 5 - }, - { - "Occupied": true, - "Hit": true, - "X": 0, - "Y": 6 - }, - { - "Occupied": true, - "Hit": true, - - "X": 0, - "Y": 7 - }, - { - "Occupied": true, - "Hit": false, - "X": 0, - "Y": 8 - }, - { - "Occupied": false, - "Hit": true, - "X": 0, - "Y": 9 - }, - { - "Occupied": false, - "Hit": false, - "X": 1, - "Y": 0 - }, - { - "Occupied": false, - "Hit": false, - "X": 1, - "Y": 1 - }, - { - "Occupied": false, - "Hit": false, - "X": 1, - "Y": 2 - }, - { - "Occupied": false, - "Hit": true, - "X": 1, - "Y": 3 - }, - { - "Occupied": false, - "Hit": false, - "X": 1, - "Y": 4 - }, - { - "Occupied": false, - "Hit": false, - "X": 1, - "Y": 5 - }, - { - "Occupied": false, - "Hit": false, - "X": 1, - "Y": 6 - }, - { - "Occupied": false, - "Hit": false, - "X": 1, - "Y": 7 - }, - { - "Occupied": true, - "Hit": false, - "X": 1, - "Y": 8 - }, - { - "Occupied": false, - "Hit": true, - "X": 1, - "Y": 9 - }, - { - "Occupied": false, - "Hit": false, - "X": 2, - "Y": 0 - }, - { - "Occupied": false, - "Hit": false, - "X": 2, - "Y": 1 - }, - { - "Occupied": false, - "Hit": false, - "X": 2, - "Y": 2 - }, - { - "Occupied": false, - "Hit": false, - "X": 2, - "Y": 3 - }, - { - "Occupied": false, - "Hit": true, - "X": 2, - "Y": 4 - }, - { - "Occupied": false, - "Hit": true, - "X": 2, - "Y": 5 - }, - { - "Occupied": false, - "Hit": false, - "X": 2, - "Y": 6 - }, - { - "Occupied": false, - "Hit": false, - "X": 2, - "Y": 7 - }, - { - "Occupied": true, - "Hit": true, - "X": 2, - "Y": 8 - }, - { - "Occupied": false, - "Hit": false, - "X": 2, - "Y": 9 - }, - { - "Occupied": false, - "Hit": true, - "X": 3, - "Y": 0 - }, - { - "Occupied": false, - "Hit": true, - "X": 3, - "Y": 1 - }, - { - "Occupied": false, - "Hit": true, - "X": 3, - "Y": 2 - }, - { - "Occupied": false, - "Hit": false, - "X": 3, - "Y": 3 - }, - { - "Occupied": false, - "Hit": false, - "X": 3, - "Y": 4 - }, - { - "Occupied": false, - "Hit": false, - "X": 3, - "Y": 5 - }, - { - "Occupied": false, - "Hit": false, - "X": 3, - "Y": 6 - }, - { - "Occupied": false, - "Hit": false, - "X": 3, - "Y": 7 - }, - { - "Occupied": true, - "Hit": false, - "X": 3, - "Y": 8 - }, - { - "Occupied": false, - "Hit": false, - "X": 3, - "Y": 9 - }, - { - "Occupied": false, - "Hit": false, - "X": 4, - "Y": 0 - }, - { - "Occupied": false, - "Hit": true, - "X": 4, - "Y": 1 - }, - { - "Occupied": false, - "Hit": true, - "X": 4, - "Y": 2 - }, - { - "Occupied": false, - "Hit": false, - "X": 4, - "Y": 3 - }, - { - "Occupied": false, - "Hit": false, - "X": 4, - "Y": 4 - }, - { - "Occupied": false, - "Hit": false, - "X": 4, - "Y": 5 - }, - { - "Occupied": false, - "Hit": false, - "X": 4, - "Y": 6 - }, - { - "Occupied": false, - "Hit": true, - "X": 4, - "Y": 7 - }, - { - "Occupied": true, - "Hit": false, - "X": 4, - "Y": 8 - }, - { - "Occupied": false, - "Hit": true, - "X": 4, - "Y": 9 - }, - { - "Occupied": false, - "Hit": false, - "X": 5, - "Y": 0 - }, - { - "Occupied": true, - "Hit": true, - "X": 5, - "Y": 1 - }, - { - "Occupied": false, - "Hit": false, - "X": 5, - "Y": 2 - }, - { - "Occupied": false, - "Hit": false, - "X": 5, - "Y": 3 - }, - { - "Occupied": false, - "Hit": false, - "X": 5, - "Y": 4 - }, - { - "Occupied": false, - "Hit": false, - "X": 5, - "Y": 5 - }, - { - "Occupied": false, - "Hit": false, - "X": 5, - "Y": 6 - }, - { - "Occupied": false, - "Hit": false, - "X": 5, - "Y": 7 - }, - { - "Occupied": true, - "Hit": false, - "X": 5, - "Y": 8 - }, - { - "Occupied": false, - "Hit": false, - "X": 5, - "Y": 9 - }, - { - "Occupied": false, - "Hit": false, - "X": 6, - "Y": 0 - }, - { - "Occupied": true, - "Hit": false, - "X": 6, - "Y": 1 - }, - { - "Occupied": false, - "Hit": false, - "X": 6, - "Y": 2 - }, - { - "Occupied": false, - "Hit": true, - "X": 6, - "Y": 3 - }, - { - "Occupied": false, - "Hit": false, - "X": 6, - "Y": 4 - }, - { - "Occupied": false, - "Hit": true, - "X": 6, - "Y": 5 - }, - { - "Occupied": false, - "Hit": false, - "X": 6, - "Y": 6 - }, - { - "Occupied": false, - "Hit": false, - "X": 6, - "Y": 7 - }, - { - "Occupied": false, - "Hit": true, - "X": 6, - "Y": 8 - }, - { - "Occupied": false, - "Hit": false, - "X": 6, - "Y": 9 - }, - { - "Occupied": false, - "Hit": false, - "X": 7, - "Y": 0 - }, - { - "Occupied": true, - "Hit": true, - "X": 7, - "Y": 1 - }, - { - "Occupied": false, - "Hit": false, - "X": 7, - "Y": 2 - }, - { - "Occupied": false, - "Hit": false, - "X": 7, - "Y": 3 - }, - { - "Occupied": false, - "Hit": false, - "X": 7, - "Y": 4 - }, - { - "Occupied": true, - "Hit": false, - "X": 7, - "Y": 5 - }, - { - "Occupied": true, - "Hit": false, - "X": 7, - "Y": 6 - }, - { - "Occupied": false, - "Hit": false, - "X": 7, - "Y": 7 - }, - { - "Occupied": false, - "Hit": false, - "X": 7, - "Y": 8 - }, - { - "Occupied": false, - "Hit": false, - "X": 7, - "Y": 9 - }, - { - "Occupied": false, - "Hit": false, - "X": 8, - "Y": 0 - }, - { - "Occupied": true, - "Hit": false, - "X": 8, - "Y": 1 - }, - { - "Occupied": false, - "Hit": false, - "X": 8, - "Y": 2 - }, - { - "Occupied": false, - "Hit": true, - "X": 8, - "Y": 3 - }, - { - "Occupied": false, - "Hit": true, - "X": 8, - "Y": 4 - }, - { - "Occupied": false, - "Hit": false, - "X": 8, - "Y": 5 - }, - { - "Occupied": false, - "Hit": false, - "X": 8, - "Y": 6 - }, - { - "Occupied": false, - "Hit": false, - "X": 8, - "Y": 7 - }, - { - "Occupied": false, - "Hit": false, - "X": 8, - "Y": 8 - }, - { - "Occupied": false, - "Hit": true, - "X": 8, - "Y": 9 - }, - { - "Occupied": false, - "Hit": false, - "X": 9, - "Y": 0 - }, - { - "Occupied": false, - "Hit": false, - "X": 9, - "Y": 1 - }, - { - "Occupied": false, - "Hit": false, - "X": 9, - "Y": 2 - }, - { - "Occupied": false, - "Hit": false, - "X": 9, - "Y": 3 - }, - { - "Occupied": false, - "Hit": false, - "X": 9, - "Y": 4 - }, - { - "Occupied": false, - "Hit": false, - "X": 9, - "Y": 5 - }, - { - "Occupied": false, - "Hit": false, - "X": 9, - "Y": 6 - }, - { - "Occupied": false, - "Hit": false, - "X": 9, - "Y": 7 - }, - { - "Occupied": false, - "Hit": true, - "X": 9, - "Y": 8 - }, - { - "Occupied": false, - "Hit": false, - "X": 9, - "Y": 9 - } - ], - "Owner": { - "FailedFirstPhaseCommands": 0, - "Name": "Admiral Worthebot", - "Ships": [ - { - "Cells": [ - { - "Occupied": true, - "Hit": false, - "X": 0, - "Y": 2 - }, - { - "Occupied": true, - "Hit": false, - "X": 0, - "Y": 3 - }, - { - "Occupied": true, - "Hit": false, - "X": 0, - "Y": 4 - } - ], - "Destroyed": false, - "Placed": true, - "ShipType": "Submarine", - "Weapons": [ - { - "WeaponType": "SingleShot" - } - ] - }, - { - "Cells": [ - { - "Occupied": true, - "Hit": false, - "X": 7, - "Y": 6 - }, - { - "Occupied": true, - "Hit": false, - "X": 7, - "Y": 5 - } - ], - "Destroyed": false, - "Placed": true, - "ShipType": "Destroyer", - "Weapons": [ - { - "WeaponType": "SingleShot" - } - ] - }, - { - "Cells": [ - { - "Occupied": true, - "Hit": true, - "X": 5, - "Y": 1 - }, - { - "Occupied": true, - "Hit": false, - "X": 6, - "Y": 1 - }, - { - "Occupied": true, - "Hit": true, - "X": 7, - "Y": 1 - }, - { - "Occupied": true, - "Hit": false, - "X": 8, - "Y": 1 - } - ], - "Destroyed": false, - "Placed": true, - "ShipType": "Battleship", - "Weapons": [ - { - "WeaponType": "SingleShot" - } - ] - }, - { - "Cells": [ - { - "Occupied": true, - "Hit": false, - "X": 5, - "Y": 8 - }, - { - "Occupied": true, - "Hit": false, - "X": 4, - "Y": 8 - }, - { - "Occupied": true, - "Hit": false, - "X": 3, - "Y": 8 - }, - { - "Occupied": true, - "Hit": true, - "X": 2, - "Y": 8 - }, - { - "Occupied": true, - "Hit": false, - "X": 1, - "Y": 8 - } - ], - "Destroyed": false, - "Placed": true, - "ShipType": "Carrier", - "Weapons": [ - { - "WeaponType": "SingleShot" - } - ] - }, - { - "Cells": [ - { - "Occupied": true, - "Hit": true, - "X": 0, - "Y": 6 - }, - { - "Occupied": true, - "Hit": true, - "X": 0, - "Y": 7 - }, - { - "Occupied": true, - "Hit": false, - "X": 0, - "Y": 8 - } - ], - "Destroyed": false, - "Placed": true, - "ShipType": "Cruiser", - "Weapons": [ - { - "WeaponType": "SingleShot" - } - ] - } - ], - "Points": 280, - "Killed": false, - "IsWinner": false, - "ShotsFired": 86, - "ShotsHit": 16, - "ShipsRemaining": 5, - "Key": "B" - }, - "MapWidth": 10, - "MapHeight": 10 - }, - "OpponentMap": { - "Ships": [ - { - "Destroyed": false, - "ShipType": "Submarine" - }, - { - "Destroyed": true, - "ShipType": "Destroyer" - }, - { - "Destroyed": true, - "ShipType": "Battleship" - }, - { - "Destroyed": true, - "ShipType": "Carrier" - }, - { - "Destroyed": true, - "ShipType": "Cruiser" - } - ], - "Alive": true, - "Points": 50, - "Name": "John", - "Cells": [ - { - "Damaged": false, - "Missed": true, - "X": 0, - "Y": 0 - }, - { - "Damaged": false, - "Missed": true, - "X": 0, - "Y": 1 - }, - { - "Damaged": false, - "Missed": true, - "X": 0, - "Y": 2 - }, - { - "Damaged": false, - "Missed": true, - "X": 0, - "Y": 3 - }, - { - "Damaged": false, - "Missed": true, - "X": 0, - "Y": 4 - }, - { - "Damaged": false, - "Missed": false, - "X": 0, - "Y": 5 - }, - { - "Damaged": false, - "Missed": true, - "X": 0, - "Y": 6 - }, - { - "Damaged": false, - "Missed": true, - "X": 0, - "Y": 7 - }, - { - "Damaged": false, - "Missed": true, - "X": 0, - "Y": 8 - }, - { - "Damaged": false, - "Missed": true, - "X": 0, - "Y": 9 - }, - { - "Damaged": true, - "Missed": false, - "X": 1, - "Y": 0 - }, - { - "Damaged": true, - "Missed": false, - "X": 1, - "Y": 1 - }, - { - "Damaged": true, - "Missed": false, - "X": 1, - "Y": 2 - }, - { - "Damaged": true, - "Missed": false, - "X": 1, - "Y": 3 - }, - { - "Damaged": false, - "Missed": true, - "X": 1, - "Y": 4 - }, - { - "Damaged": false, - "Missed": true, - "X": 1, - "Y": 5 - }, - { - "Damaged": false, - "Missed": true, - "X": 1, - "Y": 6 - }, - { - "Damaged": false, - "Missed": true, - "X": 1, - "Y": 7 - }, - { - "Damaged": true, - "Missed": false, - "X": 1, - "Y": 8 - }, - { - "Damaged": false, - "Missed": true, - "X": 1, - "Y": 9 - }, - { - "Damaged": false, - "Missed": true, - "X": 2, - "Y": 0 - }, - { - "Damaged": false, - "Missed": true, - "X": 2, - "Y": 1 - }, - { - "Damaged": false, - "Missed": true, - "X": 2, - "Y": 2 - }, - { - "Damaged": false, - "Missed": false, - "X": 2, - "Y": 3 - }, - { - "Damaged": false, - "Missed": true, - "X": 2, - "Y": 4 - }, - { - "Damaged": false, - "Missed": true, - "X": 2, - "Y": 5 - }, - { - "Damaged": false, - "Missed": true, - "X": 2, - "Y": 6 - }, - { - "Damaged": false, - "Missed": true, - "X": 2, - "Y": 7 - }, - { - "Damaged": false, - "Missed": false, - "X": 2, - "Y": 8 - }, - { - "Damaged": false, - "Missed": true, - "X": 2, - "Y": 9 - }, - { - "Damaged": false, - "Missed": false, - "X": 3, - "Y": 0 - }, - { - "Damaged": true, - "Missed": false, - "X": 3, - "Y": 1 - }, - { - "Damaged": false, - "Missed": false, - "X": 3, - "Y": 2 - }, - { - "Damaged": false, - "Missed": false, - "X": 3, - "Y": 3 - }, - { - "Damaged": false, - "Missed": true, - "X": 3, - "Y": 4 - }, - { - "Damaged": false, - "Missed": true, - "X": 3, - "Y": 5 - }, - { - "Damaged": false, - "Missed": true, - "X": 3, - "Y": 6 - }, - { - "Damaged": false, - "Missed": true, - "X": 3, - "Y": 7 - }, - { - "Damaged": true, - "Missed": false, - "X": 3, - "Y": 8 - }, - { - "Damaged": false, - "Missed": false, - "X": 3, - "Y": 9 - }, - { - "Damaged": false, - "Missed": false, - "X": 4, - "Y": 0 - }, - { - "Damaged": true, - "Missed": false, - "X": 4, - "Y": 1 - }, - { - "Damaged": true, - "Missed": false, - "X": 4, - "Y": 2 - }, - { - "Damaged": true, - "Missed": false, - "X": 4, - "Y": 3 - }, - { - "Damaged": true, - "Missed": false, - "X": 4, - "Y": 4 - }, - { - "Damaged": false, - "Missed": true, - "X": 4, - "Y": 5 - }, - { - "Damaged": false, - "Missed": true, - "X": 4, - "Y": 6 - }, - { - "Damaged": false, - "Missed": false, - "X": 4, - "Y": 7 - }, - { - "Damaged": false, - "Missed": true, - "X": 4, - "Y": 8 - }, - { - "Damaged": false, - "Missed": true, - "X": 4, - "Y": 9 - }, - { - "Damaged": false, - "Missed": true, - "X": 5, - "Y": 0 - }, - { - "Damaged": true, - "Missed": false, - "X": 5, - "Y": 1 - }, - { - "Damaged": false, - "Missed": true, - "X": 5, - "Y": 2 - }, - { - "Damaged": false, - "Missed": true, - "X": 5, - "Y": 3 - }, - { - "Damaged": false, - "Missed": true, - "X": 5, - "Y": 4 - }, - { - "Damaged": false, - "Missed": true, - "X": 5, - "Y": 5 - }, - { - "Damaged": false, - "Missed": true, - "X": 5, - "Y": 6 - }, - { - "Damaged": false, - "Missed": true, - "X": 5, - "Y": 7 - }, - { - "Damaged": false, - "Missed": true, - "X": 5, - "Y": 8 - }, - { - "Damaged": false, - "Missed": false, - "X": 5, - "Y": 9 - }, - { - "Damaged": false, - "Missed": true, - "X": 6, - "Y": 0 - }, - { - "Damaged": true, - "Missed": false, - "X": 6, - "Y": 1 - }, - { - "Damaged": false, - "Missed": true, - "X": 6, - "Y": 2 - }, - { - "Damaged": false, - "Missed": true, - "X": 6, - "Y": 3 - }, - { - "Damaged": false, - "Missed": true, - "X": 6, - "Y": 4 - }, - { - "Damaged": false, - "Missed": true, - "X": 6, - "Y": 5 - }, - { - "Damaged": false, - "Missed": true, - "X": 6, - "Y": 6 - }, - { - "Damaged": false, - "Missed": true, - "X": 6, - "Y": 7 - }, - { - "Damaged": false, - "Missed": true, - "X": 6, - "Y": 8 - }, - { - "Damaged": false, - "Missed": false, - "X": 6, - "Y": 9 - }, - { - "Damaged": false, - "Missed": false, - "X": 7, - "Y": 0 - }, - { - "Damaged": true, - "Missed": false, - "X": 7, - "Y": 1 - }, - { - "Damaged": false, - "Missed": true, - "X": 7, - "Y": 2 - }, - { - "Damaged": true, - "Missed": false, - "X": 7, - "Y": 3 - }, - { - "Damaged": true, - "Missed": false, - "X": 7, - "Y": 4 - }, - { - "Damaged": false, - "Missed": true, - "X": 7, - "Y": 5 - }, - { - "Damaged": false, - "Missed": true, - "X": 7, - "Y": 6 - }, - { - "Damaged": false, - "Missed": true, - "X": 7, - "Y": 7 - }, - { - "Damaged": false, - "Missed": true, - "X": 7, - "Y": 8 - }, - { - "Damaged": false, - "Missed": true, - "X": 7, - "Y": 9 - }, - { - "Damaged": false, - "Missed": true, - "X": 8, - "Y": 0 - }, - { - "Damaged": false, - "Missed": true, - "X": 8, - "Y": 1 - }, - { - "Damaged": false, - "Missed": false, - "X": 8, - "Y": 2 - }, - { - "Damaged": false, - "Missed": true, - "X": 8, - "Y": 3 - }, - { - "Damaged": false, - "Missed": true, - "X": 8, - "Y": 4 - }, - { - "Damaged": false, - "Missed": true, - "X": 8, - "Y": 5 - }, - { - "Damaged": false, - "Missed": true, - "X": 8, - "Y": 6 - }, - { - "Damaged": false, - "Missed": true, - "X": 8, - "Y": 7 - }, - { - "Damaged": false, - "Missed": false, - "X": 8, - "Y": 8 - }, - { - "Damaged": false, - "Missed": true, - "X": 8, - "Y": 9 - }, - { - "Damaged": false, - "Missed": true, - "X": 9, - "Y": 0 - }, - { - "Damaged": false, - "Missed": true, - "X": 9, - "Y": 1 - }, - { - "Damaged": false, - "Missed": true, - "X": 9, - "Y": 2 - }, - { - "Damaged": false, - "Missed": true, - "X": 9, - "Y": 3 - }, - { - "Damaged": false, - "Missed": true, - "X": 9, - "Y": 4 - }, - { - "Damaged": false, - "Missed": true, - "X": 9, - "Y": 5 - }, - { - "Damaged": false, - "Missed": true, - "X": 9, - "Y": 6 - }, - { - "Damaged": false, - "Missed": true, - "X": 9, - "Y": 7 - }, - { - "Damaged": false, - "Missed": true, - "X": 9, - "Y": 8 - }, - { - "Damaged": false, - "Missed": true, - "X": 9, - "Y": 9 - } - ] - }, - "GameVersion": "1.0.0", - "GameLevel": 1, - "Round": 87, - "MapDimension": 10, - "Phase": 2, - "Player1Map": null, - "Player2Map": null -} -#+END_SRC diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..ffffa2f --- /dev/null +++ b/readme.txt @@ -0,0 +1,20 @@ +* Admiral Worthebot + +** Compilation Instructions + +As per the Rust sample bot. Install the Rust build toolchain from https://www.rust-lang.org/en-US/install.html, then from the root directory of the project run + +cargo build --release + +** Project Structure + +Cargo.toml - Cargo project config, including project dependencies +src/ - Soure code directory +src/main.rs - Command line entrypoint (main function) and command line argument parsing +src/lib.rs - Programs public interface (as used by main.rs and any integration tests) + +** Strategy + +- Track all possible ways that an opponent may have placed their ships +- After every move, deduce which possibilities are now impossible +- Shoot in an attempt to (possibly) eliminate as many possibilities as possible -- cgit v1.2.3 From 862897ea1d183810c2783eaeeaf40f648ef3dc2d Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Sun, 18 Jun 2017 15:15:11 +0200 Subject: Added knowledge of weapons Next step: knowledge of weapon's effects. --- src/actions.rs | 4 ++-- src/knowledge.rs | 35 ++++++++++++++++++++++++---- src/ships.rs | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/shooting.rs | 6 +++-- src/state.rs | 44 ++++++++++++++++++++++++++++++++++++ 5 files changed, 149 insertions(+), 9 deletions(-) diff --git a/src/actions.rs b/src/actions.rs index 2291de6..ae1f6ce 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -8,13 +8,13 @@ use std::collections::HashSet; #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] pub enum Action { PlaceShips(Vec), - Shoot(Point) + Shoot(Weapon, Point) } impl fmt::Display for Action { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - &Action::Shoot(p) => writeln!(f, "1,{},{}", p.x, p.y), + &Action::Shoot(w, p) => writeln!(f, "{},{},{}", w, p.x, p.y), &Action::PlaceShips(ref ships) => ships.iter().map(|ref ship| { writeln!(f, "{} {} {} {}", ship.ship_type, ship.point.x, ship.point.y, ship.direction) }).fold(Ok(()), |acc, next| acc.and(next)) diff --git a/src/knowledge.rs b/src/knowledge.rs index 5e45f07..e12a8b2 100644 --- a/src/knowledge.rs +++ b/src/knowledge.rs @@ -9,7 +9,9 @@ use std::collections::HashMap; pub struct Knowledge { pub last_action: Action, pub opponent_map: OpponentMapKnowledge, - pub map_size: u16 + pub map_size: u16, + pub available_weapons: Vec, + pub charging_weapons: HashMap } impl Knowledge { @@ -17,24 +19,47 @@ impl Knowledge { Knowledge { last_action: action, opponent_map: OpponentMapKnowledge::new(map_size), - map_size: map_size + map_size: map_size, + available_weapons: Vec::new(), + charging_weapons: HashMap::new() } } pub fn with_action(&self, action: Action) -> Knowledge { Knowledge { last_action: action, - opponent_map: self.opponent_map.clone(), - map_size: self.map_size + ..self.clone() } } pub fn resolve_last_action(&self, state: &State) -> Knowledge { let mut new_knowledge = self.clone(); + + let energy = state.player_map.energy; + let mut available_weapons: Vec<_> = state.player_map.ships.iter() + .filter(|&(_, ship_data)| !ship_data.destroyed) + .flat_map(|(ship, _)| ship.weapons()) + .collect(); + + available_weapons.sort_by_key(|weapon| format!("{}",weapon)); + available_weapons.dedup(); + new_knowledge.available_weapons = available_weapons.iter() + .filter(|weapon| weapon.energy_cost(state.map_size) <= energy) + .cloned() + .collect(); + + new_knowledge.charging_weapons = available_weapons.iter() + .filter(|weapon| weapon.energy_cost(state.map_size) > energy) + .map(|weapon| (weapon.clone(), weapon.energy_cost(state.map_size) - energy)) + .collect(); + match self.last_action { Action::PlaceShips(_) => {}, - Action::Shoot(p) => { + Action::Shoot(Weapon::SingleShot, p) => { new_knowledge.opponent_map.update_from_shot(p, &state); + }, + Action::Shoot(w, p) => { + //TODO } }; diff --git a/src/ships.rs b/src/ships.rs index ef29fe9..e37fe33 100644 --- a/src/ships.rs +++ b/src/ships.rs @@ -1,6 +1,62 @@ use std::fmt; use std::str; +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] +pub enum Weapon { + SingleShot, + DoubleShotVertical, + DoubleShotHorizontal, + CornerShot, + CrossShotDiagonal, + CrossShotHorizontal, + SeekerMissle +} + +impl fmt::Display for Weapon { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use Weapon::*; + + f.write_str( + match self { + &SingleShot => "1", + &DoubleShotVertical => "2", + &DoubleShotHorizontal => "3", + &CornerShot => "4", + &CrossShotDiagonal => "5", + &CrossShotHorizontal => "6", + &SeekerMissle => "7" + } + ) + } +} + +impl Weapon { + pub fn energy_per_round(map_size: u16) -> u16 { + if map_size < 10 { + 2 + } + else if map_size < 14 { + 3 + } + else { + 4 + } + } + pub fn energy_cost(&self, map_size: u16) -> u16 { + use Weapon::*; + let epr = Weapon::energy_per_round(map_size); + match self { + &SingleShot => 1, + &DoubleShotVertical | &DoubleShotHorizontal => 8*epr, + &CornerShot => 10*epr, + &CrossShotDiagonal => 12*epr, + &CrossShotHorizontal => 14*epr, + &SeekerMissle => 10*epr + } + } +} + + #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] pub enum Ship { Battleship, @@ -55,6 +111,19 @@ impl Ship { } } + pub fn weapons(&self) -> Vec { + use Ship::*; + use Weapon::*; + + match self { + &Battleship => vec!(SingleShot, CrossShotDiagonal), + &Carrier => vec!(SingleShot, CornerShot), + &Cruiser => vec!(SingleShot, CrossShotHorizontal), + &Destroyer => vec!(SingleShot, DoubleShotVertical, DoubleShotHorizontal), + &Submarine => vec!(SingleShot, SeekerMissle) + } + } + pub fn all_types() -> Vec { use Ship::*; diff --git a/src/shooting.rs b/src/shooting.rs index 4d87d86..bcdf783 100644 --- a/src/shooting.rs +++ b/src/shooting.rs @@ -4,6 +4,7 @@ use rand::distributions::{IndependentSample, Range}; use actions::*; use math::*; use knowledge::*; +use ships::*; pub fn shoot_smartly(knowledge: &Knowledge) -> Action { let shot = if knowledge.has_unknown_hits() { @@ -12,8 +13,9 @@ pub fn shoot_smartly(knowledge: &Knowledge) -> Action { else { seek_shoot(&knowledge) }; - - Action::Shoot(shot) + + //TODO shoot other weapons + Action::Shoot(Weapon::SingleShot, shot) } fn seek_shoot(knowledge: &Knowledge) -> Point { diff --git a/src/state.rs b/src/state.rs index 8c176e1..1756ad0 100644 --- a/src/state.rs +++ b/src/state.rs @@ -4,17 +4,23 @@ use ships::*; pub struct State { pub map_size: u16, + pub player_map: PlayerMap, pub opponent_map: OpponentMap } impl State { pub fn new(json: &json::JsonValue) -> Result { let map_size = State::map_size_from_json(&json)?; + + let ref player_map_json = json["PlayerMap"]; + let player_map = PlayerMap::new(&player_map_json)?; + let ref opponent_map_json = json["OpponentMap"]; let opponent_map = OpponentMap::new(&opponent_map_json, map_size)?; Ok(State { map_size: map_size, + player_map: player_map, opponent_map: opponent_map }) } @@ -100,3 +106,41 @@ impl Cell { } } } + +pub struct PlayerMap { + pub ships: HashMap, + pub energy: u16 +} + +impl PlayerMap { + fn new(json: &json::JsonValue) -> Result { + let mut ships = HashMap::new(); + for json_ship in json["Owner"]["Ships"].members() { + let ship_type_string = json_ship["ShipType"] + .as_str() + .ok_or(String::from("Failed to read ShipType value of player map ship in json file"))?; + let ship_type = ship_type_string.parse::()?; + + let destroyed = json_ship["Destroyed"] + .as_bool() + .ok_or(String::from("Failed to read Destroyed value of player map ship in json file"))?; + ships.insert(ship_type, PlayerShip { + destroyed: destroyed + }); + } + + let energy = json["Owner"]["Energy"] + .as_u16() + .ok_or(String::from("Did not find the energy in the state json file"))?; + + Ok(PlayerMap { + ships: ships, + energy: energy + }) + } +} + + +pub struct PlayerShip { + pub destroyed: bool +} -- cgit v1.2.3 From 29719e34889f14377c0865e320eb4c1571f60a42 Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Thu, 22 Jun 2017 20:08:54 +0200 Subject: Added turns to weapon being available to knowledge --- src/knowledge.rs | 2 +- src/ships.rs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/knowledge.rs b/src/knowledge.rs index e12a8b2..a9f39e3 100644 --- a/src/knowledge.rs +++ b/src/knowledge.rs @@ -50,7 +50,7 @@ impl Knowledge { new_knowledge.charging_weapons = available_weapons.iter() .filter(|weapon| weapon.energy_cost(state.map_size) > energy) - .map(|weapon| (weapon.clone(), weapon.energy_cost(state.map_size) - energy)) + .map(|weapon| (weapon.clone(), weapon.single_shot_rounds_to_ready(energy, state.map_size))) .collect(); match self.last_action { diff --git a/src/ships.rs b/src/ships.rs index e37fe33..104e986 100644 --- a/src/ships.rs +++ b/src/ships.rs @@ -54,6 +54,13 @@ impl Weapon { &SeekerMissle => 10*epr } } + pub fn single_shot_rounds_to_ready(&self, current_energy: u16, map_size: u16) -> u16 { + let single_shot_cost = Weapon::SingleShot.energy_cost(map_size); + let energy_per_round = Weapon::energy_per_round(map_size) - single_shot_cost; + let required_energy = self.energy_cost(map_size) - current_energy; + //weird plus is to make the integer rounding up instead of down + (required_energy + energy_per_round - 1) / energy_per_round + } } -- cgit v1.2.3 From d7a5ae25a5a15064f695d79e2b510c9c305fb841 Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Sat, 24 Jun 2017 15:48:05 +0200 Subject: Knowledge update that can handle different weapons --- src/knowledge.rs | 90 ++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 55 insertions(+), 35 deletions(-) diff --git a/src/knowledge.rs b/src/knowledge.rs index a9f39e3..4ade5f5 100644 --- a/src/knowledge.rs +++ b/src/knowledge.rs @@ -53,15 +53,24 @@ impl Knowledge { .map(|weapon| (weapon.clone(), weapon.single_shot_rounds_to_ready(energy, state.map_size))) .collect(); - match self.last_action { - Action::PlaceShips(_) => {}, + let points = match self.last_action { + Action::PlaceShips(_) => { + vec!() + }, Action::Shoot(Weapon::SingleShot, p) => { - new_knowledge.opponent_map.update_from_shot(p, &state); + vec!(p) }, Action::Shoot(w, p) => { + vec!() //TODO } }; + + let misses = points.iter().filter(|p| state.opponent_map.cells[p.x as usize][p.y as usize].missed).cloned().collect(); + let hits = points.iter().filter(|p| !state.opponent_map.cells[p.x as usize][p.y as usize].missed).cloned().collect(); + let sunk_ships = new_knowledge.opponent_map.update_sunk_ships(&state); + + new_knowledge.opponent_map.update_from_shot(hits, misses, sunk_ships); new_knowledge } @@ -157,60 +166,69 @@ impl OpponentMapKnowledge { } } - fn update_from_shot(&mut self, p: Point, state: &State) { - let ref shot_cell = state.opponent_map.cells[p.x as usize][p.y as usize]; - let sunk_ship = self.ships.iter() + fn update_sunk_ships(&mut self, state: &State) -> Vec { + let sunk_ships = self.ships.iter() .filter(|&(_, x)| !x.destroyed) .filter(|&(s, _)| state.opponent_map.ships.get(s).map(|x| x.destroyed) == Some(true)) .map(|(s, _)| s.clone()) - .next(); //only one ship can be sunk at a time + .collect(); - - sunk_ship - .and_then(|ship| self.ships.get_mut(&ship)) - .map(|ref mut ship_knowledge| ship_knowledge.destroyed = true); + for &ship in &sunk_ships { + self.ships.get_mut(&ship).map(|ref mut ship_knowledge| ship_knowledge.destroyed = true); + } - if shot_cell.missed { - self.cells[p.x as usize][p.y as usize].missed = true; - for knowledge in self.ships.values_mut() { - knowledge.possible_placements.retain(|x| !x.touches_point(p)); - } + sunk_ships + } + + fn update_from_shot(&mut self, hit_cells: Vec, missed_cells: Vec, sunk_ships: Vec) { + for &missed in &missed_cells { + self.cells[missed.x as usize][missed.y as usize].missed = true; } - else { - self.cells[p.x as usize][p.y as usize].hit = true; - self.cells[p.x as usize][p.y as usize].known_ship = sunk_ship; + for &hit in &hit_cells { + self.cells[hit.x as usize][hit.y as usize].hit = true; } - let cells_copy = self.cells.clone(); - if sunk_ship.is_some() { - for knowledge in self.ships.values_mut() { - knowledge.possible_placements.retain(|x| { - (sunk_ship != Some(x.ship) && !x.touches_point(p)) || - (sunk_ship == Some(x.ship) && x.touches_point(p) && x.all_are_hits(&cells_copy)) + self.clear_sunk_ship_impossible_placements(&sunk_ships, &hit_cells); - }); - } + let mut more_changes = true; + while more_changes { + more_changes = self.derive_ship_positions() || self.clear_impossible_placements(); } - - self.derive_ship_positions(); } - fn derive_ship_positions(&mut self) { + fn derive_ship_positions(&mut self) -> bool { + let mut any_changes = false; for knowledge in self.ships.values() { if knowledge.possible_placements.len() == 1 { let ref true_placement = knowledge.possible_placements[0]; for p in true_placement.points_on_ship() { self.cells[p.x as usize][p.y as usize].known_ship = Some(true_placement.ship); } + any_changes = true; } } - self.clear_impossible_placements(); + any_changes } - fn clear_impossible_placements(&mut self) { + fn clear_impossible_placements(&mut self) -> bool { + let mut any_changes = false; let ref cells = self.cells; for knowledge in self.ships.values_mut() { + let before = knowledge.possible_placements.len(); knowledge.possible_placements.retain(|x| x.all_could_be_hits(&cells)); + let after = knowledge.possible_placements.len(); + if before != after { + any_changes = true; + } + } + any_changes + } + + fn clear_sunk_ship_impossible_placements(&mut self, sunk_ships: &Vec, must_touch_any: &Vec) { + let cells_copy = self.cells.clone(); + + for knowledge in self.ships.values_mut() { + knowledge.possible_placements.retain(|x| sunk_ships.contains(&x.ship) && x.touches_any_point(&must_touch_any) && x.all_are_hits(&cells_copy)); } } @@ -292,6 +310,9 @@ impl PossibleShipPlacement { pub fn touches_point(&self, p: Point) -> bool { p.check_for_ship_collision(self.position, self.direction, self.ship.length()) } + pub fn touches_any_point(&self, ps: &Vec) -> bool { + ps.iter().any(|&p| self.touches_point(p)) + } pub fn points_on_ship(&self) -> Vec { (0..self.ship.length() as i32).map(|i| { @@ -308,10 +329,9 @@ impl PossibleShipPlacement { fn all_could_be_hits(&self, cells: &Vec>) -> bool { self.points_on_ship() .iter() - .fold(true, |acc, p| { + .all(|p| { let ref cell = cells[p.x as usize][p.y as usize]; - acc && !cell.missed && - cell.known_ship.map(|ship| ship == self.ship).unwrap_or(true) + !cell.missed && cell.known_ship.map(|ship| ship == self.ship).unwrap_or(true) }) } -- cgit v1.2.3 From 7355491e14081c6dff749d6c536e876cfebc5d0d Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Sat, 24 Jun 2017 17:49:35 +0200 Subject: Implemented knowledge updates based on all weapon types --- src/knowledge.rs | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++----- src/math.rs | 36 +++++++++++++----- 2 files changed, 131 insertions(+), 18 deletions(-) diff --git a/src/knowledge.rs b/src/knowledge.rs index 4ade5f5..f271dbe 100644 --- a/src/knowledge.rs +++ b/src/knowledge.rs @@ -53,21 +53,57 @@ impl Knowledge { .map(|weapon| (weapon.clone(), weapon.single_shot_rounds_to_ready(energy, state.map_size))) .collect(); - let points = match self.last_action { + let (hits, misses, _) = match self.last_action { Action::PlaceShips(_) => { - vec!() + (vec!(), vec!(), vec!()) }, Action::Shoot(Weapon::SingleShot, p) => { - vec!(p) + Knowledge::to_hits_and_misses(vec!(Some(p)), &state) }, - Action::Shoot(w, p) => { - vec!() - //TODO + Action::Shoot(Weapon::DoubleShotVertical, p) => { + Knowledge::to_hits_and_misses(vec!( + p.move_point(Direction::North, 1, state.map_size), + p.move_point(Direction::South, 1, state.map_size) + ), &state) + }, + Action::Shoot(Weapon::DoubleShotHorizontal, p) => { + Knowledge::to_hits_and_misses(vec!( + p.move_point(Direction::East, 1, state.map_size), + p.move_point(Direction::West, 1, state.map_size) + ), &state) + }, + Action::Shoot(Weapon::CornerShot, p) => { + Knowledge::to_hits_and_misses(vec!( + p.move_point(Direction::NorthEast, 1, state.map_size), + p.move_point(Direction::SouthEast, 1, state.map_size), + p.move_point(Direction::NorthWest, 1, state.map_size), + p.move_point(Direction::SouthWest, 1, state.map_size), + ), &state) + }, + Action::Shoot(Weapon::CrossShotDiagonal, p) => { + Knowledge::to_hits_and_misses(vec!( + p.move_point(Direction::NorthEast, 1, state.map_size), + p.move_point(Direction::SouthEast, 1, state.map_size), + p.move_point(Direction::NorthWest, 1, state.map_size), + p.move_point(Direction::SouthWest, 1, state.map_size), + Some(p) + ), &state) + }, + Action::Shoot(Weapon::CrossShotHorizontal, p) => { + Knowledge::to_hits_and_misses(vec!( + p.move_point(Direction::North, 1, state.map_size), + p.move_point(Direction::East, 1, state.map_size), + p.move_point(Direction::South, 1, state.map_size), + p.move_point(Direction::West, 1, state.map_size), + Some(p) + ), &state) + }, + + Action::Shoot(Weapon::SeekerMissle, p) => { + Knowledge::seeker_hits_and_misses(p, &state) } }; - - let misses = points.iter().filter(|p| state.opponent_map.cells[p.x as usize][p.y as usize].missed).cloned().collect(); - let hits = points.iter().filter(|p| !state.opponent_map.cells[p.x as usize][p.y as usize].missed).cloned().collect(); + let sunk_ships = new_knowledge.opponent_map.update_sunk_ships(&state); new_knowledge.opponent_map.update_from_shot(hits, misses, sunk_ships); @@ -75,6 +111,65 @@ impl Knowledge { new_knowledge } + fn to_hits_and_misses(points: Vec>, state: &State) -> (Vec, Vec, Vec) { + let points = points.iter().filter_map(|&p| p).collect::>(); + + let hits = points.iter().filter(|p| !state.opponent_map.cells[p.x as usize][p.y as usize].damaged).cloned().collect(); + let misses = points.iter().filter(|p| state.opponent_map.cells[p.x as usize][p.y as usize].missed).cloned().collect(); + let unknown = points.iter().filter(|p| !state.opponent_map.cells[p.x as usize][p.y as usize].missed && !state.opponent_map.cells[p.x as usize][p.y as usize].damaged).cloned().collect(); + + (hits, misses, unknown) + } + + fn seeker_hits_and_misses(p: Point, state: &State) -> (Vec, Vec, Vec) { + let mut misses: Vec = Vec::new(); + let mut hits: Vec = Vec::new(); + + let rings = vec!( + vec!( + //0 + Some(p) + ), + vec!( + //1 + p.move_point(Direction::North, 1, state.map_size), + p.move_point(Direction::East, 1, state.map_size), + p.move_point(Direction::South, 1, state.map_size), + p.move_point(Direction::West, 1, state.map_size), + ), + vec!( + //1.44 + p.move_point(Direction::NorthEast, 1, state.map_size), + p.move_point(Direction::SouthEast, 1, state.map_size), + p.move_point(Direction::NorthWest, 1, state.map_size), + p.move_point(Direction::SouthWest, 1, state.map_size) + ), + vec!( + //2 + p.move_point(Direction::North, 2, state.map_size), + p.move_point(Direction::East, 2, state.map_size), + p.move_point(Direction::South, 2, state.map_size), + p.move_point(Direction::West, 2, state.map_size), + ) + ); + + //start in the center. Add rings, until I find a hit + //don't add more after a hit is found + for ring in rings { + if hits.is_empty() { + let (mut new_hits, mut new_misses, mut unknown) = Knowledge::to_hits_and_misses(ring, &state); + misses.append(&mut new_misses); + if !new_hits.is_empty() { + hits.append(&mut new_hits); + } else { + misses.append(&mut unknown); + } + } + } + + (hits, misses, vec!()) + } + pub fn has_unknown_hits(&self) -> bool { self.opponent_map.cells.iter().fold(false, |acc, x| { x.iter().fold(acc, |acc, y| acc || y.unknown_hit()) diff --git a/src/math.rs b/src/math.rs index a93d03e..3187829 100644 --- a/src/math.rs +++ b/src/math.rs @@ -5,9 +5,13 @@ use rand::distributions::{IndependentSample, Range}; #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] pub enum Direction { North, + NorthEast, East, + SouthEast, South, + SouthWest, West, + NorthWest } use Direction::*; @@ -19,7 +23,11 @@ impl fmt::Display for Direction { &North => "North", &East => "East", &South => "South", - &West => "West" + &West => "West", + &NorthEast => "NorthEast", + &SouthEast => "SouthEast", + &NorthWest => "NorthWest", + &SouthWest => "SouthWest" } ) } @@ -77,13 +85,13 @@ impl Point { pub fn move_point(&self, direction: Direction, distance: i32, map_size: u16) -> Option { let x = self.x as i32 + match direction { - West => -distance, - East => distance, + West|NorthWest|SouthWest => -distance, + East|NorthEast|SouthEast => distance, _ => 0 }; let y = self.y as i32 + match direction { - South => -distance, - North => distance, + South|SouthWest|SouthEast => -distance, + North|NorthWest|NorthEast => distance, _ => 0 }; let max = map_size as i32; @@ -114,17 +122,20 @@ impl Point { pub fn check_for_ship_collision(&self, ship_start: Point, direction: Direction, length: u16) -> bool { let reverse = match direction { West | South => true, - East | North => false + East | North => false, + _ => false //ships cannot go diagonally }; let same_cross = match direction { East | West => self.y == ship_start.y, - North | South => self.x == ship_start.x + North | South => self.x == ship_start.x, + _ => false //ships cannot go diagonally }; let (parr_self, parr_ship) = match direction { East | West => (self.x, ship_start.x), - North | South => (self.y, ship_start.y) + North | South => (self.y, ship_start.y), + _ => (self.x, self.y) //ships cannot go diagonally }; let corrected_parr_ship = match reverse { @@ -163,7 +174,14 @@ mod point_tests { } } - + #[test] + fn move_point_works_north_west() { + assert_eq!(Some(Point::new(3,7)), Point::new(5,5).move_point(NorthWest, 2, 10)); + assert_eq!(Some(Point::new(7,3)), Point::new(5,5).move_point(NorthWest, -2, 10)); + assert_eq!(None, Point::new(5,5).move_point(NorthWest, 6, 10)); + assert_eq!(None, Point::new(5,5).move_point(NorthWest, -5, 10)); + } + #[test] fn move_point_works_west() { assert_eq!(Some(Point::new(3,5)), Point::new(5,5).move_point(West, 2, 10)); -- cgit v1.2.3 From e09f191ac88fadd02d2ecf4f7a0aea76a19e3d0a Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Wed, 12 Jul 2017 21:21:49 +0200 Subject: Avoided placing adjacent ships --- src/actions.rs | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/actions.rs b/src/actions.rs index ae1f6ce..cf0059a 100644 --- a/src/actions.rs +++ b/src/actions.rs @@ -45,12 +45,11 @@ impl ShipPlacement { } pub fn valid_placements(placements: &Vec, map_size: u16) -> bool { let mut occupied = HashSet::new(); - let mut individuals_valid = true; - let mut no_overlaps = true; - + + let individuals_valid = placements.iter().all(|p| p.valid(map_size)); + + let mut no_overlaps = true; for placement in placements { - individuals_valid = individuals_valid && placement.valid(map_size); - for i in 0..placement.ship_type.length() as i32 { match placement.point.move_point(placement.direction, i, map_size) { Some(block) => { @@ -62,6 +61,29 @@ impl ShipPlacement { } } } + + //block out the area around the current ship to prevent adjacent ships + for i in 0..placement.ship_type.length() as i32 { + match placement.point.move_point(placement.direction, i, map_size) { + Some(current_block) => { + if let Some(p) = current_block.move_point(Direction::North, 1, map_size) { + occupied.insert(p); + } + if let Some(p) = current_block.move_point(Direction::South, 1, map_size) { + occupied.insert(p); + } + if let Some(p) = current_block.move_point(Direction::East, 1, map_size) { + occupied.insert(p); + } + if let Some(p) = current_block.move_point(Direction::West, 1, map_size) { + occupied.insert(p); + } + }, + None => { + //invalid case here is handled above + } + } + } } individuals_valid && no_overlaps } -- cgit v1.2.3 From c14a74190d72ced3e6fdbf718fce25b4a0e8cc8f Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Wed, 12 Jul 2017 21:42:31 +0200 Subject: Fixed bugs in knowledge update --- src/knowledge.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/knowledge.rs b/src/knowledge.rs index f271dbe..44693e5 100644 --- a/src/knowledge.rs +++ b/src/knowledge.rs @@ -114,7 +114,7 @@ impl Knowledge { fn to_hits_and_misses(points: Vec>, state: &State) -> (Vec, Vec, Vec) { let points = points.iter().filter_map(|&p| p).collect::>(); - let hits = points.iter().filter(|p| !state.opponent_map.cells[p.x as usize][p.y as usize].damaged).cloned().collect(); + let hits = points.iter().filter(|p| state.opponent_map.cells[p.x as usize][p.y as usize].damaged).cloned().collect(); let misses = points.iter().filter(|p| state.opponent_map.cells[p.x as usize][p.y as usize].missed).cloned().collect(); let unknown = points.iter().filter(|p| !state.opponent_map.cells[p.x as usize][p.y as usize].missed && !state.opponent_map.cells[p.x as usize][p.y as usize].damaged).cloned().collect(); @@ -297,9 +297,11 @@ impl OpponentMapKnowledge { if knowledge.possible_placements.len() == 1 { let ref true_placement = knowledge.possible_placements[0]; for p in true_placement.points_on_ship() { - self.cells[p.x as usize][p.y as usize].known_ship = Some(true_placement.ship); + if self.cells[p.x as usize][p.y as usize].known_ship == None { + self.cells[p.x as usize][p.y as usize].known_ship = Some(true_placement.ship); + any_changes = true; + } } - any_changes = true; } } any_changes @@ -323,7 +325,7 @@ impl OpponentMapKnowledge { let cells_copy = self.cells.clone(); for knowledge in self.ships.values_mut() { - knowledge.possible_placements.retain(|x| sunk_ships.contains(&x.ship) && x.touches_any_point(&must_touch_any) && x.all_are_hits(&cells_copy)); + knowledge.possible_placements.retain(|x| !sunk_ships.contains(&x.ship) || (x.touches_any_point(&must_touch_any) && x.all_are_hits(&cells_copy))); } } -- cgit v1.2.3 From 6379c1252e8ec51e607865638ff7d5ae79bd9c96 Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Sat, 15 Jul 2017 19:17:49 +0200 Subject: Adding shooting of new weapons --- src/knowledge.rs | 134 +++++++++++++++++++++++++++++-------------------------- src/ships.rs | 71 +++++++++++++++++++++++++++++ src/shooting.rs | 19 ++++---- 3 files changed, 151 insertions(+), 73 deletions(-) diff --git a/src/knowledge.rs b/src/knowledge.rs index 44693e5..4fe60da 100644 --- a/src/knowledge.rs +++ b/src/knowledge.rs @@ -11,6 +11,7 @@ pub struct Knowledge { pub opponent_map: OpponentMapKnowledge, pub map_size: u16, pub available_weapons: Vec, + pub shootable_weapons: Vec, pub charging_weapons: HashMap } @@ -21,6 +22,7 @@ impl Knowledge { opponent_map: OpponentMapKnowledge::new(map_size), map_size: map_size, available_weapons: Vec::new(), + shootable_weapons: Vec::new(), charging_weapons: HashMap::new() } } @@ -43,12 +45,14 @@ impl Knowledge { available_weapons.sort_by_key(|weapon| format!("{}",weapon)); available_weapons.dedup(); - new_knowledge.available_weapons = available_weapons.iter() + new_knowledge.available_weapons = available_weapons; + + new_knowledge.shootable_weapons = new_knowledge.available_weapons.iter() .filter(|weapon| weapon.energy_cost(state.map_size) <= energy) .cloned() .collect(); - new_knowledge.charging_weapons = available_weapons.iter() + new_knowledge.charging_weapons = new_knowledge.available_weapons.iter() .filter(|weapon| weapon.energy_cost(state.map_size) > energy) .map(|weapon| (weapon.clone(), weapon.single_shot_rounds_to_ready(energy, state.map_size))) .collect(); @@ -57,51 +61,13 @@ impl Knowledge { Action::PlaceShips(_) => { (vec!(), vec!(), vec!()) }, - Action::Shoot(Weapon::SingleShot, p) => { - Knowledge::to_hits_and_misses(vec!(Some(p)), &state) - }, - Action::Shoot(Weapon::DoubleShotVertical, p) => { - Knowledge::to_hits_and_misses(vec!( - p.move_point(Direction::North, 1, state.map_size), - p.move_point(Direction::South, 1, state.map_size) - ), &state) - }, - Action::Shoot(Weapon::DoubleShotHorizontal, p) => { - Knowledge::to_hits_and_misses(vec!( - p.move_point(Direction::East, 1, state.map_size), - p.move_point(Direction::West, 1, state.map_size) - ), &state) - }, - Action::Shoot(Weapon::CornerShot, p) => { - Knowledge::to_hits_and_misses(vec!( - p.move_point(Direction::NorthEast, 1, state.map_size), - p.move_point(Direction::SouthEast, 1, state.map_size), - p.move_point(Direction::NorthWest, 1, state.map_size), - p.move_point(Direction::SouthWest, 1, state.map_size), - ), &state) - }, - Action::Shoot(Weapon::CrossShotDiagonal, p) => { - Knowledge::to_hits_and_misses(vec!( - p.move_point(Direction::NorthEast, 1, state.map_size), - p.move_point(Direction::SouthEast, 1, state.map_size), - p.move_point(Direction::NorthWest, 1, state.map_size), - p.move_point(Direction::SouthWest, 1, state.map_size), - Some(p) - ), &state) - }, - Action::Shoot(Weapon::CrossShotHorizontal, p) => { - Knowledge::to_hits_and_misses(vec!( - p.move_point(Direction::North, 1, state.map_size), - p.move_point(Direction::East, 1, state.map_size), - p.move_point(Direction::South, 1, state.map_size), - p.move_point(Direction::West, 1, state.map_size), - Some(p) - ), &state) - }, - Action::Shoot(Weapon::SeekerMissle, p) => { Knowledge::seeker_hits_and_misses(p, &state) } + + Action::Shoot(w, p) => { + Knowledge::to_hits_and_misses(w.affected_cells(p, state.map_size), &state) + } }; let sunk_ships = new_knowledge.opponent_map.update_sunk_ships(&state); @@ -111,9 +77,7 @@ impl Knowledge { new_knowledge } - fn to_hits_and_misses(points: Vec>, state: &State) -> (Vec, Vec, Vec) { - let points = points.iter().filter_map(|&p| p).collect::>(); - + fn to_hits_and_misses(points: Vec, state: &State) -> (Vec, Vec, Vec) { let hits = points.iter().filter(|p| state.opponent_map.cells[p.x as usize][p.y as usize].damaged).cloned().collect(); let misses = points.iter().filter(|p| state.opponent_map.cells[p.x as usize][p.y as usize].missed).cloned().collect(); let unknown = points.iter().filter(|p| !state.opponent_map.cells[p.x as usize][p.y as usize].missed && !state.opponent_map.cells[p.x as usize][p.y as usize].damaged).cloned().collect(); @@ -157,7 +121,7 @@ impl Knowledge { //don't add more after a hit is found for ring in rings { if hits.is_empty() { - let (mut new_hits, mut new_misses, mut unknown) = Knowledge::to_hits_and_misses(ring, &state); + let (mut new_hits, mut new_misses, mut unknown) = Knowledge::to_hits_and_misses(ring.iter().filter_map(|&p| p).collect::>(), &state); misses.append(&mut new_misses); if !new_hits.is_empty() { hits.append(&mut new_hits); @@ -201,26 +165,64 @@ impl Knowledge { best_cells } - pub fn get_most_possibility_shots_on_lattice(&self) -> Vec { - let on_lattice = self.opponent_map.cells_on_lattice(self.lattice_size()); + pub fn get_best_seek_shots(&self) -> (Weapon, Vec) { let possible_placements = self.opponent_map.possible_placements(); + // let lattice = self.lattice_size(); //TODO use the lattice still? - let mut max_possibilities = 1; - let mut best_cells = Vec::new(); - - for cell in on_lattice { - let possibilities = possible_placements.iter() - .filter(|placement| placement.touches_point(cell)).count(); - if possibilities > max_possibilities { - max_possibilities = possibilities; - best_cells = vec!(cell); - } - else if possibilities == max_possibilities { - best_cells.push(cell); + let mut best_shots: HashMap, usize)> = HashMap::new(); + + for &weapon in self.available_weapons.iter() { + let mut current_best_score = 1; + let mut best_cells = Vec::new(); + + for target in self.opponent_map.flat_cell_position_list() { + let cells = if weapon == Weapon::SeekerMissle { + let full_range = weapon.affected_cells(target, self.map_size); + let has_hits = full_range.iter().any(|p| self.opponent_map.cells[p.x as usize][p.y as usize].hit); + if has_hits { + vec!() + } + else { + full_range + } + } + else { + weapon.affected_cells(target, self.map_size) + }; + + let possibilities = possible_placements.iter() + .filter(|placement| placement.touches_any_point(&cells)) + .count(); + + if possibilities > current_best_score { + current_best_score = possibilities; + best_cells = vec!(target); + } + else if possibilities == current_best_score { + best_cells.push(target); + } } + + best_shots.insert(weapon, (best_cells, current_best_score)); } - best_cells + let best_single: Option<(Weapon, (Vec, usize))> = + best_shots.get(&Weapon::SingleShot).map(|x| (Weapon::SingleShot, x.clone())); + + let best: (Weapon, (Vec, usize)) = + best_shots.iter() + .max_by_key(|&(_, &(_, score))| score) + .and_then(|(&weapon, x)| { + if self.shootable_weapons.contains(&weapon) { + Some((weapon, x.clone())) + } else { + best_single + } + }) + .unwrap_or((Weapon::SingleShot, (vec!(), 0))); + + + (best.0.clone(), (best.1).0) } fn lattice_size(&self) -> u16 { @@ -344,6 +346,12 @@ impl OpponentMapKnowledge { }).collect() } + fn flat_cell_position_list(&self) -> Vec { + self.cells.iter().flat_map(|x| { + x.iter().map(|y| y.position) + }).collect() + } + fn cells_on_lattice(&self, lattice_size: u16) -> Vec { self.cells.iter().flat_map(|x| { x.iter() diff --git a/src/ships.rs b/src/ships.rs index 104e986..422f24e 100644 --- a/src/ships.rs +++ b/src/ships.rs @@ -1,6 +1,8 @@ use std::fmt; use std::str; +use math::*; + #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] pub enum Weapon { SingleShot, @@ -61,6 +63,75 @@ impl Weapon { //weird plus is to make the integer rounding up instead of down (required_energy + energy_per_round - 1) / energy_per_round } + + pub fn affected_cells(&self, target: Point, map_size: u16) -> Vec { + use Weapon::*; + + let p = target; + match self { + &SingleShot => { + vec!(Some(p)) + }, + &DoubleShotVertical => { + vec!( + p.move_point(Direction::North, 1, map_size), + p.move_point(Direction::South, 1, map_size) + ) + }, + &DoubleShotHorizontal => { + vec!( + p.move_point(Direction::East, 1, map_size), + p.move_point(Direction::West, 1, map_size) + ) + }, + &CornerShot => { + vec!( + p.move_point(Direction::NorthEast, 1, map_size), + p.move_point(Direction::SouthEast, 1, map_size), + p.move_point(Direction::NorthWest, 1, map_size), + p.move_point(Direction::SouthWest, 1, map_size), + ) + }, + &CrossShotDiagonal => { + vec!( + p.move_point(Direction::NorthEast, 1, map_size), + p.move_point(Direction::SouthEast, 1, map_size), + p.move_point(Direction::NorthWest, 1, map_size), + p.move_point(Direction::SouthWest, 1, map_size), + Some(p) + ) + }, + &CrossShotHorizontal => { + vec!( + p.move_point(Direction::North, 1, map_size), + p.move_point(Direction::East, 1, map_size), + p.move_point(Direction::South, 1, map_size), + p.move_point(Direction::West, 1, map_size), + Some(p) + ) + }, + &SeekerMissle => { + vec!( + Some(p), + + p.move_point(Direction::North, 1, map_size), + p.move_point(Direction::East, 1, map_size), + p.move_point(Direction::South, 1, map_size), + p.move_point(Direction::West, 1, map_size), + + p.move_point(Direction::NorthEast, 1, map_size), + p.move_point(Direction::SouthEast, 1, map_size), + p.move_point(Direction::NorthWest, 1, map_size), + p.move_point(Direction::SouthWest, 1, map_size), + + p.move_point(Direction::North, 2, map_size), + p.move_point(Direction::East, 2, map_size), + p.move_point(Direction::South, 2, map_size), + p.move_point(Direction::West, 2, map_size) + ) + } + }.iter().filter_map(|&p| p).collect::>() + } } diff --git a/src/shooting.rs b/src/shooting.rs index bcdf783..e0358ee 100644 --- a/src/shooting.rs +++ b/src/shooting.rs @@ -7,33 +7,32 @@ use knowledge::*; use ships::*; pub fn shoot_smartly(knowledge: &Knowledge) -> Action { - let shot = if knowledge.has_unknown_hits() { + let (weapon, target) = if knowledge.has_unknown_hits() { destroy_shoot(&knowledge) } else { seek_shoot(&knowledge) }; - - //TODO shoot other weapons - Action::Shoot(Weapon::SingleShot, shot) + + Action::Shoot(weapon, target) } -fn seek_shoot(knowledge: &Knowledge) -> Point { - let possibilities = knowledge.get_most_possibility_shots_on_lattice(); +fn seek_shoot(knowledge: &Knowledge) -> (Weapon, Point) { + let (weapon, possibilities) = knowledge.get_best_seek_shots(); if possibilities.is_empty() { println!("All possible shots on the current lattice have been tried!"); - Point::new(0,0) + (Weapon::SingleShot, Point::new(0,0)) } else { let mut rng = rand::thread_rng(); let between = Range::new(0, possibilities.len()); let i = between.ind_sample(&mut rng); - possibilities[i as usize] + (weapon, possibilities[i as usize]) } } -fn destroy_shoot(knowledge: &Knowledge) -> Point { +fn destroy_shoot(knowledge: &Knowledge) -> (Weapon, Point) { let possibilities = knowledge.get_best_adjacent_shots(); if possibilities.is_empty() { seek_shoot(&knowledge) @@ -43,6 +42,6 @@ fn destroy_shoot(knowledge: &Knowledge) -> Point { let between = Range::new(0, possibilities.len()); let i = between.ind_sample(&mut rng); - possibilities[i as usize] + (Weapon::SingleShot, possibilities[i as usize]) } } -- cgit v1.2.3 From 0cc8c49bce23090099f564d56844691323c705aa Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Sat, 15 Jul 2017 19:38:18 +0200 Subject: Avoided counting hit cells as eliminating unknowns --- src/knowledge.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/knowledge.rs b/src/knowledge.rs index 4fe60da..f8d71eb 100644 --- a/src/knowledge.rs +++ b/src/knowledge.rs @@ -188,6 +188,10 @@ impl Knowledge { } else { weapon.affected_cells(target, self.map_size) + .iter() + .filter(|p| !self.opponent_map.cells[p.x as usize][p.y as usize].hit) + .cloned() + .collect() }; let possibilities = possible_placements.iter() -- cgit v1.2.3 From 9255b4040b192aca63c1dd374e8ae5171a73ecb5 Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Sat, 15 Jul 2017 19:52:25 +0200 Subject: Updated shot ordering to choose cheeper one if scores are equal --- src/knowledge.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/knowledge.rs b/src/knowledge.rs index f8d71eb..1923f41 100644 --- a/src/knowledge.rs +++ b/src/knowledge.rs @@ -4,6 +4,7 @@ use state::*; use math::*; use std::collections::HashMap; +use std::cmp::Ordering; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct Knowledge { @@ -215,7 +216,11 @@ impl Knowledge { let best: (Weapon, (Vec, usize)) = best_shots.iter() - .max_by_key(|&(_, &(_, score))| score) + .max_by(|&(weapon_a, &(_, score_a)), &(weapon_b, &(_, score_b))| { + let score = score_a.cmp(&score_b); + let cost = weapon_a.energy_cost(self.map_size).cmp(&weapon_b.energy_cost(self.map_size)); + if score == Ordering::Equal { cost } else { score } + }) .and_then(|(&weapon, x)| { if self.shootable_weapons.contains(&weapon) { Some((weapon, x.clone())) -- cgit v1.2.3 From 237c67f87eaab0bab51d83d3dbf7a9632db75b50 Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Sat, 22 Jul 2017 10:17:57 +0200 Subject: Added special case for a point that can be known to be a hit while seeking Shooting here first will eliminate a ship immediately that might otherwise be dumbly left until the end of the game, where a spot that can only be one ship shows up on the overall probability density graph. It also does meaningful shots while recharging specials shots to speed up the random part of searching. --- src/knowledge.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/knowledge.rs b/src/knowledge.rs index 1923f41..4a66e8b 100644 --- a/src/knowledge.rs +++ b/src/knowledge.rs @@ -170,6 +170,11 @@ impl Knowledge { let possible_placements = self.opponent_map.possible_placements(); // let lattice = self.lattice_size(); //TODO use the lattice still? + let guaranteed_hits = self.get_points_that_touch_all_possibilities_on_unsunk_ship(); + if !guaranteed_hits.is_empty() { + return (Weapon::SingleShot, guaranteed_hits); + } + let mut best_shots: HashMap, usize)> = HashMap::new(); for &weapon in self.available_weapons.iter() { @@ -234,6 +239,16 @@ impl Knowledge { (best.0.clone(), (best.1).0) } + fn get_points_that_touch_all_possibilities_on_unsunk_ship(&self) -> Vec { + self.opponent_map.flat_cell_position_list().iter().cloned().filter(|&point| { + self.opponent_map.ships.values() + .any(|ref ship| !ship.destroyed && + ship.possible_placements.iter().all(|placement| { + placement.touches_point(point) + })) + }).collect() + } + fn lattice_size(&self) -> u16 { let any_long_ships = self.opponent_map.ships.iter() .any(|(ship, knowledge)| ship.length() >= 4 && !knowledge.destroyed); -- cgit v1.2.3 From a866bde485c7d8bc82820f2def70af7b6c70a066 Mon Sep 17 00:00:00 2001 From: Justin Wernick Date: Tue, 19 Apr 2022 21:25:36 +0200 Subject: Refile for merging repos --- .gitignore | 3 - 2017-battleships/.gitignore | 3 + 2017-battleships/Cargo.lock | 122 ++++ 2017-battleships/Cargo.toml | 11 + 2017-battleships/bot.json | 8 + 2017-battleships/notes.org | 1447 +++++++++++++++++++++++++++++++++++++ 2017-battleships/readme.txt | 20 + 2017-battleships/src/actions.rs | 90 +++ 2017-battleships/src/files.rs | 57 ++ 2017-battleships/src/knowledge.rs | 504 +++++++++++++ 2017-battleships/src/lib.rs | 67 ++ 2017-battleships/src/main.rs | 19 + 2017-battleships/src/math.rs | 338 +++++++++ 2017-battleships/src/placement.rs | 23 + 2017-battleships/src/ships.rs | 216 ++++++ 2017-battleships/src/shooting.rs | 47 ++ 2017-battleships/src/state.rs | 146 ++++ Cargo.lock | 122 ---- Cargo.toml | 11 - bot.json | 8 - notes.org | 1447 ------------------------------------- readme.txt | 20 - src/actions.rs | 90 --- src/files.rs | 57 -- src/knowledge.rs | 504 ------------- src/lib.rs | 67 -- src/main.rs | 19 - src/math.rs | 338 --------- src/placement.rs | 23 - src/ships.rs | 216 ------ src/shooting.rs | 47 -- src/state.rs | 146 ---- 32 files changed, 3118 insertions(+), 3118 deletions(-) delete mode 100644 .gitignore create mode 100644 2017-battleships/.gitignore create mode 100644 2017-battleships/Cargo.lock create mode 100644 2017-battleships/Cargo.toml create mode 100644 2017-battleships/bot.json create mode 100644 2017-battleships/notes.org create mode 100644 2017-battleships/readme.txt create mode 100644 2017-battleships/src/actions.rs create mode 100644 2017-battleships/src/files.rs create mode 100644 2017-battleships/src/knowledge.rs create mode 100644 2017-battleships/src/lib.rs create mode 100644 2017-battleships/src/main.rs create mode 100644 2017-battleships/src/math.rs create mode 100644 2017-battleships/src/placement.rs create mode 100644 2017-battleships/src/ships.rs create mode 100644 2017-battleships/src/shooting.rs create mode 100644 2017-battleships/src/state.rs delete mode 100644 Cargo.lock delete mode 100644 Cargo.toml delete mode 100644 bot.json delete mode 100644 notes.org delete mode 100644 readme.txt delete mode 100644 src/actions.rs delete mode 100644 src/files.rs delete mode 100644 src/knowledge.rs delete mode 100644 src/lib.rs delete mode 100644 src/main.rs delete mode 100644 src/math.rs delete mode 100644 src/placement.rs delete mode 100644 src/ships.rs delete mode 100644 src/shooting.rs delete mode 100644 src/state.rs diff --git a/.gitignore b/.gitignore deleted file mode 100644 index a41bb2d..0000000 --- a/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/target - -/knowledge-state.json diff --git a/2017-battleships/.gitignore b/2017-battleships/.gitignore new file mode 100644 index 0000000..a41bb2d --- /dev/null +++ b/2017-battleships/.gitignore @@ -0,0 +1,3 @@ +/target + +/knowledge-state.json diff --git a/2017-battleships/Cargo.lock b/2017-battleships/Cargo.lock new file mode 100644 index 0000000..96ea518 --- /dev/null +++ b/2017-battleships/Cargo.lock @@ -0,0 +1,122 @@ +[root] +name = "worthebot_battleships" +version = "0.1.0" +dependencies = [ + "json 0.11.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "dtoa" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "itoa" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "json" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num-traits" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "quote" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde_derive" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive_internals 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive_internals" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", + "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_json" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synom" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-xid" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90" +"checksum itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2f404fbc66fd9aac13e998248505e7ecb2ad8e44ab6388684c5fb11c6c251c" +"checksum json 0.11.6 (registry+https://github.com/rust-lang/crates.io-index)" = "27600e8bb3b71bcc6213fb36b66b8dce60adc17a624257687ef5d1d4facafba7" +"checksum libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)" = "babb8281da88cba992fa1f4ddec7d63ed96280a1a53ec9b919fd37b53d71e502" +"checksum num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "e1cbfa3781f3fe73dc05321bed52a06d2d491eaa764c52335cf4399f046ece99" +"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" +"checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d" +"checksum serde 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "991ef6be409a3b7a46cb9ee701d86156ce851825c65dbee7f16dbd5c4e7e2d47" +"checksum serde_derive 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9fd81eef9f0b4ec341b11095335b6a4b28ed85581b12dd27585dee1529df35e0" +"checksum serde_derive_internals 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "021c338d22c7e30f957a6ab7e388cb6098499dda9fd4ba1661ee074ca7a180d1" +"checksum serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "48b04779552e92037212c3615370f6bd57a40ebba7f20e554ff9f55e41a69a7b" +"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" +"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" diff --git a/2017-battleships/Cargo.toml b/2017-battleships/Cargo.toml new file mode 100644 index 0000000..cdebbe8 --- /dev/null +++ b/2017-battleships/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "worthebot_battleships" +version = "0.1.0" +authors = ["Justin Worthe "] + +[dependencies] +rand = "0.3" +json = "0.11.6" +serde = "1.0.4" +serde_json = "1.0.2" +serde_derive = "1.0.4" \ No newline at end of file diff --git a/2017-battleships/bot.json b/2017-battleships/bot.json new file mode 100644 index 0000000..6245b49 --- /dev/null +++ b/2017-battleships/bot.json @@ -0,0 +1,8 @@ +{ + "Author":"Justin Worthe", + "Email":"justin.worthe@gmail.com", + "NickName" :"Admiral Worthebot", + "BotType": 8, + "ProjectLocation" : "", + "RunFile" : "target\\release\\worthebot_battleships.exe" +} diff --git a/2017-battleships/notes.org b/2017-battleships/notes.org new file mode 100644 index 0000000..ff3e319 --- /dev/null +++ b/2017-battleships/notes.org @@ -0,0 +1,1447 @@ +* State.json + +#+BEGIN_EXAMPLE +State = {PlayerMap, OpponentMap, GameVersion=String, GameLevel=u16, Round=u16, MapDimension=u16, Phase=u16, Player1Map=null, Player2Map=null} + +PlayerMap = {Cells=[PlayerCell], Owner, MapWidth=u16, MapHeight=u16} + +PlayerCell = {Occupied=bool, Hit=bool, X=u16, Y=u16} + +Owner = {FailedFirstPhaseCommands=u16, Name=String, Ships=[PlayerShip], Points=u16, Killed=bool, IsWinner=bool, ShotsFired=u16, ShotsHit=u16, ShipsRemaining=u16, Key=String} + +PlayerShip = {Cells=[PlayerCell], Destroyed=bool, Placed=bool, ShipType=String, Weapons=[PlayerWeapon], } + +PlayerWeapon = {WeaponType=String} + +OpponentMap = {Ships=[OpponentShip], Alive=bool, Points=u16, Name=String, Cells=[OpponentCell]} + +OpponentShip = {Destroyed=bool, ShipType=String} + +OpponentCell = {Damaged=bool, Missed=bool, X=u16, Y=u16} + +#+END_EXAMPLE + +* State.json example + +#+BEGIN_SRC json +{ + "PlayerMap": { + "Cells": [ + { + "Occupied": false, + "Hit": true, + "X": 0, + "Y": 0 + }, + { + "Occupied": false, + "Hit": false, + "X": 0, + "Y": 1 + }, + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 2 + }, + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 3 + }, + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 4 + }, + { + "Occupied": false, + "Hit": false, + "X": 0, + "Y": 5 + }, + { + "Occupied": true, + "Hit": true, + "X": 0, + "Y": 6 + }, + { + "Occupied": true, + "Hit": true, + + "X": 0, + "Y": 7 + }, + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 8 + }, + { + "Occupied": false, + "Hit": true, + "X": 0, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 1, + "Y": 0 + }, + { + "Occupied": false, + "Hit": false, + "X": 1, + "Y": 1 + }, + { + "Occupied": false, + "Hit": false, + "X": 1, + "Y": 2 + }, + { + "Occupied": false, + "Hit": true, + "X": 1, + "Y": 3 + }, + { + "Occupied": false, + "Hit": false, + "X": 1, + "Y": 4 + }, + { + "Occupied": false, + "Hit": false, + "X": 1, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 1, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 1, + "Y": 7 + }, + { + "Occupied": true, + "Hit": false, + "X": 1, + "Y": 8 + }, + { + "Occupied": false, + "Hit": true, + "X": 1, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 2, + "Y": 0 + }, + { + "Occupied": false, + "Hit": false, + "X": 2, + "Y": 1 + }, + { + "Occupied": false, + "Hit": false, + "X": 2, + "Y": 2 + }, + { + "Occupied": false, + "Hit": false, + "X": 2, + "Y": 3 + }, + { + "Occupied": false, + "Hit": true, + "X": 2, + "Y": 4 + }, + { + "Occupied": false, + "Hit": true, + "X": 2, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 2, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 2, + "Y": 7 + }, + { + "Occupied": true, + "Hit": true, + "X": 2, + "Y": 8 + }, + { + "Occupied": false, + "Hit": false, + "X": 2, + "Y": 9 + }, + { + "Occupied": false, + "Hit": true, + "X": 3, + "Y": 0 + }, + { + "Occupied": false, + "Hit": true, + "X": 3, + "Y": 1 + }, + { + "Occupied": false, + "Hit": true, + "X": 3, + "Y": 2 + }, + { + "Occupied": false, + "Hit": false, + "X": 3, + "Y": 3 + }, + { + "Occupied": false, + "Hit": false, + "X": 3, + "Y": 4 + }, + { + "Occupied": false, + "Hit": false, + "X": 3, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 3, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 3, + "Y": 7 + }, + { + "Occupied": true, + "Hit": false, + "X": 3, + "Y": 8 + }, + { + "Occupied": false, + "Hit": false, + "X": 3, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 4, + "Y": 0 + }, + { + "Occupied": false, + "Hit": true, + "X": 4, + "Y": 1 + }, + { + "Occupied": false, + "Hit": true, + "X": 4, + "Y": 2 + }, + { + "Occupied": false, + "Hit": false, + "X": 4, + "Y": 3 + }, + { + "Occupied": false, + "Hit": false, + "X": 4, + "Y": 4 + }, + { + "Occupied": false, + "Hit": false, + "X": 4, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 4, + "Y": 6 + }, + { + "Occupied": false, + "Hit": true, + "X": 4, + "Y": 7 + }, + { + "Occupied": true, + "Hit": false, + "X": 4, + "Y": 8 + }, + { + "Occupied": false, + "Hit": true, + "X": 4, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 0 + }, + { + "Occupied": true, + "Hit": true, + "X": 5, + "Y": 1 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 2 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 3 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 4 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 7 + }, + { + "Occupied": true, + "Hit": false, + "X": 5, + "Y": 8 + }, + { + "Occupied": false, + "Hit": false, + "X": 5, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 6, + "Y": 0 + }, + { + "Occupied": true, + "Hit": false, + "X": 6, + "Y": 1 + }, + { + "Occupied": false, + "Hit": false, + "X": 6, + "Y": 2 + }, + { + "Occupied": false, + "Hit": true, + "X": 6, + "Y": 3 + }, + { + "Occupied": false, + "Hit": false, + "X": 6, + "Y": 4 + }, + { + "Occupied": false, + "Hit": true, + "X": 6, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 6, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 6, + "Y": 7 + }, + { + "Occupied": false, + "Hit": true, + "X": 6, + "Y": 8 + }, + { + "Occupied": false, + "Hit": false, + "X": 6, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 7, + "Y": 0 + }, + { + "Occupied": true, + "Hit": true, + "X": 7, + "Y": 1 + }, + { + "Occupied": false, + "Hit": false, + "X": 7, + "Y": 2 + }, + { + "Occupied": false, + "Hit": false, + "X": 7, + "Y": 3 + }, + { + "Occupied": false, + "Hit": false, + "X": 7, + "Y": 4 + }, + { + "Occupied": true, + "Hit": false, + "X": 7, + "Y": 5 + }, + { + "Occupied": true, + "Hit": false, + "X": 7, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 7, + "Y": 7 + }, + { + "Occupied": false, + "Hit": false, + "X": 7, + "Y": 8 + }, + { + "Occupied": false, + "Hit": false, + "X": 7, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 8, + "Y": 0 + }, + { + "Occupied": true, + "Hit": false, + "X": 8, + "Y": 1 + }, + { + "Occupied": false, + "Hit": false, + "X": 8, + "Y": 2 + }, + { + "Occupied": false, + "Hit": true, + "X": 8, + "Y": 3 + }, + { + "Occupied": false, + "Hit": true, + "X": 8, + "Y": 4 + }, + { + "Occupied": false, + "Hit": false, + "X": 8, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 8, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 8, + "Y": 7 + }, + { + "Occupied": false, + "Hit": false, + "X": 8, + "Y": 8 + }, + { + "Occupied": false, + "Hit": true, + "X": 8, + "Y": 9 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 0 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 1 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 2 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 3 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 4 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 5 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 6 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 7 + }, + { + "Occupied": false, + "Hit": true, + "X": 9, + "Y": 8 + }, + { + "Occupied": false, + "Hit": false, + "X": 9, + "Y": 9 + } + ], + "Owner": { + "FailedFirstPhaseCommands": 0, + "Name": "Admiral Worthebot", + "Ships": [ + { + "Cells": [ + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 2 + }, + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 3 + }, + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 4 + } + ], + "Destroyed": false, + "Placed": true, + "ShipType": "Submarine", + "Weapons": [ + { + "WeaponType": "SingleShot" + } + ] + }, + { + "Cells": [ + { + "Occupied": true, + "Hit": false, + "X": 7, + "Y": 6 + }, + { + "Occupied": true, + "Hit": false, + "X": 7, + "Y": 5 + } + ], + "Destroyed": false, + "Placed": true, + "ShipType": "Destroyer", + "Weapons": [ + { + "WeaponType": "SingleShot" + } + ] + }, + { + "Cells": [ + { + "Occupied": true, + "Hit": true, + "X": 5, + "Y": 1 + }, + { + "Occupied": true, + "Hit": false, + "X": 6, + "Y": 1 + }, + { + "Occupied": true, + "Hit": true, + "X": 7, + "Y": 1 + }, + { + "Occupied": true, + "Hit": false, + "X": 8, + "Y": 1 + } + ], + "Destroyed": false, + "Placed": true, + "ShipType": "Battleship", + "Weapons": [ + { + "WeaponType": "SingleShot" + } + ] + }, + { + "Cells": [ + { + "Occupied": true, + "Hit": false, + "X": 5, + "Y": 8 + }, + { + "Occupied": true, + "Hit": false, + "X": 4, + "Y": 8 + }, + { + "Occupied": true, + "Hit": false, + "X": 3, + "Y": 8 + }, + { + "Occupied": true, + "Hit": true, + "X": 2, + "Y": 8 + }, + { + "Occupied": true, + "Hit": false, + "X": 1, + "Y": 8 + } + ], + "Destroyed": false, + "Placed": true, + "ShipType": "Carrier", + "Weapons": [ + { + "WeaponType": "SingleShot" + } + ] + }, + { + "Cells": [ + { + "Occupied": true, + "Hit": true, + "X": 0, + "Y": 6 + }, + { + "Occupied": true, + "Hit": true, + "X": 0, + "Y": 7 + }, + { + "Occupied": true, + "Hit": false, + "X": 0, + "Y": 8 + } + ], + "Destroyed": false, + "Placed": true, + "ShipType": "Cruiser", + "Weapons": [ + { + "WeaponType": "SingleShot" + } + ] + } + ], + "Points": 280, + "Killed": false, + "IsWinner": false, + "ShotsFired": 86, + "ShotsHit": 16, + "ShipsRemaining": 5, + "Key": "B" + }, + "MapWidth": 10, + "MapHeight": 10 + }, + "OpponentMap": { + "Ships": [ + { + "Destroyed": false, + "ShipType": "Submarine" + }, + { + "Destroyed": true, + "ShipType": "Destroyer" + }, + { + "Destroyed": true, + "ShipType": "Battleship" + }, + { + "Destroyed": true, + "ShipType": "Carrier" + }, + { + "Destroyed": true, + "ShipType": "Cruiser" + } + ], + "Alive": true, + "Points": 50, + "Name": "John", + "Cells": [ + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 0 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 1 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 2 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 4 + }, + { + "Damaged": false, + "Missed": false, + "X": 0, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 7 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 8 + }, + { + "Damaged": false, + "Missed": true, + "X": 0, + "Y": 9 + }, + { + "Damaged": true, + "Missed": false, + "X": 1, + "Y": 0 + }, + { + "Damaged": true, + "Missed": false, + "X": 1, + "Y": 1 + }, + { + "Damaged": true, + "Missed": false, + "X": 1, + "Y": 2 + }, + { + "Damaged": true, + "Missed": false, + "X": 1, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 1, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 1, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 1, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 1, + "Y": 7 + }, + { + "Damaged": true, + "Missed": false, + "X": 1, + "Y": 8 + }, + { + "Damaged": false, + "Missed": true, + "X": 1, + "Y": 9 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 0 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 1 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 2 + }, + { + "Damaged": false, + "Missed": false, + "X": 2, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 7 + }, + { + "Damaged": false, + "Missed": false, + "X": 2, + "Y": 8 + }, + { + "Damaged": false, + "Missed": true, + "X": 2, + "Y": 9 + }, + { + "Damaged": false, + "Missed": false, + "X": 3, + "Y": 0 + }, + { + "Damaged": true, + "Missed": false, + "X": 3, + "Y": 1 + }, + { + "Damaged": false, + "Missed": false, + "X": 3, + "Y": 2 + }, + { + "Damaged": false, + "Missed": false, + "X": 3, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 3, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 3, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 3, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 3, + "Y": 7 + }, + { + "Damaged": true, + "Missed": false, + "X": 3, + "Y": 8 + }, + { + "Damaged": false, + "Missed": false, + "X": 3, + "Y": 9 + }, + { + "Damaged": false, + "Missed": false, + "X": 4, + "Y": 0 + }, + { + "Damaged": true, + "Missed": false, + "X": 4, + "Y": 1 + }, + { + "Damaged": true, + "Missed": false, + "X": 4, + "Y": 2 + }, + { + "Damaged": true, + "Missed": false, + "X": 4, + "Y": 3 + }, + { + "Damaged": true, + "Missed": false, + "X": 4, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 4, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 4, + "Y": 6 + }, + { + "Damaged": false, + "Missed": false, + "X": 4, + "Y": 7 + }, + { + "Damaged": false, + "Missed": true, + "X": 4, + "Y": 8 + }, + { + "Damaged": false, + "Missed": true, + "X": 4, + "Y": 9 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 0 + }, + { + "Damaged": true, + "Missed": false, + "X": 5, + "Y": 1 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 2 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 7 + }, + { + "Damaged": false, + "Missed": true, + "X": 5, + "Y": 8 + }, + { + "Damaged": false, + "Missed": false, + "X": 5, + "Y": 9 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 0 + }, + { + "Damaged": true, + "Missed": false, + "X": 6, + "Y": 1 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 2 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 7 + }, + { + "Damaged": false, + "Missed": true, + "X": 6, + "Y": 8 + }, + { + "Damaged": false, + "Missed": false, + "X": 6, + "Y": 9 + }, + { + "Damaged": false, + "Missed": false, + "X": 7, + "Y": 0 + }, + { + "Damaged": true, + "Missed": false, + "X": 7, + "Y": 1 + }, + { + "Damaged": false, + "Missed": true, + "X": 7, + "Y": 2 + }, + { + "Damaged": true, + "Missed": false, + "X": 7, + "Y": 3 + }, + { + "Damaged": true, + "Missed": false, + "X": 7, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 7, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 7, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 7, + "Y": 7 + }, + { + "Damaged": false, + "Missed": true, + "X": 7, + "Y": 8 + }, + { + "Damaged": false, + "Missed": true, + "X": 7, + "Y": 9 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 0 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 1 + }, + { + "Damaged": false, + "Missed": false, + "X": 8, + "Y": 2 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 7 + }, + { + "Damaged": false, + "Missed": false, + "X": 8, + "Y": 8 + }, + { + "Damaged": false, + "Missed": true, + "X": 8, + "Y": 9 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 0 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 1 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 2 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 3 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 4 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 5 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 6 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 7 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 8 + }, + { + "Damaged": false, + "Missed": true, + "X": 9, + "Y": 9 + } + ] + }, + "GameVersion": "1.0.0", + "GameLevel": 1, + "Round": 87, + "MapDimension": 10, + "Phase": 2, + "Player1Map": null, + "Player2Map": null +} +#+END_SRC diff --git a/2017-battleships/readme.txt b/2017-battleships/readme.txt new file mode 100644 index 0000000..ffffa2f --- /dev/null +++ b/2017-battleships/readme.txt @@ -0,0 +1,20 @@ +* Admiral Worthebot + +** Compilation Instructions + +As per the Rust sample bot. Install the Rust build toolchain from https://www.rust-lang.org/en-US/install.html, then from the root directory of the project run + +cargo build --release + +** Project Structure + +Cargo.toml - Cargo project config, including project dependencies +src/ - Soure code directory +src/main.rs - Command line entrypoint (main function) and command line argument parsing +src/lib.rs - Programs public interface (as used by main.rs and any integration tests) + +** Strategy + +- Track all possible ways that an opponent may have placed their ships +- After every move, deduce which possibilities are now impossible +- Shoot in an attempt to (possibly) eliminate as many possibilities as possible diff --git a/2017-battleships/src/actions.rs b/2017-battleships/src/actions.rs new file mode 100644 index 0000000..cf0059a --- /dev/null +++ b/2017-battleships/src/actions.rs @@ -0,0 +1,90 @@ +use math::*; +use ships::*; + +use std::fmt; + +use std::collections::HashSet; + +#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] +pub enum Action { + PlaceShips(Vec), + Shoot(Weapon, Point) +} + +impl fmt::Display for Action { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + &Action::Shoot(w, p) => writeln!(f, "{},{},{}", w, p.x, p.y), + &Action::PlaceShips(ref ships) => ships.iter().map(|ref ship| { + writeln!(f, "{} {} {} {}", ship.ship_type, ship.point.x, ship.point.y, ship.direction) + }).fold(Ok(()), |acc, next| acc.and(next)) + } + } +} + +#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] +pub struct ShipPlacement { + ship_type: Ship, + point: Point, + direction: Direction +} + +impl ShipPlacement { + pub fn new(ship_type: Ship, point: Point, direction: Direction) -> ShipPlacement { + ShipPlacement { + ship_type: ship_type, + point: point, + direction: direction + } + } + + pub fn valid(&self, map_size: u16) -> bool { + let start = self.point; + let end = start.move_point(self.direction, self.ship_type.length() as i32, map_size); + start.x < map_size && start.y < map_size && end.is_some() + } + pub fn valid_placements(placements: &Vec, map_size: u16) -> bool { + let mut occupied = HashSet::new(); + + let individuals_valid = placements.iter().all(|p| p.valid(map_size)); + + let mut no_overlaps = true; + for placement in placements { + for i in 0..placement.ship_type.length() as i32 { + match placement.point.move_point(placement.direction, i, map_size) { + Some(block) => { + no_overlaps = no_overlaps && !occupied.contains(&block); + occupied.insert(block); + }, + None => { + //invalid case here is handled above + } + } + } + + //block out the area around the current ship to prevent adjacent ships + for i in 0..placement.ship_type.length() as i32 { + match placement.point.move_point(placement.direction, i, map_size) { + Some(current_block) => { + if let Some(p) = current_block.move_point(Direction::North, 1, map_size) { + occupied.insert(p); + } + if let Some(p) = current_block.move_point(Direction::South, 1, map_size) { + occupied.insert(p); + } + if let Some(p) = current_block.move_point(Direction::East, 1, map_size) { + occupied.insert(p); + } + if let Some(p) = current_block.move_point(Direction::West, 1, map_size) { + occupied.insert(p); + } + }, + None => { + //invalid case here is handled above + } + } + } + } + individuals_valid && no_overlaps + } +} diff --git a/2017-battleships/src/files.rs b/2017-battleships/src/files.rs new file mode 100644 index 0000000..0810a4e --- /dev/null +++ b/2017-battleships/src/files.rs @@ -0,0 +1,57 @@ +use json; +use serde_json; + +use std::io::prelude::*; +use std::fs::File; +use std::path::PathBuf; + +use actions::*; +use knowledge::*; + +const STATE_FILE: &'static str = "state.json"; + +const COMMAND_FILE: &'static str = "command.txt"; +const PLACE_FILE: &'static str = "place.txt"; + +const KNOWLEDGE_FILE: &'static str = "knowledge-state.json"; + + +pub fn read_file(working_dir: &PathBuf) -> Result { + let state_path = working_dir.join(STATE_FILE); + let mut file = File::open(state_path.as_path()).map_err(|e| e.to_string())?; + let mut content = String::new(); + file.read_to_string(&mut content).map_err(|e| e.to_string())?; + json::parse(content.as_ref()).map_err(|e| e.to_string()) +} + +pub fn write_action(working_dir: &PathBuf, is_place_phase: bool, action: Action) -> Result<(), String> { + let filename = if is_place_phase { + PLACE_FILE + } + else { + COMMAND_FILE + }; + + let full_filename = working_dir.join(filename); + let mut file = File::create(full_filename.as_path()).map_err(|e| e.to_string())?; + write!(file, "{}", action).map_err(|e| e.to_string())?; + + println!("Making move: {}", action); + + Ok(()) +} + +pub fn read_knowledge() -> Result { + let mut file = File::open(KNOWLEDGE_FILE).map_err(|e| e.to_string())?; + let mut content = String::new(); + file.read_to_string(&mut content).map_err(|e| e.to_string())?; + serde_json::from_str(content.as_ref()).map_err(|e| e.to_string()) +} + +pub fn write_knowledge(knowledge: &Knowledge) -> Result<(), String> { + let json = serde_json::to_string(knowledge).map_err(|e| e.to_string())?; + let mut file = File::create(KNOWLEDGE_FILE).map_err(|e| e.to_string())?; + write!(file, "{}", json).map_err(|e| e.to_string())?; + + Ok(()) +} diff --git a/2017-battleships/src/knowledge.rs b/2017-battleships/src/knowledge.rs new file mode 100644 index 0000000..4a66e8b --- /dev/null +++ b/2017-battleships/src/knowledge.rs @@ -0,0 +1,504 @@ +use actions::*; +use ships::*; +use state::*; +use math::*; + +use std::collections::HashMap; +use std::cmp::Ordering; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct Knowledge { + pub last_action: Action, + pub opponent_map: OpponentMapKnowledge, + pub map_size: u16, + pub available_weapons: Vec, + pub shootable_weapons: Vec, + pub charging_weapons: HashMap +} + +impl Knowledge { + pub fn new(map_size: u16, action: Action) -> Knowledge { + Knowledge { + last_action: action, + opponent_map: OpponentMapKnowledge::new(map_size), + map_size: map_size, + available_weapons: Vec::new(), + shootable_weapons: Vec::new(), + charging_weapons: HashMap::new() + } + } + + pub fn with_action(&self, action: Action) -> Knowledge { + Knowledge { + last_action: action, + ..self.clone() + } + } + + pub fn resolve_last_action(&self, state: &State) -> Knowledge { + let mut new_knowledge = self.clone(); + + let energy = state.player_map.energy; + let mut available_weapons: Vec<_> = state.player_map.ships.iter() + .filter(|&(_, ship_data)| !ship_data.destroyed) + .flat_map(|(ship, _)| ship.weapons()) + .collect(); + + available_weapons.sort_by_key(|weapon| format!("{}",weapon)); + available_weapons.dedup(); + new_knowledge.available_weapons = available_weapons; + + new_knowledge.shootable_weapons = new_knowledge.available_weapons.iter() + .filter(|weapon| weapon.energy_cost(state.map_size) <= energy) + .cloned() + .collect(); + + new_knowledge.charging_weapons = new_knowledge.available_weapons.iter() + .filter(|weapon| weapon.energy_cost(state.map_size) > energy) + .map(|weapon| (weapon.clone(), weapon.single_shot_rounds_to_ready(energy, state.map_size))) + .collect(); + + let (hits, misses, _) = match self.last_action { + Action::PlaceShips(_) => { + (vec!(), vec!(), vec!()) + }, + Action::Shoot(Weapon::SeekerMissle, p) => { + Knowledge::seeker_hits_and_misses(p, &state) + } + + Action::Shoot(w, p) => { + Knowledge::to_hits_and_misses(w.affected_cells(p, state.map_size), &state) + } + }; + + let sunk_ships = new_knowledge.opponent_map.update_sunk_ships(&state); + + new_knowledge.opponent_map.update_from_shot(hits, misses, sunk_ships); + + new_knowledge + } + + fn to_hits_and_misses(points: Vec, state: &State) -> (Vec, Vec, Vec) { + let hits = points.iter().filter(|p| state.opponent_map.cells[p.x as usize][p.y as usize].damaged).cloned().collect(); + let misses = points.iter().filter(|p| state.opponent_map.cells[p.x as usize][p.y as usize].missed).cloned().collect(); + let unknown = points.iter().filter(|p| !state.opponent_map.cells[p.x as usize][p.y as usize].missed && !state.opponent_map.cells[p.x as usize][p.y as usize].damaged).cloned().collect(); + + (hits, misses, unknown) + } + + fn seeker_hits_and_misses(p: Point, state: &State) -> (Vec, Vec, Vec) { + let mut misses: Vec = Vec::new(); + let mut hits: Vec = Vec::new(); + + let rings = vec!( + vec!( + //0 + Some(p) + ), + vec!( + //1 + p.move_point(Direction::North, 1, state.map_size), + p.move_point(Direction::East, 1, state.map_size), + p.move_point(Direction::South, 1, state.map_size), + p.move_point(Direction::West, 1, state.map_size), + ), + vec!( + //1.44 + p.move_point(Direction::NorthEast, 1, state.map_size), + p.move_point(Direction::SouthEast, 1, state.map_size), + p.move_point(Direction::NorthWest, 1, state.map_size), + p.move_point(Direction::SouthWest, 1, state.map_size) + ), + vec!( + //2 + p.move_point(Direction::North, 2, state.map_size), + p.move_point(Direction::East, 2, state.map_size), + p.move_point(Direction::South, 2, state.map_size), + p.move_point(Direction::West, 2, state.map_size), + ) + ); + + //start in the center. Add rings, until I find a hit + //don't add more after a hit is found + for ring in rings { + if hits.is_empty() { + let (mut new_hits, mut new_misses, mut unknown) = Knowledge::to_hits_and_misses(ring.iter().filter_map(|&p| p).collect::>(), &state); + misses.append(&mut new_misses); + if !new_hits.is_empty() { + hits.append(&mut new_hits); + } else { + misses.append(&mut unknown); + } + } + } + + (hits, misses, vec!()) + } + + pub fn has_unknown_hits(&self) -> bool { + self.opponent_map.cells.iter().fold(false, |acc, x| { + x.iter().fold(acc, |acc, y| acc || y.unknown_hit()) + }) + } + + pub fn get_best_adjacent_shots(&self) -> Vec { + let unknown_hits = self.opponent_map.cells_with_unknown_hits(); + let adjacent_cells = self.opponent_map.adjacent_unshot_cells(&unknown_hits); + + let possible_placements = self.opponent_map.possible_placements(); + + let mut max_score = 1; + let mut best_cells = Vec::new(); + + for placement in possible_placements { + for &cell in &adjacent_cells { + let score = placement.count_hit_cells(cell, &unknown_hits); + if score > max_score { + max_score = score; + best_cells = vec!(cell); + } + else if score == max_score { + best_cells.push(cell); + } + } + } + + best_cells + } + + pub fn get_best_seek_shots(&self) -> (Weapon, Vec) { + let possible_placements = self.opponent_map.possible_placements(); + // let lattice = self.lattice_size(); //TODO use the lattice still? + + let guaranteed_hits = self.get_points_that_touch_all_possibilities_on_unsunk_ship(); + if !guaranteed_hits.is_empty() { + return (Weapon::SingleShot, guaranteed_hits); + } + + let mut best_shots: HashMap, usize)> = HashMap::new(); + + for &weapon in self.available_weapons.iter() { + let mut current_best_score = 1; + let mut best_cells = Vec::new(); + + for target in self.opponent_map.flat_cell_position_list() { + let cells = if weapon == Weapon::SeekerMissle { + let full_range = weapon.affected_cells(target, self.map_size); + let has_hits = full_range.iter().any(|p| self.opponent_map.cells[p.x as usize][p.y as usize].hit); + if has_hits { + vec!() + } + else { + full_range + } + } + else { + weapon.affected_cells(target, self.map_size) + .iter() + .filter(|p| !self.opponent_map.cells[p.x as usize][p.y as usize].hit) + .cloned() + .collect() + }; + + let possibilities = possible_placements.iter() + .filter(|placement| placement.touches_any_point(&cells)) + .count(); + + if possibilities > current_best_score { + current_best_score = possibilities; + best_cells = vec!(target); + } + else if possibilities == current_best_score { + best_cells.push(target); + } + } + + best_shots.insert(weapon, (best_cells, current_best_score)); + } + + let best_single: Option<(Weapon, (Vec, usize))> = + best_shots.get(&Weapon::SingleShot).map(|x| (Weapon::SingleShot, x.clone())); + + let best: (Weapon, (Vec, usize)) = + best_shots.iter() + .max_by(|&(weapon_a, &(_, score_a)), &(weapon_b, &(_, score_b))| { + let score = score_a.cmp(&score_b); + let cost = weapon_a.energy_cost(self.map_size).cmp(&weapon_b.energy_cost(self.map_size)); + if score == Ordering::Equal { cost } else { score } + }) + .and_then(|(&weapon, x)| { + if self.shootable_weapons.contains(&weapon) { + Some((weapon, x.clone())) + } else { + best_single + } + }) + .unwrap_or((Weapon::SingleShot, (vec!(), 0))); + + + (best.0.clone(), (best.1).0) + } + + fn get_points_that_touch_all_possibilities_on_unsunk_ship(&self) -> Vec { + self.opponent_map.flat_cell_position_list().iter().cloned().filter(|&point| { + self.opponent_map.ships.values() + .any(|ref ship| !ship.destroyed && + ship.possible_placements.iter().all(|placement| { + placement.touches_point(point) + })) + }).collect() + } + + fn lattice_size(&self) -> u16 { + let any_long_ships = self.opponent_map.ships.iter() + .any(|(ship, knowledge)| ship.length() >= 4 && !knowledge.destroyed); + if any_long_ships { + 4 + } + else { + 2 + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct OpponentMapKnowledge { + pub ships: HashMap, + pub cells: Vec> +} + +impl OpponentMapKnowledge { + fn new(map_size: u16) -> OpponentMapKnowledge { + let mut cells = Vec::with_capacity(map_size as usize); + for x in 0..map_size { + cells.push(Vec::with_capacity(map_size as usize)); + for y in 0..map_size { + cells[x as usize].push(KnowledgeCell::new(x, y)); + } + } + + let ships = Ship::all_types().iter() + .map(|s| (s.clone(), OpponentShipKnowledge::new(s.clone(), map_size))) + .collect::>(); + + OpponentMapKnowledge { + ships: ships, + cells: cells + } + } + + fn update_sunk_ships(&mut self, state: &State) -> Vec { + let sunk_ships = self.ships.iter() + .filter(|&(_, x)| !x.destroyed) + .filter(|&(s, _)| state.opponent_map.ships.get(s).map(|x| x.destroyed) == Some(true)) + .map(|(s, _)| s.clone()) + .collect(); + + for &ship in &sunk_ships { + self.ships.get_mut(&ship).map(|ref mut ship_knowledge| ship_knowledge.destroyed = true); + } + + sunk_ships + } + + fn update_from_shot(&mut self, hit_cells: Vec, missed_cells: Vec, sunk_ships: Vec) { + for &missed in &missed_cells { + self.cells[missed.x as usize][missed.y as usize].missed = true; + } + for &hit in &hit_cells { + self.cells[hit.x as usize][hit.y as usize].hit = true; + } + + self.clear_sunk_ship_impossible_placements(&sunk_ships, &hit_cells); + + let mut more_changes = true; + while more_changes { + more_changes = self.derive_ship_positions() || self.clear_impossible_placements(); + } + } + + fn derive_ship_positions(&mut self) -> bool { + let mut any_changes = false; + for knowledge in self.ships.values() { + if knowledge.possible_placements.len() == 1 { + let ref true_placement = knowledge.possible_placements[0]; + for p in true_placement.points_on_ship() { + if self.cells[p.x as usize][p.y as usize].known_ship == None { + self.cells[p.x as usize][p.y as usize].known_ship = Some(true_placement.ship); + any_changes = true; + } + } + } + } + any_changes + } + + fn clear_impossible_placements(&mut self) -> bool { + let mut any_changes = false; + let ref cells = self.cells; + for knowledge in self.ships.values_mut() { + let before = knowledge.possible_placements.len(); + knowledge.possible_placements.retain(|x| x.all_could_be_hits(&cells)); + let after = knowledge.possible_placements.len(); + if before != after { + any_changes = true; + } + } + any_changes + } + + fn clear_sunk_ship_impossible_placements(&mut self, sunk_ships: &Vec, must_touch_any: &Vec) { + let cells_copy = self.cells.clone(); + + for knowledge in self.ships.values_mut() { + knowledge.possible_placements.retain(|x| !sunk_ships.contains(&x.ship) || (x.touches_any_point(&must_touch_any) && x.all_are_hits(&cells_copy))); + } + } + + fn cells_with_unknown_hits(&self) -> Vec { + self.cells.iter().flat_map(|x| { + x.iter().filter(|y| y.unknown_hit()).map(|y| y.position) + }).collect() + } + + fn adjacent_unshot_cells(&self, cells: &Vec) -> Vec { + self.cells.iter().flat_map(|x| { + x.iter() + .filter(|y| !y.shot_attempted()) + .map(|y| y.position) + .filter(|&y| cells.iter().any(|z| z.is_adjacent(y))) + }).collect() + } + + fn flat_cell_position_list(&self) -> Vec { + self.cells.iter().flat_map(|x| { + x.iter().map(|y| y.position) + }).collect() + } + + fn cells_on_lattice(&self, lattice_size: u16) -> Vec { + self.cells.iter().flat_map(|x| { + x.iter() + .filter(|y| !y.shot_attempted() && y.position.is_on_lattice(lattice_size)) + .map(|y| y.position) + }).collect() + } + + fn possible_placements(&self) -> Vec { + self.ships + .values() + .flat_map(|knowledge| knowledge.possible_placements.clone()) + .collect() + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct OpponentShipKnowledge { + pub ship: Ship, + pub destroyed: bool, + pub possible_placements: Vec +} + +impl OpponentShipKnowledge { + fn new(ship: Ship, map_size: u16) -> OpponentShipKnowledge { + OpponentShipKnowledge { + ship: ship, + destroyed: false, + possible_placements: PossibleShipPlacement::enumerate(ship, map_size) + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct PossibleShipPlacement { + pub ship: Ship, + pub direction: Direction, + pub position: Point +} + +impl PossibleShipPlacement { + fn enumerate(ship: Ship, map_size: u16) -> Vec { + (0..(map_size-ship.length()+1)).flat_map(move |par| { + (0..map_size).flat_map(move |per| { + vec!( + PossibleShipPlacement { + ship: ship, + direction: Direction::East, + position: Point::new(par, per) + }, + PossibleShipPlacement { + ship: ship, + direction: Direction::North, + position: Point::new(per, par) + } + ) + }) + }).collect() + } + + pub fn touches_point(&self, p: Point) -> bool { + p.check_for_ship_collision(self.position, self.direction, self.ship.length()) + } + pub fn touches_any_point(&self, ps: &Vec) -> bool { + ps.iter().any(|&p| self.touches_point(p)) + } + + pub fn points_on_ship(&self) -> Vec { + (0..self.ship.length() as i32).map(|i| { + self.position.move_point_no_bounds_check(self.direction, i) + }).collect() + } + + fn all_are_hits(&self, cells: &Vec>) -> bool { + self.points_on_ship() + .iter() + .fold(true, |acc, p| acc && cells[p.x as usize][p.y as usize].hit) + } + + fn all_could_be_hits(&self, cells: &Vec>) -> bool { + self.points_on_ship() + .iter() + .all(|p| { + let ref cell = cells[p.x as usize][p.y as usize]; + !cell.missed && cell.known_ship.map(|ship| ship == self.ship).unwrap_or(true) + }) + } + + fn count_hit_cells(&self, required: Point, wanted: &Vec) -> u16 { + if !self.touches_point(required) { + return 0; + } + + wanted.iter().filter(|&&x| self.touches_point(x)).count() as u16 + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct KnowledgeCell { + pub missed: bool, + pub hit: bool, + pub known_ship: Option, + pub position: Point +} + +impl KnowledgeCell { + fn new(x: u16, y: u16) -> KnowledgeCell { + KnowledgeCell { + missed: false, + hit: false, + position: Point::new(x, y), + known_ship: None + } + } + + pub fn shot_attempted(&self) -> bool { + self.missed || self.hit + } + + pub fn unknown_hit(&self) -> bool { + self.hit && self.known_ship.is_none() + } + +} + + diff --git a/2017-battleships/src/lib.rs b/2017-battleships/src/lib.rs new file mode 100644 index 0000000..00eaf02 --- /dev/null +++ b/2017-battleships/src/lib.rs @@ -0,0 +1,67 @@ +extern crate json; +extern crate rand; +extern crate serde; +extern crate serde_json; +#[macro_use] +extern crate serde_derive; + +mod actions; +mod math; +mod files; +mod ships; +mod placement; +mod shooting; +mod state; +mod knowledge; + +use actions::*; +use math::*; +use files::*; +use ships::*; +use placement::*; +use shooting::*; +use state::*; +use knowledge::*; + +use std::path::PathBuf; + +pub fn write_move(working_dir: PathBuf) -> Result<(), String> { + let state_json = read_file(&working_dir)?; + + let is_place_phase = state_json["Phase"] == 1; + let map_size = State::map_size_from_json(&state_json)?; + + let (action, knowledge) = if is_place_phase { + placement(map_size) + } + else { + let state = State::new(&state_json)?; + shoot(&state)? + }; + + write_knowledge(&knowledge) + .map_err(|e| format!("Failed to write knowledge to file. Error: {}", e))?; + + write_action(&working_dir, is_place_phase, action) + .map_err(|e| format!("Failed to write action to file. Error: {}", e))?; + + println!("Knowledge:\n{}\n\n", serde_json::to_string(&knowledge).unwrap_or(String::from(""))); + + Ok(()) +} + +fn placement(map_size: u16) -> (Action, Knowledge) { + let action = place_ships_randomly(map_size); + let knowledge = Knowledge::new(map_size, action.clone()); + + (action, knowledge) +} + +fn shoot(state: &State) -> Result<(Action, Knowledge), String> { + let old_knowledge = read_knowledge()?; + let knowledge = old_knowledge.resolve_last_action(&state); + let action = shoot_smartly(&knowledge); + + Ok((action.clone(), knowledge.with_action(action))) +} + diff --git a/2017-battleships/src/main.rs b/2017-battleships/src/main.rs new file mode 100644 index 0000000..ee0ba59 --- /dev/null +++ b/2017-battleships/src/main.rs @@ -0,0 +1,19 @@ +extern crate worthebot_battleships; + +use worthebot_battleships as bot; +use std::env; +use std::path::PathBuf; + +fn main() { + let working_dir = env::args() + .nth(2) + .map(|x| PathBuf::from(x)) + .ok_or(String::from("Requires game state folder to be passed as the second parameter")); + + let result = working_dir.and_then(|working_dir| bot::write_move(working_dir)); + + match result { + Ok(()) => println!("Bot terminated successfully"), + Err(e) => println!("Error in bot execution: {}", e) + } +} diff --git a/2017-battleships/src/math.rs b/2017-battleships/src/math.rs new file mode 100644 index 0000000..3187829 --- /dev/null +++ b/2017-battleships/src/math.rs @@ -0,0 +1,338 @@ +use std::fmt; +use rand; +use rand::distributions::{IndependentSample, Range}; + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] +pub enum Direction { + North, + NorthEast, + East, + SouthEast, + South, + SouthWest, + West, + NorthWest +} + +use Direction::*; + +impl fmt::Display for Direction { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str( + match self { + &North => "North", + &East => "East", + &South => "South", + &West => "West", + &NorthEast => "NorthEast", + &SouthEast => "SouthEast", + &NorthWest => "NorthWest", + &SouthWest => "SouthWest" + } + ) + } +} + +impl Direction { + pub fn random() -> Direction { + let mut rng = rand::thread_rng(); + let between = Range::new(0, 4); + let dir = between.ind_sample(&mut rng); + match dir { + 0 => North, + 1 => East, + 2 => South, + 3 => West, + _ => panic!("Invalid number generated by random number generator") + } + } +} + +#[cfg(test)] +mod direction_tests { + use super::*; + + #[test] + fn random_direction_does_not_panic() { + for _ in 0..10000 { + Direction::random(); + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] +pub struct Point { + pub x: u16, + pub y: u16 +} + +impl Point { + pub fn new(x: u16, y: u16) -> Point { + Point { + x: x, + y: y + } + } + + pub fn random(map_size: u16) -> Point { + let mut rng = rand::thread_rng(); + let between = Range::new(0, map_size); + let x = between.ind_sample(&mut rng); + let y = between.ind_sample(&mut rng); + Point::new(x, y) + } + + + pub fn move_point(&self, direction: Direction, distance: i32, map_size: u16) -> Option { + let x = self.x as i32 + match direction { + West|NorthWest|SouthWest => -distance, + East|NorthEast|SouthEast => distance, + _ => 0 + }; + let y = self.y as i32 + match direction { + South|SouthWest|SouthEast => -distance, + North|NorthWest|NorthEast => distance, + _ => 0 + }; + let max = map_size as i32; + + if x >= max || y >= max || x < 0 || y < 0 { + None + } + else { + Some(Point::new(x as u16, y as u16)) + } + } + + pub fn move_point_no_bounds_check(&self, direction: Direction, distance: i32) -> Point { + let x = self.x as i32 + match direction { + West => -distance, + East => distance, + _ => 0 + }; + let y = self.y as i32 + match direction { + South => -distance, + North => distance, + _ => 0 + }; + + Point::new(x as u16, y as u16) + } + + pub fn check_for_ship_collision(&self, ship_start: Point, direction: Direction, length: u16) -> bool { + let reverse = match direction { + West | South => true, + East | North => false, + _ => false //ships cannot go diagonally + }; + + let same_cross = match direction { + East | West => self.y == ship_start.y, + North | South => self.x == ship_start.x, + _ => false //ships cannot go diagonally + }; + + let (parr_self, parr_ship) = match direction { + East | West => (self.x, ship_start.x), + North | South => (self.y, ship_start.y), + _ => (self.x, self.y) //ships cannot go diagonally + }; + + let corrected_parr_ship = match reverse { + true => 1 + parr_ship - length, + false => parr_ship + }; + + same_cross && parr_self >= corrected_parr_ship && parr_self < corrected_parr_ship + length + } + + pub fn is_adjacent(&self, other: Point) -> bool { + let dx = if self.x > other.x {self.x - other.x} else {other.x - self.x}; + let dy = if self.y > other.y {self.y - other.y} else {other.y - self.y}; + + (dx == 0 && dy == 1) || + (dx == 1 && dy == 0) + + } + + pub fn is_on_lattice(&self, lattice_size: u16) -> bool { + (self.x + self.y) % lattice_size == 0 + } +} + + +#[cfg(test)] +mod point_tests { + use super::*; + + #[test] + fn random_point_is_in_correct_range() { + for _ in 0..10000 { + let point = Point::random(15); + assert!(point.x < 15); + assert!(point.y < 15); + } + } + + #[test] + fn move_point_works_north_west() { + assert_eq!(Some(Point::new(3,7)), Point::new(5,5).move_point(NorthWest, 2, 10)); + assert_eq!(Some(Point::new(7,3)), Point::new(5,5).move_point(NorthWest, -2, 10)); + assert_eq!(None, Point::new(5,5).move_point(NorthWest, 6, 10)); + assert_eq!(None, Point::new(5,5).move_point(NorthWest, -5, 10)); + } + + #[test] + fn move_point_works_west() { + assert_eq!(Some(Point::new(3,5)), Point::new(5,5).move_point(West, 2, 10)); + assert_eq!(Some(Point::new(7,5)), Point::new(5,5).move_point(West, -2, 10)); + assert_eq!(None, Point::new(5,5).move_point(West, 6, 10)); + assert_eq!(None, Point::new(5,5).move_point(West, -5, 10)); + } + + #[test] + fn move_point_works_east() { + assert_eq!(Some(Point::new(7,5)), Point::new(5,5).move_point(East, 2, 10)); + assert_eq!(Some(Point::new(3,5)), Point::new(5,5).move_point(East, -2, 10)); + assert_eq!(None, Point::new(5,5).move_point(East, 5, 10)); + assert_eq!(None, Point::new(5,5).move_point(East, -6, 10)); + } + + #[test] + fn move_point_works_south() { + assert_eq!(Some(Point::new(5,3)), Point::new(5,5).move_point(South, 2, 10)); + assert_eq!(Some(Point::new(5,7)), Point::new(5,5).move_point(South, -2, 10)); + assert_eq!(None, Point::new(5,5).move_point(South, 6, 10)); + assert_eq!(None, Point::new(5,5).move_point(South, -5, 10)); + } + + #[test] + fn move_point_works_north() { + assert_eq!(Some(Point::new(5,7)), Point::new(5,5).move_point(North, 2, 10)); + assert_eq!(Some(Point::new(5,3)), Point::new(5,5).move_point(North, -2, 10)); + assert_eq!(None, Point::new(5,5).move_point(North, 5, 10)); + assert_eq!(None, Point::new(5,5).move_point(North, -6, 10)); + } + + #[test] + fn unrestricted_move_point_works_west() { + assert_eq!(Point::new(3,5), Point::new(5,5).move_point_no_bounds_check(West, 2)); + assert_eq!(Point::new(7,5), Point::new(5,5).move_point_no_bounds_check(West, -2)); + } + + #[test] + fn unrestricted_move_point_works_east() { + assert_eq!(Point::new(7,5), Point::new(5,5).move_point_no_bounds_check(East, 2)); + assert_eq!(Point::new(3,5), Point::new(5,5).move_point_no_bounds_check(East, -2)); + } + + #[test] + fn unrestricted_move_point_works_south() { + assert_eq!(Point::new(5,3), Point::new(5,5).move_point_no_bounds_check(South, 2)); + assert_eq!(Point::new(5,7), Point::new(5,5).move_point_no_bounds_check(South, -2)); + } + + #[test] + fn unrestricted_move_point_works_north() { + assert_eq!(Point::new(5,7), Point::new(5,5).move_point_no_bounds_check(North, 2)); + assert_eq!(Point::new(5,3), Point::new(5,5).move_point_no_bounds_check(North, -2)); + } + + #[test] + fn ship_collision_check_works_west() { + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,5), West, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(6,5), West, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(7,5), West, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(8,5), West, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(9,5), West, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(10,5), West, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(4,5), West, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(6,4), West, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(6,6), West, 5)); + } + + #[test] + fn ship_collision_check_works_east() { + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,5), East, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(4,5), East, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(3,5), East, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(2,5), East, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(1,5), East, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(0,5), East, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(6,5), East, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(4,4), East, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(4,6), East, 5)); + } + + #[test] + fn ship_collision_check_works_north() { + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,5), North, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,4), North, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,3), North, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,2), North, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,1), North, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(5,0), North, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(5,6), North, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(4,4), North, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(6,4), North, 5)); + } + + #[test] + fn ship_collision_check_works_south() { + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,5), South, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,6), South, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,7), South, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,8), South, 5)); + assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,9), South, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(5,10), South, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(5,4), South, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(4,6), South, 5)); + assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(6,6), South, 5)); + } + + #[test] + fn adjacency_check_works() { + assert_eq!(true, Point::new(5,5).is_adjacent(Point::new(4,5))); + assert_eq!(true, Point::new(5,5).is_adjacent(Point::new(6,5))); + assert_eq!(true, Point::new(5,5).is_adjacent(Point::new(5,4))); + assert_eq!(true, Point::new(5,5).is_adjacent(Point::new(5,6))); + + assert_eq!(false, Point::new(5,5).is_adjacent(Point::new(4,4))); + assert_eq!(false, Point::new(5,5).is_adjacent(Point::new(6,6))); + assert_eq!(false, Point::new(5,5).is_adjacent(Point::new(6,4))); + assert_eq!(false, Point::new(5,5).is_adjacent(Point::new(4,6))); + assert_eq!(false, Point::new(5,5).is_adjacent(Point::new(5,5))); + + assert_eq!(false, Point::new(5,5).is_adjacent(Point::new(10,5))); + + } + + #[test] + fn point_on_4_lattice_works() { + assert_eq!(true, Point::new(0,0).is_on_lattice(4)); + assert_eq!(true, Point::new(4,0).is_on_lattice(4)); + assert_eq!(true, Point::new(0,4).is_on_lattice(4)); + assert_eq!(true, Point::new(4,4).is_on_lattice(4)); + assert_eq!(true, Point::new(1,3).is_on_lattice(4)); + assert_eq!(true, Point::new(3,1).is_on_lattice(4)); + + assert_eq!(false, Point::new(0,1).is_on_lattice(4)); + assert_eq!(false, Point::new(0,2).is_on_lattice(4)); + assert_eq!(false, Point::new(0,3).is_on_lattice(4)); + assert_eq!(false, Point::new(1,0).is_on_lattice(4)); + assert_eq!(false, Point::new(2,0).is_on_lattice(4)); + assert_eq!(false, Point::new(3,0).is_on_lattice(4)); + } + + #[test] + fn point_on_2_lattice_works() { + assert_eq!(true, Point::new(0,0).is_on_lattice(2)); + assert_eq!(true, Point::new(2,0).is_on_lattice(2)); + assert_eq!(true, Point::new(0,2).is_on_lattice(2)); + assert_eq!(true, Point::new(2,2).is_on_lattice(2)); + assert_eq!(true, Point::new(1,1).is_on_lattice(2)); + + assert_eq!(false, Point::new(0,1).is_on_lattice(2)); + assert_eq!(false, Point::new(1,0).is_on_lattice(2)); + } +} diff --git a/2017-battleships/src/placement.rs b/2017-battleships/src/placement.rs new file mode 100644 index 0000000..4740d76 --- /dev/null +++ b/2017-battleships/src/placement.rs @@ -0,0 +1,23 @@ +use actions::*; +use math::*; +use ships::*; + +pub fn place_ships_randomly(map_size: u16) -> Action { + let mut current_placement: Vec; + + while { + current_placement = create_random_placement(map_size); + !ShipPlacement::valid_placements(¤t_placement, map_size) + } {} + Action::PlaceShips(current_placement) +} + +fn create_random_placement(map_size: u16) -> Vec { + vec!( + ShipPlacement::new(Ship::Battleship, Point::random(map_size), Direction::random()), + ShipPlacement::new(Ship::Carrier, Point::random(map_size), Direction::random()), + ShipPlacement::new(Ship::Cruiser, Point::random(map_size), Direction::random()), + ShipPlacement::new(Ship::Destroyer, Point::random(map_size), Direction::random()), + ShipPlacement::new(Ship::Submarine, Point::random(map_size), Direction::random()) + ) +} diff --git a/2017-battleships/src/ships.rs b/2017-battleships/src/ships.rs new file mode 100644 index 0000000..422f24e --- /dev/null +++ b/2017-battleships/src/ships.rs @@ -0,0 +1,216 @@ +use std::fmt; +use std::str; + +use math::*; + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] +pub enum Weapon { + SingleShot, + DoubleShotVertical, + DoubleShotHorizontal, + CornerShot, + CrossShotDiagonal, + CrossShotHorizontal, + SeekerMissle +} + +impl fmt::Display for Weapon { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use Weapon::*; + + f.write_str( + match self { + &SingleShot => "1", + &DoubleShotVertical => "2", + &DoubleShotHorizontal => "3", + &CornerShot => "4", + &CrossShotDiagonal => "5", + &CrossShotHorizontal => "6", + &SeekerMissle => "7" + } + ) + } +} + +impl Weapon { + pub fn energy_per_round(map_size: u16) -> u16 { + if map_size < 10 { + 2 + } + else if map_size < 14 { + 3 + } + else { + 4 + } + } + pub fn energy_cost(&self, map_size: u16) -> u16 { + use Weapon::*; + let epr = Weapon::energy_per_round(map_size); + match self { + &SingleShot => 1, + &DoubleShotVertical | &DoubleShotHorizontal => 8*epr, + &CornerShot => 10*epr, + &CrossShotDiagonal => 12*epr, + &CrossShotHorizontal => 14*epr, + &SeekerMissle => 10*epr + } + } + pub fn single_shot_rounds_to_ready(&self, current_energy: u16, map_size: u16) -> u16 { + let single_shot_cost = Weapon::SingleShot.energy_cost(map_size); + let energy_per_round = Weapon::energy_per_round(map_size) - single_shot_cost; + let required_energy = self.energy_cost(map_size) - current_energy; + //weird plus is to make the integer rounding up instead of down + (required_energy + energy_per_round - 1) / energy_per_round + } + + pub fn affected_cells(&self, target: Point, map_size: u16) -> Vec { + use Weapon::*; + + let p = target; + match self { + &SingleShot => { + vec!(Some(p)) + }, + &DoubleShotVertical => { + vec!( + p.move_point(Direction::North, 1, map_size), + p.move_point(Direction::South, 1, map_size) + ) + }, + &DoubleShotHorizontal => { + vec!( + p.move_point(Direction::East, 1, map_size), + p.move_point(Direction::West, 1, map_size) + ) + }, + &CornerShot => { + vec!( + p.move_point(Direction::NorthEast, 1, map_size), + p.move_point(Direction::SouthEast, 1, map_size), + p.move_point(Direction::NorthWest, 1, map_size), + p.move_point(Direction::SouthWest, 1, map_size), + ) + }, + &CrossShotDiagonal => { + vec!( + p.move_point(Direction::NorthEast, 1, map_size), + p.move_point(Direction::SouthEast, 1, map_size), + p.move_point(Direction::NorthWest, 1, map_size), + p.move_point(Direction::SouthWest, 1, map_size), + Some(p) + ) + }, + &CrossShotHorizontal => { + vec!( + p.move_point(Direction::North, 1, map_size), + p.move_point(Direction::East, 1, map_size), + p.move_point(Direction::South, 1, map_size), + p.move_point(Direction::West, 1, map_size), + Some(p) + ) + }, + &SeekerMissle => { + vec!( + Some(p), + + p.move_point(Direction::North, 1, map_size), + p.move_point(Direction::East, 1, map_size), + p.move_point(Direction::South, 1, map_size), + p.move_point(Direction::West, 1, map_size), + + p.move_point(Direction::NorthEast, 1, map_size), + p.move_point(Direction::SouthEast, 1, map_size), + p.move_point(Direction::NorthWest, 1, map_size), + p.move_point(Direction::SouthWest, 1, map_size), + + p.move_point(Direction::North, 2, map_size), + p.move_point(Direction::East, 2, map_size), + p.move_point(Direction::South, 2, map_size), + p.move_point(Direction::West, 2, map_size) + ) + } + }.iter().filter_map(|&p| p).collect::>() + } +} + + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] +pub enum Ship { + Battleship, + Carrier, + Cruiser, + Destroyer, + Submarine +} + +impl fmt::Display for Ship { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use Ship::*; + + f.write_str( + match self { + &Battleship => "Battleship", + &Carrier => "Carrier", + &Cruiser => "Cruiser", + &Destroyer => "Destroyer", + &Submarine => "Submarine" + } + ) + } +} + +impl str::FromStr for Ship { + type Err = String; + fn from_str(s: &str) -> Result { + use Ship::*; + + match s { + "Battleship" => Ok(Battleship), + "Carrier" => Ok(Carrier), + "Cruiser" => Ok(Cruiser), + "Destroyer" => Ok(Destroyer), + "Submarine" => Ok(Submarine), + _ => Err(String::from("ship type is not known")) + } + } +} + +impl Ship { + pub fn length(&self) -> u16 { + use Ship::*; + + match self { + &Battleship => 4, + &Carrier => 5, + &Cruiser => 3, + &Destroyer => 2, + &Submarine => 3 + } + } + + pub fn weapons(&self) -> Vec { + use Ship::*; + use Weapon::*; + + match self { + &Battleship => vec!(SingleShot, CrossShotDiagonal), + &Carrier => vec!(SingleShot, CornerShot), + &Cruiser => vec!(SingleShot, CrossShotHorizontal), + &Destroyer => vec!(SingleShot, DoubleShotVertical, DoubleShotHorizontal), + &Submarine => vec!(SingleShot, SeekerMissle) + } + } + + pub fn all_types() -> Vec { + use Ship::*; + + vec!( + Battleship, + Carrier, + Cruiser, + Destroyer, + Submarine + ) + } +} diff --git a/2017-battleships/src/shooting.rs b/2017-battleships/src/shooting.rs new file mode 100644 index 0000000..e0358ee --- /dev/null +++ b/2017-battleships/src/shooting.rs @@ -0,0 +1,47 @@ +use rand; +use rand::distributions::{IndependentSample, Range}; + +use actions::*; +use math::*; +use knowledge::*; +use ships::*; + +pub fn shoot_smartly(knowledge: &Knowledge) -> Action { + let (weapon, target) = if knowledge.has_unknown_hits() { + destroy_shoot(&knowledge) + } + else { + seek_shoot(&knowledge) + }; + + Action::Shoot(weapon, target) +} + +fn seek_shoot(knowledge: &Knowledge) -> (Weapon, Point) { + let (weapon, possibilities) = knowledge.get_best_seek_shots(); + if possibilities.is_empty() { + println!("All possible shots on the current lattice have been tried!"); + (Weapon::SingleShot, Point::new(0,0)) + } + else { + let mut rng = rand::thread_rng(); + let between = Range::new(0, possibilities.len()); + let i = between.ind_sample(&mut rng); + + (weapon, possibilities[i as usize]) + } +} + +fn destroy_shoot(knowledge: &Knowledge) -> (Weapon, Point) { + let possibilities = knowledge.get_best_adjacent_shots(); + if possibilities.is_empty() { + seek_shoot(&knowledge) + } + else { + let mut rng = rand::thread_rng(); + let between = Range::new(0, possibilities.len()); + let i = between.ind_sample(&mut rng); + + (Weapon::SingleShot, possibilities[i as usize]) + } +} diff --git a/2017-battleships/src/state.rs b/2017-battleships/src/state.rs new file mode 100644 index 0000000..1756ad0 --- /dev/null +++ b/2017-battleships/src/state.rs @@ -0,0 +1,146 @@ +use json; +use std::collections::HashMap; +use ships::*; + +pub struct State { + pub map_size: u16, + pub player_map: PlayerMap, + pub opponent_map: OpponentMap +} + +impl State { + pub fn new(json: &json::JsonValue) -> Result { + let map_size = State::map_size_from_json(&json)?; + + let ref player_map_json = json["PlayerMap"]; + let player_map = PlayerMap::new(&player_map_json)?; + + let ref opponent_map_json = json["OpponentMap"]; + let opponent_map = OpponentMap::new(&opponent_map_json, map_size)?; + + Ok(State { + map_size: map_size, + player_map: player_map, + opponent_map: opponent_map + }) + } + + pub fn map_size_from_json(json: &json::JsonValue) -> Result { + json["MapDimension"] + .as_u16() + .ok_or(String::from("Did not find the map dimension in the state json file")) + } +} + +pub struct OpponentMap { + pub cells: Vec>, + pub ships: HashMap, +} + +impl OpponentMap { + fn new(json: &json::JsonValue, map_size: u16) -> Result { + let mut cells = Vec::with_capacity(map_size as usize); + for _ in 0..map_size { + let mut row = Vec::with_capacity(map_size as usize); + for _ in 0..map_size { + row.push(Cell::new()); + } + cells.push(row); + } + + for json_cell in json["Cells"].members() { + let x = json_cell["X"] + .as_u16() + .ok_or(String::from("Failed to read X value of opponent map cell in json file"))?; + let y = json_cell["Y"] + .as_u16() + .ok_or(String::from("Failed to read Y value of opponent map cell in json file"))?; + let damaged = json_cell["Damaged"] + .as_bool() + .ok_or(String::from("Failed to read Damaged value of opponent map cell in json file"))?; + let missed = json_cell["Missed"] + .as_bool() + .ok_or(String::from("Failed to read Missed value of opponent map cell in json file"))?; + + cells[x as usize][y as usize].damaged = damaged; + cells[x as usize][y as usize].missed = missed; + } + + let mut ships = HashMap::new(); + for json_ship in json["Ships"].members() { + let ship_type_string = json_ship["ShipType"] + .as_str() + .ok_or(String::from("Failed to read ShipType value of opponent map ship in json file"))?; + let ship_type = ship_type_string.parse::()?; + + let destroyed = json_ship["Destroyed"] + .as_bool() + .ok_or(String::from("Failed to read Destroyed value of opponent map ship in json file"))?; + ships.insert(ship_type, OpponentShip { + destroyed: destroyed + }); + } + + + Ok(OpponentMap { + cells: cells, + ships: ships + }) + } +} + +pub struct OpponentShip { + pub destroyed: bool +} + +pub struct Cell { + pub damaged: bool, + pub missed: bool +} + +impl Cell { + fn new() -> Cell { + Cell { + damaged: false, + missed: false + } + } +} + +pub struct PlayerMap { + pub ships: HashMap, + pub energy: u16 +} + +impl PlayerMap { + fn new(json: &json::JsonValue) -> Result { + let mut ships = HashMap::new(); + for json_ship in json["Owner"]["Ships"].members() { + let ship_type_string = json_ship["ShipType"] + .as_str() + .ok_or(String::from("Failed to read ShipType value of player map ship in json file"))?; + let ship_type = ship_type_string.parse::()?; + + let destroyed = json_ship["Destroyed"] + .as_bool() + .ok_or(String::from("Failed to read Destroyed value of player map ship in json file"))?; + ships.insert(ship_type, PlayerShip { + destroyed: destroyed + }); + } + + let energy = json["Owner"]["Energy"] + .as_u16() + .ok_or(String::from("Did not find the energy in the state json file"))?; + + Ok(PlayerMap { + ships: ships, + energy: energy + }) + } +} + + +pub struct PlayerShip { + pub destroyed: bool +} diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 96ea518..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,122 +0,0 @@ -[root] -name = "worthebot_battleships" -version = "0.1.0" -dependencies = [ - "json 0.11.6 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "dtoa" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "itoa" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "json" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "libc" -version = "0.2.22" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "num-traits" -version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "quote" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "rand" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "serde" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "serde_derive" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive_internals 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "serde_derive_internals" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", - "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "serde_json" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "syn" -version = "0.11.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", - "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "synom" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "unicode-xid" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[metadata] -"checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90" -"checksum itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2f404fbc66fd9aac13e998248505e7ecb2ad8e44ab6388684c5fb11c6c251c" -"checksum json 0.11.6 (registry+https://github.com/rust-lang/crates.io-index)" = "27600e8bb3b71bcc6213fb36b66b8dce60adc17a624257687ef5d1d4facafba7" -"checksum libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)" = "babb8281da88cba992fa1f4ddec7d63ed96280a1a53ec9b919fd37b53d71e502" -"checksum num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "e1cbfa3781f3fe73dc05321bed52a06d2d491eaa764c52335cf4399f046ece99" -"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" -"checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d" -"checksum serde 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "991ef6be409a3b7a46cb9ee701d86156ce851825c65dbee7f16dbd5c4e7e2d47" -"checksum serde_derive 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9fd81eef9f0b4ec341b11095335b6a4b28ed85581b12dd27585dee1529df35e0" -"checksum serde_derive_internals 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "021c338d22c7e30f957a6ab7e388cb6098499dda9fd4ba1661ee074ca7a180d1" -"checksum serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "48b04779552e92037212c3615370f6bd57a40ebba7f20e554ff9f55e41a69a7b" -"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" -"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" -"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index cdebbe8..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "worthebot_battleships" -version = "0.1.0" -authors = ["Justin Worthe "] - -[dependencies] -rand = "0.3" -json = "0.11.6" -serde = "1.0.4" -serde_json = "1.0.2" -serde_derive = "1.0.4" \ No newline at end of file diff --git a/bot.json b/bot.json deleted file mode 100644 index 6245b49..0000000 --- a/bot.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Author":"Justin Worthe", - "Email":"justin.worthe@gmail.com", - "NickName" :"Admiral Worthebot", - "BotType": 8, - "ProjectLocation" : "", - "RunFile" : "target\\release\\worthebot_battleships.exe" -} diff --git a/notes.org b/notes.org deleted file mode 100644 index ff3e319..0000000 --- a/notes.org +++ /dev/null @@ -1,1447 +0,0 @@ -* State.json - -#+BEGIN_EXAMPLE -State = {PlayerMap, OpponentMap, GameVersion=String, GameLevel=u16, Round=u16, MapDimension=u16, Phase=u16, Player1Map=null, Player2Map=null} - -PlayerMap = {Cells=[PlayerCell], Owner, MapWidth=u16, MapHeight=u16} - -PlayerCell = {Occupied=bool, Hit=bool, X=u16, Y=u16} - -Owner = {FailedFirstPhaseCommands=u16, Name=String, Ships=[PlayerShip], Points=u16, Killed=bool, IsWinner=bool, ShotsFired=u16, ShotsHit=u16, ShipsRemaining=u16, Key=String} - -PlayerShip = {Cells=[PlayerCell], Destroyed=bool, Placed=bool, ShipType=String, Weapons=[PlayerWeapon], } - -PlayerWeapon = {WeaponType=String} - -OpponentMap = {Ships=[OpponentShip], Alive=bool, Points=u16, Name=String, Cells=[OpponentCell]} - -OpponentShip = {Destroyed=bool, ShipType=String} - -OpponentCell = {Damaged=bool, Missed=bool, X=u16, Y=u16} - -#+END_EXAMPLE - -* State.json example - -#+BEGIN_SRC json -{ - "PlayerMap": { - "Cells": [ - { - "Occupied": false, - "Hit": true, - "X": 0, - "Y": 0 - }, - { - "Occupied": false, - "Hit": false, - "X": 0, - "Y": 1 - }, - { - "Occupied": true, - "Hit": false, - "X": 0, - "Y": 2 - }, - { - "Occupied": true, - "Hit": false, - "X": 0, - "Y": 3 - }, - { - "Occupied": true, - "Hit": false, - "X": 0, - "Y": 4 - }, - { - "Occupied": false, - "Hit": false, - "X": 0, - "Y": 5 - }, - { - "Occupied": true, - "Hit": true, - "X": 0, - "Y": 6 - }, - { - "Occupied": true, - "Hit": true, - - "X": 0, - "Y": 7 - }, - { - "Occupied": true, - "Hit": false, - "X": 0, - "Y": 8 - }, - { - "Occupied": false, - "Hit": true, - "X": 0, - "Y": 9 - }, - { - "Occupied": false, - "Hit": false, - "X": 1, - "Y": 0 - }, - { - "Occupied": false, - "Hit": false, - "X": 1, - "Y": 1 - }, - { - "Occupied": false, - "Hit": false, - "X": 1, - "Y": 2 - }, - { - "Occupied": false, - "Hit": true, - "X": 1, - "Y": 3 - }, - { - "Occupied": false, - "Hit": false, - "X": 1, - "Y": 4 - }, - { - "Occupied": false, - "Hit": false, - "X": 1, - "Y": 5 - }, - { - "Occupied": false, - "Hit": false, - "X": 1, - "Y": 6 - }, - { - "Occupied": false, - "Hit": false, - "X": 1, - "Y": 7 - }, - { - "Occupied": true, - "Hit": false, - "X": 1, - "Y": 8 - }, - { - "Occupied": false, - "Hit": true, - "X": 1, - "Y": 9 - }, - { - "Occupied": false, - "Hit": false, - "X": 2, - "Y": 0 - }, - { - "Occupied": false, - "Hit": false, - "X": 2, - "Y": 1 - }, - { - "Occupied": false, - "Hit": false, - "X": 2, - "Y": 2 - }, - { - "Occupied": false, - "Hit": false, - "X": 2, - "Y": 3 - }, - { - "Occupied": false, - "Hit": true, - "X": 2, - "Y": 4 - }, - { - "Occupied": false, - "Hit": true, - "X": 2, - "Y": 5 - }, - { - "Occupied": false, - "Hit": false, - "X": 2, - "Y": 6 - }, - { - "Occupied": false, - "Hit": false, - "X": 2, - "Y": 7 - }, - { - "Occupied": true, - "Hit": true, - "X": 2, - "Y": 8 - }, - { - "Occupied": false, - "Hit": false, - "X": 2, - "Y": 9 - }, - { - "Occupied": false, - "Hit": true, - "X": 3, - "Y": 0 - }, - { - "Occupied": false, - "Hit": true, - "X": 3, - "Y": 1 - }, - { - "Occupied": false, - "Hit": true, - "X": 3, - "Y": 2 - }, - { - "Occupied": false, - "Hit": false, - "X": 3, - "Y": 3 - }, - { - "Occupied": false, - "Hit": false, - "X": 3, - "Y": 4 - }, - { - "Occupied": false, - "Hit": false, - "X": 3, - "Y": 5 - }, - { - "Occupied": false, - "Hit": false, - "X": 3, - "Y": 6 - }, - { - "Occupied": false, - "Hit": false, - "X": 3, - "Y": 7 - }, - { - "Occupied": true, - "Hit": false, - "X": 3, - "Y": 8 - }, - { - "Occupied": false, - "Hit": false, - "X": 3, - "Y": 9 - }, - { - "Occupied": false, - "Hit": false, - "X": 4, - "Y": 0 - }, - { - "Occupied": false, - "Hit": true, - "X": 4, - "Y": 1 - }, - { - "Occupied": false, - "Hit": true, - "X": 4, - "Y": 2 - }, - { - "Occupied": false, - "Hit": false, - "X": 4, - "Y": 3 - }, - { - "Occupied": false, - "Hit": false, - "X": 4, - "Y": 4 - }, - { - "Occupied": false, - "Hit": false, - "X": 4, - "Y": 5 - }, - { - "Occupied": false, - "Hit": false, - "X": 4, - "Y": 6 - }, - { - "Occupied": false, - "Hit": true, - "X": 4, - "Y": 7 - }, - { - "Occupied": true, - "Hit": false, - "X": 4, - "Y": 8 - }, - { - "Occupied": false, - "Hit": true, - "X": 4, - "Y": 9 - }, - { - "Occupied": false, - "Hit": false, - "X": 5, - "Y": 0 - }, - { - "Occupied": true, - "Hit": true, - "X": 5, - "Y": 1 - }, - { - "Occupied": false, - "Hit": false, - "X": 5, - "Y": 2 - }, - { - "Occupied": false, - "Hit": false, - "X": 5, - "Y": 3 - }, - { - "Occupied": false, - "Hit": false, - "X": 5, - "Y": 4 - }, - { - "Occupied": false, - "Hit": false, - "X": 5, - "Y": 5 - }, - { - "Occupied": false, - "Hit": false, - "X": 5, - "Y": 6 - }, - { - "Occupied": false, - "Hit": false, - "X": 5, - "Y": 7 - }, - { - "Occupied": true, - "Hit": false, - "X": 5, - "Y": 8 - }, - { - "Occupied": false, - "Hit": false, - "X": 5, - "Y": 9 - }, - { - "Occupied": false, - "Hit": false, - "X": 6, - "Y": 0 - }, - { - "Occupied": true, - "Hit": false, - "X": 6, - "Y": 1 - }, - { - "Occupied": false, - "Hit": false, - "X": 6, - "Y": 2 - }, - { - "Occupied": false, - "Hit": true, - "X": 6, - "Y": 3 - }, - { - "Occupied": false, - "Hit": false, - "X": 6, - "Y": 4 - }, - { - "Occupied": false, - "Hit": true, - "X": 6, - "Y": 5 - }, - { - "Occupied": false, - "Hit": false, - "X": 6, - "Y": 6 - }, - { - "Occupied": false, - "Hit": false, - "X": 6, - "Y": 7 - }, - { - "Occupied": false, - "Hit": true, - "X": 6, - "Y": 8 - }, - { - "Occupied": false, - "Hit": false, - "X": 6, - "Y": 9 - }, - { - "Occupied": false, - "Hit": false, - "X": 7, - "Y": 0 - }, - { - "Occupied": true, - "Hit": true, - "X": 7, - "Y": 1 - }, - { - "Occupied": false, - "Hit": false, - "X": 7, - "Y": 2 - }, - { - "Occupied": false, - "Hit": false, - "X": 7, - "Y": 3 - }, - { - "Occupied": false, - "Hit": false, - "X": 7, - "Y": 4 - }, - { - "Occupied": true, - "Hit": false, - "X": 7, - "Y": 5 - }, - { - "Occupied": true, - "Hit": false, - "X": 7, - "Y": 6 - }, - { - "Occupied": false, - "Hit": false, - "X": 7, - "Y": 7 - }, - { - "Occupied": false, - "Hit": false, - "X": 7, - "Y": 8 - }, - { - "Occupied": false, - "Hit": false, - "X": 7, - "Y": 9 - }, - { - "Occupied": false, - "Hit": false, - "X": 8, - "Y": 0 - }, - { - "Occupied": true, - "Hit": false, - "X": 8, - "Y": 1 - }, - { - "Occupied": false, - "Hit": false, - "X": 8, - "Y": 2 - }, - { - "Occupied": false, - "Hit": true, - "X": 8, - "Y": 3 - }, - { - "Occupied": false, - "Hit": true, - "X": 8, - "Y": 4 - }, - { - "Occupied": false, - "Hit": false, - "X": 8, - "Y": 5 - }, - { - "Occupied": false, - "Hit": false, - "X": 8, - "Y": 6 - }, - { - "Occupied": false, - "Hit": false, - "X": 8, - "Y": 7 - }, - { - "Occupied": false, - "Hit": false, - "X": 8, - "Y": 8 - }, - { - "Occupied": false, - "Hit": true, - "X": 8, - "Y": 9 - }, - { - "Occupied": false, - "Hit": false, - "X": 9, - "Y": 0 - }, - { - "Occupied": false, - "Hit": false, - "X": 9, - "Y": 1 - }, - { - "Occupied": false, - "Hit": false, - "X": 9, - "Y": 2 - }, - { - "Occupied": false, - "Hit": false, - "X": 9, - "Y": 3 - }, - { - "Occupied": false, - "Hit": false, - "X": 9, - "Y": 4 - }, - { - "Occupied": false, - "Hit": false, - "X": 9, - "Y": 5 - }, - { - "Occupied": false, - "Hit": false, - "X": 9, - "Y": 6 - }, - { - "Occupied": false, - "Hit": false, - "X": 9, - "Y": 7 - }, - { - "Occupied": false, - "Hit": true, - "X": 9, - "Y": 8 - }, - { - "Occupied": false, - "Hit": false, - "X": 9, - "Y": 9 - } - ], - "Owner": { - "FailedFirstPhaseCommands": 0, - "Name": "Admiral Worthebot", - "Ships": [ - { - "Cells": [ - { - "Occupied": true, - "Hit": false, - "X": 0, - "Y": 2 - }, - { - "Occupied": true, - "Hit": false, - "X": 0, - "Y": 3 - }, - { - "Occupied": true, - "Hit": false, - "X": 0, - "Y": 4 - } - ], - "Destroyed": false, - "Placed": true, - "ShipType": "Submarine", - "Weapons": [ - { - "WeaponType": "SingleShot" - } - ] - }, - { - "Cells": [ - { - "Occupied": true, - "Hit": false, - "X": 7, - "Y": 6 - }, - { - "Occupied": true, - "Hit": false, - "X": 7, - "Y": 5 - } - ], - "Destroyed": false, - "Placed": true, - "ShipType": "Destroyer", - "Weapons": [ - { - "WeaponType": "SingleShot" - } - ] - }, - { - "Cells": [ - { - "Occupied": true, - "Hit": true, - "X": 5, - "Y": 1 - }, - { - "Occupied": true, - "Hit": false, - "X": 6, - "Y": 1 - }, - { - "Occupied": true, - "Hit": true, - "X": 7, - "Y": 1 - }, - { - "Occupied": true, - "Hit": false, - "X": 8, - "Y": 1 - } - ], - "Destroyed": false, - "Placed": true, - "ShipType": "Battleship", - "Weapons": [ - { - "WeaponType": "SingleShot" - } - ] - }, - { - "Cells": [ - { - "Occupied": true, - "Hit": false, - "X": 5, - "Y": 8 - }, - { - "Occupied": true, - "Hit": false, - "X": 4, - "Y": 8 - }, - { - "Occupied": true, - "Hit": false, - "X": 3, - "Y": 8 - }, - { - "Occupied": true, - "Hit": true, - "X": 2, - "Y": 8 - }, - { - "Occupied": true, - "Hit": false, - "X": 1, - "Y": 8 - } - ], - "Destroyed": false, - "Placed": true, - "ShipType": "Carrier", - "Weapons": [ - { - "WeaponType": "SingleShot" - } - ] - }, - { - "Cells": [ - { - "Occupied": true, - "Hit": true, - "X": 0, - "Y": 6 - }, - { - "Occupied": true, - "Hit": true, - "X": 0, - "Y": 7 - }, - { - "Occupied": true, - "Hit": false, - "X": 0, - "Y": 8 - } - ], - "Destroyed": false, - "Placed": true, - "ShipType": "Cruiser", - "Weapons": [ - { - "WeaponType": "SingleShot" - } - ] - } - ], - "Points": 280, - "Killed": false, - "IsWinner": false, - "ShotsFired": 86, - "ShotsHit": 16, - "ShipsRemaining": 5, - "Key": "B" - }, - "MapWidth": 10, - "MapHeight": 10 - }, - "OpponentMap": { - "Ships": [ - { - "Destroyed": false, - "ShipType": "Submarine" - }, - { - "Destroyed": true, - "ShipType": "Destroyer" - }, - { - "Destroyed": true, - "ShipType": "Battleship" - }, - { - "Destroyed": true, - "ShipType": "Carrier" - }, - { - "Destroyed": true, - "ShipType": "Cruiser" - } - ], - "Alive": true, - "Points": 50, - "Name": "John", - "Cells": [ - { - "Damaged": false, - "Missed": true, - "X": 0, - "Y": 0 - }, - { - "Damaged": false, - "Missed": true, - "X": 0, - "Y": 1 - }, - { - "Damaged": false, - "Missed": true, - "X": 0, - "Y": 2 - }, - { - "Damaged": false, - "Missed": true, - "X": 0, - "Y": 3 - }, - { - "Damaged": false, - "Missed": true, - "X": 0, - "Y": 4 - }, - { - "Damaged": false, - "Missed": false, - "X": 0, - "Y": 5 - }, - { - "Damaged": false, - "Missed": true, - "X": 0, - "Y": 6 - }, - { - "Damaged": false, - "Missed": true, - "X": 0, - "Y": 7 - }, - { - "Damaged": false, - "Missed": true, - "X": 0, - "Y": 8 - }, - { - "Damaged": false, - "Missed": true, - "X": 0, - "Y": 9 - }, - { - "Damaged": true, - "Missed": false, - "X": 1, - "Y": 0 - }, - { - "Damaged": true, - "Missed": false, - "X": 1, - "Y": 1 - }, - { - "Damaged": true, - "Missed": false, - "X": 1, - "Y": 2 - }, - { - "Damaged": true, - "Missed": false, - "X": 1, - "Y": 3 - }, - { - "Damaged": false, - "Missed": true, - "X": 1, - "Y": 4 - }, - { - "Damaged": false, - "Missed": true, - "X": 1, - "Y": 5 - }, - { - "Damaged": false, - "Missed": true, - "X": 1, - "Y": 6 - }, - { - "Damaged": false, - "Missed": true, - "X": 1, - "Y": 7 - }, - { - "Damaged": true, - "Missed": false, - "X": 1, - "Y": 8 - }, - { - "Damaged": false, - "Missed": true, - "X": 1, - "Y": 9 - }, - { - "Damaged": false, - "Missed": true, - "X": 2, - "Y": 0 - }, - { - "Damaged": false, - "Missed": true, - "X": 2, - "Y": 1 - }, - { - "Damaged": false, - "Missed": true, - "X": 2, - "Y": 2 - }, - { - "Damaged": false, - "Missed": false, - "X": 2, - "Y": 3 - }, - { - "Damaged": false, - "Missed": true, - "X": 2, - "Y": 4 - }, - { - "Damaged": false, - "Missed": true, - "X": 2, - "Y": 5 - }, - { - "Damaged": false, - "Missed": true, - "X": 2, - "Y": 6 - }, - { - "Damaged": false, - "Missed": true, - "X": 2, - "Y": 7 - }, - { - "Damaged": false, - "Missed": false, - "X": 2, - "Y": 8 - }, - { - "Damaged": false, - "Missed": true, - "X": 2, - "Y": 9 - }, - { - "Damaged": false, - "Missed": false, - "X": 3, - "Y": 0 - }, - { - "Damaged": true, - "Missed": false, - "X": 3, - "Y": 1 - }, - { - "Damaged": false, - "Missed": false, - "X": 3, - "Y": 2 - }, - { - "Damaged": false, - "Missed": false, - "X": 3, - "Y": 3 - }, - { - "Damaged": false, - "Missed": true, - "X": 3, - "Y": 4 - }, - { - "Damaged": false, - "Missed": true, - "X": 3, - "Y": 5 - }, - { - "Damaged": false, - "Missed": true, - "X": 3, - "Y": 6 - }, - { - "Damaged": false, - "Missed": true, - "X": 3, - "Y": 7 - }, - { - "Damaged": true, - "Missed": false, - "X": 3, - "Y": 8 - }, - { - "Damaged": false, - "Missed": false, - "X": 3, - "Y": 9 - }, - { - "Damaged": false, - "Missed": false, - "X": 4, - "Y": 0 - }, - { - "Damaged": true, - "Missed": false, - "X": 4, - "Y": 1 - }, - { - "Damaged": true, - "Missed": false, - "X": 4, - "Y": 2 - }, - { - "Damaged": true, - "Missed": false, - "X": 4, - "Y": 3 - }, - { - "Damaged": true, - "Missed": false, - "X": 4, - "Y": 4 - }, - { - "Damaged": false, - "Missed": true, - "X": 4, - "Y": 5 - }, - { - "Damaged": false, - "Missed": true, - "X": 4, - "Y": 6 - }, - { - "Damaged": false, - "Missed": false, - "X": 4, - "Y": 7 - }, - { - "Damaged": false, - "Missed": true, - "X": 4, - "Y": 8 - }, - { - "Damaged": false, - "Missed": true, - "X": 4, - "Y": 9 - }, - { - "Damaged": false, - "Missed": true, - "X": 5, - "Y": 0 - }, - { - "Damaged": true, - "Missed": false, - "X": 5, - "Y": 1 - }, - { - "Damaged": false, - "Missed": true, - "X": 5, - "Y": 2 - }, - { - "Damaged": false, - "Missed": true, - "X": 5, - "Y": 3 - }, - { - "Damaged": false, - "Missed": true, - "X": 5, - "Y": 4 - }, - { - "Damaged": false, - "Missed": true, - "X": 5, - "Y": 5 - }, - { - "Damaged": false, - "Missed": true, - "X": 5, - "Y": 6 - }, - { - "Damaged": false, - "Missed": true, - "X": 5, - "Y": 7 - }, - { - "Damaged": false, - "Missed": true, - "X": 5, - "Y": 8 - }, - { - "Damaged": false, - "Missed": false, - "X": 5, - "Y": 9 - }, - { - "Damaged": false, - "Missed": true, - "X": 6, - "Y": 0 - }, - { - "Damaged": true, - "Missed": false, - "X": 6, - "Y": 1 - }, - { - "Damaged": false, - "Missed": true, - "X": 6, - "Y": 2 - }, - { - "Damaged": false, - "Missed": true, - "X": 6, - "Y": 3 - }, - { - "Damaged": false, - "Missed": true, - "X": 6, - "Y": 4 - }, - { - "Damaged": false, - "Missed": true, - "X": 6, - "Y": 5 - }, - { - "Damaged": false, - "Missed": true, - "X": 6, - "Y": 6 - }, - { - "Damaged": false, - "Missed": true, - "X": 6, - "Y": 7 - }, - { - "Damaged": false, - "Missed": true, - "X": 6, - "Y": 8 - }, - { - "Damaged": false, - "Missed": false, - "X": 6, - "Y": 9 - }, - { - "Damaged": false, - "Missed": false, - "X": 7, - "Y": 0 - }, - { - "Damaged": true, - "Missed": false, - "X": 7, - "Y": 1 - }, - { - "Damaged": false, - "Missed": true, - "X": 7, - "Y": 2 - }, - { - "Damaged": true, - "Missed": false, - "X": 7, - "Y": 3 - }, - { - "Damaged": true, - "Missed": false, - "X": 7, - "Y": 4 - }, - { - "Damaged": false, - "Missed": true, - "X": 7, - "Y": 5 - }, - { - "Damaged": false, - "Missed": true, - "X": 7, - "Y": 6 - }, - { - "Damaged": false, - "Missed": true, - "X": 7, - "Y": 7 - }, - { - "Damaged": false, - "Missed": true, - "X": 7, - "Y": 8 - }, - { - "Damaged": false, - "Missed": true, - "X": 7, - "Y": 9 - }, - { - "Damaged": false, - "Missed": true, - "X": 8, - "Y": 0 - }, - { - "Damaged": false, - "Missed": true, - "X": 8, - "Y": 1 - }, - { - "Damaged": false, - "Missed": false, - "X": 8, - "Y": 2 - }, - { - "Damaged": false, - "Missed": true, - "X": 8, - "Y": 3 - }, - { - "Damaged": false, - "Missed": true, - "X": 8, - "Y": 4 - }, - { - "Damaged": false, - "Missed": true, - "X": 8, - "Y": 5 - }, - { - "Damaged": false, - "Missed": true, - "X": 8, - "Y": 6 - }, - { - "Damaged": false, - "Missed": true, - "X": 8, - "Y": 7 - }, - { - "Damaged": false, - "Missed": false, - "X": 8, - "Y": 8 - }, - { - "Damaged": false, - "Missed": true, - "X": 8, - "Y": 9 - }, - { - "Damaged": false, - "Missed": true, - "X": 9, - "Y": 0 - }, - { - "Damaged": false, - "Missed": true, - "X": 9, - "Y": 1 - }, - { - "Damaged": false, - "Missed": true, - "X": 9, - "Y": 2 - }, - { - "Damaged": false, - "Missed": true, - "X": 9, - "Y": 3 - }, - { - "Damaged": false, - "Missed": true, - "X": 9, - "Y": 4 - }, - { - "Damaged": false, - "Missed": true, - "X": 9, - "Y": 5 - }, - { - "Damaged": false, - "Missed": true, - "X": 9, - "Y": 6 - }, - { - "Damaged": false, - "Missed": true, - "X": 9, - "Y": 7 - }, - { - "Damaged": false, - "Missed": true, - "X": 9, - "Y": 8 - }, - { - "Damaged": false, - "Missed": true, - "X": 9, - "Y": 9 - } - ] - }, - "GameVersion": "1.0.0", - "GameLevel": 1, - "Round": 87, - "MapDimension": 10, - "Phase": 2, - "Player1Map": null, - "Player2Map": null -} -#+END_SRC diff --git a/readme.txt b/readme.txt deleted file mode 100644 index ffffa2f..0000000 --- a/readme.txt +++ /dev/null @@ -1,20 +0,0 @@ -* Admiral Worthebot - -** Compilation Instructions - -As per the Rust sample bot. Install the Rust build toolchain from https://www.rust-lang.org/en-US/install.html, then from the root directory of the project run - -cargo build --release - -** Project Structure - -Cargo.toml - Cargo project config, including project dependencies -src/ - Soure code directory -src/main.rs - Command line entrypoint (main function) and command line argument parsing -src/lib.rs - Programs public interface (as used by main.rs and any integration tests) - -** Strategy - -- Track all possible ways that an opponent may have placed their ships -- After every move, deduce which possibilities are now impossible -- Shoot in an attempt to (possibly) eliminate as many possibilities as possible diff --git a/src/actions.rs b/src/actions.rs deleted file mode 100644 index cf0059a..0000000 --- a/src/actions.rs +++ /dev/null @@ -1,90 +0,0 @@ -use math::*; -use ships::*; - -use std::fmt; - -use std::collections::HashSet; - -#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] -pub enum Action { - PlaceShips(Vec), - Shoot(Weapon, Point) -} - -impl fmt::Display for Action { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - &Action::Shoot(w, p) => writeln!(f, "{},{},{}", w, p.x, p.y), - &Action::PlaceShips(ref ships) => ships.iter().map(|ref ship| { - writeln!(f, "{} {} {} {}", ship.ship_type, ship.point.x, ship.point.y, ship.direction) - }).fold(Ok(()), |acc, next| acc.and(next)) - } - } -} - -#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] -pub struct ShipPlacement { - ship_type: Ship, - point: Point, - direction: Direction -} - -impl ShipPlacement { - pub fn new(ship_type: Ship, point: Point, direction: Direction) -> ShipPlacement { - ShipPlacement { - ship_type: ship_type, - point: point, - direction: direction - } - } - - pub fn valid(&self, map_size: u16) -> bool { - let start = self.point; - let end = start.move_point(self.direction, self.ship_type.length() as i32, map_size); - start.x < map_size && start.y < map_size && end.is_some() - } - pub fn valid_placements(placements: &Vec, map_size: u16) -> bool { - let mut occupied = HashSet::new(); - - let individuals_valid = placements.iter().all(|p| p.valid(map_size)); - - let mut no_overlaps = true; - for placement in placements { - for i in 0..placement.ship_type.length() as i32 { - match placement.point.move_point(placement.direction, i, map_size) { - Some(block) => { - no_overlaps = no_overlaps && !occupied.contains(&block); - occupied.insert(block); - }, - None => { - //invalid case here is handled above - } - } - } - - //block out the area around the current ship to prevent adjacent ships - for i in 0..placement.ship_type.length() as i32 { - match placement.point.move_point(placement.direction, i, map_size) { - Some(current_block) => { - if let Some(p) = current_block.move_point(Direction::North, 1, map_size) { - occupied.insert(p); - } - if let Some(p) = current_block.move_point(Direction::South, 1, map_size) { - occupied.insert(p); - } - if let Some(p) = current_block.move_point(Direction::East, 1, map_size) { - occupied.insert(p); - } - if let Some(p) = current_block.move_point(Direction::West, 1, map_size) { - occupied.insert(p); - } - }, - None => { - //invalid case here is handled above - } - } - } - } - individuals_valid && no_overlaps - } -} diff --git a/src/files.rs b/src/files.rs deleted file mode 100644 index 0810a4e..0000000 --- a/src/files.rs +++ /dev/null @@ -1,57 +0,0 @@ -use json; -use serde_json; - -use std::io::prelude::*; -use std::fs::File; -use std::path::PathBuf; - -use actions::*; -use knowledge::*; - -const STATE_FILE: &'static str = "state.json"; - -const COMMAND_FILE: &'static str = "command.txt"; -const PLACE_FILE: &'static str = "place.txt"; - -const KNOWLEDGE_FILE: &'static str = "knowledge-state.json"; - - -pub fn read_file(working_dir: &PathBuf) -> Result { - let state_path = working_dir.join(STATE_FILE); - let mut file = File::open(state_path.as_path()).map_err(|e| e.to_string())?; - let mut content = String::new(); - file.read_to_string(&mut content).map_err(|e| e.to_string())?; - json::parse(content.as_ref()).map_err(|e| e.to_string()) -} - -pub fn write_action(working_dir: &PathBuf, is_place_phase: bool, action: Action) -> Result<(), String> { - let filename = if is_place_phase { - PLACE_FILE - } - else { - COMMAND_FILE - }; - - let full_filename = working_dir.join(filename); - let mut file = File::create(full_filename.as_path()).map_err(|e| e.to_string())?; - write!(file, "{}", action).map_err(|e| e.to_string())?; - - println!("Making move: {}", action); - - Ok(()) -} - -pub fn read_knowledge() -> Result { - let mut file = File::open(KNOWLEDGE_FILE).map_err(|e| e.to_string())?; - let mut content = String::new(); - file.read_to_string(&mut content).map_err(|e| e.to_string())?; - serde_json::from_str(content.as_ref()).map_err(|e| e.to_string()) -} - -pub fn write_knowledge(knowledge: &Knowledge) -> Result<(), String> { - let json = serde_json::to_string(knowledge).map_err(|e| e.to_string())?; - let mut file = File::create(KNOWLEDGE_FILE).map_err(|e| e.to_string())?; - write!(file, "{}", json).map_err(|e| e.to_string())?; - - Ok(()) -} diff --git a/src/knowledge.rs b/src/knowledge.rs deleted file mode 100644 index 4a66e8b..0000000 --- a/src/knowledge.rs +++ /dev/null @@ -1,504 +0,0 @@ -use actions::*; -use ships::*; -use state::*; -use math::*; - -use std::collections::HashMap; -use std::cmp::Ordering; - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct Knowledge { - pub last_action: Action, - pub opponent_map: OpponentMapKnowledge, - pub map_size: u16, - pub available_weapons: Vec, - pub shootable_weapons: Vec, - pub charging_weapons: HashMap -} - -impl Knowledge { - pub fn new(map_size: u16, action: Action) -> Knowledge { - Knowledge { - last_action: action, - opponent_map: OpponentMapKnowledge::new(map_size), - map_size: map_size, - available_weapons: Vec::new(), - shootable_weapons: Vec::new(), - charging_weapons: HashMap::new() - } - } - - pub fn with_action(&self, action: Action) -> Knowledge { - Knowledge { - last_action: action, - ..self.clone() - } - } - - pub fn resolve_last_action(&self, state: &State) -> Knowledge { - let mut new_knowledge = self.clone(); - - let energy = state.player_map.energy; - let mut available_weapons: Vec<_> = state.player_map.ships.iter() - .filter(|&(_, ship_data)| !ship_data.destroyed) - .flat_map(|(ship, _)| ship.weapons()) - .collect(); - - available_weapons.sort_by_key(|weapon| format!("{}",weapon)); - available_weapons.dedup(); - new_knowledge.available_weapons = available_weapons; - - new_knowledge.shootable_weapons = new_knowledge.available_weapons.iter() - .filter(|weapon| weapon.energy_cost(state.map_size) <= energy) - .cloned() - .collect(); - - new_knowledge.charging_weapons = new_knowledge.available_weapons.iter() - .filter(|weapon| weapon.energy_cost(state.map_size) > energy) - .map(|weapon| (weapon.clone(), weapon.single_shot_rounds_to_ready(energy, state.map_size))) - .collect(); - - let (hits, misses, _) = match self.last_action { - Action::PlaceShips(_) => { - (vec!(), vec!(), vec!()) - }, - Action::Shoot(Weapon::SeekerMissle, p) => { - Knowledge::seeker_hits_and_misses(p, &state) - } - - Action::Shoot(w, p) => { - Knowledge::to_hits_and_misses(w.affected_cells(p, state.map_size), &state) - } - }; - - let sunk_ships = new_knowledge.opponent_map.update_sunk_ships(&state); - - new_knowledge.opponent_map.update_from_shot(hits, misses, sunk_ships); - - new_knowledge - } - - fn to_hits_and_misses(points: Vec, state: &State) -> (Vec, Vec, Vec) { - let hits = points.iter().filter(|p| state.opponent_map.cells[p.x as usize][p.y as usize].damaged).cloned().collect(); - let misses = points.iter().filter(|p| state.opponent_map.cells[p.x as usize][p.y as usize].missed).cloned().collect(); - let unknown = points.iter().filter(|p| !state.opponent_map.cells[p.x as usize][p.y as usize].missed && !state.opponent_map.cells[p.x as usize][p.y as usize].damaged).cloned().collect(); - - (hits, misses, unknown) - } - - fn seeker_hits_and_misses(p: Point, state: &State) -> (Vec, Vec, Vec) { - let mut misses: Vec = Vec::new(); - let mut hits: Vec = Vec::new(); - - let rings = vec!( - vec!( - //0 - Some(p) - ), - vec!( - //1 - p.move_point(Direction::North, 1, state.map_size), - p.move_point(Direction::East, 1, state.map_size), - p.move_point(Direction::South, 1, state.map_size), - p.move_point(Direction::West, 1, state.map_size), - ), - vec!( - //1.44 - p.move_point(Direction::NorthEast, 1, state.map_size), - p.move_point(Direction::SouthEast, 1, state.map_size), - p.move_point(Direction::NorthWest, 1, state.map_size), - p.move_point(Direction::SouthWest, 1, state.map_size) - ), - vec!( - //2 - p.move_point(Direction::North, 2, state.map_size), - p.move_point(Direction::East, 2, state.map_size), - p.move_point(Direction::South, 2, state.map_size), - p.move_point(Direction::West, 2, state.map_size), - ) - ); - - //start in the center. Add rings, until I find a hit - //don't add more after a hit is found - for ring in rings { - if hits.is_empty() { - let (mut new_hits, mut new_misses, mut unknown) = Knowledge::to_hits_and_misses(ring.iter().filter_map(|&p| p).collect::>(), &state); - misses.append(&mut new_misses); - if !new_hits.is_empty() { - hits.append(&mut new_hits); - } else { - misses.append(&mut unknown); - } - } - } - - (hits, misses, vec!()) - } - - pub fn has_unknown_hits(&self) -> bool { - self.opponent_map.cells.iter().fold(false, |acc, x| { - x.iter().fold(acc, |acc, y| acc || y.unknown_hit()) - }) - } - - pub fn get_best_adjacent_shots(&self) -> Vec { - let unknown_hits = self.opponent_map.cells_with_unknown_hits(); - let adjacent_cells = self.opponent_map.adjacent_unshot_cells(&unknown_hits); - - let possible_placements = self.opponent_map.possible_placements(); - - let mut max_score = 1; - let mut best_cells = Vec::new(); - - for placement in possible_placements { - for &cell in &adjacent_cells { - let score = placement.count_hit_cells(cell, &unknown_hits); - if score > max_score { - max_score = score; - best_cells = vec!(cell); - } - else if score == max_score { - best_cells.push(cell); - } - } - } - - best_cells - } - - pub fn get_best_seek_shots(&self) -> (Weapon, Vec) { - let possible_placements = self.opponent_map.possible_placements(); - // let lattice = self.lattice_size(); //TODO use the lattice still? - - let guaranteed_hits = self.get_points_that_touch_all_possibilities_on_unsunk_ship(); - if !guaranteed_hits.is_empty() { - return (Weapon::SingleShot, guaranteed_hits); - } - - let mut best_shots: HashMap, usize)> = HashMap::new(); - - for &weapon in self.available_weapons.iter() { - let mut current_best_score = 1; - let mut best_cells = Vec::new(); - - for target in self.opponent_map.flat_cell_position_list() { - let cells = if weapon == Weapon::SeekerMissle { - let full_range = weapon.affected_cells(target, self.map_size); - let has_hits = full_range.iter().any(|p| self.opponent_map.cells[p.x as usize][p.y as usize].hit); - if has_hits { - vec!() - } - else { - full_range - } - } - else { - weapon.affected_cells(target, self.map_size) - .iter() - .filter(|p| !self.opponent_map.cells[p.x as usize][p.y as usize].hit) - .cloned() - .collect() - }; - - let possibilities = possible_placements.iter() - .filter(|placement| placement.touches_any_point(&cells)) - .count(); - - if possibilities > current_best_score { - current_best_score = possibilities; - best_cells = vec!(target); - } - else if possibilities == current_best_score { - best_cells.push(target); - } - } - - best_shots.insert(weapon, (best_cells, current_best_score)); - } - - let best_single: Option<(Weapon, (Vec, usize))> = - best_shots.get(&Weapon::SingleShot).map(|x| (Weapon::SingleShot, x.clone())); - - let best: (Weapon, (Vec, usize)) = - best_shots.iter() - .max_by(|&(weapon_a, &(_, score_a)), &(weapon_b, &(_, score_b))| { - let score = score_a.cmp(&score_b); - let cost = weapon_a.energy_cost(self.map_size).cmp(&weapon_b.energy_cost(self.map_size)); - if score == Ordering::Equal { cost } else { score } - }) - .and_then(|(&weapon, x)| { - if self.shootable_weapons.contains(&weapon) { - Some((weapon, x.clone())) - } else { - best_single - } - }) - .unwrap_or((Weapon::SingleShot, (vec!(), 0))); - - - (best.0.clone(), (best.1).0) - } - - fn get_points_that_touch_all_possibilities_on_unsunk_ship(&self) -> Vec { - self.opponent_map.flat_cell_position_list().iter().cloned().filter(|&point| { - self.opponent_map.ships.values() - .any(|ref ship| !ship.destroyed && - ship.possible_placements.iter().all(|placement| { - placement.touches_point(point) - })) - }).collect() - } - - fn lattice_size(&self) -> u16 { - let any_long_ships = self.opponent_map.ships.iter() - .any(|(ship, knowledge)| ship.length() >= 4 && !knowledge.destroyed); - if any_long_ships { - 4 - } - else { - 2 - } - } -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct OpponentMapKnowledge { - pub ships: HashMap, - pub cells: Vec> -} - -impl OpponentMapKnowledge { - fn new(map_size: u16) -> OpponentMapKnowledge { - let mut cells = Vec::with_capacity(map_size as usize); - for x in 0..map_size { - cells.push(Vec::with_capacity(map_size as usize)); - for y in 0..map_size { - cells[x as usize].push(KnowledgeCell::new(x, y)); - } - } - - let ships = Ship::all_types().iter() - .map(|s| (s.clone(), OpponentShipKnowledge::new(s.clone(), map_size))) - .collect::>(); - - OpponentMapKnowledge { - ships: ships, - cells: cells - } - } - - fn update_sunk_ships(&mut self, state: &State) -> Vec { - let sunk_ships = self.ships.iter() - .filter(|&(_, x)| !x.destroyed) - .filter(|&(s, _)| state.opponent_map.ships.get(s).map(|x| x.destroyed) == Some(true)) - .map(|(s, _)| s.clone()) - .collect(); - - for &ship in &sunk_ships { - self.ships.get_mut(&ship).map(|ref mut ship_knowledge| ship_knowledge.destroyed = true); - } - - sunk_ships - } - - fn update_from_shot(&mut self, hit_cells: Vec, missed_cells: Vec, sunk_ships: Vec) { - for &missed in &missed_cells { - self.cells[missed.x as usize][missed.y as usize].missed = true; - } - for &hit in &hit_cells { - self.cells[hit.x as usize][hit.y as usize].hit = true; - } - - self.clear_sunk_ship_impossible_placements(&sunk_ships, &hit_cells); - - let mut more_changes = true; - while more_changes { - more_changes = self.derive_ship_positions() || self.clear_impossible_placements(); - } - } - - fn derive_ship_positions(&mut self) -> bool { - let mut any_changes = false; - for knowledge in self.ships.values() { - if knowledge.possible_placements.len() == 1 { - let ref true_placement = knowledge.possible_placements[0]; - for p in true_placement.points_on_ship() { - if self.cells[p.x as usize][p.y as usize].known_ship == None { - self.cells[p.x as usize][p.y as usize].known_ship = Some(true_placement.ship); - any_changes = true; - } - } - } - } - any_changes - } - - fn clear_impossible_placements(&mut self) -> bool { - let mut any_changes = false; - let ref cells = self.cells; - for knowledge in self.ships.values_mut() { - let before = knowledge.possible_placements.len(); - knowledge.possible_placements.retain(|x| x.all_could_be_hits(&cells)); - let after = knowledge.possible_placements.len(); - if before != after { - any_changes = true; - } - } - any_changes - } - - fn clear_sunk_ship_impossible_placements(&mut self, sunk_ships: &Vec, must_touch_any: &Vec) { - let cells_copy = self.cells.clone(); - - for knowledge in self.ships.values_mut() { - knowledge.possible_placements.retain(|x| !sunk_ships.contains(&x.ship) || (x.touches_any_point(&must_touch_any) && x.all_are_hits(&cells_copy))); - } - } - - fn cells_with_unknown_hits(&self) -> Vec { - self.cells.iter().flat_map(|x| { - x.iter().filter(|y| y.unknown_hit()).map(|y| y.position) - }).collect() - } - - fn adjacent_unshot_cells(&self, cells: &Vec) -> Vec { - self.cells.iter().flat_map(|x| { - x.iter() - .filter(|y| !y.shot_attempted()) - .map(|y| y.position) - .filter(|&y| cells.iter().any(|z| z.is_adjacent(y))) - }).collect() - } - - fn flat_cell_position_list(&self) -> Vec { - self.cells.iter().flat_map(|x| { - x.iter().map(|y| y.position) - }).collect() - } - - fn cells_on_lattice(&self, lattice_size: u16) -> Vec { - self.cells.iter().flat_map(|x| { - x.iter() - .filter(|y| !y.shot_attempted() && y.position.is_on_lattice(lattice_size)) - .map(|y| y.position) - }).collect() - } - - fn possible_placements(&self) -> Vec { - self.ships - .values() - .flat_map(|knowledge| knowledge.possible_placements.clone()) - .collect() - } -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct OpponentShipKnowledge { - pub ship: Ship, - pub destroyed: bool, - pub possible_placements: Vec -} - -impl OpponentShipKnowledge { - fn new(ship: Ship, map_size: u16) -> OpponentShipKnowledge { - OpponentShipKnowledge { - ship: ship, - destroyed: false, - possible_placements: PossibleShipPlacement::enumerate(ship, map_size) - } - } -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct PossibleShipPlacement { - pub ship: Ship, - pub direction: Direction, - pub position: Point -} - -impl PossibleShipPlacement { - fn enumerate(ship: Ship, map_size: u16) -> Vec { - (0..(map_size-ship.length()+1)).flat_map(move |par| { - (0..map_size).flat_map(move |per| { - vec!( - PossibleShipPlacement { - ship: ship, - direction: Direction::East, - position: Point::new(par, per) - }, - PossibleShipPlacement { - ship: ship, - direction: Direction::North, - position: Point::new(per, par) - } - ) - }) - }).collect() - } - - pub fn touches_point(&self, p: Point) -> bool { - p.check_for_ship_collision(self.position, self.direction, self.ship.length()) - } - pub fn touches_any_point(&self, ps: &Vec) -> bool { - ps.iter().any(|&p| self.touches_point(p)) - } - - pub fn points_on_ship(&self) -> Vec { - (0..self.ship.length() as i32).map(|i| { - self.position.move_point_no_bounds_check(self.direction, i) - }).collect() - } - - fn all_are_hits(&self, cells: &Vec>) -> bool { - self.points_on_ship() - .iter() - .fold(true, |acc, p| acc && cells[p.x as usize][p.y as usize].hit) - } - - fn all_could_be_hits(&self, cells: &Vec>) -> bool { - self.points_on_ship() - .iter() - .all(|p| { - let ref cell = cells[p.x as usize][p.y as usize]; - !cell.missed && cell.known_ship.map(|ship| ship == self.ship).unwrap_or(true) - }) - } - - fn count_hit_cells(&self, required: Point, wanted: &Vec) -> u16 { - if !self.touches_point(required) { - return 0; - } - - wanted.iter().filter(|&&x| self.touches_point(x)).count() as u16 - } -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct KnowledgeCell { - pub missed: bool, - pub hit: bool, - pub known_ship: Option, - pub position: Point -} - -impl KnowledgeCell { - fn new(x: u16, y: u16) -> KnowledgeCell { - KnowledgeCell { - missed: false, - hit: false, - position: Point::new(x, y), - known_ship: None - } - } - - pub fn shot_attempted(&self) -> bool { - self.missed || self.hit - } - - pub fn unknown_hit(&self) -> bool { - self.hit && self.known_ship.is_none() - } - -} - - diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 00eaf02..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,67 +0,0 @@ -extern crate json; -extern crate rand; -extern crate serde; -extern crate serde_json; -#[macro_use] -extern crate serde_derive; - -mod actions; -mod math; -mod files; -mod ships; -mod placement; -mod shooting; -mod state; -mod knowledge; - -use actions::*; -use math::*; -use files::*; -use ships::*; -use placement::*; -use shooting::*; -use state::*; -use knowledge::*; - -use std::path::PathBuf; - -pub fn write_move(working_dir: PathBuf) -> Result<(), String> { - let state_json = read_file(&working_dir)?; - - let is_place_phase = state_json["Phase"] == 1; - let map_size = State::map_size_from_json(&state_json)?; - - let (action, knowledge) = if is_place_phase { - placement(map_size) - } - else { - let state = State::new(&state_json)?; - shoot(&state)? - }; - - write_knowledge(&knowledge) - .map_err(|e| format!("Failed to write knowledge to file. Error: {}", e))?; - - write_action(&working_dir, is_place_phase, action) - .map_err(|e| format!("Failed to write action to file. Error: {}", e))?; - - println!("Knowledge:\n{}\n\n", serde_json::to_string(&knowledge).unwrap_or(String::from(""))); - - Ok(()) -} - -fn placement(map_size: u16) -> (Action, Knowledge) { - let action = place_ships_randomly(map_size); - let knowledge = Knowledge::new(map_size, action.clone()); - - (action, knowledge) -} - -fn shoot(state: &State) -> Result<(Action, Knowledge), String> { - let old_knowledge = read_knowledge()?; - let knowledge = old_knowledge.resolve_last_action(&state); - let action = shoot_smartly(&knowledge); - - Ok((action.clone(), knowledge.with_action(action))) -} - diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index ee0ba59..0000000 --- a/src/main.rs +++ /dev/null @@ -1,19 +0,0 @@ -extern crate worthebot_battleships; - -use worthebot_battleships as bot; -use std::env; -use std::path::PathBuf; - -fn main() { - let working_dir = env::args() - .nth(2) - .map(|x| PathBuf::from(x)) - .ok_or(String::from("Requires game state folder to be passed as the second parameter")); - - let result = working_dir.and_then(|working_dir| bot::write_move(working_dir)); - - match result { - Ok(()) => println!("Bot terminated successfully"), - Err(e) => println!("Error in bot execution: {}", e) - } -} diff --git a/src/math.rs b/src/math.rs deleted file mode 100644 index 3187829..0000000 --- a/src/math.rs +++ /dev/null @@ -1,338 +0,0 @@ -use std::fmt; -use rand; -use rand::distributions::{IndependentSample, Range}; - -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] -pub enum Direction { - North, - NorthEast, - East, - SouthEast, - South, - SouthWest, - West, - NorthWest -} - -use Direction::*; - -impl fmt::Display for Direction { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str( - match self { - &North => "North", - &East => "East", - &South => "South", - &West => "West", - &NorthEast => "NorthEast", - &SouthEast => "SouthEast", - &NorthWest => "NorthWest", - &SouthWest => "SouthWest" - } - ) - } -} - -impl Direction { - pub fn random() -> Direction { - let mut rng = rand::thread_rng(); - let between = Range::new(0, 4); - let dir = between.ind_sample(&mut rng); - match dir { - 0 => North, - 1 => East, - 2 => South, - 3 => West, - _ => panic!("Invalid number generated by random number generator") - } - } -} - -#[cfg(test)] -mod direction_tests { - use super::*; - - #[test] - fn random_direction_does_not_panic() { - for _ in 0..10000 { - Direction::random(); - } - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] -pub struct Point { - pub x: u16, - pub y: u16 -} - -impl Point { - pub fn new(x: u16, y: u16) -> Point { - Point { - x: x, - y: y - } - } - - pub fn random(map_size: u16) -> Point { - let mut rng = rand::thread_rng(); - let between = Range::new(0, map_size); - let x = between.ind_sample(&mut rng); - let y = between.ind_sample(&mut rng); - Point::new(x, y) - } - - - pub fn move_point(&self, direction: Direction, distance: i32, map_size: u16) -> Option { - let x = self.x as i32 + match direction { - West|NorthWest|SouthWest => -distance, - East|NorthEast|SouthEast => distance, - _ => 0 - }; - let y = self.y as i32 + match direction { - South|SouthWest|SouthEast => -distance, - North|NorthWest|NorthEast => distance, - _ => 0 - }; - let max = map_size as i32; - - if x >= max || y >= max || x < 0 || y < 0 { - None - } - else { - Some(Point::new(x as u16, y as u16)) - } - } - - pub fn move_point_no_bounds_check(&self, direction: Direction, distance: i32) -> Point { - let x = self.x as i32 + match direction { - West => -distance, - East => distance, - _ => 0 - }; - let y = self.y as i32 + match direction { - South => -distance, - North => distance, - _ => 0 - }; - - Point::new(x as u16, y as u16) - } - - pub fn check_for_ship_collision(&self, ship_start: Point, direction: Direction, length: u16) -> bool { - let reverse = match direction { - West | South => true, - East | North => false, - _ => false //ships cannot go diagonally - }; - - let same_cross = match direction { - East | West => self.y == ship_start.y, - North | South => self.x == ship_start.x, - _ => false //ships cannot go diagonally - }; - - let (parr_self, parr_ship) = match direction { - East | West => (self.x, ship_start.x), - North | South => (self.y, ship_start.y), - _ => (self.x, self.y) //ships cannot go diagonally - }; - - let corrected_parr_ship = match reverse { - true => 1 + parr_ship - length, - false => parr_ship - }; - - same_cross && parr_self >= corrected_parr_ship && parr_self < corrected_parr_ship + length - } - - pub fn is_adjacent(&self, other: Point) -> bool { - let dx = if self.x > other.x {self.x - other.x} else {other.x - self.x}; - let dy = if self.y > other.y {self.y - other.y} else {other.y - self.y}; - - (dx == 0 && dy == 1) || - (dx == 1 && dy == 0) - - } - - pub fn is_on_lattice(&self, lattice_size: u16) -> bool { - (self.x + self.y) % lattice_size == 0 - } -} - - -#[cfg(test)] -mod point_tests { - use super::*; - - #[test] - fn random_point_is_in_correct_range() { - for _ in 0..10000 { - let point = Point::random(15); - assert!(point.x < 15); - assert!(point.y < 15); - } - } - - #[test] - fn move_point_works_north_west() { - assert_eq!(Some(Point::new(3,7)), Point::new(5,5).move_point(NorthWest, 2, 10)); - assert_eq!(Some(Point::new(7,3)), Point::new(5,5).move_point(NorthWest, -2, 10)); - assert_eq!(None, Point::new(5,5).move_point(NorthWest, 6, 10)); - assert_eq!(None, Point::new(5,5).move_point(NorthWest, -5, 10)); - } - - #[test] - fn move_point_works_west() { - assert_eq!(Some(Point::new(3,5)), Point::new(5,5).move_point(West, 2, 10)); - assert_eq!(Some(Point::new(7,5)), Point::new(5,5).move_point(West, -2, 10)); - assert_eq!(None, Point::new(5,5).move_point(West, 6, 10)); - assert_eq!(None, Point::new(5,5).move_point(West, -5, 10)); - } - - #[test] - fn move_point_works_east() { - assert_eq!(Some(Point::new(7,5)), Point::new(5,5).move_point(East, 2, 10)); - assert_eq!(Some(Point::new(3,5)), Point::new(5,5).move_point(East, -2, 10)); - assert_eq!(None, Point::new(5,5).move_point(East, 5, 10)); - assert_eq!(None, Point::new(5,5).move_point(East, -6, 10)); - } - - #[test] - fn move_point_works_south() { - assert_eq!(Some(Point::new(5,3)), Point::new(5,5).move_point(South, 2, 10)); - assert_eq!(Some(Point::new(5,7)), Point::new(5,5).move_point(South, -2, 10)); - assert_eq!(None, Point::new(5,5).move_point(South, 6, 10)); - assert_eq!(None, Point::new(5,5).move_point(South, -5, 10)); - } - - #[test] - fn move_point_works_north() { - assert_eq!(Some(Point::new(5,7)), Point::new(5,5).move_point(North, 2, 10)); - assert_eq!(Some(Point::new(5,3)), Point::new(5,5).move_point(North, -2, 10)); - assert_eq!(None, Point::new(5,5).move_point(North, 5, 10)); - assert_eq!(None, Point::new(5,5).move_point(North, -6, 10)); - } - - #[test] - fn unrestricted_move_point_works_west() { - assert_eq!(Point::new(3,5), Point::new(5,5).move_point_no_bounds_check(West, 2)); - assert_eq!(Point::new(7,5), Point::new(5,5).move_point_no_bounds_check(West, -2)); - } - - #[test] - fn unrestricted_move_point_works_east() { - assert_eq!(Point::new(7,5), Point::new(5,5).move_point_no_bounds_check(East, 2)); - assert_eq!(Point::new(3,5), Point::new(5,5).move_point_no_bounds_check(East, -2)); - } - - #[test] - fn unrestricted_move_point_works_south() { - assert_eq!(Point::new(5,3), Point::new(5,5).move_point_no_bounds_check(South, 2)); - assert_eq!(Point::new(5,7), Point::new(5,5).move_point_no_bounds_check(South, -2)); - } - - #[test] - fn unrestricted_move_point_works_north() { - assert_eq!(Point::new(5,7), Point::new(5,5).move_point_no_bounds_check(North, 2)); - assert_eq!(Point::new(5,3), Point::new(5,5).move_point_no_bounds_check(North, -2)); - } - - #[test] - fn ship_collision_check_works_west() { - assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,5), West, 5)); - assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(6,5), West, 5)); - assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(7,5), West, 5)); - assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(8,5), West, 5)); - assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(9,5), West, 5)); - assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(10,5), West, 5)); - assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(4,5), West, 5)); - assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(6,4), West, 5)); - assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(6,6), West, 5)); - } - - #[test] - fn ship_collision_check_works_east() { - assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,5), East, 5)); - assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(4,5), East, 5)); - assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(3,5), East, 5)); - assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(2,5), East, 5)); - assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(1,5), East, 5)); - assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(0,5), East, 5)); - assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(6,5), East, 5)); - assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(4,4), East, 5)); - assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(4,6), East, 5)); - } - - #[test] - fn ship_collision_check_works_north() { - assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,5), North, 5)); - assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,4), North, 5)); - assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,3), North, 5)); - assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,2), North, 5)); - assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,1), North, 5)); - assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(5,0), North, 5)); - assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(5,6), North, 5)); - assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(4,4), North, 5)); - assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(6,4), North, 5)); - } - - #[test] - fn ship_collision_check_works_south() { - assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,5), South, 5)); - assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,6), South, 5)); - assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,7), South, 5)); - assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,8), South, 5)); - assert_eq!(true, Point::new(5,5).check_for_ship_collision(Point::new(5,9), South, 5)); - assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(5,10), South, 5)); - assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(5,4), South, 5)); - assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(4,6), South, 5)); - assert_eq!(false, Point::new(5,5).check_for_ship_collision(Point::new(6,6), South, 5)); - } - - #[test] - fn adjacency_check_works() { - assert_eq!(true, Point::new(5,5).is_adjacent(Point::new(4,5))); - assert_eq!(true, Point::new(5,5).is_adjacent(Point::new(6,5))); - assert_eq!(true, Point::new(5,5).is_adjacent(Point::new(5,4))); - assert_eq!(true, Point::new(5,5).is_adjacent(Point::new(5,6))); - - assert_eq!(false, Point::new(5,5).is_adjacent(Point::new(4,4))); - assert_eq!(false, Point::new(5,5).is_adjacent(Point::new(6,6))); - assert_eq!(false, Point::new(5,5).is_adjacent(Point::new(6,4))); - assert_eq!(false, Point::new(5,5).is_adjacent(Point::new(4,6))); - assert_eq!(false, Point::new(5,5).is_adjacent(Point::new(5,5))); - - assert_eq!(false, Point::new(5,5).is_adjacent(Point::new(10,5))); - - } - - #[test] - fn point_on_4_lattice_works() { - assert_eq!(true, Point::new(0,0).is_on_lattice(4)); - assert_eq!(true, Point::new(4,0).is_on_lattice(4)); - assert_eq!(true, Point::new(0,4).is_on_lattice(4)); - assert_eq!(true, Point::new(4,4).is_on_lattice(4)); - assert_eq!(true, Point::new(1,3).is_on_lattice(4)); - assert_eq!(true, Point::new(3,1).is_on_lattice(4)); - - assert_eq!(false, Point::new(0,1).is_on_lattice(4)); - assert_eq!(false, Point::new(0,2).is_on_lattice(4)); - assert_eq!(false, Point::new(0,3).is_on_lattice(4)); - assert_eq!(false, Point::new(1,0).is_on_lattice(4)); - assert_eq!(false, Point::new(2,0).is_on_lattice(4)); - assert_eq!(false, Point::new(3,0).is_on_lattice(4)); - } - - #[test] - fn point_on_2_lattice_works() { - assert_eq!(true, Point::new(0,0).is_on_lattice(2)); - assert_eq!(true, Point::new(2,0).is_on_lattice(2)); - assert_eq!(true, Point::new(0,2).is_on_lattice(2)); - assert_eq!(true, Point::new(2,2).is_on_lattice(2)); - assert_eq!(true, Point::new(1,1).is_on_lattice(2)); - - assert_eq!(false, Point::new(0,1).is_on_lattice(2)); - assert_eq!(false, Point::new(1,0).is_on_lattice(2)); - } -} diff --git a/src/placement.rs b/src/placement.rs deleted file mode 100644 index 4740d76..0000000 --- a/src/placement.rs +++ /dev/null @@ -1,23 +0,0 @@ -use actions::*; -use math::*; -use ships::*; - -pub fn place_ships_randomly(map_size: u16) -> Action { - let mut current_placement: Vec; - - while { - current_placement = create_random_placement(map_size); - !ShipPlacement::valid_placements(¤t_placement, map_size) - } {} - Action::PlaceShips(current_placement) -} - -fn create_random_placement(map_size: u16) -> Vec { - vec!( - ShipPlacement::new(Ship::Battleship, Point::random(map_size), Direction::random()), - ShipPlacement::new(Ship::Carrier, Point::random(map_size), Direction::random()), - ShipPlacement::new(Ship::Cruiser, Point::random(map_size), Direction::random()), - ShipPlacement::new(Ship::Destroyer, Point::random(map_size), Direction::random()), - ShipPlacement::new(Ship::Submarine, Point::random(map_size), Direction::random()) - ) -} diff --git a/src/ships.rs b/src/ships.rs deleted file mode 100644 index 422f24e..0000000 --- a/src/ships.rs +++ /dev/null @@ -1,216 +0,0 @@ -use std::fmt; -use std::str; - -use math::*; - -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] -pub enum Weapon { - SingleShot, - DoubleShotVertical, - DoubleShotHorizontal, - CornerShot, - CrossShotDiagonal, - CrossShotHorizontal, - SeekerMissle -} - -impl fmt::Display for Weapon { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use Weapon::*; - - f.write_str( - match self { - &SingleShot => "1", - &DoubleShotVertical => "2", - &DoubleShotHorizontal => "3", - &CornerShot => "4", - &CrossShotDiagonal => "5", - &CrossShotHorizontal => "6", - &SeekerMissle => "7" - } - ) - } -} - -impl Weapon { - pub fn energy_per_round(map_size: u16) -> u16 { - if map_size < 10 { - 2 - } - else if map_size < 14 { - 3 - } - else { - 4 - } - } - pub fn energy_cost(&self, map_size: u16) -> u16 { - use Weapon::*; - let epr = Weapon::energy_per_round(map_size); - match self { - &SingleShot => 1, - &DoubleShotVertical | &DoubleShotHorizontal => 8*epr, - &CornerShot => 10*epr, - &CrossShotDiagonal => 12*epr, - &CrossShotHorizontal => 14*epr, - &SeekerMissle => 10*epr - } - } - pub fn single_shot_rounds_to_ready(&self, current_energy: u16, map_size: u16) -> u16 { - let single_shot_cost = Weapon::SingleShot.energy_cost(map_size); - let energy_per_round = Weapon::energy_per_round(map_size) - single_shot_cost; - let required_energy = self.energy_cost(map_size) - current_energy; - //weird plus is to make the integer rounding up instead of down - (required_energy + energy_per_round - 1) / energy_per_round - } - - pub fn affected_cells(&self, target: Point, map_size: u16) -> Vec { - use Weapon::*; - - let p = target; - match self { - &SingleShot => { - vec!(Some(p)) - }, - &DoubleShotVertical => { - vec!( - p.move_point(Direction::North, 1, map_size), - p.move_point(Direction::South, 1, map_size) - ) - }, - &DoubleShotHorizontal => { - vec!( - p.move_point(Direction::East, 1, map_size), - p.move_point(Direction::West, 1, map_size) - ) - }, - &CornerShot => { - vec!( - p.move_point(Direction::NorthEast, 1, map_size), - p.move_point(Direction::SouthEast, 1, map_size), - p.move_point(Direction::NorthWest, 1, map_size), - p.move_point(Direction::SouthWest, 1, map_size), - ) - }, - &CrossShotDiagonal => { - vec!( - p.move_point(Direction::NorthEast, 1, map_size), - p.move_point(Direction::SouthEast, 1, map_size), - p.move_point(Direction::NorthWest, 1, map_size), - p.move_point(Direction::SouthWest, 1, map_size), - Some(p) - ) - }, - &CrossShotHorizontal => { - vec!( - p.move_point(Direction::North, 1, map_size), - p.move_point(Direction::East, 1, map_size), - p.move_point(Direction::South, 1, map_size), - p.move_point(Direction::West, 1, map_size), - Some(p) - ) - }, - &SeekerMissle => { - vec!( - Some(p), - - p.move_point(Direction::North, 1, map_size), - p.move_point(Direction::East, 1, map_size), - p.move_point(Direction::South, 1, map_size), - p.move_point(Direction::West, 1, map_size), - - p.move_point(Direction::NorthEast, 1, map_size), - p.move_point(Direction::SouthEast, 1, map_size), - p.move_point(Direction::NorthWest, 1, map_size), - p.move_point(Direction::SouthWest, 1, map_size), - - p.move_point(Direction::North, 2, map_size), - p.move_point(Direction::East, 2, map_size), - p.move_point(Direction::South, 2, map_size), - p.move_point(Direction::West, 2, map_size) - ) - } - }.iter().filter_map(|&p| p).collect::>() - } -} - - -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] -pub enum Ship { - Battleship, - Carrier, - Cruiser, - Destroyer, - Submarine -} - -impl fmt::Display for Ship { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use Ship::*; - - f.write_str( - match self { - &Battleship => "Battleship", - &Carrier => "Carrier", - &Cruiser => "Cruiser", - &Destroyer => "Destroyer", - &Submarine => "Submarine" - } - ) - } -} - -impl str::FromStr for Ship { - type Err = String; - fn from_str(s: &str) -> Result { - use Ship::*; - - match s { - "Battleship" => Ok(Battleship), - "Carrier" => Ok(Carrier), - "Cruiser" => Ok(Cruiser), - "Destroyer" => Ok(Destroyer), - "Submarine" => Ok(Submarine), - _ => Err(String::from("ship type is not known")) - } - } -} - -impl Ship { - pub fn length(&self) -> u16 { - use Ship::*; - - match self { - &Battleship => 4, - &Carrier => 5, - &Cruiser => 3, - &Destroyer => 2, - &Submarine => 3 - } - } - - pub fn weapons(&self) -> Vec { - use Ship::*; - use Weapon::*; - - match self { - &Battleship => vec!(SingleShot, CrossShotDiagonal), - &Carrier => vec!(SingleShot, CornerShot), - &Cruiser => vec!(SingleShot, CrossShotHorizontal), - &Destroyer => vec!(SingleShot, DoubleShotVertical, DoubleShotHorizontal), - &Submarine => vec!(SingleShot, SeekerMissle) - } - } - - pub fn all_types() -> Vec { - use Ship::*; - - vec!( - Battleship, - Carrier, - Cruiser, - Destroyer, - Submarine - ) - } -} diff --git a/src/shooting.rs b/src/shooting.rs deleted file mode 100644 index e0358ee..0000000 --- a/src/shooting.rs +++ /dev/null @@ -1,47 +0,0 @@ -use rand; -use rand::distributions::{IndependentSample, Range}; - -use actions::*; -use math::*; -use knowledge::*; -use ships::*; - -pub fn shoot_smartly(knowledge: &Knowledge) -> Action { - let (weapon, target) = if knowledge.has_unknown_hits() { - destroy_shoot(&knowledge) - } - else { - seek_shoot(&knowledge) - }; - - Action::Shoot(weapon, target) -} - -fn seek_shoot(knowledge: &Knowledge) -> (Weapon, Point) { - let (weapon, possibilities) = knowledge.get_best_seek_shots(); - if possibilities.is_empty() { - println!("All possible shots on the current lattice have been tried!"); - (Weapon::SingleShot, Point::new(0,0)) - } - else { - let mut rng = rand::thread_rng(); - let between = Range::new(0, possibilities.len()); - let i = between.ind_sample(&mut rng); - - (weapon, possibilities[i as usize]) - } -} - -fn destroy_shoot(knowledge: &Knowledge) -> (Weapon, Point) { - let possibilities = knowledge.get_best_adjacent_shots(); - if possibilities.is_empty() { - seek_shoot(&knowledge) - } - else { - let mut rng = rand::thread_rng(); - let between = Range::new(0, possibilities.len()); - let i = between.ind_sample(&mut rng); - - (Weapon::SingleShot, possibilities[i as usize]) - } -} diff --git a/src/state.rs b/src/state.rs deleted file mode 100644 index 1756ad0..0000000 --- a/src/state.rs +++ /dev/null @@ -1,146 +0,0 @@ -use json; -use std::collections::HashMap; -use ships::*; - -pub struct State { - pub map_size: u16, - pub player_map: PlayerMap, - pub opponent_map: OpponentMap -} - -impl State { - pub fn new(json: &json::JsonValue) -> Result { - let map_size = State::map_size_from_json(&json)?; - - let ref player_map_json = json["PlayerMap"]; - let player_map = PlayerMap::new(&player_map_json)?; - - let ref opponent_map_json = json["OpponentMap"]; - let opponent_map = OpponentMap::new(&opponent_map_json, map_size)?; - - Ok(State { - map_size: map_size, - player_map: player_map, - opponent_map: opponent_map - }) - } - - pub fn map_size_from_json(json: &json::JsonValue) -> Result { - json["MapDimension"] - .as_u16() - .ok_or(String::from("Did not find the map dimension in the state json file")) - } -} - -pub struct OpponentMap { - pub cells: Vec>, - pub ships: HashMap, -} - -impl OpponentMap { - fn new(json: &json::JsonValue, map_size: u16) -> Result { - let mut cells = Vec::with_capacity(map_size as usize); - for _ in 0..map_size { - let mut row = Vec::with_capacity(map_size as usize); - for _ in 0..map_size { - row.push(Cell::new()); - } - cells.push(row); - } - - for json_cell in json["Cells"].members() { - let x = json_cell["X"] - .as_u16() - .ok_or(String::from("Failed to read X value of opponent map cell in json file"))?; - let y = json_cell["Y"] - .as_u16() - .ok_or(String::from("Failed to read Y value of opponent map cell in json file"))?; - let damaged = json_cell["Damaged"] - .as_bool() - .ok_or(String::from("Failed to read Damaged value of opponent map cell in json file"))?; - let missed = json_cell["Missed"] - .as_bool() - .ok_or(String::from("Failed to read Missed value of opponent map cell in json file"))?; - - cells[x as usize][y as usize].damaged = damaged; - cells[x as usize][y as usize].missed = missed; - } - - let mut ships = HashMap::new(); - for json_ship in json["Ships"].members() { - let ship_type_string = json_ship["ShipType"] - .as_str() - .ok_or(String::from("Failed to read ShipType value of opponent map ship in json file"))?; - let ship_type = ship_type_string.parse::()?; - - let destroyed = json_ship["Destroyed"] - .as_bool() - .ok_or(String::from("Failed to read Destroyed value of opponent map ship in json file"))?; - ships.insert(ship_type, OpponentShip { - destroyed: destroyed - }); - } - - - Ok(OpponentMap { - cells: cells, - ships: ships - }) - } -} - -pub struct OpponentShip { - pub destroyed: bool -} - -pub struct Cell { - pub damaged: bool, - pub missed: bool -} - -impl Cell { - fn new() -> Cell { - Cell { - damaged: false, - missed: false - } - } -} - -pub struct PlayerMap { - pub ships: HashMap, - pub energy: u16 -} - -impl PlayerMap { - fn new(json: &json::JsonValue) -> Result { - let mut ships = HashMap::new(); - for json_ship in json["Owner"]["Ships"].members() { - let ship_type_string = json_ship["ShipType"] - .as_str() - .ok_or(String::from("Failed to read ShipType value of player map ship in json file"))?; - let ship_type = ship_type_string.parse::()?; - - let destroyed = json_ship["Destroyed"] - .as_bool() - .ok_or(String::from("Failed to read Destroyed value of player map ship in json file"))?; - ships.insert(ship_type, PlayerShip { - destroyed: destroyed - }); - } - - let energy = json["Owner"]["Energy"] - .as_u16() - .ok_or(String::from("Did not find the energy in the state json file"))?; - - Ok(PlayerMap { - ships: ships, - energy: energy - }) - } -} - - -pub struct PlayerShip { - pub destroyed: bool -} -- cgit v1.2.3