From 3f5492b2bb67326be43cd7c5ba02ccf0ba1ae0e3 Mon Sep 17 00:00:00 2001 From: Justin Wernick Date: Tue, 19 Apr 2022 21:27:56 +0200 Subject: Refile for merging repos --- 2019-worms/src/json.rs | 554 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 554 insertions(+) create mode 100644 2019-worms/src/json.rs (limited to '2019-worms/src/json.rs') diff --git a/2019-worms/src/json.rs b/2019-worms/src/json.rs new file mode 100644 index 0000000..a83f102 --- /dev/null +++ b/2019-worms/src/json.rs @@ -0,0 +1,554 @@ +use std::error::Error; +use std::fs::File; +use std::io::prelude::*; +use std::path::Path; + +use serde::{Deserialize, Serialize}; +use serde_json; + +pub fn read_state_from_json_file(filename: &Path) -> Result> { + 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) +} + +// TODO: Narrow numeric types +// TODO: comment out stuff I don't want / need + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct State { + pub current_round: u16, + pub max_rounds: u16, + pub pushback_damage: i32, + pub lava_damage: i32, + pub map_size: u8, + pub current_worm_id: i32, + pub consecutive_do_nothing_count: u32, + pub my_player: Player, + pub opponents: Vec, + pub map: Vec>, +} + +#[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, + pub remaining_worm_selections: u8, +} + +impl Player { + pub fn health_score(&self) -> i32 { + self.health / 3 + } +} + +#[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 rounds_until_unfrozen: u8, + pub weapon: Weapon, + pub banana_bombs: Option, + pub snowballs: Option, + pub profession: WormType, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Opponent { + pub id: i32, + pub score: i32, + pub current_worm_id: i32, + pub previous_command: String, + pub worms: Vec, + pub remaining_worm_selections: u8, +} + +impl Opponent { + pub fn health_score(&self) -> i32 { + self.worms.iter().map(|w| w.health).sum::() / 3 + } +} + +#[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, + pub rounds_until_unfrozen: u8, + pub profession: WormType, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "PascalCase")] +pub enum WormType { + Commando, + Agent, + Technologist, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Cell { + pub x: i8, + pub y: i8, + #[serde(rename = "type")] + pub cell_type: CellType, + pub occupier: Option, + pub powerup: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum CellType { + Air, + Dirt, + Lava, + 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: i8, + pub y: i8, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Weapon { + pub damage: i32, + pub range: u8, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Bomb { + pub damage: i32, + pub range: u8, + pub count: u8, + pub damage_radius: u8, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Snowball { + pub freeze_duration: u8, + pub range: u8, + pub count: u8, + pub freeze_radius: u8, +} + +impl State { + pub fn active_worm_index(&self) -> Option { + self.my_player + .worms + .iter() + .filter(|w| w.health > 0) + .position(|w| w.id == self.current_worm_id) + } + + pub fn opponent_active_worm_index(&self) -> Option { + self.opponents[0] + .worms + .iter() + .filter(|w| w.health > 0) + .position(|w| w.id == self.opponents[0].current_worm_id) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn example_parses_correctly() { + let example = r#" +{ + "currentRound": 0, + "maxRounds": 200, + "pushbackDamage": 20, + "lavaDamage": 3, + "mapSize": 33, + "currentWormId": 1, + "consecutiveDoNothingCount": 0, + "myPlayer": { + "id": 1, + "score": 100, + "health": 300, + "currentWormId": 1, + "remainingWormSelections": 1, + "worms": [ + { + "id": 1, + "health": 100, + "position": { + "x": 24, + "y": 29 + }, + "weapon": { + "damage": 1, + "range": 3 + }, + "bananaBombs": { + "damage": 20, + "range": 5, + "count": 3, + "damageRadius": 2 + }, + "diggingRange": 1, + "movementRange": 1, + "roundsUntilUnfrozen": 0, + "profession": "Agent" + }, + { + "id": 2, + "health": 150, + "position": { + "x": 1, + "y": 16 + }, + "weapon": { + "damage": 1, + "range": 3 + }, + "diggingRange": 1, + "movementRange": 1, + "roundsUntilUnfrozen": 0, + "profession": "Commando" + }, + { + "id": 3, + "health": 100, + "position": { + "x": 24, + "y": 4 + }, + "weapon": { + "damage": 8, + "range": 4 + }, + "snowballs": { + "freezeDuration": 5, + "range": 5, + "count": 3, + "freezeRadius": 1 + }, + "diggingRange": 1, + "movementRange": 1, + "roundsUntilUnfrozen": 3, + "profession": "Technologist" + } + ] + }, + "opponents": [ + { + "id": 2, + "score": 100, + "currentWormId": 3, + "remainingWormSelections": 2, + "previousCommand": "nothing", + "worms": [ + { + "id": 1, + "health": 100, + "position": { + "x": 31, + "y": 16 + }, + "diggingRange": 1, + "movementRange": 1, + "roundsUntilUnfrozen": 0, + "profession": "Commando" + } + ] + } + ], + "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, + pushback_damage: 20, + lava_damage: 3, + map_size: 33, + current_worm_id: 1, + consecutive_do_nothing_count: 0, + my_player: Player { + id: 1, + score: 100, + health: 300, + remaining_worm_selections: 1, + 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, + banana_bombs: Some(Bomb { + damage: 20, + range: 5, + count: 3, + damage_radius: 2, + }), + snowballs: None, + rounds_until_unfrozen: 0, + profession: WormType::Agent, + }, + PlayerWorm { + id: 2, + health: 150, + position: Position { x: 1, y: 16 }, + weapon: Weapon { + damage: 1, + range: 3, + }, + digging_range: 1, + movement_range: 1, + banana_bombs: None, + snowballs: None, + rounds_until_unfrozen: 0, + profession: WormType::Commando, + }, + PlayerWorm { + id: 3, + health: 100, + position: Position { x: 24, y: 4 }, + weapon: Weapon { + damage: 8, + range: 4, + }, + digging_range: 1, + movement_range: 1, + banana_bombs: None, + snowballs: Some(Snowball { + freeze_duration: 5, + range: 5, + count: 3, + freeze_radius: 1, + }), + rounds_until_unfrozen: 3, + profession: WormType::Technologist, + }, + ], + }, + opponents: vec![Opponent { + id: 2, + score: 100, + remaining_worm_selections: 2, + current_worm_id: 3, + previous_command: "nothing".into(), + worms: vec![OpponentWorm { + id: 1, + health: 100, + position: Position { x: 31, y: 16 }, + digging_range: 1, + movement_range: 1, + rounds_until_unfrozen: 0, + profession: WormType::Commando, + }], + }], + 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 + ); + } +} -- cgit v1.2.3