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}