-
Notifications
You must be signed in to change notification settings - Fork 1
/
God.py
371 lines (280 loc) · 11 KB
/
God.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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
import attr
from random import randint, shuffle
from Artist import GridSystem
from Life import Plant
@attr.s
class God(object):
firstGenerationSize = attr.ib()
lifeFormWidth = attr.ib()
lifeFormHeight = attr.ib()
rootStartPercent = attr.ib()
grids = GridSystem()
"""
Picks the most fit members from a group.
Args:
lifeforms (Array) : array of lifeform DNAs
survivorCount (int) : number to pick
Returns:
Array: Array of lifeform arrays
"""
def pickMostFit(self, lifeforms, survivorCount):
print "Selecting most fit members.."
lifeforms.sort(self.lifeFormSort)
# selects best for surviors
chosenLifeforms = lifeforms[0:survivorCount-1]
# selects a random survivor
chosenLifeforms.append(lifeforms[randint(0, len(lifeforms)-1)])
return chosenLifeforms
# Sort method for determining which members are most fit
def lifeFormSort(self, a, b):
aScore = self.judgeLifeform(a)
bScore = self.judgeLifeform(b)
if aScore < bScore:
return 1
elif aScore == bScore:
return 0
else:
return -1
"""
Crossbreeds lifeforms from a a given set of lifeforms.
Args:
lifeforms (Array) : array of lifeform DNAs
Returns:
Array: Array of lifeform arrays
"""
def breed(self, lifeforms):
print "Breeding..."
nextGen = []
segments = self.pickRandomDivisor_(len(lifeforms[0].dna))
print "DNA Splice length: ", segments
segmentedPlants = self.sliceToSegments_(lifeforms, segments)
print "Recombinating DNA..."
for i in range(0, len(segmentedPlants)):
parent1 = segmentedPlants[i]
for k in range(0, 2):
parent2 = segmentedPlants[randint(0, len(segmentedPlants)-1)]#segmentedPlants[(i+1) % len(segmentedPlants)]
breeders = [parent1, parent2]
newPlantDNA = []
for j in range(0, len(segmentedPlants[i])):
side = randint(0, 1)
newPlantDNA += breeders[side][j]
plant = Plant(newPlantDNA)
nextGen.append(plant)
print len(nextGen), " offspring produced."
return nextGen
def sliceToSegments_(self, lifeforms, segments):
segmentedPlants = []
for plant in lifeforms:
plantSegments = []
for i in range(0, len(plant.dna), segments):
plantSegments.append(plant.dna[i:i + segments])
segmentedPlants.append(plantSegments)
return segmentedPlants
def pickRandomDivisor_(self, length):
segments = 1
moduloRemainder = 1
while moduloRemainder != 0:
segments = randint(4, length/6)
moduloRemainder = length % segments
return segments
"""
Performs random mutations on random lifeforms in a set of random lifeforms.
Args:
lifeforms (Array) : array of lifeform DNAs
Returns:
Array: Array of lifeform arrays
"""
def mutate(self, lifeforms):
print "Mutating offspring..."
mutateLifeforms = []
for lifeform in lifeforms:
numMutations = randint(0, 30)
if randint(0, 100) < 1:
print "RADICAL MUTATION!!!"
lifeform = self.createRandomLifeform((self.lifeFormWidth, self.lifeFormHeight))
else:
for i in range(0, numMutations):
newVal = hex(randint(0, 15) )[2:]
lifeform.dna[randint(0, len(lifeform.dna))-1] = newVal
mutateLifeforms.append(lifeform)
return mutateLifeforms
def trimDeadCells(self, lifeforms):
print "Trimming detached cells..."
trimmedLifeforms = []
for lifeform in lifeforms:
fixedLifeform = []
lifeFormGrid = self.grids.arrayToGrid(lifeform.dna, self.lifeFormWidth)
# All energy is calculated by checking all cells
for y in range(0, self.lifeFormHeight):
for x in range(0, self.lifeFormWidth):
cell = self.grids.getCellAtIndex(lifeFormGrid, x, y)
if cell.siblingsCount <= 1 and cell.adjacentSiblingsCount == 0:
lifeFormGrid[y][x] = '0'
for row in lifeFormGrid:
fixedLifeform += row
plant = Plant(fixedLifeform)
trimmedLifeforms.append(plant)
return trimmedLifeforms
"""
Calculates the score for a lifeform.
Args:
lifeform (Plant) : Plant lifeform
Returns:
int: score for how successful a lifeform is
"""
def judgeLifeform(self, lifeform):
if lifeform.fitness:
return lifeform.fitness
lifeFormGrid = self.grids.arrayToGrid(lifeform.dna, self.lifeFormWidth)
# Base values of resources needed for any lifeform
energyNeeded = 100
nutrientsNeeded = 100
# Total resources produced by lifeforms
energyProduced = 0
nutrientsProduced = 0
# All energy is calculated by checking all cells
for y in range(0, self.lifeFormHeight):
for x in range(0, self.lifeFormWidth):
cell = self.grids.getCellAtIndex(lifeFormGrid, x, y)
energyNeeded += self.getCellEnergyConsumption_(cell)
energyProduced += self.getCellEnergyProduction_(cell)
nutrientsNeeded += self.getCellNutrientsConsumption_(cell)
nutrientsProduced += self.getCellNutrientsProduced_(cell)
# print ('energyNeeded', energyNeeded), ('energyProduced', energyProduced)
# print ('nutrientsNeeded', nutrientsNeeded), ('nutrientsProduced', nutrientsProduced)
energyOffset = energyProduced - energyNeeded#abs(energyNeeded - energyProduced)
nutrientOffset = nutrientsProduced - nutrientsNeeded#abs(nutrientsNeeded - nutrientsProduced)
score = 0
# Offset of energy and nutrients
score = energyOffset**2 + nutrientOffset**2
# bonus for living cells
score += self.countRealCells_(lifeFormGrid)
lifeform.fitness = score
return score
"""
Counts the number of living cells in a lifeform
Args:
lifeFormArray (Array) : lifeform DNA
Returns:
int: number of non-zero value cells
"""
def countRealCells_(self, lifeFormArray):
return len(lifeFormArray) - lifeFormArray.count(0)
"""
Calculates the amount of nutirents produced by a cell
Args:
cell (GridCell) : cell to calulate energy needed for
Returns:
int: nutrients provided by the cell
"""
def getCellNutrientsProduced_(self, cell):
nutrients = 0
if cell.adjacentSiblingsCount == 0 or cell.siblingsCount > 4:
return -1
if cell.y > self.lifeFormHeight * self.rootStartPercent:
nutrients += 1
nutrients += cell.siblingsCount
nutrients += cell.adjacentSiblingsCount*2
return nutrients
"""
Calculates the nutirents needed for a particular cell to survive.
Args:
cell (GridCell) : cell to calulate energy needed for
Returns:
int: nutrients needed by the cell
"""
def getCellNutrientsConsumption_(self, cell):
nutrients = 0
if cell.target == 0:
return nutrients
else:
nutrients += 1
# Unattached cells require MUCH nutrients
if cell.siblingsCount == 0 or cell.adjacentSiblingsCount == 0:
nutrients += 99
return nutrients
"""
Calculates the amount of energy produced by a cell.
Cells closer to the sun recieve more energy and cells with
higher pigment values are better at consuming the energy available
to them, while cells that are blocked from recieving sublight are
less performant.
Args:
cell (GridCell) : cell to calulate energy needed for
Returns:
int: energy produced by the cell
"""
def getCellEnergyProduction_(self, cell):
energy = 0
# Roots do not produce energy
if cell.y > self.lifeFormHeight * self.rootStartPercent:
return energy
# Nonfilled cells are not part of plant
if cell.target == 0:
return energy
# High pigment cells produce more energy when closer to sunlight
if cell.y < self.lifeFormHeight/4:
energy += cell.target/4
# Cells closer to the "Sun" recieve more energy
energy += self.lifeFormHeight - cell.y
# Energy penalty for being blocked from sunlight
if cell.n > 0:
energy = energy/5
return energy
"""
Calculates the amount of energy needed to sustain a cell.
This is determined by how blocked a cell is by its siblings cells,
while ensuring that a cell is still connected to the organism at
large. "Roots" do not require energy, unless they are not connected
to the rest of the plant (EG are their own organisms).
Args:
cell (GridCell) : cell to calulate energy needed for
Returns:
int: energy needed by cell to survive
"""
def getCellEnergyConsumption_(self, cell):
energy = 0
# Nonfilled cells are not part of plant
if cell.target == 0:
return energy
# Major deficits for not being attached
if cell.siblingsCount < 2:
energy += 30
elif cell.adjacentSiblingsCount == 0:
energy += 20
# Roots do not reqire energy
if cell.y > self.lifeFormHeight * self.rootStartPercent:
return energy
if cell.y > self.lifeFormHeight * (1.0 - self.rootStartPercent):
energy += cell.y
# Requires one energy per sibling
energy += cell.siblingsCount - cell.adjacentSiblingsCount
# and additional energy if cell above is of a high value pigment
energy += cell.n
# High pigment values cells are more performant
energy -= cell.target/4
# Energy is conserved by having similar pigment siblings
# @TODO: exclude target from sibling list..
for sibling in cell.siblings:
if cell.target == sibling:
energy -= 2
# A non-root cell requires at least one energy
if energy < 1:
energy = 1
return energy
def createLife(self):
lifeForms = []
for i in range(0, self.firstGenerationSize):
lifeForm = self.createRandomLifeform((self.lifeFormWidth, self.lifeFormHeight))
lifeForms.append(lifeForm)
return lifeForms
def createRandomLifeform(self, size):
lifeFormDNA = []
for i in range(0, size[0]*size[1]):
# Favors some empty space for starting generation
if randint(0, 1) > 0:
lifeFormDNA.append('0')
else:
lifeFormDNA.append(hex(randint(0, 15) )[2:])
plant = Plant(lifeFormDNA)
return plant