common_game/components/resource.rs
1//! # Resource Module
2//!
3//! This module defines the resources that can be generated and combined in the game.
4//! It provides a framework for creating basic and complex resources, and for defining
5//! the recipes that govern their creation.
6//!
7//! ## Resources
8//!
9//! Resources are defined by the [`Resource`] trait, which provides a common interface for all
10//! resources. There are two types of resources:
11//!
12//! - **Basic Resources**: These are the simplest resources, and can be generated with
13//! just an [`EnergyCell`]. Examples include `Oxygen` and `Hydrogen`.
14//! - **Complex Resources**: These are created by combining other resources and an energy cell.
15//! Examples include `Water` and `Diamond`.
16//!
17//!
18//! ## Generator and Combinator
19//!
20//! The [`Generator`] and [`Combinator`] structs are used to manage the recipes for
21//! creating resources. The `Generator` is responsible for creating basic resources,
22//! while the `Combinator` is responsible for creating complex resources.
23//!
24//! Each planet has its own `Generator` and `Combinator`, which are initialized with
25//! the recipes that are available to that planet.
26use crate::components::energy_cell::EnergyCell;
27use std::collections::HashSet;
28use std::fmt::Display;
29use std::hash::Hash;
30
31/// A trait that provides a common interface for all resources.
32pub trait Resource: Display {
33 /// Returns a static string representation of the resource.
34 fn to_static_str(&self) -> &'static str;
35}
36
37/// An enum that identifies a resource, which can be either a [`BasicResourceType`] or a
38/// [`ComplexResourceType`], without actually containing the underlying resource.
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40pub enum ResourceType {
41 /// A basic resource type.
42 Basic(BasicResourceType),
43 /// A complex resource type.
44 Complex(ComplexResourceType),
45}
46
47/// An enum that contains a resource, which can be either a [`BasicResource`] or a
48/// [`ComplexResource`].
49#[derive(Debug, PartialEq, Eq, Hash)]
50pub enum GenericResource {
51 /// A basic resource.
52 BasicResources(BasicResource),
53 /// A complex resource.
54 ComplexResources(ComplexResource),
55}
56
57impl GenericResource {
58 /// Returns the [`ResourceType`] of the `GenericResource`.
59 #[must_use]
60 pub fn get_type(&self) -> ResourceType {
61 match self {
62 GenericResource::BasicResources(basic) => ResourceType::Basic(basic.get_type()),
63 GenericResource::ComplexResources(complex) => ResourceType::Complex(complex.get_type()),
64 }
65 }
66}
67
68impl Hash for ComplexResourceType {
69 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
70 std::mem::discriminant(self).hash(state);
71 }
72}
73
74impl Hash for BasicResourceType {
75 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
76 std::mem::discriminant(self).hash(state);
77 }
78}
79
80/// Manages the recipes and production of complex resources for a planet.
81///
82/// The `Combinator` is responsible for storing the allowed recipes for [`ComplexResource`]s
83/// and validating creation requests.
84///
85/// It works in conjunction with an [`EnergyCell`]. To create a complex resource,
86/// the combinator:
87/// 1. Checks if the requested resource type is in its set of allowed recipes.
88/// 2. Consumes the required input resources.
89/// 3. Discharges the provided `EnergyCell` to power the combination process.
90///
91/// Each planet instance has its own `Combinator` initialized with a specific set of rules.
92#[derive(Debug)]
93pub struct Combinator {
94 set: HashSet<ComplexResourceType>,
95}
96
97impl Default for Combinator {
98 fn default() -> Self {
99 Self::new()
100 }
101}
102
103impl Combinator {
104 /// Creates a new `Combinator` with no recipes.
105 #[must_use]
106 pub fn new() -> Combinator {
107 Combinator {
108 set: HashSet::default(),
109 }
110 }
111
112 /// Returns `true` if the `Combinator` contains a recipe for the specified
113 /// [`ComplexResourceType`].
114 #[must_use]
115 pub fn contains(&self, complex: ComplexResourceType) -> bool {
116 matches!(&self.set.get(&complex), Some(_f))
117 }
118
119 /// # Internal API - Do not use directly
120 ///
121 /// Adds a recipe for the specified [`ComplexResourceType`] to the `Combinator`.
122 /// This method is intended for internal use only, to initialize a planet's `Combinator`.
123 #[doc(hidden)]
124 pub(crate) fn add(&mut self, complex: ComplexResourceType) -> Result<(), String> {
125 if self.set.insert(complex) {
126 Ok(())
127 } else {
128 Err(format!(
129 "There was already a recipe for {complex:?}, the value should never be updated"
130 ))
131 }
132 }
133
134 /// Returns a `HashSet` of all the recipes available in the `Combinator`.
135 #[must_use]
136 pub fn all_available_recipes(&self) -> HashSet<ComplexResourceType> {
137 self.set.iter().copied().collect()
138 }
139}
140
141/// Manages the recipes and production of basic resources for a planet.
142///
143/// The `Generator` is responsible for storing the allowed recipes for [`BasicResource`]s
144/// and validating creation requests.
145///
146/// Unlike the [`Combinator`], the `Generator` creates resources "from scratch" (using only energy).
147/// To create a basic resource, the generator:
148/// 1. Checks if the requested resource type is in its set of allowed recipes.
149/// 2. Discharges the provided [`EnergyCell`] to power the generation process.
150///
151/// Each planet instance has its own `Generator` initialized with a specific set of rules.
152#[derive(Debug)]
153pub struct Generator {
154 set: HashSet<BasicResourceType>,
155}
156
157impl Default for Generator {
158 fn default() -> Self {
159 Self::new()
160 }
161}
162
163impl Generator {
164 /// Creates a new `Generator` with no recipes.
165 #[must_use]
166 pub fn new() -> Generator {
167 Generator {
168 set: HashSet::default(),
169 }
170 }
171
172 /// Returns `true` if the `Generator` contains a recipe for the specified
173 /// [`BasicResourceType`].
174 #[must_use]
175 pub fn contains(&self, basic: BasicResourceType) -> bool {
176 matches!(&self.set.get(&basic), Some(_f))
177 }
178
179 /// # Internal API - Do not use directly
180 ///
181 /// Adds a recipe for the specified [`BasicResourceType`] to the `Generator`.
182 /// This method is intended for internal use only, to initialize a planet's `Generator`.
183 #[doc(hidden)]
184 pub(crate) fn add(&mut self, basic: BasicResourceType) -> Result<(), String> {
185 if self.set.insert(basic) {
186 Ok(())
187 } else {
188 Err(format!(
189 "There was already a recipe for {basic:?}, the value should never be updated"
190 ))
191 }
192 }
193
194 /// Returns a `HashSet` of all the recipes available in the `Generator`.
195 #[must_use]
196 pub fn all_available_recipes(&self) -> HashSet<BasicResourceType> {
197 self.set.iter().copied().collect()
198 }
199}
200
201/// A macro for defining the basic and complex resources.
202///
203/// This macro defines the structs and enums for the resources, and implements the
204/// [`Resource`] trait for them. It also defines the methods for converting between
205/// the different resource types.
206///
207/// ## Arguments
208///
209/// * `Basic`: A list of the basic resources to define.
210/// * `Complex`: A list of the complex resources to define.
211///
212/// ## Generated Code
213///
214/// This macro generates the following code:
215///
216/// * A struct for each basic and complex resource.
217/// * An implementation of the [`Resource`] trait for each resource.
218/// * An implementation of the `Display` trait for each resource.
219/// * Methods for converting between the different resource types.
220/// * An enum for each of the following:
221/// * `BasicResourceType`: An enum that identifies a basic resource.
222/// * `ComplexResourceType`: An enum that identifies a complex resource.
223/// * `BasicResource`: An enum that contains a basic resource.
224/// * `ComplexResource`: An enum that contains a complex resource.
225/// * Methods for the `Generator` and `Combinator` structs that allow to create
226/// the resources.
227///
228macro_rules! define_resources {
229 (Basic: [$($basic:ident),* $(,)?], Complex: [$($complex:ident),* $(,)?]) => {
230
231 $(
232 /// A basic resource.
233 ///
234 /// This struct represents the basic resource `$basic`.
235 #[derive(Debug, PartialEq,Eq,Hash)]
236 pub struct $basic { _private: () }
237
238 impl Display for $basic {
239 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
240 write!(f, "Basic Resource {}", stringify!($basic))
241 }
242 }
243
244 impl $basic {
245 /// Converts this resource to a [`ResourceType`].
246 pub fn to_type(&self) -> ResourceType {
247 match self {
248 $basic { .. } => ResourceType::Basic(BasicResourceType::$basic),
249 }
250 }
251
252 /// Converts this resource to a [`GenericResource`].
253 pub fn to_generic(self) -> GenericResource {
254 GenericResource::BasicResources( BasicResource::$basic(self) )
255 }
256
257 /// Converts this resource to a [`BasicResource`].
258 pub fn to_basic(self) -> BasicResource {
259 BasicResource::$basic( self )
260 }
261
262 /// Returns the [`BasicResourceType`] of this resource.
263 pub fn to_basic_type(&self) -> BasicResourceType {
264 match self {
265 $basic { .. } => BasicResourceType::$basic,
266 }
267 }
268 }
269
270
271
272 impl Resource for $basic {
273 fn to_static_str(&self) -> &'static str {
274 stringify!($basic)
275 }
276 }
277
278 paste::paste!{
279 fn [<generate_ $basic:lower>] (energy_cell: &mut EnergyCell) -> Result<$basic , String> {
280 energy_cell.discharge().and_then(|()| Ok($basic { _private: () }))
281 }
282 }
283 )*
284
285 $(
286 /// A complex resource.
287 ///
288 /// This struct represents the complex resource `$complex`.
289 #[derive(Debug, PartialEq,Eq,Hash)]
290 pub struct $complex {
291 _private: (),
292 }
293 impl Display for $complex {
294 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295 write!(f, "Complex Resource {}", stringify!($complex))
296 }
297 }
298
299 impl Resource for $complex {
300 fn to_static_str(&self) -> &'static str {
301 stringify!($complex)
302 }
303 }
304
305 impl $complex {
306 /// Converts this resource to a [`ResourceType`].
307 pub fn to_type(&self) -> ResourceType {
308 match self {
309 $complex { .. } => ResourceType::Complex(ComplexResourceType::$complex),
310 }
311 }
312
313 /// Converts this resource to a [`GenericResource`].
314 pub fn to_generic(self) -> GenericResource {
315 GenericResource::ComplexResources( ComplexResource::$complex(self) )
316 }
317
318 /// Converts this resource to a [`ComplexResource`].
319 pub fn to_complex(self) -> ComplexResource {
320 ComplexResource::$complex( self )
321 }
322
323 /// Returns the [`ComplexResourceType`] of this resource.
324 pub fn to_complex_type(&self) -> ComplexResourceType {
325 match self {
326 $complex { .. } => ComplexResourceType::$complex,
327 }
328 }
329 }
330
331
332
333
334 )*
335
336
337 impl ResourceType{
338 paste::paste! {
339 $(
340 /// Creates a new [`ResourceType::Complex`] variant for `$complex`.
341 pub fn [< make_ $complex:lower >] () -> Self {
342 ResourceType::Complex(ComplexResourceType::$complex)
343 }
344 )*
345 }
346
347 paste::paste! {
348 $(
349 /// Returns `true` if the resource type is [`ComplexResourceType::$complex`].
350 pub fn [< is_ $complex:lower >] (&self) -> bool {
351 if let ResourceType::Complex(ComplexResourceType::$complex) = self {
352 true
353 } else {
354 false
355 }
356 }
357 )*
358 }
359
360 paste::paste! {
361 $(
362 /// Creates a new [`ResourceType::Basic`] variant for `$basic`.
363 pub fn [< make_ $basic:lower >] () -> Self {
364 ResourceType::Basic(BasicResourceType::$basic)
365 }
366 )*
367 }
368
369 paste::paste! {
370 $(
371 /// Returns `true` if the resource type is [`BasicResourceType::$basic`].
372 pub fn [< is_ $basic:lower >] (&self) -> bool {
373 if let ResourceType::Basic(BasicResourceType::$basic) = self {
374 true
375 } else {
376 false
377 }
378 }
379 )*
380 }
381
382 }
383
384 impl BasicResourceType{
385
386 paste::paste! {
387 $(
388 /// Returns `true` if the resource type is `$basic`.
389 pub fn [< is_ $basic:lower >] (&self) -> bool {
390 if let BasicResourceType::$basic = self {
391 true
392 } else {
393 false
394 }
395 }
396 )*
397 }
398
399 }
400
401
402 impl ComplexResourceType{
403
404 paste::paste! {
405 $(
406 /// Returns `true` if the resource type is `$complex`.
407 pub fn [< is_ $complex:lower >] (&self) -> bool {
408 if let ComplexResourceType::$complex = self {
409 true
410 } else {
411 false
412 }
413 }
414 )*
415 }
416
417 }
418
419 /// An enum that identifies a [`ComplexResource`] type without actually containing the
420 /// underlying resource.
421 ///
422 #[derive(Debug,Clone,Copy, Eq)]
423 pub enum ComplexResourceType {
424 $(
425 $complex,
426 )*
427 }
428
429 impl BasicResource {
430 /// Returns the [`BasicResourceType`] of this resource.
431 pub fn get_type(&self) -> BasicResourceType {
432 match self {
433 $( BasicResource:: $basic (_) => BasicResourceType::$basic, )*
434 }
435 }
436 paste::paste!{
437 $(
438 /// Attempts to convert the `BasicResource` into a `$basic`.
439 ///
440 /// # Returns
441 /// * `Ok($basic)` if the resource is `$basic`.
442 ///
443 /// # Errors
444 /// Returns an error if the resource is of a different type.
445 pub fn [< to_ $basic:lower >] (self) -> Result< $basic , String> {
446 match self {
447 BasicResource:: $basic (h) => Ok(h) ,
448 _ => Err( "Different type found".into() )
449 }
450 }
451 )*
452 }
453 }
454 impl GenericResource {
455 paste::paste! {
456 $(
457 /// Attempts to convert the `GenericResource` into a `$complex`.
458 ///
459 /// # Returns
460 /// * `Ok($complex)` if the resource is `$complex`.
461 ///
462 /// # Errors
463 /// Returns an error if the resource is of a different type.
464 pub fn [< to_ $complex:lower >] (self) -> Result< $complex,String> {
465 match self {
466 GenericResource::ComplexResources(ComplexResource:: $complex(h)) => Ok(h),
467 _ => Err("Different type found".into())
468 }
469 }
470 )*
471 }
472
473 paste::paste! {
474 $(
475 /// Attempts to convert the `GenericResource` into a `$basic`.
476 ///
477 /// # Returns
478 /// * `Ok($basic)` if the resource is `$basic`.
479 ///
480 /// # Errors
481 /// Returns an error if the resource is of a different type.
482 pub fn [< to_ $basic:lower >] (self) -> Result< $basic , String> {
483 match self {
484 GenericResource::BasicResources(BasicResource:: $basic(h)) => Ok(h),
485 _ => Err("Different type found".into())
486 }
487 }
488 )*
489 }
490 }
491
492 impl ComplexResource {
493 /// Returns the [`ComplexResourceType`] of this resource.
494 pub fn get_type(&self) -> ComplexResourceType {
495 match self {
496 $( ComplexResource:: $complex (_) => ComplexResourceType::$complex, )*
497 }
498 }
499
500 paste::paste!{
501 $(
502 /// Attempts to convert the `ComplexResource` into a `$complex`.
503 ///
504 /// # Returns
505 /// * `Ok($complex)` if the resource is `$complex`.
506 ///
507 /// # Errors
508 /// Returns an error if the resource is of a different type.
509 pub fn [< to_ $complex:lower >] (self) -> Result< $complex,String> {
510 match self {
511 ComplexResource:: $complex( h) => Ok(h) ,
512 _ => Err("Different type found".into())
513 }
514 }
515 )*
516 }
517 }
518
519
520 impl PartialEq<Self> for ComplexResourceType {
521 fn eq(&self, other: &Self) -> bool {
522 match (self, other) {
523 $( ( ComplexResourceType::$complex , ComplexResourceType::$complex) => { true}, )*
524 (_, _) => { false}
525 }
526 }
527 }
528 impl PartialEq<Self> for BasicResourceType {
529 fn eq(&self, other: &Self) -> bool {
530 match (self, other) {
531 $( ( BasicResourceType::$basic , BasicResourceType::$basic) => { true}, )*
532 (_, _) => { false}
533 }
534 }
535 }
536
537 /// An enum that provides a unified type for all possible basic resources.
538 ///
539 /// This enum wraps every generated basic resource struct (e.g., `Oxygen`, `Hydrogen`)
540 /// into a single type. It is useful when you need to store or pass around any basic
541 /// resource without knowing its specific concrete type at compile time.
542 #[derive(Debug, PartialEq,Eq,Hash)]
543 pub enum BasicResource {
544 $(
545 $basic($basic),
546 )*
547 }
548
549 /// An enum that provides a unified type for all possible complex resources.
550 ///
551 /// This enum wraps every generated complex resource struct (e.g., `Water`, `Diamond`)
552 /// into a single type. It is useful when you need to store or pass around any complex
553 /// resource without knowing its specific concrete type at compile time.
554 #[derive(Debug ,PartialEq,Eq,Hash)]
555 pub enum ComplexResource {
556 $(
557 $complex($complex),
558 )*
559 }
560
561 /// An enum that identifies a [`BasicResource`] type without actually containing the
562 /// underlying resource.
563 ///
564 /// This enum is generated by the `define_resources!` macro and contains a variant for
565 /// each basic resource defined in the macro invocation. It is primarily used for
566 /// type identification and recipe definitions within the [`Generator`].
567 #[derive(Debug,Clone,Copy,Eq)]
568 pub enum BasicResourceType {
569 $(
570 $basic,
571 )*
572 }
573
574
575 impl Generator {
576 paste::paste! {
577 $(
578 /// Creates a new `[<$basic>]` resource.
579 ///
580 /// This method attempts to create a new instance of the corresponding basic
581 /// resource by discharging an `EnergyCell`.
582 ///
583 /// # Arguments
584 ///
585 /// * `energy_cell` - A mutable reference to an `EnergyCell` which will be
586 /// discharged to create the resource.
587 ///
588 /// # Returns
589 ///
590 /// A `Result` indicating success:
591 /// * `Ok([<$basic>])`: The resource was successfully created.
592 ///
593 /// # Errors
594 ///
595 /// Returns an error if there is no recipe for this resource or if the `energy_cell` is not charged.
596 pub fn [<make_ $basic:lower>] (&self, energy_cell : &mut EnergyCell ) -> Result<$basic, String > {
597 let b = BasicResourceType::$basic;
598 if let Some(_f_enum) = &self.set.get(&b) {
599 Ok( [<generate_ $basic:lower>] (energy_cell )?)
600 } else {
601 Err(format!("there isn't a recipe for {:?}", b))
602 }
603 }
604 )*
605 }
606
607 /// Attempts to create a basic resource of the specified type.
608 ///
609 /// This method provides a generic way to request the creation of any basic
610 /// resource that the generator has a recipe for.
611 ///
612 /// # Arguments
613 ///
614 /// * `req` - The `BasicResourceType` enum variant representing the desired resource.
615 /// * `energy_cell` - A mutable reference to an `EnergyCell` to be used for
616 /// discharging during resource creation.
617 ///
618 /// # Returns
619 ///
620 /// A `Result` indicating success:
621 /// * `Ok(BasicResource)`: The requested resource was successfully created and
622 /// wrapped in the `BasicResource` enum.
623 ///
624 /// # Errors
625 ///
626 /// Returns an error if the `energy_cell` is not charged or if there is no recipe
627 /// for the requested resource type.
628 pub fn try_make(&self , req : BasicResourceType , energy_cell: &mut EnergyCell) -> Result<BasicResource, String> {
629 if !energy_cell.is_charged() {
630 return Err("The energy is not charged".to_string());
631 }
632 match req {
633 $(
634 BasicResourceType::$basic => {
635 if self.set.contains( &BasicResourceType::$basic ) {
636 energy_cell.discharge()?;
637 Ok($basic{ _private: () }.to_basic())
638 }
639 else {
640 Err(format!("Missing recipe for {:?}", stringify!($basic) ))
641 }
642 },
643 )*
644 }
645 }
646
647 }
648 };
649 }
650
651/// A macro for defining the combination rules for complex resources.
652///
653/// This macro defines the functions for creating complex resources from other
654/// resources.
655///
656/// ## Arguments
657///
658/// * A list of rules, where each rule has the following format:
659/// `result from lhs + rhs`
660///
661/// ## Generated Code
662///
663/// This macro generates the following code:
664///
665/// * A function for each combination rule that creates the complex resource.
666/// * An enum that gives a structured way to pass around the request to produce a
667/// complex resource.
668/// * An implementation of the `try_make` method for the `Combinator` struct that
669/// allows to create the complex resources.
670///
671macro_rules! define_combination_rules {
672 ($($result:ident from $lhs:ident + $rhs:ident ),* $(,)?) => {
673 $(
674 paste::paste! {
675 fn [< $result:lower _fn >] ( r1: $lhs , r2: $rhs , energy_cell: &mut EnergyCell) -> Result<$result, (String ,$lhs , $rhs ) > {
676 match energy_cell.discharge(){
677 Ok(_) => Ok($result { _private: () }),
678 Err(e) => Err( (e, r1, r2 )),
679 }
680 }
681 }
682 )*
683
684 paste::paste! {
685 /// An enum that represents a structured request to produce a specific complex resource.
686 ///
687 /// Each variant corresponds
688 /// to a combination rule and holds the necessary input resources (`lhs` and `rhs`) required
689 /// to produce the target complex resource.
690 ///
691 /// It allows passing all ingredients for a reaction as a single object to the [`Combinator`].
692 #[derive(Debug, PartialEq,Eq,Hash )]
693 pub enum ComplexResourceRequest{
694 $(
695 [<$result >]( $lhs, $rhs ),
696 )*
697 }
698 }
699
700 impl Combinator {
701 paste::paste! {
702 $(
703 /// Creates a new `[<$result>]` resource.
704 ///
705 /// This method attempts to create a new instance of the corresponding
706 /// complex resource by combining two input resources (`r1` and `r2`) and
707 /// discharging an `EnergyCell`.
708 ///
709 /// # Arguments
710 ///
711 /// * `r1` - The first input resource ([`$lhs`]).
712 /// * `r2` - The second input resource ([`$rhs`]).
713 /// * `energy_cell` - A mutable reference to an `EnergyCell` which will be
714 /// discharged to create the resource.
715 ///
716 /// # Returns
717 ///
718 /// A `Result` indicating success:
719 /// * `Ok([<$result>])`: The complex resource was successfully created.
720 ///
721 /// # Errors
722 ///
723 /// Returns an error if there is no recipe for this resource, if the `energy_cell` is not charged, or if the energy discharge fails. The input resources are returned in the error tuple to prevent ownership loss.
724 pub fn [<make_ $result:lower>] (&self, r1 : $lhs ,r2 : $rhs , energy_cell: &mut EnergyCell ) -> Result<$result, (String, $lhs , $rhs ) > {
725 let c = ComplexResourceType::$result;
726 if let Some(_f_enum) = &self.set.get( &c ) {
727 [<$result:lower _fn >](r1,r2 , energy_cell )
728 } else {
729 Err((format!("there isn't a recipe for {:?}", c), r1 ,r2 ) )
730 }
731 }
732 )*
733 }
734
735 /// Attempts to create a complex resource based on a given request.
736 ///
737 /// This method provides a generic way to request the creation of any complex
738 /// resource that the combinator has a recipe for.
739 ///
740 /// # Arguments
741 ///
742 /// * `req` - The `ComplexResourceRequest` enum variant representing the desired
743 /// complex resource and its required input resources.
744 /// * `energy_cell` - A mutable reference to an `EnergyCell` which will be
745 /// discharged during resource creation.
746 ///
747 /// # Returns
748 ///
749 /// A `Result` indicating success:
750 /// * `Ok(ComplexResource)`: The complex resource was successfully created.
751 ///
752 /// # Errors
753 ///
754 /// Returns an error if there is no recipe for the requested complex resource or if the
755 /// energy cell discharge fails. The input resources are returned in the error tuple to
756 /// prevent ownership loss on failure.
757 pub fn try_make(&self , req : ComplexResourceRequest , energy_cell: &mut EnergyCell) -> Result<ComplexResource, (String, GenericResource , GenericResource )> {
758 match req {
759 $(
760 ComplexResourceRequest::$result(r1, r2) => {
761 if self.set.contains( &ComplexResourceType::$result ) {
762 paste::paste! {
763 [<$result:lower _fn >](r1,r2 , energy_cell ).map(|r| {
764 r.to_complex()
765 }).map_err(|(s , r1 ,r2)| {
766 (s , r1.to_generic() ,r2.to_generic())
767 })
768 }
769 }
770 else {
771 Err((format!("there isn't a recipe for {:?}", stringify!($result)), r1.to_generic() ,r2.to_generic() ) )
772 }
773 },
774 )*
775 }
776 }
777
778 }
779
780 };
781 }
782
783define_resources!(
784 Basic: [Oxygen , Hydrogen, Carbon, Silicon],
785 Complex: [Diamond, Water , Life , Robot , Dolphin , AIPartner]
786);
787
788define_combination_rules!(
789 Water from Hydrogen + Oxygen,
790 Diamond from Carbon + Carbon,
791 Life from Water + Carbon ,
792 Robot from Silicon + Life ,
793 Dolphin from Water + Life ,
794 AIPartner from Robot + Diamond
795);
796
797#[cfg(test)]
798mod tests {
799 use super::*;
800 // Adjust these imports based on where your files are located in the crate.
801 // Based on previous context, I assume:
802 use crate::components::energy_cell::EnergyCell;
803 use crate::components::sunray::Sunray;
804
805 // --- Helper to get a charged cell ---
806 fn get_charged_cell() -> EnergyCell {
807 let mut cell = EnergyCell::new();
808 // We use the real Sunray constructor now
809 cell.charge(Sunray::new());
810 cell
811 }
812
813 #[test]
814 fn test_generator_success() {
815 let mut generator = Generator::new();
816 let mut cell = get_charged_cell();
817
818 // 1. Add recipe
819 assert!(generator.add(BasicResourceType::Oxygen).is_ok());
820
821 // 2. Generate resource
822 let result = generator.make_oxygen(&mut cell);
823
824 assert!(result.is_ok());
825 assert_eq!(result.unwrap().to_static_str(), "Oxygen");
826
827 // 3. Ensure cell is discharged
828 assert!(!cell.is_charged());
829 }
830
831 #[test]
832 fn test_generator_fail_no_charge() {
833 let mut generator = Generator::new();
834 let mut cell = EnergyCell::new(); // Not charged
835
836 generator.add(BasicResourceType::Oxygen).unwrap();
837
838 let result = generator.make_oxygen(&mut cell);
839
840 assert!(result.is_err());
841 assert_eq!(result.err().unwrap(), "EnergyCell not charged!");
842 }
843
844 #[test]
845 fn test_generator_fail_no_recipe() {
846 let generator = Generator::new(); // Empty, no recipes added
847 let mut cell = get_charged_cell();
848
849 // Try to make Oxygen without adding the recipe first
850 let result = generator.make_oxygen(&mut cell);
851
852 assert!(result.is_err());
853 assert!(result.err().unwrap().contains("there isn't a recipe for"));
854 }
855
856 #[test]
857 fn test_combinator_success() {
858 let mut generator = Generator::new();
859 let mut comb = Combinator::new();
860 let mut cell = get_charged_cell();
861
862 // Setup
863 generator.add(BasicResourceType::Oxygen).unwrap();
864 generator.add(BasicResourceType::Hydrogen).unwrap();
865 comb.add(ComplexResourceType::Water).unwrap();
866
867 let oxygen = generator.make_oxygen(&mut cell).unwrap();
868
869 // Recharge cell using real Sunray
870 cell.charge(Sunray::new());
871 let hydrogen = generator.make_hydrogen(&mut cell).unwrap();
872
873 // Test Combination: Water = Hydrogen + Oxygen
874 cell.charge(Sunray::new());
875 let result = comb.make_water(hydrogen, oxygen, &mut cell);
876
877 assert!(result.is_ok());
878 assert_eq!(result.unwrap().to_static_str(), "Water");
879 }
880
881 #[test]
882 fn test_combinator_fail_no_recipe_returns_resources() {
883 let mut generator = Generator::new();
884 let mut comb = Combinator::new(); // No recipes added
885 let mut cell = get_charged_cell();
886
887 generator.add(BasicResourceType::Oxygen).unwrap();
888 generator.add(BasicResourceType::Hydrogen).unwrap();
889
890 let oxygen = generator.make_oxygen(&mut cell).unwrap();
891
892 cell.charge(Sunray::new());
893 let hydrogen = generator.make_hydrogen(&mut cell).unwrap();
894
895 // Attempt make_water without recipe
896 let result = comb.make_water(hydrogen, oxygen, &mut cell);
897
898 assert!(result.is_err());
899 let (_s, r1, r2) = result.err().unwrap();
900 comb.add(ComplexResourceType::Water).unwrap();
901 let result = comb.make_water(r1, r2, &mut cell);
902 assert!(result.is_err());
903
904 // Critical: Ensure we got our resources back in the error tuple
905 let (_err_msg, returned_h, returned_o) = result.err().unwrap();
906
907 assert_eq!(returned_h.to_static_str(), "Hydrogen");
908 assert_eq!(returned_o.to_static_str(), "Oxygen");
909 }
910
911 #[test]
912 fn test_recipe_management() {
913 let mut generator = Generator::new();
914
915 assert!(generator.add(BasicResourceType::Carbon).is_ok());
916 assert!(generator.contains(BasicResourceType::Carbon));
917 assert!(!generator.contains(BasicResourceType::Silicon));
918
919 // Test duplicate addition error
920 assert!(generator.add(BasicResourceType::Carbon).is_err());
921 }
922
923 #[test]
924 fn test_enum_equality_and_hashing() {
925 let t1 = BasicResourceType::Oxygen;
926 let t2 = BasicResourceType::Oxygen;
927 let t3 = BasicResourceType::Carbon;
928
929 assert_eq!(t1, t2);
930 assert_ne!(t1, t3);
931
932 // Test Hashing implicitly via HashSet
933 let mut set = HashSet::new();
934 set.insert(BasicResourceType::Oxygen);
935 set.insert(BasicResourceType::Oxygen);
936 assert_eq!(set.len(), 1);
937 }
938
939 #[test]
940 fn test_complex_chain() {
941 // Tests a multi-step chain: Carbon + Carbon -> Diamond; Robot + Diamond -> AIPartner
942 let mut generator = Generator::new();
943 let mut comb = Combinator::new();
944 let mut cell = get_charged_cell();
945
946 // Add Recipes
947 generator.add(BasicResourceType::Carbon).unwrap();
948 generator.add(BasicResourceType::Silicon).unwrap();
949 generator.add(BasicResourceType::Oxygen).unwrap();
950 generator.add(BasicResourceType::Hydrogen).unwrap();
951
952 comb.add(ComplexResourceType::Diamond).unwrap();
953 comb.add(ComplexResourceType::Water).unwrap();
954 comb.add(ComplexResourceType::Life).unwrap();
955 comb.add(ComplexResourceType::Robot).unwrap();
956 comb.add(ComplexResourceType::AIPartner).unwrap();
957
958 // 1. Make Diamond (Carbon + Carbon)
959 let c1 = generator.make_carbon(&mut cell).unwrap();
960 cell.charge(Sunray::new());
961 let c2 = generator.make_carbon(&mut cell).unwrap();
962 cell.charge(Sunray::new());
963 let diamond = comb.make_diamond(c1, c2, &mut cell).unwrap();
964
965 // 2. Make Robot (Silicon + Life) -> Needs Life (Water + Carbon) -> Needs Water (H + O)
966
967 // Make Water
968 cell.charge(Sunray::new());
969 let h = generator.make_hydrogen(&mut cell).unwrap();
970 cell.charge(Sunray::new());
971 let o = generator.make_oxygen(&mut cell).unwrap();
972 cell.charge(Sunray::new());
973 let water = comb.make_water(h, o, &mut cell).unwrap();
974
975 // Make Life
976 cell.charge(Sunray::new());
977 let c3 = generator.make_carbon(&mut cell).unwrap();
978 cell.charge(Sunray::new());
979 let life = comb.make_life(water, c3, &mut cell).unwrap();
980
981 // Make Robot
982 cell.charge(Sunray::new());
983 let silicon = generator.make_silicon(&mut cell).unwrap();
984 cell.charge(Sunray::new());
985 let robot = comb.make_robot(silicon, life, &mut cell).unwrap();
986
987 // 3. Make AIPartner (Robot + Diamond)
988 cell.charge(Sunray::new());
989 let ai = comb.make_aipartner(robot, diamond, &mut cell);
990
991 assert!(ai.is_ok());
992 assert_eq!(ai.unwrap().to_static_str(), "AIPartner");
993 }
994
995 #[test]
996 fn test_generator_try_make() {
997 let mut generator = Generator::new();
998 let mut cell = get_charged_cell();
999 generator.add(BasicResourceType::Oxygen).unwrap();
1000
1001 // Test success
1002 let result = generator.try_make(BasicResourceType::Oxygen, &mut cell);
1003 assert!(result.is_ok());
1004 let resource = result.unwrap();
1005 assert_eq!(resource.get_type(), BasicResourceType::Oxygen);
1006 assert!(!cell.is_charged());
1007
1008 // Test fail no charge
1009 let result = generator.try_make(BasicResourceType::Oxygen, &mut cell);
1010 assert!(result.is_err());
1011 assert_eq!(result.err().unwrap(), "The energy is not charged");
1012
1013 // Test fail no recipe
1014 let mut cell = get_charged_cell();
1015 let result = generator.try_make(BasicResourceType::Hydrogen, &mut cell);
1016 assert!(result.is_err());
1017 assert!(result.err().unwrap().contains("Missing recipe for"));
1018 }
1019
1020 #[test]
1021 fn test_combinator_try_make() {
1022 let mut generator = Generator::new();
1023 let mut combinator = Combinator::new();
1024 combinator.add(ComplexResourceType::Water).unwrap();
1025 generator.add(BasicResourceType::Hydrogen).unwrap();
1026 generator.add(BasicResourceType::Oxygen).unwrap();
1027
1028 let mut cell = get_charged_cell();
1029 let hydrogen = generator.make_hydrogen(&mut cell).unwrap();
1030 cell.charge(Sunray::new());
1031 let oxygen = generator.make_oxygen(&mut cell).unwrap();
1032
1033 // Test success
1034 cell.charge(Sunray::new());
1035 let request = ComplexResourceRequest::Water(hydrogen, oxygen);
1036 let result = combinator.try_make(request, &mut cell);
1037 assert!(result.is_ok());
1038 let resource = result.unwrap();
1039 assert_eq!(resource.get_type(), ComplexResourceType::Water);
1040 assert!(!cell.is_charged());
1041
1042 let hydrogen = generator.make_hydrogen(&mut get_charged_cell()).unwrap();
1043 let oxygen = generator.make_oxygen(&mut get_charged_cell()).unwrap();
1044
1045 // Test fail no charge
1046 let request = ComplexResourceRequest::Water(hydrogen, oxygen);
1047 let result = combinator.try_make(request, &mut cell);
1048 assert!(result.is_err());
1049 let (err, _, _) = result.err().unwrap();
1050 assert_eq!(err, "EnergyCell not charged!");
1051
1052 // Test fail no recipe
1053 let mut cell = get_charged_cell();
1054 let combinator = Combinator::new(); // No recipes
1055 let hydrogen = generator.make_hydrogen(&mut get_charged_cell()).unwrap();
1056 let oxygen = generator.make_oxygen(&mut get_charged_cell()).unwrap();
1057 let request = ComplexResourceRequest::Water(hydrogen, oxygen);
1058 let result = combinator.try_make(request, &mut cell);
1059 assert!(result.is_err());
1060 let (err, _, _) = result.err().unwrap();
1061 assert!(err.contains("there isn't a recipe for"));
1062 }
1063
1064 #[test]
1065 fn test_generic_resource_conversions() {
1066 let oxygen = Oxygen { _private: () };
1067 let generic_basic = oxygen.to_generic();
1068 assert_eq!(
1069 generic_basic.get_type(),
1070 ResourceType::Basic(BasicResourceType::Oxygen)
1071 );
1072 assert!(generic_basic.to_oxygen().is_ok());
1073
1074 let water = Water { _private: () };
1075 let generic_complex = water.to_generic();
1076 assert_eq!(
1077 generic_complex.get_type(),
1078 ResourceType::Complex(ComplexResourceType::Water)
1079 );
1080 assert!(generic_complex.to_water().is_ok());
1081 }
1082}