1use crate::components::energy_cell::EnergyCell;
100use crate::components::resource::{BasicResourceType, Combinator, ComplexResourceType, Generator};
101use crate::components::rocket::Rocket;
102use crate::components::sunray::Sunray;
103use crate::protocols::orchestrator_planet::{OrchestratorToPlanet, PlanetToOrchestrator};
104use crate::protocols::planet_explorer::{ExplorerToPlanet, PlanetToExplorer};
105use crate::utils::ID;
106use crossbeam_channel::{Receiver, Sender, select_biased};
107use std::collections::HashMap;
108use std::slice::{Iter, IterMut};
109
110pub trait PlanetAI: Send {
122 fn handle_sunray(
126 &mut self,
127 state: &mut PlanetState,
128 generator: &Generator,
129 combinator: &Combinator,
130 sunray: Sunray,
131 );
132
133 fn handle_asteroid(
141 &mut self,
142 state: &mut PlanetState,
143 generator: &Generator,
144 combinator: &Combinator,
145 ) -> Option<Rocket>;
146
147 fn handle_internal_state_req(
154 &mut self,
155 state: &mut PlanetState,
156 generator: &Generator,
157 combinator: &Combinator,
158 ) -> DummyPlanetState;
159
160 fn handle_explorer_msg(
168 &mut self,
169 state: &mut PlanetState,
170 generator: &Generator,
171 combinator: &Combinator,
172 msg: ExplorerToPlanet,
173 ) -> Option<PlanetToExplorer>;
174
175 #[allow(unused_variables)]
178 fn on_explorer_arrival(
179 &mut self,
180 state: &mut PlanetState,
181 generator: &Generator,
182 combinator: &Combinator,
183 explorer_id: ID,
184 ) {
185 }
186
187 #[allow(unused_variables)]
190 fn on_explorer_departure(
191 &mut self,
192 state: &mut PlanetState,
193 generator: &Generator,
194 combinator: &Combinator,
195 explorer_id: ID,
196 ) {
197 }
198
199 #[allow(unused_variables)]
204 fn on_start(&mut self, state: &PlanetState, generator: &Generator, combinator: &Combinator) {}
205
206 #[allow(unused_variables)]
211 fn on_stop(&mut self, state: &PlanetState, generator: &Generator, combinator: &Combinator) {}
212}
213
214pub struct PlanetConstraints {
216 n_energy_cells: usize,
217 unbounded_gen_rules: bool,
218 can_have_rocket: bool,
219 n_comb_rules: usize,
220}
221
222#[derive(Debug, Clone, Copy)]
226pub enum PlanetType {
227 A,
228 B,
229 C,
230 D,
231}
232
233impl PlanetType {
234 const N_ENERGY_CELLS: usize = 5;
235 const N_RESOURCE_COMB_RULES: usize = 6;
236
237 #[must_use]
240 pub fn constraints(&self) -> PlanetConstraints {
241 match self {
242 PlanetType::A => PlanetConstraints {
243 n_energy_cells: Self::N_ENERGY_CELLS,
244 unbounded_gen_rules: false,
245 can_have_rocket: true,
246 n_comb_rules: 0,
247 },
248 PlanetType::B => PlanetConstraints {
249 n_energy_cells: 1,
250 unbounded_gen_rules: true,
251 can_have_rocket: false,
252 n_comb_rules: 1,
253 },
254 PlanetType::C => PlanetConstraints {
255 n_energy_cells: 1,
256 unbounded_gen_rules: false,
257 can_have_rocket: true,
258 n_comb_rules: Self::N_RESOURCE_COMB_RULES,
259 },
260 PlanetType::D => PlanetConstraints {
261 n_energy_cells: Self::N_ENERGY_CELLS,
262 unbounded_gen_rules: true,
263 can_have_rocket: false,
264 n_comb_rules: 0,
265 },
266 }
267 }
268}
269
270pub struct PlanetState {
274 id: ID,
275 energy_cells: Vec<EnergyCell>,
276 rocket: Option<Rocket>,
277 can_have_rocket: bool,
278}
279
280impl PlanetState {
281 #[must_use]
283 pub fn id(&self) -> ID {
284 self.id
285 }
286
287 #[must_use]
296 pub fn cell(&self, i: usize) -> &EnergyCell {
297 &self.energy_cells[i]
298 }
299
300 pub fn cell_mut(&mut self, i: usize) -> &mut EnergyCell {
309 &mut self.energy_cells[i]
310 }
311
312 #[must_use]
316 pub fn cells_count(&self) -> usize {
317 self.energy_cells.len()
318 }
319
320 pub fn cells_iter(&self) -> Iter<'_, EnergyCell> {
322 self.energy_cells.iter()
323 }
324
325 pub fn cells_iter_mut(&mut self) -> IterMut<'_, EnergyCell> {
327 self.energy_cells.iter_mut()
328 }
329
330 pub fn charge_cell(&mut self, sunray: Sunray) -> Option<Sunray> {
333 match self.empty_cell() {
334 None => Some(sunray),
335 Some((cell, _)) => {
336 cell.charge(sunray);
337 None
338 }
339 }
340 }
341
342 pub fn empty_cell(&mut self) -> Option<(&mut EnergyCell, usize)> {
345 let idx = self.energy_cells.iter().position(|cell| !cell.is_charged());
346 idx.map(|i| (&mut self.energy_cells[i], i))
347 }
348
349 pub fn full_cell(&mut self) -> Option<(&mut EnergyCell, usize)> {
352 let idx = self
353 .energy_cells
354 .iter()
355 .position(super::energy_cell::EnergyCell::is_charged);
356 idx.map(|i| (&mut self.energy_cells[i], i))
357 }
358
359 #[must_use]
361 pub fn can_have_rocket(&self) -> bool {
362 self.can_have_rocket
363 }
364
365 #[must_use]
367 pub fn has_rocket(&self) -> bool {
368 self.rocket.is_some()
369 }
370
371 pub fn take_rocket(&mut self) -> Option<Rocket> {
374 self.rocket.take()
375 }
376
377 pub fn build_rocket(&mut self, i: usize) -> Result<(), String> {
390 if !self.can_have_rocket {
391 Err("This planet type can't have rockets.".to_string())
392 } else if self.has_rocket() {
393 Err("This planet already has a rocket.".to_string())
394 } else {
395 let energy_cell = self.cell_mut(i);
396 Rocket::new(energy_cell).map(|rocket| {
397 self.rocket = Some(rocket);
398 })
399 }
400 }
401
402 #[must_use]
404 pub fn to_dummy(&self) -> DummyPlanetState {
405 DummyPlanetState {
406 energy_cells: self
407 .energy_cells
408 .iter()
409 .map(super::energy_cell::EnergyCell::is_charged)
410 .collect(),
411 charged_cells_count: self
412 .energy_cells
413 .iter()
414 .filter(|cell| cell.is_charged())
415 .count(),
416 has_rocket: self.has_rocket(),
417 }
418 }
419}
420
421#[derive(Debug, Clone)]
426pub struct DummyPlanetState {
427 pub energy_cells: Vec<bool>,
428 pub charged_cells_count: usize,
429 pub has_rocket: bool,
430}
431
432pub struct Planet {
441 state: PlanetState,
442 type_: PlanetType,
443 pub ai: Box<dyn PlanetAI>,
444 generator: Generator,
445 combinator: Combinator,
446
447 from_orchestrator: Receiver<OrchestratorToPlanet>,
448 to_orchestrator: Sender<PlanetToOrchestrator>,
449 from_explorers: Receiver<ExplorerToPlanet>,
450 to_explorers: HashMap<ID, Sender<PlanetToExplorer>>,
451}
452
453impl Planet {
454 const ORCH_DISCONNECT_ERR: &'static str = "Orchestrator disconnected.";
455
456 pub fn new(
472 id: ID,
473 type_: PlanetType,
474 ai: Box<dyn PlanetAI>,
475 gen_rules: Vec<BasicResourceType>,
476 comb_rules: Vec<ComplexResourceType>,
477 orchestrator_channels: (Receiver<OrchestratorToPlanet>, Sender<PlanetToOrchestrator>),
478 explorers_receiver: Receiver<ExplorerToPlanet>,
479 ) -> Result<Planet, String> {
480 let PlanetConstraints {
481 n_energy_cells,
482 unbounded_gen_rules,
483 can_have_rocket,
484 n_comb_rules,
485 } = type_.constraints();
486 let (from_orchestrator, to_orchestrator) = orchestrator_channels;
487
488 if gen_rules.is_empty() {
489 Err("gen_rules is empty".to_string())
490 } else if !unbounded_gen_rules && gen_rules.len() > 1 {
491 Err(format!(
492 "Too many generation rules (Planet type {type_:?} is limited to 1)"
493 ))
494 } else if comb_rules.len() > n_comb_rules {
495 Err(format!(
496 "Too many combination rules (Planet type {type_:?} is limited to {n_comb_rules})"
497 ))
498 } else {
499 let mut generator = Generator::new();
500 let mut combinator = Combinator::new();
501
502 for r in gen_rules {
504 let _ = generator.add(r);
505 }
506 for r in comb_rules {
507 let _ = combinator.add(r);
508 }
509
510 Ok(Planet {
511 state: PlanetState {
512 id,
513 energy_cells: (0..n_energy_cells).map(|_| EnergyCell::new()).collect(),
514 can_have_rocket,
515 rocket: None,
516 },
517 type_,
518 ai,
519 generator,
520 combinator,
521 from_orchestrator,
522 to_orchestrator,
523 from_explorers: explorers_receiver,
524 to_explorers: HashMap::new(),
525 })
526 }
527 }
528
529 fn handle_orchestrator_msg(
533 &mut self,
534 msg: OrchestratorToPlanet,
535 ) -> Result<Option<bool>, String> {
536 match msg {
537 OrchestratorToPlanet::StartPlanetAI => Ok(None),
538
539 OrchestratorToPlanet::StopPlanetAI => {
540 self.to_orchestrator
541 .send(PlanetToOrchestrator::StopPlanetAIResult {
542 planet_id: self.id(),
543 })
544 .map_err(|_| Self::ORCH_DISCONNECT_ERR.to_string())?;
545
546 self.ai
547 .on_stop(&self.state, &self.generator, &self.combinator);
548
549 let kill = self.wait_for_start()?; if kill {
551 return Ok(Some(true));
552 }
553
554 self.ai
556 .on_start(&self.state, &self.generator, &self.combinator);
557 Ok(None)
558 }
559
560 OrchestratorToPlanet::KillPlanet => {
561 self.to_orchestrator
562 .send(PlanetToOrchestrator::KillPlanetResult {
563 planet_id: self.id(),
564 })
565 .map_err(|_| Self::ORCH_DISCONNECT_ERR.to_string())?;
566
567 Ok(Some(true))
568 }
569
570 OrchestratorToPlanet::Sunray(sunray) => {
571 self.ai
572 .handle_sunray(&mut self.state, &self.generator, &self.combinator, sunray);
573
574 self.to_orchestrator
575 .send(PlanetToOrchestrator::SunrayAck {
576 planet_id: self.id(),
577 })
578 .map_err(|_| Self::ORCH_DISCONNECT_ERR.to_string())?;
579
580 Ok(None)
581 }
582
583 OrchestratorToPlanet::Asteroid(_) => {
584 let rocket =
585 self.ai
586 .handle_asteroid(&mut self.state, &self.generator, &self.combinator);
587
588 self.to_orchestrator
589 .send(PlanetToOrchestrator::AsteroidAck {
590 planet_id: self.id(),
591 rocket,
592 })
593 .map_err(|_| Self::ORCH_DISCONNECT_ERR.to_string())?;
594
595 Ok(None)
596 }
597
598 OrchestratorToPlanet::IncomingExplorerRequest {
599 explorer_id,
600 new_sender,
601 } => {
602 self.to_explorers.insert(explorer_id, new_sender);
603 self.ai.on_explorer_arrival(
604 &mut self.state,
605 &self.generator,
606 &self.combinator,
607 explorer_id,
608 );
609
610 self.to_orchestrator
611 .send(PlanetToOrchestrator::IncomingExplorerResponse {
612 planet_id: self.id(),
613 explorer_id,
614 res: Ok(()),
615 })
616 .map_err(|_| Self::ORCH_DISCONNECT_ERR.to_string())?;
617
618 Ok(None)
619 }
620
621 OrchestratorToPlanet::OutgoingExplorerRequest { explorer_id } => {
622 self.to_explorers.remove(&explorer_id);
623 self.ai.on_explorer_departure(
624 &mut self.state,
625 &self.generator,
626 &self.combinator,
627 explorer_id,
628 );
629
630 self.to_orchestrator
631 .send(PlanetToOrchestrator::OutgoingExplorerResponse {
632 planet_id: self.id(),
633 explorer_id,
634 res: Ok(()),
635 })
636 .map_err(|_| Self::ORCH_DISCONNECT_ERR.to_string())?;
637
638 Ok(None)
639 }
640
641 OrchestratorToPlanet::InternalStateRequest => {
642 let dummy_state = self.ai.handle_internal_state_req(
643 &mut self.state,
644 &self.generator,
645 &self.combinator,
646 );
647
648 self.to_orchestrator
649 .send(PlanetToOrchestrator::InternalStateResponse {
650 planet_id: self.id(),
651 planet_state: dummy_state,
652 })
653 .map_err(|_| Self::ORCH_DISCONNECT_ERR.to_string())?;
654
655 Ok(None)
656 }
657 }
658 }
659
660 pub fn run(&mut self) -> Result<(), String> {
671 let kill = self.wait_for_start()?;
674 if kill {
675 return Ok(());
676 }
677
678 self.ai
679 .on_start(&self.state, &self.generator, &self.combinator);
680
681 loop {
682 select_biased! {
683 recv(self.from_orchestrator) -> msg => match msg {
685 Ok(m) => {
686 if let Some(true) = self.handle_orchestrator_msg(m)? {
687 return Ok(());
688 }
689 }
690
691 Err(_) => {
692 return Err(Self::ORCH_DISCONNECT_ERR.to_string())
693 }
694 },
695
696 recv(self.from_explorers) -> msg => if let Ok(msg) = msg {
698 let explorer_id = msg.explorer_id();
699
700 if let Some(to_explorer) = self.to_explorers.get(&explorer_id)
703 && let Some(response) = self.ai.handle_explorer_msg(
704 &mut self.state,
705 &self.generator,
706 &self.combinator,
707 msg,
708 )
709 {
710 to_explorer
711 .send(response)
712 .map_err(|_| format!("Explorer {explorer_id} disconnected."))?;
713 }
714 }
715 }
716 }
717 }
718
719 fn wait_for_start(&self) -> Result<bool, String> {
722 loop {
723 select_biased! {
724 recv(self.from_orchestrator) -> msg => match msg {
726 Ok(OrchestratorToPlanet::StartPlanetAI) => {
728 self.to_orchestrator
729 .send(PlanetToOrchestrator::StartPlanetAIResult {
730 planet_id: self.id(),
731 })
732 .map_err(|_| Self::ORCH_DISCONNECT_ERR.to_string())?;
733
734 return Ok(false);
735 }
736 Ok(OrchestratorToPlanet::KillPlanet) => {
738 self.to_orchestrator
739 .send(PlanetToOrchestrator::KillPlanetResult { planet_id: self.id() })
740 .map_err(|_| Self::ORCH_DISCONNECT_ERR.to_string())?;
741
742 return Ok(true)
743 }
744 Ok(_) => {
746 self.to_orchestrator
747 .send(PlanetToOrchestrator::Stopped {
748 planet_id: self.id(),
749 })
750 .map_err(|_| Self::ORCH_DISCONNECT_ERR.to_string())?;
751 }
752
753 Err(_) => return Err(Self::ORCH_DISCONNECT_ERR.to_string()),
754 },
755
756 recv(self.from_explorers) -> msg => if let Ok(msg) = msg &&
758 let Some(to_explorer) = self.to_explorers.get(&msg.explorer_id())
759 {
760 let _ = to_explorer.send(PlanetToExplorer::Stopped);
761 }
762 }
763 }
764 }
765
766 #[must_use]
768 pub fn id(&self) -> ID {
769 self.state.id
770 }
771
772 #[must_use]
774 pub fn planet_type(&self) -> PlanetType {
775 self.type_
776 }
777
778 #[must_use]
780 pub fn state(&self) -> &PlanetState {
781 &self.state
782 }
783
784 #[must_use]
786 pub fn generator(&self) -> &Generator {
787 &self.generator
788 }
789
790 #[must_use]
792 pub fn combinator(&self) -> &Combinator {
793 &self.combinator
794 }
795}
796
797#[cfg(test)]
798mod tests {
799 use super::*;
800 use crossbeam_channel::{Receiver, Sender, unbounded};
801 use std::thread;
802 use std::time::Duration;
803
804 use crate::components::asteroid::Asteroid;
805 use crate::components::energy_cell::EnergyCell;
806 use crate::components::resource::{BasicResourceType, Combinator, Generator};
807 use crate::components::rocket::Rocket;
808 use crate::components::sunray::Sunray;
809 use crate::protocols::orchestrator_planet::{OrchestratorToPlanet, PlanetToOrchestrator};
810
811 struct MockAI {
813 start_called: bool,
814 stop_called: bool,
815 sunray_count: ID,
816 }
817
818 impl MockAI {
819 fn new() -> Self {
820 Self {
821 start_called: false,
822 stop_called: false,
823 sunray_count: 0,
824 }
825 }
826 }
827
828 impl PlanetAI for MockAI {
829 fn handle_sunray(
830 &mut self,
831 state: &mut PlanetState,
832 _generator: &Generator,
833 _combinator: &Combinator,
834 sunray: Sunray,
835 ) {
836 self.sunray_count += 1;
837
838 if let Some(cell) = state.cells_iter_mut().next() {
839 cell.charge(sunray);
840 }
841 }
842
843 fn handle_asteroid(
844 &mut self,
845 state: &mut PlanetState,
846 _generator: &Generator,
847 _combinator: &Combinator,
848 ) -> Option<Rocket> {
849 match state.full_cell() {
850 None => None,
851 Some((_cell, i)) => {
852 let _ = state.build_rocket(i);
854 state.take_rocket()
855 }
856 }
857 }
858
859 fn handle_internal_state_req(
860 &mut self,
861 state: &mut PlanetState,
862 _generator: &Generator,
863 _combinator: &Combinator,
864 ) -> DummyPlanetState {
865 state.to_dummy()
866 }
867
868 fn handle_explorer_msg(
869 &mut self,
870 _state: &mut PlanetState,
871 _generator: &Generator,
872 _combinator: &Combinator,
873 msg: ExplorerToPlanet,
874 ) -> Option<PlanetToExplorer> {
875 match msg {
876 ExplorerToPlanet::AvailableEnergyCellRequest { .. } => {
877 Some(PlanetToExplorer::AvailableEnergyCellResponse { available_cells: 5 })
878 }
879 _ => None,
880 }
881 }
882
883 fn on_start(
884 &mut self,
885 _state: &PlanetState,
886 _generator: &Generator,
887 _combinator: &Combinator,
888 ) {
889 self.start_called = true;
890 }
891
892 fn on_stop(
893 &mut self,
894 _state: &PlanetState,
895 _generator: &Generator,
896 _combinator: &Combinator,
897 ) {
898 self.stop_called = true;
899 }
900 }
901
902 type PlanetOrchHalfChannels = (Receiver<OrchestratorToPlanet>, Sender<PlanetToOrchestrator>);
905
906 type PlanetExplHalfChannels = (Receiver<ExplorerToPlanet>, Sender<PlanetToExplorer>);
907
908 type OrchPlanetHalfChannels = (Sender<OrchestratorToPlanet>, Receiver<PlanetToOrchestrator>);
909
910 type ExplPlanetHalfChannels = (Sender<ExplorerToPlanet>, Receiver<PlanetToExplorer>);
911
912 fn get_test_channels() -> (
913 PlanetOrchHalfChannels,
914 PlanetExplHalfChannels,
915 OrchPlanetHalfChannels,
916 ExplPlanetHalfChannels,
917 ) {
918 let (tx_orch_in, rx_orch_in) = unbounded::<OrchestratorToPlanet>();
920 let (tx_orch_out, rx_orch_out) = unbounded::<PlanetToOrchestrator>();
922
923 let (tx_expl_in, rx_expl_in) = unbounded::<ExplorerToPlanet>();
925 let (tx_expl_out, rx_expl_out) = unbounded::<PlanetToExplorer>();
927
928 (
929 (rx_orch_in, tx_orch_out),
930 (rx_expl_in, tx_expl_out),
931 (tx_orch_in, rx_orch_out),
932 (tx_expl_in, rx_expl_out),
933 )
934 }
935
936 #[test]
939 fn test_planet_state_rocket_construction() {
940 let mut state = PlanetState {
941 id: 0,
942 energy_cells: vec![EnergyCell::new()],
943 rocket: None,
944 can_have_rocket: true,
945 };
946
947 let cell = state.cell_mut(0);
948 let sunray = Sunray::new();
949 cell.charge(sunray);
950
951 let res = state.build_rocket(0);
953 assert!(res.is_ok());
954 assert!(state.has_rocket());
955 assert!(!state.cell(0).is_charged());
956
957 let rocket = state.take_rocket();
959 assert!(rocket.is_some());
960 assert!(!state.has_rocket());
961 }
962
963 #[test]
964 fn test_planet_state_type_b_no_rocket() {
965 let mut state = PlanetState {
966 id: 0,
967 energy_cells: vec![EnergyCell::new()],
968 rocket: None,
969 can_have_rocket: false, };
971
972 let cell = state.cell_mut(0);
973 cell.charge(Sunray::new());
974
975 let res = state.build_rocket(0);
976 assert!(res.is_err(), "Type B should not be able to build rockets");
977 }
978
979 #[test]
982 fn test_planet_construction_constraints() {
983 let (orch_ch, expl_ch, _, _) = get_test_channels();
985 let valid_gen = vec![BasicResourceType::Oxygen];
986
987 let valid_planet = Planet::new(
988 1,
989 PlanetType::A,
990 Box::new(MockAI::new()),
991 valid_gen,
992 vec![],
993 orch_ch,
994 expl_ch.0,
995 );
996 assert!(valid_planet.is_ok());
997
998 let (orch_ch, expl_ch, _, _) = get_test_channels();
1000 let invalid_empty = Planet::new(
1001 1,
1002 PlanetType::A,
1003 Box::new(MockAI::new()),
1004 vec![], vec![],
1006 orch_ch,
1007 expl_ch.0,
1008 );
1009 assert!(invalid_empty.is_err());
1010
1011 let (orch_ch, expl_ch, _, _) = get_test_channels();
1013 let invalid_gen = Planet::new(
1014 1,
1015 PlanetType::A,
1016 Box::new(MockAI::new()),
1017 vec![BasicResourceType::Oxygen, BasicResourceType::Hydrogen], vec![],
1019 orch_ch,
1020 expl_ch.0,
1021 );
1022 assert!(invalid_gen.is_err());
1023 }
1024
1025 #[test]
1028 fn test_planet_run_loop_survival() {
1029 let (planet_orch_ch, planet_expl_ch, orch_planet_ch, _) = get_test_channels();
1030
1031 let (rx_from_orch, tx_from_planet_orch) = planet_orch_ch;
1032 let (rx_from_expl, _) = planet_expl_ch;
1033 let (tx_to_planet_orch, rx_to_orch) = orch_planet_ch;
1034
1035 let mut planet = Planet::new(
1037 100,
1038 PlanetType::A,
1039 Box::new(MockAI::new()),
1040 vec![BasicResourceType::Oxygen],
1041 vec![],
1042 (rx_from_orch, tx_from_planet_orch),
1043 rx_from_expl,
1044 )
1045 .expect("Failed to create planet");
1046
1047 let handle = thread::spawn(move || {
1049 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1050 let res = planet.run();
1051 match res {
1052 Ok(()) => {}
1053 Err(err) => {
1054 dbg!(err);
1055 }
1056 }
1057 }));
1058 });
1059
1060 tx_to_planet_orch
1062 .send(OrchestratorToPlanet::StartPlanetAI)
1063 .unwrap();
1064 match rx_to_orch.recv_timeout(Duration::from_millis(50)) {
1065 Ok(PlanetToOrchestrator::StartPlanetAIResult { .. }) => {}
1066 _ => panic!("Planet sent incorrect response"),
1067 }
1068 thread::sleep(Duration::from_millis(50));
1069
1070 tx_to_planet_orch
1072 .send(OrchestratorToPlanet::Sunray(Sunray::new()))
1073 .unwrap();
1074
1075 if let Ok(PlanetToOrchestrator::SunrayAck { planet_id, .. }) =
1077 rx_to_orch.recv_timeout(Duration::from_millis(200))
1078 {
1079 assert_eq!(planet_id, 100);
1080 } else {
1081 panic!("Did not receive SunrayAck");
1082 }
1083
1084 tx_to_planet_orch
1086 .send(OrchestratorToPlanet::Asteroid(Asteroid::new()))
1087 .unwrap();
1088
1089 match rx_to_orch.recv_timeout(Duration::from_millis(200)) {
1091 Ok(PlanetToOrchestrator::AsteroidAck {
1092 planet_id, rocket, ..
1093 }) => {
1094 assert_eq!(planet_id, 100);
1095 assert!(rocket.is_some(), "Planet failed to build rocket!");
1096 }
1097 Ok(_) => panic!("Wrong message type"),
1098 Err(e) => panic!("Timeout waiting for AsteroidAck: {e}"),
1099 }
1100
1101 tx_to_planet_orch
1103 .send(OrchestratorToPlanet::StopPlanetAI)
1104 .unwrap();
1105 match rx_to_orch.recv_timeout(Duration::from_millis(200)) {
1106 Ok(PlanetToOrchestrator::StopPlanetAIResult { .. }) => {}
1107 _ => panic!("Planet sent incorrect response"),
1108 }
1109
1110 tx_to_planet_orch
1112 .send(OrchestratorToPlanet::InternalStateRequest)
1113 .unwrap();
1114 match rx_to_orch.recv_timeout(Duration::from_millis(200)) {
1115 Ok(PlanetToOrchestrator::Stopped { .. }) => {}
1116 _ => panic!("Planet sent incorrect response"),
1117 }
1118
1119 tx_to_planet_orch
1121 .send(OrchestratorToPlanet::KillPlanet)
1122 .unwrap();
1123 match rx_to_orch.recv_timeout(Duration::from_millis(200)) {
1124 Ok(PlanetToOrchestrator::KillPlanetResult { .. }) => {}
1125 _ => panic!("Planet sent incorrect response"),
1126 }
1127
1128 assert!(handle.join().is_ok(), "Planet thread exited with an error");
1130 }
1131
1132 #[test]
1133 fn test_resource_creation() {
1134 let (orch_ch, expl_ch, _, _) = get_test_channels();
1135 let gen_rules = vec![BasicResourceType::Oxygen, BasicResourceType::Hydrogen];
1136 let comb_rules = vec![ComplexResourceType::Water];
1137 let mut planet = Planet::new(
1138 0,
1139 PlanetType::B,
1140 Box::new(MockAI::new()),
1141 gen_rules,
1142 comb_rules,
1143 orch_ch,
1144 expl_ch.0,
1145 )
1146 .unwrap();
1147
1148 let state = &mut planet.state;
1150 let generator = &planet.generator;
1151 let combinator = &planet.combinator;
1152
1153 let cell = state.cell_mut(0);
1155 cell.charge(Sunray::new());
1156
1157 let oxygen = generator.make_oxygen(cell);
1158 assert!(oxygen.is_ok());
1159 let oxygen = oxygen.unwrap();
1160
1161 let cell = state.cell_mut(0);
1163 cell.charge(Sunray::new());
1164
1165 let hydrogen = generator.make_hydrogen(cell);
1166 assert!(hydrogen.is_ok());
1167 let hydrogen = hydrogen.unwrap();
1168
1169 let cell = state.cell_mut(0);
1171 cell.charge(Sunray::new());
1172
1173 let diamond = combinator.make_water(hydrogen, oxygen, cell);
1174 assert!(diamond.is_ok());
1175
1176 let carbon = generator.make_carbon(cell);
1178 assert!(carbon.is_err());
1179 }
1180
1181 #[test]
1182 fn test_explorer_comms() {
1183 let (
1185 planet_orch_channels,
1186 planet_expl_channels,
1187 (orch_tx, orch_rx),
1188 (expl_tx_global, _expl_rx_global),
1189 ) = get_test_channels();
1190
1191 let (planet_expl_rx, _) = planet_expl_channels;
1196
1197 let mut planet = Planet::new(
1198 1,
1199 PlanetType::A,
1200 Box::new(MockAI::new()),
1201 vec![BasicResourceType::Oxygen],
1202 vec![],
1203 planet_orch_channels,
1204 planet_expl_rx,
1205 )
1206 .expect("Failed to create planet");
1207
1208 let handle = thread::spawn(move || {
1210 let res = planet.run();
1211 match res {
1212 Ok(()) => {}
1213 Err(err) => {
1214 dbg!(err);
1215 }
1216 }
1217 });
1218
1219 orch_tx.send(OrchestratorToPlanet::StartPlanetAI).unwrap();
1221 match orch_rx.recv_timeout(Duration::from_millis(50)) {
1222 Ok(PlanetToOrchestrator::StartPlanetAIResult { .. }) => {}
1223 _ => panic!("Planet sent incorrect response"),
1224 }
1225 thread::sleep(Duration::from_millis(50));
1226
1227 let explorer_id = 101;
1230 let (expl_dedicated_tx, expl_dedicated_rx) = unbounded::<PlanetToExplorer>();
1231
1232 orch_tx
1234 .send(OrchestratorToPlanet::IncomingExplorerRequest {
1235 explorer_id,
1236 new_sender: expl_dedicated_tx,
1237 })
1238 .unwrap();
1239
1240 match orch_rx.recv_timeout(Duration::from_millis(200)) {
1242 Ok(PlanetToOrchestrator::IncomingExplorerResponse { planet_id, res, .. }) => {
1243 assert_eq!(planet_id, 1);
1244 assert!(res.is_ok());
1245 }
1246 _ => panic!("Expected IncomingExplorerResponse"),
1247 }
1248
1249 expl_tx_global
1252 .send(ExplorerToPlanet::AvailableEnergyCellRequest { explorer_id })
1253 .unwrap();
1254
1255 match expl_dedicated_rx.recv_timeout(Duration::from_millis(200)) {
1257 Ok(PlanetToExplorer::AvailableEnergyCellResponse { available_cells }) => {
1258 assert_eq!(available_cells, 5);
1259 }
1260 _ => panic!("Expected AvailableEnergyCellResponse"),
1261 }
1262
1263 orch_tx.send(OrchestratorToPlanet::StopPlanetAI).unwrap();
1265 match orch_rx.recv_timeout(Duration::from_millis(200)) {
1266 Ok(PlanetToOrchestrator::StopPlanetAIResult { .. }) => {}
1267 _ => panic!("Planet sent incorrect response"),
1268 }
1269
1270 expl_tx_global
1272 .send(ExplorerToPlanet::AvailableEnergyCellRequest { explorer_id })
1273 .unwrap();
1274 match expl_dedicated_rx.recv_timeout(Duration::from_millis(200)) {
1275 Ok(PlanetToExplorer::Stopped) => {}
1276 _ => panic!("Planet sent incorrect response"),
1277 }
1278
1279 orch_tx.send(OrchestratorToPlanet::StartPlanetAI).unwrap();
1281 match orch_rx.recv_timeout(Duration::from_millis(200)) {
1282 Ok(PlanetToOrchestrator::StartPlanetAIResult { .. }) => {}
1283 _ => panic!("Planet sent incorrect response"),
1284 }
1285
1286 orch_tx
1288 .send(OrchestratorToPlanet::OutgoingExplorerRequest { explorer_id })
1289 .unwrap();
1290
1291 match orch_rx.recv_timeout(Duration::from_millis(200)) {
1293 Ok(PlanetToOrchestrator::OutgoingExplorerResponse { planet_id, res, .. }) => {
1294 assert_eq!(planet_id, 1);
1295 assert!(res.is_ok());
1296 }
1297 _ => panic!("Expected OutgoingExplorerResponse"),
1298 }
1299
1300 expl_tx_global
1303 .send(ExplorerToPlanet::AvailableEnergyCellRequest { explorer_id })
1304 .unwrap();
1305
1306 let result = expl_dedicated_rx.recv_timeout(Duration::from_millis(200));
1308 assert!(
1309 result.is_err(),
1310 "Planet responded to explorer after it left!"
1311 );
1312
1313 drop(orch_tx);
1315 let _ = handle.join();
1316 }
1317}