summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Worthe <justin@worthe-it.co.za>2019-04-22 11:19:16 +0200
committerJustin Worthe <justin@worthe-it.co.za>2019-04-22 11:19:16 +0200
commit29a323e0a3bd3ab3e6109b23e15bb5f9e88398e3 (patch)
treea151c612b5993f127d99c29d4c4fdcf252528436
Start the project from the starter bot
-rw-r--r--.gitignore2
-rw-r--r--Cargo.lock258
-rw-r--r--Cargo.toml9
-rw-r--r--README.md81
-rw-r--r--bot.json8
-rw-r--r--src/command.rs61
-rw-r--r--src/json.rs441
-rw-r--r--src/main.rs296
8 files changed, 1156 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..84a323e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+target
+**/*.rs.bk
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..054485b
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,258 @@
+[[package]]
+name = "autocfg"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "bitflags"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "cloudabi"
+version = "0.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "fuchsia-cprng"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "itoa"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "libc"
+version = "0.2.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "proc-macro2"
+version = "0.4.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "quote"
+version = "0.6.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "rand_hc"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_isaac"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_jitter"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_os"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_pcg"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_xorshift"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rdrand"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "ryu"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "serde"
+version = "1.0.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "steam-powered-wyrm"
+version = "0.1.0"
+dependencies = [
+ "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "syn"
+version = "0.15.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[metadata]
+"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799"
+"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
+"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
+"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
+"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b"
+"checksum libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "bedcc7a809076656486ffe045abeeac163da1b558e963a31e29fbfbeba916917"
+"checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915"
+"checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db"
+"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
+"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
+"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
+"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0"
+"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
+"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
+"checksum rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b9ea758282efe12823e0d952ddb269d2e1897227e464919a554f2a03ef1b832"
+"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
+"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
+"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
+"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
+"checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7"
+"checksum serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)" = "aa5f7c20820475babd2c077c3ab5f8c77a31c15e16ea38687b4c02d3e48680f4"
+"checksum serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)" = "58fc82bec244f168b23d1963b45c8bf5726e9a15a9d146a067f9081aeed2de79"
+"checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d"
+"checksum syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)" = "d2b4cfac95805274c6afdb12d8f770fa2d27c045953e7b630a81801953699a9a"
+"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
+"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770"
+"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..c18aa9d
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "steam-powered-wyrm"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
+serde = { version = "1.0.90", features = ["derive"] }
+serde_json = "1.0.39"
+rand = "0.6.5"
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a0c3008
--- /dev/null
+++ b/README.md
@@ -0,0 +1,81 @@
+# Steam Powered Wyrm
+
+This is an entry to the 2019 Entelect Challenge.
+
+## Environment Setup
+
+The Rust compiler toolchain can be downloaded from the Rust project
+website.
+
+https://www.rust-lang.org/en-US/install.html
+
+The project requires these versions of the toolchain (or later).
+
+- cargo 1.34.0
+- rustc 1.34.0
+
+## Building
+
+Rust's official build tool is called Cargo. It will download
+dependencies and call the Rust compiler as required. Dependencies are
+configured in [Cargo.toml](./Cargo.toml).
+
+Cargo needs to be called from the root of the bot (the folder with the
+Cargo.toml file).
+
+```sh
+cargo build --release
+```
+
+## Running Tests
+
+Rust has support for unit testing built into the language. Any
+functions marked with the `#[test]` annotation are considered tests.
+
+Tests can be run using Cargo:
+
+```sh
+cargo test
+```
+
+More information on how to write tests is available in
+[The Rust Programming Language](https://doc.rust-lang.org/book/ch11-00-testing.html).
+
+## Exporting the compiled executable
+
+By default, Rust produces statically linked binaries, so you can just
+copy out the executable file from the target directory and put it
+wherever you want.
+
+The name of the binary will match the name of the binary crate in
+Cargo.toml.
+
+Note: This binary has been built for the platform that it was compiled
+on. In other words, if it was compiled on 64 bit Linux, you cannot
+copy the binary to a Windows machine and run it. You WILL be able to
+copy the binary between similar 64 bit Linux machines.
+
+The machine that the compiled binary is run on does not need to have
+the Rust toolchain installed.
+
+```sh
+cp ./target/release/<botFileName> <dest>
+```
+
+## Running
+
+The compiled binary can be executed directly.
+
+```sh
+./target/release/<botFileName>
+```
+
+For convenience in development, you can compile and run through Cargo.
+
+Note: This is not recommended for the tournament servers, since there
+is a small runtime cost in Cargo checking that the compiled binary is
+up to date before running it.
+
+```sh
+cargo run --release
+```
diff --git a/bot.json b/bot.json
new file mode 100644
index 0000000..5578730
--- /dev/null
+++ b/bot.json
@@ -0,0 +1,8 @@
+{
+ "author": "Justin Wernick",
+ "email": "justin@worthe-it.co.za",
+ "nickName": "Steam Powered Wyrm",
+ "botLocation": "/target/release/",
+ "botFileName": "steam-powered-wyrm",
+ "botLanguage": "rust"
+}
diff --git a/src/command.rs b/src/command.rs
new file mode 100644
index 0000000..06dd400
--- /dev/null
+++ b/src/command.rs
@@ -0,0 +1,61 @@
+use std::fmt;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum Command {
+ Move(u32, u32),
+ Dig(u32, u32),
+ Shoot(Direction),
+ DoNothing,
+}
+
+impl fmt::Display for Command {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use Command::*;
+ match self {
+ Move(x, y) => write!(f, "move {} {}", x, y),
+ Dig(x, y) => write!(f, "dig {} {}", x, y),
+ Shoot(dir) => write!(f, "shoot {}", dir),
+ DoNothing => write!(f, "nothing"),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum Direction {
+ North,
+ NorthEast,
+ East,
+ SouthEast,
+ South,
+ SouthWest,
+ West,
+ NorthWest,
+}
+
+impl fmt::Display for Direction {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use Direction::*;
+ let s = match self {
+ North => "N",
+ NorthEast => "NE",
+ East => "E",
+ SouthEast => "SE",
+ South => "S",
+ SouthWest => "SW",
+ West => "W",
+ NorthWest => "NW",
+ };
+ f.write_str(s)
+ }
+}
+
+impl Direction {
+ pub fn is_diagonal(&self) -> bool {
+ use Direction::*;
+
+ match self {
+ NorthEast | SouthEast | SouthWest | NorthWest => true,
+ _ => false,
+ }
+ }
+}
diff --git a/src/json.rs b/src/json.rs
new file mode 100644
index 0000000..3046b7c
--- /dev/null
+++ b/src/json.rs
@@ -0,0 +1,441 @@
+use std::error::Error;
+use std::fs::File;
+use std::io::prelude::*;
+
+use serde::{Deserialize, Serialize};
+use serde_json;
+
+pub fn read_state_from_json_file(filename: &str) -> Result<State, Box<Error>> {
+ let mut file = File::open(filename)?;
+ let mut content = String::new();
+ file.read_to_string(&mut content)?;
+ let state: State = serde_json::from_str(content.as_ref())?;
+
+ Ok(state)
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct State {
+ pub current_round: u32,
+ pub max_rounds: u32,
+ pub map_size: u32,
+ pub current_worm_id: i32,
+ pub consecutive_do_nothing_count: u32,
+ pub my_player: Player,
+ pub opponents: Vec<Opponent>,
+ pub map: Vec<Vec<Cell>>,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct Player {
+ pub id: i32,
+ pub score: i32,
+ pub health: i32,
+ pub worms: Vec<PlayerWorm>,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct PlayerWorm {
+ pub id: i32,
+ pub health: i32,
+ pub position: Position,
+ pub digging_range: u32,
+ pub movement_range: u32,
+ pub weapon: Weapon,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct Opponent {
+ pub id: i32,
+ pub score: i32,
+ pub worms: Vec<OpponentWorm>,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct OpponentWorm {
+ pub id: i32,
+ pub health: i32,
+ pub position: Position,
+ pub digging_range: u32,
+ pub movement_range: u32,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct Cell {
+ pub x: u32,
+ pub y: u32,
+ #[serde(rename = "type")]
+ pub cell_type: CellType,
+ pub occupier: Option<CellWorm>,
+ pub powerup: Option<Powerup>,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
+#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
+pub enum CellType {
+ Air,
+ Dirt,
+ DeepSpace,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
+#[serde(untagged)]
+#[serde(rename_all = "camelCase")]
+pub enum CellWorm {
+ #[serde(rename_all = "camelCase")]
+ PlayerWorm {
+ id: i32,
+ player_id: i32,
+ health: i32,
+ position: Position,
+ digging_range: u32,
+ movement_range: u32,
+ weapon: Weapon,
+ },
+ #[serde(rename_all = "camelCase")]
+ OpponentWorm {
+ id: i32,
+ player_id: i32,
+ health: i32,
+ position: Position,
+ digging_range: u32,
+ movement_range: u32,
+ },
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct Powerup {
+ #[serde(rename = "type")]
+ pub powerup_type: PowerupType,
+ pub value: i32,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
+#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
+pub enum PowerupType {
+ HealthPack,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct Position {
+ pub x: u32,
+ pub y: u32,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct Weapon {
+ pub damage: u32,
+ pub range: u32,
+}
+
+impl State {
+ pub fn active_worm(&self) -> Option<&PlayerWorm> {
+ self.my_player
+ .worms
+ .iter()
+ .find(|w| w.id == self.current_worm_id)
+ }
+
+ pub fn cell_at(&self, pos: &Position) -> Option<&Cell> {
+ self.map
+ .iter()
+ .flatten()
+ .find(|c| c.x == pos.x && c.y == pos.y)
+ }
+}
+
+impl Position {
+ pub fn west(&self, distance: u32) -> Option<Position> {
+ self.x
+ .checked_sub(distance)
+ .map(|x| Position { x, y: self.y })
+ }
+ pub fn east(&self, distance: u32, max: u32) -> Option<Position> {
+ self.x
+ .checked_add(distance)
+ .filter(|&x| x < max)
+ .map(|x| Position { x, y: self.y })
+ }
+ pub fn north(&self, distance: u32) -> Option<Position> {
+ self.y
+ .checked_sub(distance)
+ .map(|y| Position { x: self.x, y })
+ }
+ pub fn south(&self, distance: u32, max: u32) -> Option<Position> {
+ self.y
+ .checked_add(distance)
+ .filter(|&y| y < max)
+ .map(|y| Position { x: self.x, y })
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn example_parses_correctly() {
+ let example = r#"
+{
+ "currentRound": 0,
+ "maxRounds": 200,
+ "mapSize": 33,
+ "currentWormId": 1,
+ "consecutiveDoNothingCount": 0,
+ "myPlayer": {
+ "id": 1,
+ "score": 100,
+ "health": 300,
+ "worms": [
+ {
+ "id": 1,
+ "health": 100,
+ "position": {
+ "x": 24,
+ "y": 29
+ },
+ "weapon": {
+ "damage": 1,
+ "range": 3
+ },
+ "diggingRange": 1,
+ "movementRange": 1
+ }
+ ]
+ },
+ "opponents": [
+ {
+ "id": 2,
+ "score": 100,
+ "worms": [
+ {
+ "id": 1,
+ "health": 100,
+ "position": {
+ "x": 31,
+ "y": 16
+ },
+ "diggingRange": 1,
+ "movementRange": 1
+ }
+ ]
+ }
+ ],
+ "map": [
+ [
+ {
+ "x": 0,
+ "y": 0,
+ "type": "DEEP_SPACE"
+ },
+ {
+ "x": 1,
+ "y": 0,
+ "type": "AIR"
+ },
+ {
+ "x": 2,
+ "y": 0,
+ "type": "DIRT"
+ }
+ ],
+ [
+ {
+ "x": 0,
+ "y": 1,
+ "type": "AIR",
+ "powerup": {
+ "type": "HEALTH_PACK",
+ "value": 5
+ }
+ },
+ {
+ "x": 1,
+ "y": 1,
+ "type": "AIR",
+ "occupier": {
+ "id": 1,
+ "playerId": 2,
+ "health": 100,
+ "position": {
+ "x": 1,
+ "y": 1
+ },
+ "diggingRange": 1,
+ "movementRange": 1
+ }
+ },
+ {
+ "x": 2,
+ "y": 1,
+ "type": "AIR",
+ "occupier": {
+ "id": 1,
+ "playerId": 1,
+ "health": 100,
+ "position": {
+ "x": 2,
+ "y": 1
+ },
+ "weapon": {
+ "damage": 1,
+ "range": 3
+ },
+ "diggingRange": 1,
+ "movementRange": 1
+ }
+ }
+ ]
+ ]
+}"#;
+
+ let expected = State {
+ current_round: 0,
+ max_rounds: 200,
+ map_size: 33,
+ current_worm_id: 1,
+ consecutive_do_nothing_count: 0,
+ my_player: Player {
+ id: 1,
+ score: 100,
+ health: 300,
+ worms: vec![PlayerWorm {
+ id: 1,
+ health: 100,
+ position: Position { x: 24, y: 29 },
+ weapon: Weapon {
+ damage: 1,
+ range: 3,
+ },
+ digging_range: 1,
+ movement_range: 1,
+ }],
+ },
+ opponents: vec![Opponent {
+ id: 2,
+ score: 100,
+ worms: vec![OpponentWorm {
+ id: 1,
+ health: 100,
+ position: Position { x: 31, y: 16 },
+ digging_range: 1,
+ movement_range: 1,
+ }],
+ }],
+ map: vec![
+ vec![
+ Cell {
+ x: 0,
+ y: 0,
+ cell_type: CellType::DeepSpace,
+ occupier: None,
+ powerup: None,
+ },
+ Cell {
+ x: 1,
+ y: 0,
+ cell_type: CellType::Air,
+ occupier: None,
+ powerup: None,
+ },
+ Cell {
+ x: 2,
+ y: 0,
+ cell_type: CellType::Dirt,
+ occupier: None,
+ powerup: None,
+ },
+ ],
+ vec![
+ Cell {
+ x: 0,
+ y: 1,
+ cell_type: CellType::Air,
+ occupier: None,
+ powerup: Some(Powerup {
+ powerup_type: PowerupType::HealthPack,
+ value: 5,
+ }),
+ },
+ Cell {
+ x: 1,
+ y: 1,
+ cell_type: CellType::Air,
+ occupier: Some(CellWorm::OpponentWorm {
+ id: 1,
+ player_id: 2,
+ health: 100,
+ position: Position { x: 1, y: 1 },
+ digging_range: 1,
+ movement_range: 1,
+ }),
+ powerup: None,
+ },
+ Cell {
+ x: 2,
+ y: 1,
+ cell_type: CellType::Air,
+ occupier: Some(CellWorm::PlayerWorm {
+ id: 1,
+ player_id: 1,
+ health: 100,
+ position: Position { x: 2, y: 1 },
+ digging_range: 1,
+ movement_range: 1,
+ weapon: Weapon {
+ damage: 1,
+ range: 3,
+ },
+ }),
+ powerup: None,
+ },
+ ],
+ ],
+ };
+
+ let parsed: State = serde_json::from_str(example).unwrap();
+
+ assert_eq!(
+ parsed, expected,
+ "Parsed value did not match the expected value.\nParsed = {:#?}\nExpected = {:#?}",
+ parsed, expected
+ );
+ }
+
+ #[test]
+ fn west_moving_stays_in_bounds() {
+ let pos = Position { x: 1, y: 1 };
+ assert_eq!(pos.west(1), Some(Position { x: 0, y: 1 }));
+ assert_eq!(pos.west(2), None);
+ }
+
+ #[test]
+ fn east_moving_stays_in_bounds() {
+ let pos = Position { x: 1, y: 1 };
+ assert_eq!(pos.east(1, 3), Some(Position { x: 2, y: 1 }));
+ assert_eq!(pos.east(2, 3), None);
+ }
+
+ #[test]
+ fn north_moving_stays_in_bounds() {
+ let pos = Position { x: 1, y: 1 };
+ assert_eq!(pos.north(1), Some(Position { x: 1, y: 0 }));
+ assert_eq!(pos.north(2), None);
+ }
+
+ #[test]
+ fn south_moving_stays_in_bounds() {
+ let pos = Position { x: 1, y: 1 };
+ assert_eq!(pos.south(1, 3), Some(Position { x: 1, y: 2 }));
+ assert_eq!(pos.south(2, 3), None);
+ }
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..cc39b63
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,296 @@
+use std::io::prelude::*;
+use std::io::stdin;
+
+use rand::prelude::*;
+
+mod command;
+mod json;
+
+use command::*;
+use json::*;
+
+fn main() {
+ for line in stdin().lock().lines() {
+ let round_number = line.expect("Failed to read line from stdin: {}");
+ let command =
+ match read_state_from_json_file(&format!("./rounds/{}/state.json", round_number)) {
+ Ok(state) => choose_command(state),
+ Err(e) => {
+ eprintln!("WARN: State file could not be parsed: {}", e);
+ Command::DoNothing
+ }
+ };
+ println!("C;{};{}", round_number, command);
+ }
+}
+
+fn choose_command(state: State) -> Command {
+ match state.active_worm() {
+ Some(worm) => {
+ if let Some(direction) = find_worm_in_firing_distance(&state, worm) {
+ Command::Shoot(direction)
+ } else {
+ let choices = valid_adjacent_positions(&state, &worm.position);
+ let choice = choices
+ .choose(&mut rand::thread_rng())
+ .expect("No valid directions to move in");
+ let chosen_cell = state.cell_at(&choice);
+
+ match chosen_cell.map(|c| &c.cell_type) {
+ Some(CellType::Air) => Command::Move(choice.x, choice.y),
+ Some(CellType::Dirt) => Command::Dig(choice.x, choice.y),
+ Some(CellType::DeepSpace) | None => Command::DoNothing,
+ }
+ }
+ }
+ None => {
+ eprintln!("WARN: The active worm did not appear in the state file");
+ Command::DoNothing
+ }
+ }
+}
+
+fn find_worm_in_firing_distance(state: &State, worm: &PlayerWorm) -> Option<Direction> {
+ let directions: [(Direction, Box<dyn Fn(&Position, u32) -> Option<Position>>); 8] = [
+ (Direction::West, Box::new(|p, d| p.west(d))),
+ (Direction::NorthWest, Box::new(|p, d| p.north(d).and_then(|p| p.west(d)))),
+ (Direction::North, Box::new(|p, d| p.north(d))),
+ (Direction::NorthEast, Box::new(|p, d| p.north(d).and_then(|p| p.east(d, state.map_size)))),
+ (Direction::East, Box::new(|p, d| p.east(d, state.map_size))),
+ (Direction::SouthEast, Box::new(|p, d| p.south(d, state.map_size).and_then(|p| p.east(d, state.map_size)))),
+ (Direction::South, Box::new(|p, d| p.south(d, state.map_size))),
+ (Direction::SouthWest, Box::new(|p, d| p.south(d, state.map_size).and_then(|p| p.west(d)))),
+ ];
+
+ for (dir, dir_fn) in &directions {
+ let range = adjust_range_for_diagonals(dir, worm.weapon.range);
+
+ for distance in 1..=range {
+ let target = dir_fn(&worm.position, distance);
+ match target.and_then(|t| state.cell_at(&t)) {
+ Some(Cell {
+ occupier: Some(CellWorm::OpponentWorm { .. }),
+ ..
+ }) => return Some(*dir),
+ Some(Cell {
+ cell_type: CellType::Air,
+ ..
+ }) => continue,
+ _ => break,
+ }
+ }
+ }
+ None
+}
+
+fn adjust_range_for_diagonals(dir: &Direction, straight_range: u32) -> u32 {
+ if dir.is_diagonal() {
+ ((straight_range as f32 + 1.) / 2f32.sqrt()).floor() as u32
+ } else {
+ straight_range
+ }
+}
+
+fn valid_adjacent_positions(state: &State, pos: &Position) -> Vec<Position> {
+ let choices = [
+ pos.west(1),
+ pos.west(1).and_then(|p| p.north(1)),
+ pos.north(1),
+ pos.north(1).and_then(|p| p.east(1, state.map_size)),
+ pos.east(1, state.map_size),
+ pos.east(1, state.map_size)
+ .and_then(|p| p.south(1, state.map_size)),
+ pos.south(1, state.map_size),
+ pos.south(1, state.map_size).and_then(|p| p.west(1)),
+ ];
+ choices.iter().flatten().cloned().collect()
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn adjacent_positions_give_valid_positions() {
+ let dummy_state = State {
+ current_round: 0,
+ max_rounds: 0,
+ map_size: 3,
+ current_worm_id: 0,
+ consecutive_do_nothing_count: 0,
+ my_player: Player {
+ id: 0,
+ score: 0,
+ health: 0,
+ worms: Vec::new(),
+ },
+ opponents: Vec::new(),
+ map: Vec::new(),
+ };
+
+ assert_eq!(
+ 3,
+ valid_adjacent_positions(&dummy_state, &Position { x: 0, y: 0 }).len()
+ );
+ assert_eq!(
+ 5,
+ valid_adjacent_positions(&dummy_state, &Position { x: 1, y: 0 }).len()
+ );
+ assert_eq!(
+ 3,
+ valid_adjacent_positions(&dummy_state, &Position { x: 2, y: 0 }).len()
+ );
+ assert_eq!(
+ 5,
+ valid_adjacent_positions(&dummy_state, &Position { x: 0, y: 1 }).len()
+ );
+ assert_eq!(
+ 8,
+ valid_adjacent_positions(&dummy_state, &Position { x: 1, y: 1 }).len()
+ );
+ assert_eq!(
+ 5,
+ valid_adjacent_positions(&dummy_state, &Position { x: 2, y: 1 }).len()
+ );
+ assert_eq!(
+ 3,
+ valid_adjacent_positions(&dummy_state, &Position { x: 0, y: 2 }).len()
+ );
+ assert_eq!(
+ 5,
+ valid_adjacent_positions(&dummy_state, &Position { x: 1, y: 2 }).len()
+ );
+ assert_eq!(
+ 3,
+ valid_adjacent_positions(&dummy_state, &Position { x: 2, y: 2 }).len()
+ );
+ }
+
+ #[test]
+ fn range_adjustment_matches_examples() {
+ assert_eq!(1, adjust_range_for_diagonals(&Direction::East, 1));
+ assert_eq!(2, adjust_range_for_diagonals(&Direction::East, 2));
+ assert_eq!(3, adjust_range_for_diagonals(&Direction::East, 3));
+ assert_eq!(4, adjust_range_for_diagonals(&Direction::East, 4));
+
+ assert_eq!(1, adjust_range_for_diagonals(&Direction::SouthEast, 1));
+ assert_eq!(2, adjust_range_for_diagonals(&Direction::SouthEast, 2));
+ assert_eq!(2, adjust_range_for_diagonals(&Direction::SouthEast, 3));
+ assert_eq!(3, adjust_range_for_diagonals(&Direction::SouthEast, 4));
+ }
+
+ mod find_worm_in_firing_distance {
+ use super::super::*;
+
+ fn worm_shooting_dummy_state() -> (State, PlayerWorm) {
+ let dummy_state = State {
+ current_round: 0,
+ max_rounds: 0,
+ map_size: 5,
+ current_worm_id: 0,
+ consecutive_do_nothing_count: 0,
+ my_player: Player {
+ id: 0,
+ score: 0,
+ health: 0,
+ worms: Vec::new(),
+ },
+ opponents: Vec::new(),
+ map: vec![Vec::new()],
+ };
+ let active_worm = PlayerWorm {
+ id: 0,
+ health: 100,
+ position: Position { x: 2, y: 2 },
+ digging_range: 1,
+ movement_range: 1,
+ weapon: Weapon {
+ range: 3,
+ damage: 1,
+ },
+ };
+
+ (dummy_state, active_worm)
+ }
+
+ #[test]
+ fn finds_a_worm_that_can_be_shot() {
+ let (mut dummy_state, active_worm) = worm_shooting_dummy_state();
+ dummy_state.map[0].push(Cell {
+ x: 3,
+ y: 2,
+ cell_type: CellType::Air,
+ occupier: None,
+ powerup: None,
+ });
+ dummy_state.map[0].push(Cell {
+ x: 4,
+ y: 2,
+ cell_type: CellType::Air,
+ occupier: Some(CellWorm::OpponentWorm {
+ id: 0,
+ player_id: 1,
+ health: 0,
+ position: Position { x: 4, y: 2 },
+ digging_range: 1,
+ movement_range: 1,
+ }),
+ powerup: None,
+ });
+
+ let firing_dir = find_worm_in_firing_distance(&dummy_state, &active_worm);
+ assert_eq!(Some(Direction::East), firing_dir);
+ }
+
+ #[test]
+ fn worm_cant_shoot_through_dirt() {
+ let (mut dummy_state, active_worm) = worm_shooting_dummy_state();
+ dummy_state.map[0].push(Cell {
+ x: 3,
+ y: 2,
+ cell_type: CellType::Dirt,
+ occupier: None,
+ powerup: None,
+ });
+ dummy_state.map[0].push(Cell {
+ x: 4,
+ y: 2,
+ cell_type: CellType::Air,
+ occupier: Some(CellWorm::OpponentWorm {
+ id: 0,
+ player_id: 1,
+ health: 0,
+ position: Position { x: 4, y: 2 },
+ digging_range: 1,
+ movement_range: 1,
+ }),
+ powerup: None,
+ });
+
+ let firing_dir = find_worm_in_firing_distance(&dummy_state, &active_worm);
+ assert_eq!(None, firing_dir);
+ }
+
+ #[test]
+ fn identifies_lack_of_worms_to_shoot() {
+ let (mut dummy_state, active_worm) = worm_shooting_dummy_state();
+ dummy_state.map[0].push(Cell {
+ x: 3,
+ y: 2,
+ cell_type: CellType::Air,
+ occupier: None,
+ powerup: None,
+ });
+ dummy_state.map[0].push(Cell {
+ x: 4,
+ y: 2,
+ cell_type: CellType::Air,
+ occupier: None,
+ powerup: None,
+ });
+
+ let firing_dir = find_worm_in_firing_distance(&dummy_state, &active_worm);
+ assert_eq!(None, firing_dir);
+ }
+ }
+}