diff options
-rw-r--r-- | src/constants.rs | 6 | ||||
-rw-r--r-- | src/mass.rs | 11 | ||||
-rw-r--r-- | src/math.rs | 55 | ||||
-rw-r--r-- | src/modules/navigation.rs | 15 | ||||
-rw-r--r-- | src/modules/tractorbeam.rs | 120 | ||||
-rw-r--r-- | tests/tests.rs | 150 |
6 files changed, 269 insertions, 88 deletions
diff --git a/src/constants.rs b/src/constants.rs index 4c48cac..6f2cf6f 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -15,7 +15,11 @@ pub const SHIP_NAVIGATION_RANGE: f64 = 100.0; pub const SHIP_REFINERY_TIME: u64 = 5; pub const SHIP_TRACTORBEAM_STRENGTH: f64 = 0.1; pub const SHIP_TRACTORBEAM_RANGE: f64 = 50.0; -pub const SHIP_TRACTORBEAM_BRING_TO_DISTANCE: f64 = 5.0; +pub const SHIP_TRACTORBEAM_ACQUIRE_RANGE: f64 = 1.0; +pub const SHIP_TRACTORBEAM_CONTROLSYSTEM_KP: f64 = 1.0; +pub const SHIP_TRACTORBEAM_CONTROLSYSTEM_KI: f64 = 0.01; +pub const SHIP_TRACTORBEAM_CONTROLSYSTEM_KD: f64 = 0.001; +pub const SHIP_TRACTORBEAM_CONTROLSYSTEM_DT: f64 = 0.0001; pub const SHIP_ENGINES_FUEL_START: f64 = 100.0; pub const SHIP_ENGINES_ACCELERATION: f64 = 0.1; diff --git a/src/mass.rs b/src/mass.rs index a307295..820173f 100644 --- a/src/mass.rs +++ b/src/mass.rs @@ -179,8 +179,15 @@ impl Mass { if let Some(target_name) = &navigation.target_name { let mut target = masses.remove(target_name).unwrap(); mining.process(self.position.clone(), masses, &mut target, storage); - tractorbeam.process(self.position.clone(), &mut target); - masses.insert(target_name.to_string(), target); + let acquired = tractorbeam.process(self.position.clone(), &mut target); + + if acquired { + if let MassType::Item { item } = target.mass_type { + storage.give_item(item); + } + } else { + masses.insert(target_name.to_string(), target); + } } let target = match &navigation.target_name { diff --git a/src/math.rs b/src/math.rs index e266a88..4f0558c 100644 --- a/src/math.rs +++ b/src/math.rs @@ -2,6 +2,8 @@ extern crate rand; use self::rand::distributions::Alphanumeric; use self::rand::Rng; +use crate::constants; +use crate::modules::types::ModuleType; use std::iter::repeat; pub fn rand_name() -> String { @@ -11,6 +13,59 @@ pub fn rand_name() -> String { .collect() } +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct ControlSystem { + previous_error: f64, + integral: f64, + kp: f64, + ki: f64, + kd: f64, + dt: f64, +} + +impl ControlSystem { + pub fn new(module: ModuleType) -> ControlSystem { + let previous_error = 0.0; + let integral = 0.0; + match module { + ModuleType::Tractorbeam => ControlSystem { + kp: constants::SHIP_TRACTORBEAM_CONTROLSYSTEM_KP, + ki: constants::SHIP_TRACTORBEAM_CONTROLSYSTEM_KI, + kd: constants::SHIP_TRACTORBEAM_CONTROLSYSTEM_KD, + dt: constants::SHIP_TRACTORBEAM_CONTROLSYSTEM_DT, + previous_error, + integral, + }, + _ => ControlSystem { + kp: 1.0, + ki: 1.0, + kd: 1.0, + dt: 1.0, + previous_error, + integral, + }, + } + } + + pub fn compute(&mut self, strength: f64, distance: f64, desired_distance: f64) -> f64 { + let error = desired_distance - distance; + self.integral += error * self.dt; + let derivative = (error - self.previous_error) / self.dt; + let output = self.kp * error + self.ki * self.integral + self.kd * derivative; + self.previous_error = error; + + if output.abs() > strength { + if output.is_sign_positive() { + strength + } else { + strength * -1.0 + } + } else { + output + } + } +} + #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)] pub struct Vector { pub x: f64, diff --git a/src/modules/navigation.rs b/src/modules/navigation.rs index c931338..6df3327 100644 --- a/src/modules/navigation.rs +++ b/src/modules/navigation.rs @@ -1,3 +1,4 @@ +use std::cmp::Ordering; use std::collections::HashMap; use std::time::SystemTime; @@ -60,8 +61,18 @@ impl Navigation { fn verify_target(&mut self, ship_position: Vector, masses: &HashMap<String, Mass>) { if let Some(name) = self.target_name.clone() { - let target = masses.get(&name).unwrap(); - if target.position.distance_from(ship_position) > self.range { + let good = match masses.get(&name) { + Some(target) => { + target + .position + .distance_from(ship_position) + .partial_cmp(&self.range) + == Some(Ordering::Less) + } + None => false, + }; + + if !good { self.target_name = None; self.status = Status::None; } diff --git a/src/modules/tractorbeam.rs b/src/modules/tractorbeam.rs index 28aa181..726c8b1 100644 --- a/src/modules/tractorbeam.rs +++ b/src/modules/tractorbeam.rs @@ -1,6 +1,8 @@ use crate::constants; -use crate::mass::Mass; +use crate::mass::{Mass, MassType}; +use crate::math::ControlSystem; use crate::math::Vector; +use crate::modules::types::ModuleType; #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct Tractorbeam { @@ -11,47 +13,6 @@ pub struct Tractorbeam { control_system: ControlSystem, } -#[derive(Serialize, Deserialize, Debug, Clone, Default)] -struct ControlSystem { - previous_error: f64, - integral: f64, - kp: f64, - ki: f64, - kd: f64, - dt: f64, -} - -impl ControlSystem { - pub fn new() -> ControlSystem { - ControlSystem { - previous_error: 0.0, - integral: 0.0, - kp: 1.0, - ki: 0.01, - kd: 0.001, - dt: 0.0001, - } - } - - pub fn compute(&mut self, strength: f64, distance: f64, desired_distance: f64) -> f64 { - let error = desired_distance - distance; - self.integral += error * self.dt; - let derivative = (error - self.previous_error) / self.dt; - let output = self.kp * error + self.ki * self.integral + self.kd * derivative; - self.previous_error = error; - - if output.abs() > strength { - if output.is_sign_positive() { - strength - } else { - strength * -1.0 - } - } else { - output - } - } -} - impl Tractorbeam { pub fn new() -> Tractorbeam { Tractorbeam { @@ -59,11 +20,11 @@ impl Tractorbeam { status: Status::None, strength: constants::SHIP_TRACTORBEAM_STRENGTH, desired_distance: None, - control_system: ControlSystem::new(), + control_system: ControlSystem::new(ModuleType::Tractorbeam), } } - pub fn process(&mut self, ship_position: Vector, target: &mut Mass) { + pub fn process(&mut self, ship_position: Vector, target: &mut Mass) -> bool { let distance = ship_position.distance_from(target.position.clone()); if self.range < distance { self.off() @@ -81,15 +42,37 @@ impl Tractorbeam { } None => Vector::default(), }, + Status::Acquire => { + match target.mass_type { + MassType::Item { .. } => (), + _ => { + self.status = Status::None; + Vector::default(); + } + } + if distance > constants::SHIP_TRACTORBEAM_ACQUIRE_RANGE { + direction.unitize() + * self.control_system.compute( + self.strength, + distance, + constants::SHIP_TRACTORBEAM_ACQUIRE_RANGE, + ) + } else { + self.status = Status::None; + return true; + } + } Status::None => Vector::default(), }; target.effects.give_acceleration(acceleration); } + false } pub fn get_client_data(&self, target: Option<&Mass>) -> String { let client_data = ClientData { + desired_distance: self.desired_distance, has_target: target.is_some(), status: self.status.clone(), }; @@ -98,40 +81,47 @@ impl Tractorbeam { } pub fn give_received_data(&mut self, recv: String) { - match recv.as_str() { - "o" => self.toggle_pull(), - "p" => self.toggle_push(), - "b" => self.toggle_bring(constants::SHIP_TRACTORBEAM_BRING_TO_DISTANCE), - _ => (), + let server_recv_data: Result<ServerRecvData, serde_json::Error> = + serde_json::from_str(&recv); + if let Ok(server_recv_data) = server_recv_data { + match server_recv_data.key.as_ref() { + "o" => self.toggle_pull(), + "p" => self.toggle_push(), + "b" => self.toggle_bring(server_recv_data.desired_distance), + "a" => self.toggle_acquire(), + _ => (), + } } } fn toggle_pull(&mut self) { self.status = match self.status { - Status::None => Status::Pull, - Status::Push => Status::Pull, - Status::Bring => Status::Pull, Status::Pull => Status::None, + _ => Status::Pull, } } fn toggle_push(&mut self) { self.status = match self.status { - Status::None => Status::Push, - Status::Pull => Status::Push, - Status::Bring => Status::Push, Status::Push => Status::None, + _ => Status::Push, } } - fn toggle_bring(&mut self, desired_distance: f64) { - self.desired_distance = Some(desired_distance); - self.control_system = ControlSystem::new(); + fn toggle_bring(&mut self, desired_distance: Option<f64>) { + self.desired_distance = desired_distance; + self.control_system = ControlSystem::new(ModuleType::Tractorbeam); self.status = match self.status { - Status::None => Status::Bring, - Status::Pull => Status::Bring, - Status::Push => Status::Bring, Status::Bring => Status::None, + _ => Status::Bring, + } + } + + fn toggle_acquire(&mut self) { + self.control_system = ControlSystem::new(ModuleType::Tractorbeam); + self.status = match self.status { + Status::Acquire => Status::None, + _ => Status::Acquire, } } @@ -143,15 +133,23 @@ impl Tractorbeam { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ClientData { pub has_target: bool, + pub desired_distance: Option<f64>, pub status: Status, } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ServerRecvData { + pub key: String, + pub desired_distance: Option<f64>, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub enum Status { None, Push, Pull, Bring, + Acquire, } impl Default for Status { diff --git a/tests/tests.rs b/tests/tests.rs index 574e9f6..b80be21 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -28,8 +28,8 @@ mod tests { (ship, masses) } - fn setup_ship_target(ship: &mut Mass, masses: &mut HashMap<String, Mass>) { - ship.give_received_data(ModuleType::Navigation, String::from("astroid")); + fn setup_ship_target(ship: &mut Mass, target_name: String, masses: &mut HashMap<String, Mass>) { + ship.give_received_data(ModuleType::Navigation, target_name); ship.process(masses); sleep(Duration::from_secs(constants::SHIP_NAVIGATION_TIME + 1)); ship.process(masses); @@ -56,7 +56,7 @@ mod tests { #[test] fn test_navigation_range_targeted() { let (mut ship, mut masses) = setup(); - setup_ship_target(&mut ship, &mut masses); + setup_ship_target(&mut ship, String::from("astroid"), &mut masses); let data = ship.get_client_data(ModuleType::Navigation, &masses); let navigation_data: navigation::ClientData = serde_json::from_str(&data).unwrap(); @@ -73,7 +73,7 @@ mod tests { #[test] fn test_mining_range() { let (mut ship, mut masses) = setup(); - setup_ship_target(&mut ship, &mut masses); + setup_ship_target(&mut ship, String::from("astroid"), &mut masses); ship.give_received_data(ModuleType::Mining, String::from("F")); ship.process(&mut masses); @@ -92,7 +92,7 @@ mod tests { #[test] fn test_mining_navigation_range() { let (mut ship, mut masses) = setup(); - setup_ship_target(&mut ship, &mut masses); + setup_ship_target(&mut ship, String::from("astroid"), &mut masses); ship.give_received_data(ModuleType::Mining, String::from("F")); ship.process(&mut masses); @@ -111,7 +111,7 @@ mod tests { #[test] fn test_mining() { let (mut ship, mut masses) = setup(); - setup_ship_target(&mut ship, &mut masses); + setup_ship_target(&mut ship, String::from("astroid"), &mut masses); ship.give_received_data(ModuleType::Mining, String::from("F")); ship.process(&mut masses); @@ -127,7 +127,7 @@ mod tests { #[test] fn test_mining_storage() { let (mut ship, mut masses) = setup(); - setup_ship_target(&mut ship, &mut masses); + setup_ship_target(&mut ship, String::from("astroid"), &mut masses); for _ in 0..10 { ship.give_item(Item::new(ItemType::CrudeMinerals)); @@ -144,7 +144,7 @@ mod tests { #[test] fn test_refinery() { let (mut ship, mut masses) = setup(); - setup_ship_target(&mut ship, &mut masses); + setup_ship_target(&mut ship, String::from("astroid"), &mut masses); ship.give_received_data(ModuleType::Refinery, String::from("R")); ship.process(&mut masses); @@ -197,7 +197,7 @@ mod tests { #[test] fn test_engines() { let (mut ship, mut masses) = setup(); - setup_ship_target(&mut ship, &mut masses); + setup_ship_target(&mut ship, String::from("astroid"), &mut masses); let mut astroid = masses.remove("astroid").unwrap(); astroid.velocity = Vector::new(constants::SHIP_ENGINES_ACCELERATION * 2.0, 0.0, 0.0); @@ -239,13 +239,18 @@ mod tests { #[test] fn test_tractorbeam_push_range() { let (mut ship, mut masses) = setup(); - setup_ship_target(&mut ship, &mut masses); + setup_ship_target(&mut ship, String::from("astroid"), &mut masses); let mut astroid = masses.remove("astroid").unwrap(); astroid.position = Vector::new(1.0, 0.0, 0.0); masses.insert(String::from("astroid"), astroid); - ship.give_received_data(ModuleType::Tractorbeam, String::from("p")); + let recv = serde_json::to_string(&tractorbeam::ServerRecvData { + key: String::from("p"), + desired_distance: None, + }) + .unwrap(); + ship.give_received_data(ModuleType::Tractorbeam, recv); ship.process(&mut masses); let data = ship.get_client_data(ModuleType::Tractorbeam, &masses); let tractorbeam_data: tractorbeam::ClientData = serde_json::from_str(&data).unwrap(); @@ -264,13 +269,18 @@ mod tests { #[test] fn test_tractorbeam_pull_range() { let (mut ship, mut masses) = setup(); - setup_ship_target(&mut ship, &mut masses); + setup_ship_target(&mut ship, String::from("astroid"), &mut masses); let mut astroid = masses.remove("astroid").unwrap(); astroid.position = Vector::new(1.0, 0.0, 0.0); masses.insert(String::from("astroid"), astroid); - ship.give_received_data(ModuleType::Tractorbeam, String::from("o")); + let recv = serde_json::to_string(&tractorbeam::ServerRecvData { + key: String::from("o"), + desired_distance: None, + }) + .unwrap(); + ship.give_received_data(ModuleType::Tractorbeam, recv); ship.process(&mut masses); let data = ship.get_client_data(ModuleType::Tractorbeam, &masses); let tractorbeam_data: tractorbeam::ClientData = serde_json::from_str(&data).unwrap(); @@ -289,13 +299,18 @@ mod tests { #[test] fn test_tractorbeam_bring_range() { let (mut ship, mut masses) = setup(); - setup_ship_target(&mut ship, &mut masses); + setup_ship_target(&mut ship, String::from("astroid"), &mut masses); let mut astroid = masses.remove("astroid").unwrap(); astroid.position = Vector::new(1.0, 0.0, 0.0); masses.insert(String::from("astroid"), astroid); - ship.give_received_data(ModuleType::Tractorbeam, String::from("b")); + let recv = serde_json::to_string(&tractorbeam::ServerRecvData { + key: String::from("b"), + desired_distance: Some(5.0), + }) + .unwrap(); + ship.give_received_data(ModuleType::Tractorbeam, recv); ship.process(&mut masses); let data = ship.get_client_data(ModuleType::Tractorbeam, &masses); let tractorbeam_data: tractorbeam::ClientData = serde_json::from_str(&data).unwrap(); @@ -314,7 +329,7 @@ mod tests { #[test] fn test_tractorbeam() { let (mut ship, mut masses) = setup(); - setup_ship_target(&mut ship, &mut masses); + setup_ship_target(&mut ship, String::from("astroid"), &mut masses); let mut astroid = masses.remove("astroid").unwrap(); let start = 2.0; @@ -323,7 +338,12 @@ mod tests { assert!(astroid.position == Vector::new(start, 0.0, 0.0)); masses.insert(String::from("astroid"), astroid); - ship.give_received_data(ModuleType::Tractorbeam, String::from("o")); + let recv = serde_json::to_string(&tractorbeam::ServerRecvData { + key: String::from("o"), + desired_distance: None, + }) + .unwrap(); + ship.give_received_data(ModuleType::Tractorbeam, recv); let mut iterated = 1.0; loop { ship.process(&mut masses); @@ -343,7 +363,12 @@ mod tests { } } - ship.give_received_data(ModuleType::Tractorbeam, String::from("p")); + let recv = serde_json::to_string(&tractorbeam::ServerRecvData { + key: String::from("p"), + desired_distance: None, + }) + .unwrap(); + ship.give_received_data(ModuleType::Tractorbeam, recv); let mut iterated = 1.0; loop { ship.process(&mut masses); @@ -367,15 +392,21 @@ mod tests { #[test] fn test_tractorbeam_bring() { let (mut ship, mut masses) = setup(); - setup_ship_target(&mut ship, &mut masses); + setup_ship_target(&mut ship, String::from("astroid"), &mut masses); let mut astroid = masses.remove("astroid").unwrap(); let start = 25.0; + let desired_distance = 5.0; astroid.position = Vector::new(start, 0.0, 0.0); astroid.process(&mut masses); masses.insert(String::from("astroid"), astroid); - ship.give_received_data(ModuleType::Tractorbeam, String::from("b")); + let recv = serde_json::to_string(&tractorbeam::ServerRecvData { + key: String::from("b"), + desired_distance: Some(desired_distance), + }) + .unwrap(); + ship.give_received_data(ModuleType::Tractorbeam, recv); let mut iterated = 1.0; loop { ship.process(&mut masses); @@ -383,8 +414,7 @@ mod tests { let mut astroid = masses.remove("astroid").unwrap(); astroid.process(&mut masses); - if ship.position.distance_from(astroid.position.clone()) - < constants::SHIP_TRACTORBEAM_BRING_TO_DISTANCE + if ship.position.distance_from(astroid.position.clone()) < desired_distance && astroid.velocity.magnitude() < 1.0 { break; @@ -411,4 +441,80 @@ mod tests { std::process::Command::new("feh").arg("--conversion-timeout").arg("1").arg("line.svg").output().expect("problem"); */ } + + #[test] + fn test_tractorbeam_acquire() { + let (mut ship, mut masses) = setup(); + masses.insert( + String::from("iron"), + Mass::new_item( + Item::new(ItemType::Iron), + Vector::default(), + Vector::default(), + ), + ); + setup_ship_target(&mut ship, String::from("iron"), &mut masses); + + let recv = serde_json::to_string(&tractorbeam::ServerRecvData { + key: String::from("a"), + desired_distance: None, + }) + .unwrap(); + ship.give_received_data(ModuleType::Tractorbeam, recv); + + assert!(masses.len() == 2); + assert!(ship.item_count(ItemType::Iron) == 0); + ship.process(&mut masses); + assert!(ship.item_count(ItemType::Iron) == 1); + assert!(masses.len() == 1); + } + + #[test] + fn test_tractorbeam_acquire_range() { + let (mut ship, mut masses) = setup(); + masses.insert( + String::from("iron"), + Mass::new_item( + Item::new(ItemType::Iron), + Vector::new(50.0, 0.0, 0.0), + Vector::default(), + ), + ); + setup_ship_target(&mut ship, String::from("iron"), &mut masses); + + let recv = serde_json::to_string(&tractorbeam::ServerRecvData { + key: String::from("a"), + desired_distance: None, + }) + .unwrap(); + ship.give_received_data(ModuleType::Tractorbeam, recv); + + assert!(masses.len() == 2); + assert!(ship.item_count(ItemType::Iron) == 0); + ship.process(&mut masses); + assert!(ship.item_count(ItemType::Iron) == 0); + assert!(masses.len() == 2); + + let mut iterated = 1.0; + loop { + ship.process(&mut masses); + + match masses.remove("iron") { + Some(mut item) => { + item.process(&mut masses); + masses.insert(String::from("iron"), item); + } + None => { + assert!(ship.item_count(ItemType::Iron) == 1); + break; + } + } + + iterated += 1.0; + if iterated > 100.0 { + assert!(false); + break; + } + } + } } |