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; +            } +        } +    }  }  | 
