-
Notifications
You must be signed in to change notification settings - Fork 0
/
blobs.py
251 lines (202 loc) · 8.14 KB
/
blobs.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
"""Repository for all Blob classes and related methods"""
import random
from typing import List
from helpers import find_closest_coord
class BaseBlob:
"""
Base class for blob. Each blob class should functionally contain at a
minimum a reproduce method and a move method
Attributes:
name (str): name associated with blob type
survival_prob (float): probability of survival at each epoch
reproduction_prob (float): probability of reproduction at each epoch
mutation_prob (float): probability of mutation at each epoch
mutation_class (Callable): class to mutate to
repr_class (Callable) class to produce when reproducing w/o mutation
color (str): color of Blob for display
x (float): x-coordinate of Blob
y (float): y-coordinate of Blob
size (float): effective size of blob
step (float): increment at which blob moves across environment
"""
def __init__(self) -> None:
"""Inits BaseBlob"""
self.name: str = "BaseBlob"
self.survival_prob: float = 0.5
self.reproduction_prob: float = 0.5
self.mutation_prob: float = 0.5
self.mutation_class: Callable = MutatedBaseBlob
self.repr_class: Callable = BaseBlob
self.color: str = "blue"
self.x = random.random()
self.y = random.random()
self.size = 0.1
self.step = 0.1
def reproduce(self):
"""
Generic reproduction function. If mutate, return a generic
MutatedBaseBlob with attributes defined
Returns:
(Blob): the type of Blob produced will depend on the mutation_class
and repr_class attributes declared in __init__
"""
if self.mutation_prob >= 0.01:
#Enforce minimum for mutation_prob to eliminate floating point
#comparison issues
mutation_event = random.random()
if mutation_event <= self.mutation_prob:
return self.mutation_class()
return self.repr_class()
def move(self, coords: tuple) -> None:
"""
Function for determining blob movement. BaseBlob move randomly
Args:
coords (tuple): coordinates to move towards. For BaseBlob, this
is mute since the blobs don't actually move towards the coord
"""
# Move in random direction (-1 or +1) in increments of step
self.x += self.step * [-1, 1][random.randrange(2)]
self.y += self.step * [-1, 1][random.randrange(2)]
def set_probs(
self, survival_prob: float, repr_prob: float, mutation_prob: float
) -> None:
"""Setter for the three probability attrs"""
self.survival_prob = survival_prob
self.reproduction_prob = repr_prob
self.mutation_prob = mutation_prob
def __str__(self):
"""Prints out name of blob"""
return (
f"""{self.name}(s={self.survival_prob},"""
f"""r={self.reproduction_prob},m={self.mutation_prob})"""
)
class PerfectTestBlob(BaseBlob):
"""Blob with 1.0 for all attrs. Used primarily for testing"""
def __init__(self) -> None:
super().__init__()
self.name: str = "PerfectTestBlob"
self.repr_class: Callable = MutatedBaseBlob
class MutatedBaseBlob(BaseBlob):
"""Class for mutated base blob"""
def __init__(self) -> None:
super().__init__()
self.name: str = "MutatedBaseBlob"
self.color: str = "red"
self.repr_class: Callable = MutatedBaseBlob
class SturdyBlob(BaseBlob):
"""Class for generic sturdy blob"""
def __init__(self) -> None:
super().__init__()
self.name: str = "SturdyBlob"
self.color: str = "green"
self.repr_class: Callable = SturdyBlob
self.survival_prob: float = 0.8
self.reproduction_prob: float = 0.5
self.mutation_prob: float = 0.0
class HungryBlob(BaseBlob):
"""Class for Blob with detection sense for where food is"""
def __init__(self) -> None:
"""See parent docstrings"""
super().__init__()
self.name: str = "HungryBlob"
self.color = "purple"
self.mutation_class: Callable = MutatedHungryBlob
self.repr_class: Callable = HungryBlob
def move(self, coords: tuple) -> None:
"""
Function for determining blob movement. Moves towards coords in
units of step
Args:
coords (tuple): coordinates to move towards
"""
self.x += self.step * (1 if self.x < coords[0] else -1)
self.y += self.step * (1 if self.y < coords[1] else -1)
class MutatedHungryBlob(HungryBlob):
"""Class for Mutated Blob with detection sense for where food is. This
blob is bigger and faster than the base food sense blob"""
def __init__(self) -> None:
"""See parent docstrings"""
super().__init__()
self.name: str = "MutatedHungryBlob"
self.color = "pink"
self.size = 0.3
self.step = 0.3
self.repr_class: Callable = MutatedHungryBlob
class BaseInteractingBlob(HungryBlob):
"""Base class for Blob that can interact with other blobs. Note that
this blob is hungry by default"""
def __init__(self) -> None:
"""See parent docstrings"""
super().__init__()
self.name: str = "BaseInteractingBlob"
self.color = "gray"
self.survival_prob: float = 0.8
self.reproduction_prob: float = 0.5
self.mutation_prob: float = 0.0
self.size = 0.3
self.mutation_class: Callable = BaseInteractingBlob
self.repr_class: Callable = BaseInteractingBlob
def interact_with_surroundings(self, interaction_list: List) -> None:
"""
Base function for blobs to interact with surroundings
Args:
interaction_list (List): objects to interact with"""
pass
class AttackingBlob(BaseInteractingBlob):
"""Class for Blob that will attack other nearby blobs"""
def __init__(self) -> None:
"""See parent docstrings"""
super().__init__()
self.name: str = "AttackingBlob"
self.color = "red"
self.mutation_class: Callable = AttackingBlob
self.repr_class: Callable = AttackingBlob
self.attack_dmg = 0.2
def interact_with_surroundings(self, interaction_list: List) -> None:
"""
Attacks nearby blobs, damaging their survival_prob
Args:
interaction_list (List): list of blobs to attack
"""
for b in interaction_list:
b.survival_prob -= self.attack_dmg
class TimidBlob(BaseInteractingBlob):
"""Class for Blob that will run away from other aggressive blobs"""
def __init__(self) -> None:
"""See parent docstrings"""
super().__init__()
self.name: str = "TimidBlob"
self.color = "green"
self.mutation_class: Callable = TimidBlob
self.repr_class: Callable = TimidBlob
def run_away(self, coords: tuple) -> None:
"""
Run away from coordinate of other blob. Opposite of move function
Args:
coords (tuple): (x,y) of blob to run away from
"""
self.x -= self.step * (1 if self.x < coords[0] else -1)
self.y -= self.step * (1 if self.y < coords[1] else -1)
def interact_with_surroundings(self, interaction_list: List) -> None:
"""
TimidBlob runs in the opposite direction of the closest attacking
blob
Args:
interaction_list (List): list of blobs to attack
"""
blob_coords = [(b.x, b.y) for b in interaction_list]
closest_attacker_coords, _ = find_closest_coord(
(self.x, self.y), blob_coords
)
if closest_attacker_coords:
self.run_away(closest_attacker_coords)
class QuickBlob(BaseInteractingBlob):
"""Class for very quick blob. QuickBlobs eat before any other blob, at
the expense of a generally lower survival_prob"""
def __init__(self) -> None:
"""See parent docstrings"""
super().__init__()
self.name: str = "QuickBlob"
self.color = "yellow"
self.mutation_class: Callable = QuickBlob
self.repr_class: Callable = QuickBlob