main.py
import numpy as np
import random
import math
class PWorm:
width = 1.3
def __init__(self, model, x0, y0, angle):
self.striped = model[0]
self.length = model[1]
self.x0 = x0
self.y0 = y0
self.dx = self.length * math.cos(angle)
self.dy = self.length * math.sin(angle)
def isOn(self, x, y) -> bool:
pVecX = -self.dy / math.sqrt(self.dy**2 + self.dx**2)
pVecY = self.dx / math.sqrt(self.dy**2 + self.dx**2)
ptA = (self.x0 + self.width * pVecX, self.y0 + self.width * pVecY)
ptB = (self.x0 - pVecX, self.y0 - pVecY)
ptD = (self.x0 + self.dx - self.width * pVecX,
self.y0 + self.dy - self.width * pVecY)
distSide = (-(ptB[1] - ptA[1]) * x + (ptB[0] - ptA[0]) * y -
(-(ptB[1] - ptA[1]) * ptA[0] +
(ptB[0] - ptA[0]) * ptA[0])) / math.sqrt(
(ptB[1] - ptA[1])**2 + (ptB[0] - ptA[0])**2)
distLong = (-(ptB[1] - ptD[1]) * x + (ptB[0] - ptD[0]) * y -
(-(ptB[1] - ptD[1]) * ptD[0] +
(ptB[0] - ptD[0]) * ptD[0])) / math.sqrt(
(ptB[1] - ptD[1])**2 + (ptB[0] - ptD[0])**2)
if abs(distSide) < self.width * 2 and abs(distLong) < self.length:
return True
return False
def getPos(self):
return tuple([self.x0, self.y0])
def getEnd(self):
return (self.x0 + self.dx, self.y0 + self.dy)
def getPattern(self):
return self.striped
def getLength(self):
return self.length
def getInfo(self):
return (self.striped, self.length)
def __str__(self):
return str(self.length) + '\t' + str(self.striped) + '\t' + str(
self.x0)[:6] + '\t' + str(self.y0)[:6] + '\t' + str(
self.dx)[:6] + '\t' + str(self.dy)[:6] + '\t' + str(
math.sqrt(self.dx**2 + self.dy**2))[:6]
def printCounts(worms, percentageForm):
global popModel
counter = np.zeros(len(popModel))
for worm in worms:
for i in range(len(popModel)):
if worm.getInfo() == popModel[i]:
counter[i] += 1
break
for i in range(len(counter)):
if percentageForm:
print(str(int(counter[i] / len(worms) * 100)) + '%', end=' ')
else:
print(str(int(counter[i])) + 'x', end=' ')
print(str(int(popModel[i][1])) +
(':striped' if popModel[i][0] else ':solid'),
end='\t')
print('\n')
epochs = 8
initPopSize = 32
canvasHeight = 20
canvasWidth = 30
preyLimit = 20000
popReductionFactor = 0.35
popModel = ((True, 3.0), (True, 2.0), (True, 1.0), (False, 3.0), (False, 2.0),
(False, 1.0))
eaten = 0
worms = []
verboseMode = False
liveUpdate = False
programIntro = '''\
================================================================
wormEvoSim.py
(rev: July 2022)
by nitrojector / Martin Gong / Nanami Kyosuke
This program simulates the evolution of a population of worms of
different lengths and color patterns. A blind predator is able to
randomly try to capture a worm out of a fixed area. Spots that
this predator choose to attempt capture is completely random.
After each epoch (period which the predator feeds on the worms),
the population will multiple by dividing, aka multiplied by 2 for
each worm.
Enjoy!
'''
print(programIntro)
input('Press enter to continue...')
if input('\nCustomize parameters? (y/n) ') == 'y':
print('\nFor each customization, press enter for default (value).\n')
try:
tmpEpochs = int(input(f'Define number of epochs ({epochs}) '))
if tmpEpochs < 1:
print('Number of epochs must be at least 1 (using 1)\n')
epochs = 1
else:
epochs = tmpEpochs
except:
print(f'No valid input, using default value')
try:
tmpInitPopSize = int(
input(f'\nDefine population size ({initPopSize}) '))
if tmpInitPopSize < 1:
raise ValueError
initPopSize = tmpInitPopSize
except:
print(f'No valid input, using default value')
try:
tmpPopReductionFactor = float(
input(
f'\nDefine population reduction factor - default recommended,\nbig factors make simulation slow,\nsmall factors make simulation insignificant (0.1 ≤ factor ≤ 0.7) '
))
if tmpPopReductionFactor < 0.1 or tmpPopReductionFactor > 0.7:
raise ValueError
popReductionFactor = tmpPopReductionFactor
except:
print(f'No valid input, using default value')
if input(
f'\nDefine custom canvas size? ({canvasHeight}x{canvasWidth}) (y/n) '
) == 'y':
try:
tmpCanvasHeight = int(
input(f'Define canvas height ({canvasHeight}) '))
tmpCanvasWidth = int(
input(f'Define canvas width ({canvasWidth}) '))
if tmpCanvasHeight < 6 or tmpCanvasWidth < 6:
raise ValueError
canvasHeight = tmpCanvasHeight
canvasWidth = tmpCanvasWidth
except:
print(f'No valid input, using default value')
print('\nstart creating initial population')
for i in range(initPopSize):
for model in popModel:
worms.append(
PWorm(model, (canvasWidth - model[1]) * random.random(),
(canvasHeight - model[1]) * random.random(),
math.pi / 2 * random.random()))
print('finished creating initial population as follows')
if input('\nPrint initial population? (y/n) ') == 'y':
print('len\tstripe\tx0\ty0\tdx\tdy\tlen(recalculated)')
for worm in worms:
print(worm)
print()
if input('Verbose mode? (fun to watch but a little slower overall) (y/n) '
) == 'y':
verboseMode = True
preyLimit = 5000
if input('Live update mode? (looks cool but even slower overall) (y/n) '
) == 'y':
liveUpdate = True
input('Press enter to start simulation...')
for i in range(epochs):
counts = 0
popReductionSize = int(len(worms) * (1 - popReductionFactor))
while eaten < popReductionSize:
x, y = canvasWidth * random.random(), canvasHeight * random.random()
try:
for worm in worms:
if liveUpdate:
print(
'\reaten #{:<8d}'.format(eaten + 1) +
f'[ {str(x)[:10]}\t{str(y)[:10]} ]\tattempts: {str(counts)}',
end='')
if worm.isOn(x, y):
if verboseMode:
print(
'\reaten #{:<8d}'.format(eaten + 1) +
f'[ {str(x)[:10]}\t{str(y)[:10]} ]\tattempts: {str(counts)}',
end='')
print(
f'\tlen: {worm.getLength()}\tstripe: {worm.getPattern()}\r'
)
eaten += 1
worms.remove(worm)
counts = 0
break
counts += 1
except KeyboardInterrupt:
print(
"\n\n\n======================\nSimulation Interrupted\n======================\n"
)
print('\nSpecifics of survived members are listed below\n')
print('len\tstripe\tx0\ty0\tdx\tdy\tlen(recalculated)')
for worm in worms:
print(worm)
print()
printCounts(worms, True)
exit(0)
if counts > preyLimit:
print(
'\n====== Tried looking everywhere, no clue where the worms are :( ======'
)
break
eaten = 0
print('Epoch #' + str(i + 1) + '\t', end='')
printCounts(worms, False)
setLen = len(worms)
for i in range(setLen):
worms.append(
PWorm(worms[i].getInfo(),
(canvasWidth - worms[i].getInfo()[1]) * random.random(),
(canvasHeight - worms[i].getInfo()[1]) * random.random(),
math.pi / 2 * random.random()))
print("\n\n\n===================\nSimulation Finished\n===================\n")
printCounts(worms, True)
print(f'{epochs} Total Epochs')
print(f'{len(popModel)}x{initPopSize} Initial Population Size')
print(f'-{100 * popReductionFactor}% Pop Reduction Factor / Epoch')
print(f'Under a {canvasWidth}x{canvasHeight} canvas for simulation')
print()
print(f'Survival population size: {len(worms)}')
print(f'Population delta: {len(worms) - initPopSize}')
print(
f'Population delta %: {int((len(worms) - initPopSize) / initPopSize * 100)}'
)
if input('\nShow population pool? (y/n) ') == 'y':
print('\nSpecifics of survived members are listed below\n')
print('len\tstripe\tx0\ty0\tdx\tdy\tlength(recalculated)')
for worm in worms:
print(worm)
print()