From 057a4fe886848322adf4e48ad9709a289434dc1b Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Sat, 5 May 2018 20:37:53 +0200 Subject: Initial commit with sample bot and embedded game engine --- src/engine/command.rs | 27 ++++++ src/engine/geometry.rs | 24 ++++++ src/engine/mod.rs | 220 +++++++++++++++++++++++++++++++++++++++++++++++++ src/engine/settings.rs | 10 +++ src/main.rs | 53 ++++++++++++ src/state_json.rs | 86 +++++++++++++++++++ 6 files changed, 420 insertions(+) create mode 100644 src/engine/command.rs create mode 100644 src/engine/geometry.rs create mode 100644 src/engine/mod.rs create mode 100644 src/engine/settings.rs create mode 100644 src/main.rs create mode 100644 src/state_json.rs (limited to 'src') 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 { + self.x.checked_sub(1).map(|x| Point { + x: x, + ..*self + }) + } + pub fn move_right(&self, size: &Point) -> Option { + 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, + opponent_buildings: Vec, + player_missiles: Vec, + opponent_missiles: Vec +} + +#[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, 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) { + for building in buildings.iter_mut().filter(|b| !b.is_constructed()) { + building.construction_time_left -= 1; + } + } + + fn add_missiles(buildings: &mut Vec, missiles: &mut Vec) { + 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(missiles: &mut Vec, move_fn: F, opponent_buildings: &mut Vec, opponent: &mut Player) + where F: Fn(Point) -> Option { + 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) { + player.energy += settings.energy_income; + player.energy += buildings.iter().map(|b| b.energy_generated_per_turn).sum::(); + } + + 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 { + None +} + + +fn write_command(filename: &str, command: Option) -> Result<(), Box > { + 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> { + 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, + pub game_map: Vec>, +} + +#[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, + pub missiles: Vec, + 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 +} -- cgit v1.2.3