1use crate::genome::AgentGenome;
8use phago_core::types::{AgentId, Position};
9
10pub trait SpawnPolicy {
12 fn on_death(
17 &mut self,
18 dead_id: AgentId,
19 alive_count: usize,
20 fittest_genome: Option<&AgentGenome>,
21 fittest_position: Option<Position>,
22 ) -> Option<(AgentGenome, Position)>;
23}
24
25pub struct FitnessSpawnPolicy {
27 pub max_population: usize,
29 pub mutation_rate: f64,
31 spawn_counter: u64,
33}
34
35impl FitnessSpawnPolicy {
36 pub fn new(max_population: usize, mutation_rate: f64) -> Self {
37 Self {
38 max_population,
39 mutation_rate,
40 spawn_counter: 0,
41 }
42 }
43}
44
45impl SpawnPolicy for FitnessSpawnPolicy {
46 fn on_death(
47 &mut self,
48 _dead_id: AgentId,
49 alive_count: usize,
50 fittest_genome: Option<&AgentGenome>,
51 fittest_position: Option<Position>,
52 ) -> Option<(AgentGenome, Position)> {
53 if alive_count >= self.max_population {
55 return None;
56 }
57
58 let parent_genome = fittest_genome?;
60 let parent_pos = fittest_position.unwrap_or(Position::new(0.0, 0.0));
61
62 self.spawn_counter += 1;
63 let offspring_genome = parent_genome.mutate(self.mutation_rate, self.spawn_counter);
64
65 let offset_x = ((self.spawn_counter as f64 * 2.7).sin()) * 3.0;
67 let offset_y = ((self.spawn_counter as f64 * 1.3).cos()) * 3.0;
68 let position = Position::new(parent_pos.x + offset_x, parent_pos.y + offset_y);
69
70 Some((offspring_genome, position))
71 }
72}
73
74pub struct NoSpawnPolicy;
76
77impl SpawnPolicy for NoSpawnPolicy {
78 fn on_death(
79 &mut self,
80 _dead_id: AgentId,
81 _alive_count: usize,
82 _fittest_genome: Option<&AgentGenome>,
83 _fittest_position: Option<Position>,
84 ) -> Option<(AgentGenome, Position)> {
85 None
86 }
87}
88
89pub struct RandomSpawnPolicy {
91 pub max_population: usize,
92 spawn_counter: u64,
93}
94
95impl RandomSpawnPolicy {
96 pub fn new(max_population: usize) -> Self {
97 Self {
98 max_population,
99 spawn_counter: 0,
100 }
101 }
102}
103
104impl SpawnPolicy for RandomSpawnPolicy {
105 fn on_death(
106 &mut self,
107 _dead_id: AgentId,
108 alive_count: usize,
109 _fittest_genome: Option<&AgentGenome>,
110 _fittest_position: Option<Position>,
111 ) -> Option<(AgentGenome, Position)> {
112 if alive_count >= self.max_population {
113 return None;
114 }
115
116 self.spawn_counter += 1;
117 let genome = AgentGenome::default_genome().mutate(0.8, self.spawn_counter);
119 let x = ((self.spawn_counter as f64 * 3.7).sin()) * 10.0;
120 let y = ((self.spawn_counter as f64 * 2.1).cos()) * 10.0;
121
122 Some((genome, Position::new(x, y)))
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn fitness_spawn_creates_offspring() {
132 let mut policy = FitnessSpawnPolicy::new(10, 0.1);
133 let genome = AgentGenome::default_genome();
134 let pos = Position::new(5.0, 5.0);
135
136 let result = policy.on_death(AgentId::new(), 3, Some(&genome), Some(pos));
137 assert!(result.is_some());
138 }
139
140 #[test]
141 fn fitness_spawn_respects_cap() {
142 let mut policy = FitnessSpawnPolicy::new(5, 0.1);
143 let genome = AgentGenome::default_genome();
144
145 let result = policy.on_death(AgentId::new(), 5, Some(&genome), Some(Position::new(0.0, 0.0)));
146 assert!(result.is_none());
147 }
148
149 #[test]
150 fn no_spawn_never_spawns() {
151 let mut policy = NoSpawnPolicy;
152 let genome = AgentGenome::default_genome();
153 let result = policy.on_death(AgentId::new(), 1, Some(&genome), Some(Position::new(0.0, 0.0)));
154 assert!(result.is_none());
155 }
156}