summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJustin Worthe <justin@worthe-it.co.za>2018-05-05 20:37:53 +0200
committerJustin Worthe <justin@worthe-it.co.za>2018-05-05 20:37:53 +0200
commit057a4fe886848322adf4e48ad9709a289434dc1b (patch)
tree5651d000fc642387d8c8e1ea410a26d4e7e7689f /src
Initial commit with sample bot and embedded game engine
Diffstat (limited to 'src')
-rw-r--r--src/engine/command.rs27
-rw-r--r--src/engine/geometry.rs24
-rw-r--r--src/engine/mod.rs220
-rw-r--r--src/engine/settings.rs10
-rw-r--r--src/main.rs53
-rw-r--r--src/state_json.rs86
6 files changed, 420 insertions, 0 deletions
diff --git a/src/engine/command.rs b/src/engine/command.rs
new file mode 100644
index 0000000..603ee42
--- /dev/null
+++ b/src/engine/command.rs
@@ -0,0 +1,27 @@
+use std::fmt;
+use super::geometry::Point;
+
+#[derive(Debug, Clone, Copy)]
+pub enum Command {
+ Nothing,
+ Build(Point, BuildingType),
+}
+
+impl fmt::Display for Command {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use Command::*;
+
+ match self {
+ &Nothing => write!(f, ""),
+ &Build(p, b) => write!(f, "{},{},{}", p.x, p.y, b as u8),
+ }
+ }
+}
+
+#[repr(u8)]
+#[derive(Debug, Clone, Copy)]
+pub enum BuildingType {
+ Defense = 0,
+ Attack = 1,
+ Energy = 2,
+}
diff --git a/src/engine/geometry.rs b/src/engine/geometry.rs
new file mode 100644
index 0000000..f2a2522
--- /dev/null
+++ b/src/engine/geometry.rs
@@ -0,0 +1,24 @@
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Point {
+ pub x: u8,
+ pub y: u8
+}
+
+impl Point {
+ pub fn move_left(&self) -> Option<Point> {
+ self.x.checked_sub(1).map(|x| Point {
+ x: x,
+ ..*self
+ })
+ }
+ pub fn move_right(&self, size: &Point) -> Option<Point> {
+ if self.x + 1 >= size.x {
+ None
+ } else {
+ Some(Point {
+ x: self.x + 1,
+ ..*self
+ })
+ }
+ }
+}
diff --git a/src/engine/mod.rs b/src/engine/mod.rs
new file mode 100644
index 0000000..d321572
--- /dev/null
+++ b/src/engine/mod.rs
@@ -0,0 +1,220 @@
+pub mod command;
+pub mod geometry;
+pub mod settings;
+
+use self::command::{BuildingType, Command};
+use self::geometry::Point;
+use self::settings::GameSettings;
+
+use std::ops::Fn;
+use std::cmp;
+
+#[derive(Debug, Clone)]
+struct GameState {
+ status: GameStatus,
+ player: Player,
+ opponent: Player,
+ player_buildings: Vec<Building>,
+ opponent_buildings: Vec<Building>,
+ player_missiles: Vec<Missile>,
+ opponent_missiles: Vec<Missile>
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum GameStatus {
+ Continue,
+ PlayerWon,
+ OpponentWon,
+ Draw,
+ InvalidMove
+}
+
+impl GameStatus {
+ fn is_complete(&self) -> bool {
+ *self != GameStatus::Continue
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct Player {
+ energy: u16,
+ health: u16
+}
+
+#[derive(Debug, Clone)]
+struct Building {
+ pos: Point,
+ health: u16,
+ construction_time_left: u8,
+ weapon_damage: u16,
+ weapon_speed: u8,
+ weapon_cooldown_time_left: u8,
+ weapon_cooldown_period: u8,
+ energy_generated_per_turn: u16
+}
+
+impl Building {
+ fn new(pos: Point, building: BuildingType) -> Building {
+ match building {
+ BuildingType::Defense => Building {
+ pos: pos,
+ health: 20,
+ construction_time_left: 3,
+ weapon_damage: 0,
+ weapon_speed: 0,
+ weapon_cooldown_time_left: 0,
+ weapon_cooldown_period: 0,
+ energy_generated_per_turn: 0
+ },
+ BuildingType::Attack => Building {
+ pos: pos,
+ health: 5,
+ construction_time_left: 1,
+ weapon_damage: 5,
+ weapon_speed: 1,
+ weapon_cooldown_time_left: 0,
+ weapon_cooldown_period: 3,
+ energy_generated_per_turn: 0
+ },
+ BuildingType::Energy => Building {
+ pos: pos,
+ health: 5,
+ construction_time_left: 1,
+ weapon_damage: 0,
+ weapon_speed: 0,
+ weapon_cooldown_time_left: 0,
+ weapon_cooldown_period: 0,
+ energy_generated_per_turn: 3
+ }
+ }
+
+ }
+
+ fn is_constructed(&self) -> bool {
+ self.construction_time_left == 0
+ }
+
+ fn is_shooty(&self) -> bool {
+ self.is_constructed() && self.weapon_damage >= 0
+ }
+}
+
+#[derive(Debug, Clone)]
+struct Missile {
+ pos: Point,
+ damage: u16,
+ speed: u8,
+}
+
+impl Missile {
+ fn is_stopped(&self) -> bool {
+ self.speed == 0
+ }
+}
+
+impl GameState {
+ pub fn simulate(&self, settings: &GameSettings, player_command: Command, opponent_command: Command) -> GameState {
+ if self.status.is_complete() {
+ return self.clone();
+ }
+
+ let mut state = self.clone();
+ GameState::perform_command(&mut state.player_buildings, player_command, &settings.size);
+ GameState::perform_command(&mut state.opponent_buildings, opponent_command, &settings.size);
+
+ GameState::update_construction(&mut state.player_buildings);
+ GameState::update_construction(&mut state.opponent_buildings);
+
+ GameState::add_missiles(&mut state.player_buildings, &mut state.player_missiles);
+ GameState::add_missiles(&mut state.opponent_buildings, &mut state.opponent_missiles);
+
+ GameState::move_missiles(&mut state.player_missiles, |p| p.move_right(&settings.size),
+ &mut state.opponent_buildings, &mut state.opponent);
+ GameState::move_missiles(&mut state.opponent_missiles, |p| p.move_left(),
+ &mut state.player_buildings, &mut state.player);
+
+ GameState::add_energy(&mut state.player, settings, &state.player_buildings);
+ GameState::add_energy(&mut state.opponent, settings, &state.opponent_buildings);
+
+ GameState::update_status(&mut state);
+ state
+ }
+
+ fn perform_command(buildings: &mut Vec<Building>, command: Command, size: &Point) -> bool {
+ match command {
+ Command::Nothing => { true },
+ Command::Build(p, b) => {
+ let occupied = buildings.iter().any(|b| b.pos == p);
+ let in_range = p.x < size.x && p.y < size.y;
+ buildings.push(Building::new(p, b));
+ !occupied && in_range
+ },
+ }
+ }
+
+ fn update_construction(buildings: &mut Vec<Building>) {
+ for building in buildings.iter_mut().filter(|b| !b.is_constructed()) {
+ building.construction_time_left -= 1;
+ }
+ }
+
+ fn add_missiles(buildings: &mut Vec<Building>, missiles: &mut Vec<Missile>) {
+ for building in buildings.iter_mut().filter(|b| b.is_shooty()) {
+ if building.weapon_cooldown_time_left > 0 {
+ building.weapon_cooldown_time_left -= 1;
+ } else {
+ missiles.push(Missile {
+ pos: building.pos,
+ speed: building.weapon_speed,
+ damage: building.weapon_damage,
+ });
+ building.weapon_cooldown_time_left = building.weapon_cooldown_period;
+ }
+ }
+ }
+
+ fn move_missiles<F>(missiles: &mut Vec<Missile>, move_fn: F, opponent_buildings: &mut Vec<Building>, opponent: &mut Player)
+ where F: Fn(Point) -> Option<Point> {
+ for missile in missiles.iter_mut() {
+ for _ in 0..missile.speed {
+ match move_fn(missile.pos) {
+ None => {
+ let damage = cmp::min(missile.damage, opponent.health);
+ opponent.health -= damage;
+ missile.speed = 0;
+ },
+ Some(point) => {
+ missile.pos = point;
+ for hit in opponent_buildings.iter_mut().filter(|b| b.is_constructed() && b.pos == point && b.health > 0) {
+ let damage = cmp::min(missile.damage, hit.health);
+ hit.health -= damage;
+ missile.speed = 0;
+ }
+ }
+ }
+
+ if missile.speed == 0 {
+ break;
+ }
+ }
+ }
+ missiles.retain(|m| m.speed > 0);
+ opponent_buildings.retain(|b| b.health > 0);
+ }
+
+ fn add_energy(player: &mut Player, settings: &GameSettings, buildings: &Vec<Building>) {
+ player.energy += settings.energy_income;
+ player.energy += buildings.iter().map(|b| b.energy_generated_per_turn).sum::<u16>();
+ }
+
+ fn update_status(state: &mut GameState) {
+ let player_dead = state.player.health == 0;
+ let opponent_dead = state.player.health == 0;
+ state.status = match (player_dead, opponent_dead) {
+ (true, true) => GameStatus::Draw,
+ (true, false) => GameStatus::PlayerWon,
+ (false, true) => GameStatus::OpponentWon,
+ (false, false) => GameStatus::Continue,
+ };
+ }
+}
diff --git a/src/engine/settings.rs b/src/engine/settings.rs
new file mode 100644
index 0000000..a6691d7
--- /dev/null
+++ b/src/engine/settings.rs
@@ -0,0 +1,10 @@
+use super::geometry::Point;
+
+#[derive(Debug)]
+pub struct GameSettings {
+ pub size: Point,
+ pub energy_income: u16,
+ pub energy_price: u16,
+ pub defence_price: u16,
+ pub attack_price: u16
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..d964fee
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,53 @@
+extern crate serde;
+extern crate serde_json;
+
+#[macro_use]
+extern crate serde_derive;
+
+use std::error::Error;
+
+const STATE_PATH: &str = "state.json";
+
+const COMMAND_PATH: &str = "command.txt";
+
+use std::fs::File;
+use std::io::prelude::*;
+use std::process;
+
+mod state_json;
+mod engine;
+use engine::command::Command;
+
+fn choose_move(_state: &state_json::State) -> Option<Command> {
+ None
+}
+
+
+fn write_command(filename: &str, command: Option<Command>) -> Result<(), Box<Error> > {
+ let mut file = File::create(filename)?;
+ if let Some(command) = command {
+ write!(file, "{}", command)?;
+ }
+
+ Ok(())
+}
+
+
+fn main() {
+ let state = match state_json::read_state_from_file(STATE_PATH) {
+ Ok(state) => state,
+ Err(error) => {
+ eprintln!("Failed to read the {} file. {}", STATE_PATH, error);
+ process::exit(1);
+ }
+ };
+ let command = choose_move(&state);
+
+ match write_command(COMMAND_PATH, command) {
+ Ok(()) => {}
+ Err(error) => {
+ eprintln!("Failed to write the {} file. {}", COMMAND_PATH, error);
+ process::exit(1);
+ }
+ }
+}
diff --git a/src/state_json.rs b/src/state_json.rs
new file mode 100644
index 0000000..429db6d
--- /dev/null
+++ b/src/state_json.rs
@@ -0,0 +1,86 @@
+use std::fs::File;
+use std::io::prelude::*;
+use serde_json;
+use std::error::Error;
+
+pub fn read_state_from_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 = serde_json::from_str(content.as_ref())?;
+ Ok(state)
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct State {
+ pub game_details: GameDetails,
+ pub players: Vec<Player>,
+ pub game_map: Vec<Vec<GameCell>>,
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct GameDetails {
+ pub round: u32,
+ pub map_width: u32,
+ pub map_height: u32,
+ pub building_prices: BuildingPrices
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
+pub struct BuildingPrices {
+ pub energy: u32,
+ pub defense: u32,
+ pub attack: u32
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Player {
+ pub player_type: char,
+ pub energy: u32,
+ pub health: u32,
+ pub hits_taken: u32,
+ pub score: u32
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct GameCell {
+ pub x: u32,
+ pub y: u32,
+ pub buildings: Vec<BuildingState>,
+ pub missiles: Vec<MissileState>,
+ pub cell_owner: char
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BuildingState {
+ pub health: u32,
+ pub construction_time_left: i32,
+ pub price: u32,
+ pub weapon_damage: u32,
+ pub weapon_speed: u32,
+ pub weapon_cooldown_time_left: u32,
+ pub weapon_cooldown_period: u32,
+ pub destroy_multiplier: u32,
+ pub construction_score: u32,
+ pub energy_generated_per_turn: u32,
+ pub building_type: String,
+ pub x: u32,
+ pub y: u32,
+ pub player_type: char
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct MissileState {
+ pub damage: u32,
+ pub speed: u32,
+ pub x: u32,
+ pub y: u32,
+ pub player_type: char
+}