summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/constants.rs6
-rw-r--r--src/mass.rs11
-rw-r--r--src/math.rs55
-rw-r--r--src/modules/navigation.rs15
-rw-r--r--src/modules/tractorbeam.rs120
-rw-r--r--tests/tests.rs150
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;
+ }
+ }
+ }
}