Skip to content
    wormEvolutionSimulation@nitroJector
    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()