@@ -16,28 +16,44 @@ def __init__(self, width: int, height: int) -> None:
1616 self .height = height
1717 self .water_locations : Set [Tuple [int , int ]] = set ()
1818 self .rock_locations : Set [Tuple [int , int ]] = set ()
19+ self .crystal_locations : Set [Tuple [int , int ]] = set ()
1920 self .berry_locations : Dict [Tuple [int , int ], str ] = {}
2021 self .agent_positions : Dict [str , Tuple [int , int ]] = {}
2122 self ._grid_entities : Dict [Tuple [int , int ], str ] = {}
2223
2324 def is_occupied (self , position : Tuple [int , int ]) -> bool :
24- """Check if a cell is occupied by a blocking object (agent, rock, water) ."""
25+ """Check if a cell is occupied by a blocking object."""
2526 return (
2627 position in self ._grid_entities
2728 or position in self .water_locations
2829 or position in self .rock_locations
30+ or position in self .crystal_locations
2931 )
3032
3133 def get_random_empty_cell (self ) -> Optional [Tuple [int , int ]]:
32- """Finds a random unoccupied cell."""
33- for _ in range (self .width * self .height ): # Avoid infinite loops
34- pos = (
35- random .randint (0 , self .width - 1 ),
36- random .randint (0 , self .height - 1 ),
37- )
38- if not self .is_occupied (pos ) and pos not in self .berry_locations :
39- return pos
40- return None
34+ """
35+ Finds a random unoccupied cell.
36+
37+ This implementation is deterministic in finding all empty cells and will
38+ not fail if only one empty cell remains.
39+ """
40+ # Changed from a probabilistic search to a deterministic one.
41+ # This guarantees finding an empty cell if one exists.
42+ occupied_cells = (
43+ self .water_locations
44+ | self .rock_locations
45+ | self .crystal_locations
46+ | set (self .berry_locations .keys ())
47+ | set (self .agent_positions .values ())
48+ )
49+
50+ all_cells = set ((x , y ) for x in range (self .width ) for y in range (self .height ))
51+ empty_cells = list (all_cells - occupied_cells )
52+
53+ if not empty_cells :
54+ return None
55+
56+ return random .choice (empty_cells )
4157
4258 def get_berry_toxicity (
4359 self , berry_type : str , position : Tuple [int , int ], tick : int
@@ -46,16 +62,19 @@ def get_berry_toxicity(
4662 if berry_type == "red" :
4763 return 10.0
4864 elif berry_type == "blue" :
49- return (
50- - 20.0
51- if self .is_near_feature (position , self .water_locations , 2 )
52- else 10.0
53- )
65+ is_near_water = self .is_near_feature (position , self .water_locations , 2 )
66+ is_near_crystal = self .is_near_feature (position , self .crystal_locations , 2 )
67+ if is_near_crystal :
68+ return 10.0 # Crystals purify blue berries
69+ if tick >= 1000 and is_near_water :
70+ return - 20.0 # Toxic after tick 1000 if near water
71+ return 10.0
5472 elif berry_type == "yellow" :
55- # Toxicity is random, but seeded by position and tick for reproducibility
56- seed = hash ((position , tick // 100 )) # Stable toxicity for a period
73+ seed = hash ((position , tick // 100 ))
5774 rng = random .Random (seed )
5875 return - 20.0 if rng .random () < 0.5 else 10.0
76+ elif berry_type == "orange" :
77+ return 5.0 # Small direct health boost, main effect is metabolic
5978 return 0.0
6079
6180 def is_near_feature (
@@ -73,6 +92,7 @@ def get_environmental_context(self, position: Tuple[int, int]) -> Dict[str, bool
7392 return {
7493 "near_water" : self .is_near_feature (position , self .water_locations , 2 ),
7594 "near_rocks" : self .is_near_feature (position , self .rock_locations , 2 ),
95+ "near_crystal" : self .is_near_feature (position , self .crystal_locations , 2 ),
7696 }
7797
7898 # --- Interface Methods ---
@@ -123,18 +143,22 @@ def is_valid_position(self, position: Any) -> bool:
123143 return 0 <= position [0 ] < self .width and 0 <= position [1 ] < self .height
124144
125145 def get_entities_in_radius (self , center : Any , radius : int ) -> List [Tuple [str , Any ]]:
126- return [] # Not needed for this simulation
146+ return []
127147
128148 def to_dict (self ) -> Dict [str , Any ]:
129149 return {
130150 "width" : self .width ,
131151 "height" : self .height ,
132152 "water_locations" : [list (pos ) for pos in self .water_locations ],
133153 "rock_locations" : [list (pos ) for pos in self .rock_locations ],
154+ "crystal_locations" : [list (pos ) for pos in self .crystal_locations ],
134155 }
135156
136157 def restore_from_dict (self , data : Dict [str , Any ]) -> None :
137158 self .width = data ["width" ]
138159 self .height = data ["height" ]
139- self .water_locations = {tuple (pos ) for pos in data ["water_locations" ]}
140- self .rock_locations = {tuple (pos ) for pos in data ["rock_locations" ]}
160+ self .water_locations = {tuple (pos ) for pos in data .get ("water_locations" , [])}
161+ self .rock_locations = {tuple (pos ) for pos in data .get ("rock_locations" , [])}
162+ self .crystal_locations = {
163+ tuple (pos ) for pos in data .get ("crystal_locations" , [])
164+ }
0 commit comments