diff --git a/README.md b/README.md index 704bf43..85b244c 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,15 @@ # Natural Selection Simulator Python A graphical natural selection simulation. -##To Run +## To Run Execute the file natural_selection.py. -##Credit +## Credit graphics.py is an open source library written by John Zelle and licensed under the terms of the GPL (http://www.gnu.org/licenses/gpl.html). + +## Condtibutions + +- Release of 15/01/2021 by Kristian Harge +Reorganization on sub folders, added reproduction on omnivores and carnivores, +added energy consumption relative to size diff --git a/__pycache__/analyse.cpython-36.pyc b/__pycache__/analyse.cpython-36.pyc new file mode 100644 index 0000000..a318a6d Binary files /dev/null and b/__pycache__/analyse.cpython-36.pyc differ diff --git a/__pycache__/analyse.cpython-38.pyc b/__pycache__/analyse.cpython-38.pyc new file mode 100644 index 0000000..7da5934 Binary files /dev/null and b/__pycache__/analyse.cpython-38.pyc differ diff --git a/__pycache__/configuration.cpython-36.pyc b/__pycache__/configuration.cpython-36.pyc new file mode 100644 index 0000000..6774e87 Binary files /dev/null and b/__pycache__/configuration.cpython-36.pyc differ diff --git a/__pycache__/configuration.cpython-38.pyc b/__pycache__/configuration.cpython-38.pyc new file mode 100644 index 0000000..9650f2a Binary files /dev/null and b/__pycache__/configuration.cpython-38.pyc differ diff --git a/__pycache__/graphics.cpython-36.pyc b/__pycache__/graphics.cpython-36.pyc new file mode 100644 index 0000000..95cca2f Binary files /dev/null and b/__pycache__/graphics.cpython-36.pyc differ diff --git a/__pycache__/graphics.cpython-38.pyc b/__pycache__/graphics.cpython-38.pyc new file mode 100644 index 0000000..842fd27 Binary files /dev/null and b/__pycache__/graphics.cpython-38.pyc differ diff --git a/__pycache__/organism.cpython-36.pyc b/__pycache__/organism.cpython-36.pyc new file mode 100644 index 0000000..d460a94 Binary files /dev/null and b/__pycache__/organism.cpython-36.pyc differ diff --git a/__pycache__/organism.cpython-38.pyc b/__pycache__/organism.cpython-38.pyc new file mode 100644 index 0000000..d02dea6 Binary files /dev/null and b/__pycache__/organism.cpython-38.pyc differ diff --git a/__pycache__/organisms.cpython-36.pyc b/__pycache__/organisms.cpython-36.pyc new file mode 100644 index 0000000..165c7b8 Binary files /dev/null and b/__pycache__/organisms.cpython-36.pyc differ diff --git a/__pycache__/organisms.cpython-38.pyc b/__pycache__/organisms.cpython-38.pyc new file mode 100644 index 0000000..09ea772 Binary files /dev/null and b/__pycache__/organisms.cpython-38.pyc differ diff --git a/analyse.py b/analyse.py new file mode 100644 index 0000000..a0112dd --- /dev/null +++ b/analyse.py @@ -0,0 +1,37 @@ +import numpy as np +import matplotlib.pyplot as plt + +figure = None +plot = None + +def plotValues(Organisms): + '''This function plots the interesting values of the organisms + ''' + global figure + global plot + speeds = [] + sizes = [] + + if figure != None: + + for Organism in Organisms: + speeds.append(Organism.speed) + sizes.append(Organism.size) + + plot.set_ydata(sizes) + plot.set_xdata(speeds) + + + else: + + plt.ion() + figure = plt.figure() + + for Organism in Organisms: + speeds.append(Organism.speed) + sizes.append(Organism.size) + + plot, = figure.add_subplot(1, 1, 1).plot(speeds, sizes, 'b*') + plt.title("Living organisms attributes") + plt.xlabel("Speed") + plt.ylabel("Size") \ No newline at end of file diff --git a/configuration.py b/configuration.py new file mode 100644 index 0000000..17dada5 --- /dev/null +++ b/configuration.py @@ -0,0 +1,8 @@ +# Simulation configuration +windowSize = 100 +tickTime = .5 +gameTicks = 200 +eventLog = False +speedFactor = 1 +loop = False +energyFactor = 0.0001 \ No newline at end of file diff --git a/configuration.pyc b/configuration.pyc new file mode 100644 index 0000000..d88d26d Binary files /dev/null and b/configuration.pyc differ diff --git a/graphics.py b/graphics.py index a626141..1424784 100644 --- a/graphics.py +++ b/graphics.py @@ -1,40 +1,31 @@ # graphics.py """Simple object oriented graphics library - The library is designed to make it very easy for novice programmers to experiment with computer graphics in an object oriented fashion. It is written by John Zelle for use with the book "Python Programming: An Introduction to Computer Science" (Franklin, Beedle & Associates). - LICENSE: This is open-source software released under the terms of the GPL (http://www.gnu.org/licenses/gpl.html). - PLATFORMS: The package is a wrapper around Tkinter and should run on any platform where Tkinter is available. - INSTALLATION: Put this file somewhere where Python can see it. - OVERVIEW: There are two kinds of objects in the library. The GraphWin class implements a window where drawing can be done and various GraphicsObjects are provided that can be drawn into a GraphWin. As a simple example, here is a complete program to draw a circle of radius 10 centered in a 100x100 window: - -------------------------------------------------------------------- from graphics import * - def main(): win = GraphWin("My Circle", 100, 100) c = Circle(Point(50,50), 10) c.draw(win) win.getMouse() # Pause to view result win.close() # Close window when done - main() -------------------------------------------------------------------- GraphWin objects support coordinate transformation through the setCoords method and mouse and keyboard interaction methods. - The library provides the following graphical objects: Point Line @@ -45,16 +36,13 @@ def main(): Text Entry (for text-based input) Image - Various attributes of graphical objects can be set such as outline-color, fill-color and line-width. Graphical objects also support moving and hiding for animation effects. - The library also provides a very simple class for pixel-based image manipulation, Pixmap. A pixmap can be loaded from a file and displayed using an Image object. Both getPixel and setPixel methods are provided for manipulating the image. - DOCUMENTATION: For complete documentation, see Chapter 4 of "Python Programming: An Introduction to Computer Science" by John Zelle, published by Franklin, Beedle & Associates. Also see @@ -927,7 +915,6 @@ def getHeight(self): def getPixel(self, x, y): """Returns a list [r,g,b] with the RGB color values for pixel (x,y) r,g,b are in range(256) - """ value = self.img.get(x,y) @@ -948,7 +935,6 @@ def setPixel(self, x, y, color): def save(self, filename): """Saves the pixmap image to filename. The format for the save image is determined from the filname extension. - """ path, name = os.path.split(filename) diff --git a/graphics.pyc b/graphics.pyc new file mode 100644 index 0000000..125c89b Binary files /dev/null and b/graphics.pyc differ diff --git a/natural_selection.py b/natural_selection.py index 129ffe1..1fd70e6 100644 --- a/natural_selection.py +++ b/natural_selection.py @@ -1,534 +1,183 @@ ''' 2016 CS-167 Final Project: natural_selection.py - This program runs a simulation on natural selection in a -graphical interface. Watch as the organisms move about +graphical interface. Watch as the Organisms move about targeting prey (or plants) and attempting to avoid predators. -The largest fitter organisms will survive. - +The largest fitter Organisms will survive. by Owen Davis-Bower ''' import random from graphics import * - -# Simulation configuration -windowSize = 100 -tickTime = .5 -gameTicks = 200 -eventLog = False -speedFactor = 1 -loop = False - -class organism: - ''' - This class represents organism objects that all - have different traits and allows them to move about - and consume each other every game tick. - ''' - - def __init__(self, window, x, y, size, speed, organismType): - ''' - Initializes the organism by assigning it's traits - and drawing the organism on the screen. - - Parameters: - window: The graphical window to draw on. - x, y: The x and y starting coordinates of the organism. - size: The size of the organism. This effects the graphical - size of the organism as well as the strength of the - organism in combat. - speed: The maximum speed at which an organism can move each - game tick. - organismType: The type of an organism (Plant, omnivore, - herbivore, or carnivore) - - Return Value: none - ''' - self.pos = [x, y] - self.size = size - self.speed = speed - self.type = organismType - self.prey = self.preyList() - self.hunger = 50 - - self.organismGraphic = Circle(Point(self.pos[0], self.pos[1]), self.size) - - # assign a color based upon organism type - if self.type == 'omnivore': - self.organismGraphic.setFill('orange') - elif self.type == 'herbivore': - self.organismGraphic.setFill('blue') - elif self.type == 'carnivore': - self.organismGraphic.setFill('red') - elif self.type == 'plant': - self.organismGraphic.setFill(color_rgb(10, 117, 31)) - self.organismGraphic.draw(window) - - def update(self, organismsList, window): - ''' - Updates the organism's position and hunger every - simulation tick. - - Parameters: - organismsList: the list of organisms (including - the organism itself) - window: The graphical window which the organism - resides on. - - Return Value: none - ''' - self.closestTarget = self.nearestTarget(organismsList) - - # move the organism - if self.isPrey(self.closestTarget): - self.moveToTarget(self.closestTarget) - else: - self.moveRandom() - - # keep the organisms from leaving the screen - self.stayInScreen() - - self.draw(window) - - # checks for collisions and responds accordingly - self.checkCollisions(organismsList) - - self.hunger += -0.5 - if self.hunger <= 0: - if eventLog: - print('Self starved') - self.die(organismsList) - - def isPrey(self, target): - ''' - Given a target organism, checks if the target is prey - for the checking organism. - - Parameters: - target: The target organism that is being checked. - - Return Value: - True if the target organism is prey for the self; - else False. - ''' - if target.getType() == 'plant' and 'plant' in self.prey: - return True - elif target.getType() in self.prey and target.getSize() < self.size: - return True - return False - - def preyList(self): - ''' - Taking in the self organism's type, returns a list of - prey types. For example an herbivore will only return - 'plant'. - - Return Value: - A tuple containing all of the acceptable prey types. - ''' - if self.type == 'omnivore': - return ('plant', 'omnivore', 'herbivore', 'carnivore') - elif self.type == 'herbivore': - return ('plant') - elif self.type == 'carnivore': - return ('omnivore', 'herbivore', 'carnivore') - - def getPos(self): - ''' - Returns the position of the given organism as a list - containing two points [x, y]. - ''' - return self.pos - - def getSize(self): - ''' - Returns the size of the given organism as a float value. - ''' - return self.size - - def getType(self): - ''' - Returns the type of the given organism as a string value. - ''' - return self.type - - def die(self, organismsList): - ''' - "Kills" the given organism by undrawing it and then - removing it from the list of organisms. - - Parameters: - organismsList: The list to remove the given - organism from. - - Return Value: none - ''' - self.organismGraphic.undraw() - organismsList.remove(self) - - def distanceFromTarget(self, target): - ''' - Calculates and returns the distance between the given - organism and the target organism. - - Parameters: - target: A target organism. - - Return Value: The distance between the given organism - and the target organism. - ''' - return (abs(target.getPos()[0] - self.pos[0]) + abs(target.getPos()[1] - self.pos[1])) - - def nearestTarget(self, organismsList): - ''' - Searches through the list of other organisms and - returns the organism object of the nearest organism. - - Parameters: - organismsList: The list of living organisms. - - Return Value: The organism object of the nearest - organism. - ''' - self.selfIndex = organismsList.index(self) - self.otherOrganisms = list(organismsList) - self.otherOrganisms.pop(self.selfIndex) - self.closestTarget = self.otherOrganisms[0] - for organism in self.otherOrganisms[1:]: - if self.distanceFromTarget(organism) < self.distanceFromTarget(self.closestTarget): - self.closestTarget = organism - return self.closestTarget - - def moveToTarget(self, target): - ''' - Moves the given organism towards the target organism. - - Parameters: - target: A target organism. - - Return Value: none - ''' - self.targetPosX, self.targetPosY = target.getPos() - self.distanceX, self.distanceY = (self.targetPosX - self.pos[0], self.targetPosY - self.pos[1]) - - if self.distanceX >= 0: - self.velocityX = self.speed * speedFactor - if abs(self.distanceX) < abs(self.velocityX): - self.velocityX = self.distanceX - else: - self.velocityX = -self.speed * speedFactor - if abs(self.distanceX) < abs(self.velocityX): - self.velocityX = self.distanceX - self.pos[0] += self.velocityX - - if self.distanceY >= 0: - self.velocityY = self.speed * speedFactor - if abs(self.distanceY) < abs(self.velocityY): - self.velocityX = self.distanceX - else: - self.velocityY = -self.speed * speedFactor - if abs(self.distanceY) < abs(self.velocityY): - self.velocityY = self.distanceY - self.pos[1] += self.velocityY - - def moveRandom(self): - ''' - Moves the given organism randomly in 2D space. - ''' - self.velocity = (random.uniform(-self.speed, self.speed) * speedFactor, random.uniform(-self.speed, self.speed) * speedFactor) - self.pos = [self.pos[0] + self.velocity[0], self.pos[1] + self.velocity[1]] - - def stayInScreen(self): - ''' - Prevents the organisms from leaving the screen. - ''' - if self.pos[0] + self.size >= windowSize: - self.pos[0] = windowSize - self.size - elif self.pos[0] - self.size <= -windowSize: - self.pos[0] = -windowSize + self.size - elif self.pos[1] + self.size >= windowSize: - self.pos[1] = windowSize - self.size - elif self.pos[1] - self.size <= -windowSize: - self.pos[1] = -windowSize + self.size - - def draw(self, window): - ''' - Creates a graphical object for the organism based - upon it's size and type and then draws it onto the - graphic window. - - Parameters: - window: A graphical window. - - Return Value: none - ''' - if self.type != 'plant': - if self.organismGraphic: - self.organismGraphic.undraw() - self.organismGraphic = Circle(Point(self.pos[0], self.pos[1]), self.size) - - # assign a color based upon organism type - if self.type == 'omnivore': - self.organismGraphic.setFill('orange') - elif self.type == 'herbivore': - self.organismGraphic.setFill('blue') - elif self.type == 'carnivore': - self.organismGraphic.setFill('red') - elif self.type == 'plant': - self.organismGraphic.setFill(color_rgb(10, 117, 31)) - self.organismGraphic.draw(window) - - def checkCollision(self, collidingOrganism): - ''' - Checks if the given organism is colliding with the - "collidingOrganism". - - Parameters: - collidingOrganism: The organism to be tested against. - - Return Value: - True if the given organism is colliding with the - "collidingOrganism"; else returns False. - ''' - self.collidingOrganismPos = collidingOrganism.getPos() - self.collidingOrganismsize = collidingOrganism.getSize() - - if (self.pos[0] - self.size) <= self.collidingOrganismPos[0] <= (self.pos[0] + self.size): - if (self.pos[1] - self.size) <= self.collidingOrganismPos[1] <= (self.pos[1] + self.size): - return True - elif (self.collidingOrganismPos[0] - self.collidingOrganismsize) <= self.pos[0] <= (self.collidingOrganismPos[0] + self.collidingOrganismsize): - if (self.collidingOrganismPos[1] - self.collidingOrganismsize) <= self.pos[1] <= (self.collidingOrganismPos[1] + self.collidingOrganismsize): - return True - return False - - def checkCollisions(self, organismsList): - ''' - Checks if the organism is colliding with any organisms - and reacts appropriately by killing one of the - organisms if it is prey of the other. - - Parameters: - organismsList: The list of organisms. - - Return Value: none - ''' - for organism in organismsList: - if organism != self: # prevents organism from checking if it's colliding with itself - if self.checkCollision(organism): - if self.isPrey(organism): - if eventLog: - print('Self killed organism') - self.hunger += 8 - organism.die(organismsList) - elif organism.isPrey(self): - if eventLog: - print('Organism killed self') - organism.hunger += 8 - self.die(organismsList) +from organisms import * +from configuration import * +from analyse import * class simInterface: - def __init__(self): - ''' - Initializes the simulation window. - ''' - # initialize window - self.win = GraphWin('Natural Selection Simulation', 700, 700) - # transform coordinates - self.win.setCoords(-100, -150, 100, 100) - - self.lowerInterface = self.createLowerInterface() - self.lowerInterface.draw(self.win) - - self.organismCircle = None - - def getWin(self): - ''' - Returns the simulation window. - ''' - return self.win - - def createLowerInterface(self): - ''' - Draws the interface at the bottom of the screen. - ''' - interfaceRectangle = Rectangle(Point(-100, -100), Point(100, -150)) - interfaceRectangle.setFill("gray") - return interfaceRectangle - - def close(self): - ''' - Closes the graphical window. - ''' - self.win.close() + def __init__(self): + ''' + Initializes the simulation window. + ''' + # initialize window + self.win = GraphWin('Natural Selection Simulation', 700, 600) + # transform coordinates + self.win.setCoords(-100, -150, 100, 100) + + self.lowerInterface = self.createLowerInterface() + self.lowerInterface.draw(self.win) + + self.OrganismCircle = None + + def getWin(self): + ''' + Returns the simulation window. + ''' + return self.win + + def createLowerInterface(self): + ''' + Draws the interface at the bottom of the screen. + ''' + interfaceRectangle = Rectangle(Point(-100, -100), Point(100, -150)) + interfaceRectangle.setFill("gray") + return interfaceRectangle + + def close(self): + ''' + Closes the graphical window. + ''' + self.win.close() class naturalSelectionSim: - def __init__(self): - ''' - Initializes the simulation window and interface. - ''' - self.interface = simInterface() - self.window = self.interface.getWin() - - def runSim(self): - ''' - Runs the simulation from start to finish. - ''' - self.displayInformation() - - self.waitForClick() - - self.spawnOrganisms() - - self.keepRunning = True - for i in range(gameTicks): - self.update() - - time.sleep(2) - - # destroy all remaining organisms - for organism in self.organisms: - organism.die(self.organisms) - - def waitForClick(self): - ''' - Displays a message on the screen and waits for user - input in order to continue. - ''' - self.startText = Text(Point(0, 0), 'Click anywhere to begin the simulation.') - self.startText.setSize(20) - self.startText.draw(self.window) - self.window.getMouse() - self.startText.undraw() - - def displayInformation(self): - ''' - Displays information in the lower interface of simulation - window. - ''' - self.informativeText_1 = Text(Point(0, -105), 'Click anywhere inside the habitat to spawn a new organism.') - self.informativeText_1.setSize(20) - self.informativeText_1.draw(self.window) - - self.plantExample = Circle(Point(-90, -120), 3) - self.plantExample.setFill(color_rgb(10, 117, 31)) - self.plantExample.draw(self.window) - self.plantTitle = Text(Point(-75, -120), '= Plant') - self.plantTitle.setSize(20) - self.plantTitle.draw(self.window) - - self.herbivoreExample = Circle(Point(-90, -130), 3) - self.herbivoreExample.setFill('blue') - self.herbivoreExample.draw(self.window) - self.herbivoreTitle = Text(Point(-69, -130), '= Herbivore') - self.herbivoreTitle.setSize(20) - self.herbivoreTitle.draw(self.window) - - self.omnivoreExample = Circle(Point(-90, -140), 3) - self.omnivoreExample.setFill('orange') - self.omnivoreExample.draw(self.window) - self.omnivoreTitle = Text(Point(-69, -140), '= Omnivore') - self.omnivoreTitle.setSize(20) - self.omnivoreTitle.draw(self.window) - - def randomOrganism(self, organismType, position = None): - ''' - Generates a random organism. - - Parameters: - organismType: The type of the new organism (Plant, - herbivore, omnivore, carnivore) - position (optional): The starting position of the - organism. Otherwise the start - position is randomly generated. - - Return Value: A randomly generated organism object. - ''' - if position == None: - x = random.uniform(-(windowSize * .9), windowSize * .9) - y = random.uniform(-(windowSize * .9), windowSize * .9) - else: - x, y = position - size = random.uniform(1, 7) - speed = random.uniform(5, 10) - return organism(self.window, x, y, size, speed, organismType) - - def spawnOrganisms(self): - ''' - Spawns in randomly generated organisms by adding them - to a list of organisms. - ''' - self.organisms = [] - for i in range(7): - self.organisms.append(self.randomOrganism('omnivore')) - for i in range(5): - self.organisms.append(self.randomOrganism('herbivore')) - # BUG: Carnivores are not fully implemented yet. - # for i in range(5): - # self.organisms.append(self.randomOrganism('carnivore')) - for i in range(15): - self.organisms.append(self.randomOrganism('plant')) - - def spawnOrganismFromInput(self): - ''' - Spawns in randomly generated organisms at the location - of a mouse click. (Only works if the mouse click is in - the simulation window.) - ''' - self.mousePos = self.window.checkMouse() - if self.mousePos != None: - if abs(self.mousePos.getX()) < windowSize and abs(self.mousePos.getY()) < windowSize: - self.organisms.append(self.randomOrganism(random.choice(('omnivore', 'herbivore', 'plant')), (self.mousePos.getX(), self.mousePos.getY()))) - - def close(self): - ''' - Closes the simulation. - ''' - self.interface.close() - - def update(self): - ''' - Updates everything in the simulation every time - the function is called. - ''' - while self.keepRunning: - self.livingOrganisms = 0 - - self.spawnOrganismFromInput() - - for organism in self.organisms: - if organism.getType() != 'plant': - self.livingOrganisms += 1 - organism.update(self.organisms, self.window) - - # generates new plants to sustain the organisms. - if random.randrange(10) > 2: - self.organisms.append(self.randomOrganism('plant')) - - if self.livingOrganisms <= 1: - self.keepRunning = False - break - - time.sleep(tickTime) + def __init__(self): + ''' + Initializes the simulation window and interface. + ''' + self.interface = simInterface() + self.window = self.interface.getWin() + + def runSim(self): + ''' + Runs the simulation from start to finish. + ''' + self.displayInformation() + + self.waitForClick() + + self.spawnOrganisms() + + self.keepRunning = True + for i in range(gameTicks): + self.update() + + time.sleep(2) + + # destroy all remaining Organisms + for Organism in self.Organisms: + Organism.die(self.Organisms) + + def waitForClick(self): + ''' + Displays a message on the screen and waits for user + input in order to continue. + ''' + self.startText = Text(Point(0, 0), 'Click anywhere to begin the simulation.') + self.startText.setSize(20) + self.startText.draw(self.window) + self.window.getMouse() + self.startText.undraw() + + def displayInformation(self): + ''' + Displays information in the lower interface of simulation + window. + ''' + self.informativeText_1 = Text(Point(0, -105), 'Click anywhere inside the habitat to spawn a new Organism.') + self.informativeText_1.setSize(20) + self.informativeText_1.draw(self.window) + + self.plantExample = Circle(Point(-90, -120), 3) + self.plantExample.setFill(color_rgb(10, 117, 31)) + self.plantExample.draw(self.window) + self.plantTitle = Text(Point(-67, -120), '= Plant') + self.plantTitle.setSize(20) + self.plantTitle.draw(self.window) + + self.herbivoreExample = Circle(Point(-90, -130), 3) + self.herbivoreExample.setFill('blue') + self.herbivoreExample.draw(self.window) + self.herbivoreTitle = Text(Point(-59, -130), '= Herbivore') + self.herbivoreTitle.setSize(20) + self.herbivoreTitle.draw(self.window) + + self.omnivoreExample = Circle(Point(-90, -140), 3) + self.omnivoreExample.setFill('orange') + self.omnivoreExample.draw(self.window) + self.omnivoreTitle = Text(Point(-59, -140), '= Omnivore') + self.omnivoreTitle.setSize(20) + self.omnivoreTitle.draw(self.window) + + def spawnOrganisms(self): + ''' + Spawns in randomly generated Organisms by adding them + to a list of Organisms. + ''' + self.Organisms = [] + for i in range(3): + self.Organisms.append(Omnivore(self.window)) + for i in range(5): + self.Organisms.append(Herbivore(self.window)) + # BUG: Carnivores are not fully implemented yet. + # for i in range(5): + # self.Organisms.append(self.randomOrganism('carnivore')) + for i in range(15): + self.Organisms.append(Plant(self.window)) + + def close(self): + ''' + Closes the simulation. + ''' + self.interface.close() + + def update(self): + ''' + Updates everything in the simulation every time + the function is called. + ''' + while self.keepRunning: + self.livingOrganisms = 0 + + #show the evolution data + plotValues(self.Organisms) + + for Organism in self.Organisms: + self.livingOrganisms += 1 + Organism.update(self.Organisms, self.window) + + if self.livingOrganisms <= 1: + self.keepRunning = False + break + + time.sleep(tickTime) def main(): - ''' - Calls the simulation. If loop = True then loops the simulation - otherwise the simulation only runs once. - ''' - if loop: - for i in range(100): - simulation = naturalSelectionSim() - simulation.runSim() - simulation.close() - time.sleep(1) - else: - simulation = naturalSelectionSim() - simulation.runSim() - simulation.close() + ''' + Calls the simulation. If loop = True then loops the simulation + otherwise the simulation only runs once. + ''' + if loop: + for i in range(100): + simulation = naturalSelectionSim() + simulation.runSim() + simulation.close() + time.sleep(1) + else: + simulation = naturalSelectionSim() + simulation.runSim() + simulation.close() if __name__ == '__main__': main() diff --git a/organism.py b/organism.py new file mode 100644 index 0000000..c22e87a --- /dev/null +++ b/organism.py @@ -0,0 +1,280 @@ +import random +from graphics import * +from configuration import * +from abc import ABC, abstractmethod + +class Organism (ABC): + ''' + This class represents Organism objects that all + have different traits and allows them to move about + and consume each other every game tick. + ''' + + def __init__(self, window, x, y, size, speed): + ''' + Initializes the Organism by assigning it's traits + and drawing the Organism on the screen. + Parameters: + window: The graphical window to draw on. + x, y: The x and y starting coordinates of the Organism. + size: The size of the Organism. This effects the graphical + size of the Organism as well as the strength of the + Organism in combat. + speed: The maximum speed at which an Organism can move each + game tick. + Return Value: none + ''' + self.pos = [x, y] + self.speed = speed + self.size = size + self.prey = None + self.energy = 25 + self.age = 0 + self.type = self.getType() + + self.OrganismGraphic = Circle(Point(self.pos[0], self.pos[1]), self.size) + + def update(self, OrganismsList, window): + ''' + Updates the Organism's position and energy every + simulation tick. + Parameters: + OrganismsList: the list of Organisms (including + the Organism itself) + window: The graphical window which the Organism + resides on. + Return Value: none + ''' + self.closestTarget = self.nearestTarget(OrganismsList) + + # move the Organism + if self.isPrey(self.closestTarget): + self.moveToTarget(self.closestTarget) + else: + self.moveRandom() + + # keep the Organisms from leaving the screen + self.stayInScreen() + + self.draw(window) + + # checks for collisions and responds accordingly + self.checkCollisions(OrganismsList) + + self.updateEnergy() + self.age += 1 + + if self.energy <= 0: + if eventLog: + print('Self starved') + self.die(OrganismsList) + + #reproduces if he has enough energy + if self.energy >= 30: + self.energy -= 25 + self.reproduce(window, OrganismsList) + + def updateEnergy(self): + self.energy -= self.energyEfficiency(energyFactor/20, 10)*(self.size**3)*(self.speed)**2 + + def energyEfficiency(self, factor=1, offsetx=0, offsety=0): + #Calculate the enegy efficiency with the age + return (self.age - offsetx)**2*factor + offsety + + def reproduce (self, window, OrganismsList): + ''' + It teproduces the organism at the parent location + the mutation variable helps to create some genetic variations + they should be coded in the child functions + ''' + OrganismsList.append(self.__class__(window, self.pos[0], self.pos[1], self.size, self.speed)) + + def isPrey(self, target): + ''' + Given a target Organism, checks if the target is prey + for the checking Organism. + Parameters: + target: The target Organism that is being checked. + Return Value: + True if the target Organism is prey for the self; + else False. + ''' + if self.prey == None: + return False + + if target.getType() in self.prey: + if target.getType() == self.getType(): + if target.getSize() < self.size: + return True + else: + return True + return False + + def getPos(self): + ''' + Returns the position of the given Organism as a list + containing two points [x, y]. + ''' + return self.pos + + def getSize(self): + ''' + Returns the size of the given Organism as a float value. + ''' + return self.size + + def getType(self): + ''' + Returns the type of the given Organism as a string value. + ''' + return self.__class__.__name__ + + def die(self, OrganismsList): + ''' + "Kills" the given Organism by undrawing it and then + removing it from the list of Organisms. + Parameters: + OrganismsList: The list to remove the given + Organism from. + Return Value: none + ''' + #This is to avoid dying from hunger and eaten at the same time + if self in OrganismsList: + self.OrganismGraphic.undraw() + OrganismsList.remove(self) + + def distanceFromTarget(self, target): + ''' + Calculates and returns the distance between the given + Organism and the target Organism. + Parameters: + target: A target Organism. + Return Value: The distance between the given Organism + and the target Organism. + ''' + return (abs(target.getPos()[0] - self.pos[0]) + abs(target.getPos()[1] - self.pos[1])) + + def nearestTarget(self, OrganismsList): + ''' + Searches through the list of other Organisms and + returns the Organism object of the nearest Organism. + Parameters: + OrganismsList: The list of living Organisms. + Return Value: The Organism object of the nearest + Organism. + ''' + self.selfIndex = OrganismsList.index(self) + self.otherOrganisms = list(OrganismsList) + self.otherOrganisms.pop(self.selfIndex) + self.closestTarget = self.otherOrganisms[0] + for Organism in self.otherOrganisms[1:]: + if self.distanceFromTarget(Organism) < self.distanceFromTarget(self.closestTarget): + self.closestTarget = Organism + return self.closestTarget + + def moveToTarget(self, target): + ''' + Moves the given Organism towards the target Organism. + Parameters: + target: A target Organism. + Return Value: none + ''' + self.targetPosX, self.targetPosY = target.getPos() + self.distanceX, self.distanceY = (self.targetPosX - self.pos[0], self.targetPosY - self.pos[1]) + + if self.distanceX >= 0: + self.velocityX = self.speed * speedFactor + if abs(self.distanceX) < abs(self.velocityX): + self.velocityX = self.distanceX + else: + self.velocityX = -self.speed * speedFactor + if abs(self.distanceX) < abs(self.velocityX): + self.velocityX = self.distanceX + self.pos[0] += self.velocityX + + if self.distanceY >= 0: + self.velocityY = self.speed * speedFactor + if abs(self.distanceY) < abs(self.velocityY): + self.velocityX = self.distanceX + else: + self.velocityY = -self.speed * speedFactor + if abs(self.distanceY) < abs(self.velocityY): + self.velocityY = self.distanceY + self.pos[1] += self.velocityY + + def moveRandom(self): + ''' + Moves the given Organism randomly in 2D space. + ''' + self.velocity = (random.uniform(-self.speed, self.speed) * speedFactor, random.uniform(-self.speed, self.speed) * speedFactor) + self.pos = [self.pos[0] + self.velocity[0], self.pos[1] + self.velocity[1]] + + def stayInScreen(self): + ''' + Prevents the Organisms from leaving the screen. + ''' + if self.pos[0] + self.size >= windowSize: + self.pos[0] = windowSize - self.size + elif self.pos[0] - self.size <= -windowSize: + self.pos[0] = -windowSize + self.size + elif self.pos[1] + self.size >= windowSize: + self.pos[1] = windowSize - self.size + elif self.pos[1] - self.size <= -windowSize: + self.pos[1] = -windowSize + self.size + + def draw(self, window): + ''' + Creates a graphical object for the Organism based + upon it's size and type and then draws it onto the + graphic window. + Parameters: + window: A graphical window. + Return Value: none + ''' + if self.OrganismGraphic: + self.OrganismGraphic.undraw() + self.OrganismGraphic = Circle(Point(self.pos[0], self.pos[1]), self.size) + + def checkCollision(self, collidingOrganism): + ''' + Checks if the given Organism is colliding with the + "collidingOrganism". + Parameters: + collidingOrganism: The Organism to be tested against. + Return Value: + True if the given Organism is colliding with the + "collidingOrganism"; else returns False. + ''' + self.collidingOrganismPos = collidingOrganism.getPos() + self.collidingOrganismsize = collidingOrganism.getSize() + + if (self.pos[0] - self.size) <= self.collidingOrganismPos[0] <= (self.pos[0] + self.size): + if (self.pos[1] - self.size) <= self.collidingOrganismPos[1] <= (self.pos[1] + self.size): + return True + elif (self.collidingOrganismPos[0] - self.collidingOrganismsize) <= self.pos[0] <= (self.collidingOrganismPos[0] + self.collidingOrganismsize): + if (self.collidingOrganismPos[1] - self.collidingOrganismsize) <= self.pos[1] <= (self.collidingOrganismPos[1] + self.collidingOrganismsize): + return True + return False + + def checkCollisions(self, OrganismsList): + ''' + Checks if the Organism is colliding with any Organisms + and reacts appropriately by killing one of the + Organisms if it is prey of the other. + Parameters: + OrganismsList: The list of Organisms. + Return Value: none + ''' + for Organism in OrganismsList: + if Organism != self: # prevents Organism from checking if it's colliding with itself + if self.checkCollision(Organism): + if self.isPrey(Organism): + if eventLog: + print('Self killed Organism') + + self.energy += Organism.energy + Organism.die(OrganismsList) + elif Organism.isPrey(self): + if eventLog: + print('Organism killed self') + self.die(OrganismsList) \ No newline at end of file diff --git a/organism.pyc b/organism.pyc new file mode 100644 index 0000000..1ba0a1f Binary files /dev/null and b/organism.pyc differ diff --git a/organisms.py b/organisms.py new file mode 100644 index 0000000..8e8dadc --- /dev/null +++ b/organisms.py @@ -0,0 +1,130 @@ +import random +from graphics import * +from organism import Organism +from configuration import * + +class Herbivore(Organism): + def __init__(self, window, x = None, y = None, size = None, speed = None): + ''' + Can generate a random Herbivore. + Return Value: A randomly generated Organism object. + ''' + if(x == None): + x = random.uniform(-(windowSize * .9), windowSize * .9) + if(y == None): + y = random.uniform(-(windowSize * .9), windowSize * .9) + if(size == None): + size = random.uniform(1, 7) + if(speed == None): + speed = random.uniform(5, 10) + + Organism.__init__(self, window, x, y, size, speed) + # assign a color based upon Organism type + self.OrganismGraphic.setFill('blue') + self.OrganismGraphic.draw(window) + self.prey = ['Plant'] + + def draw(self,window): + super().draw(window) + # assign a color based upon Organism type + self.OrganismGraphic.setFill('blue') + self.OrganismGraphic.draw(window) + +class Omnivore(Organism): + def __init__(self, window, x = None, y = None, size = None, speed = None): + ''' + Can generate a random Omnivore. + Return Value: A randomly generated Organism object. + ''' + if(x == None): + x = random.uniform(-(windowSize * .9), windowSize * .9) + if(y == None): + y = random.uniform(-(windowSize * .9), windowSize * .9) + if(size == None): + size = random.uniform(1, 7) + if(speed == None): + speed = random.uniform(5, 10) + + Organism.__init__(self, window, x, y, size, speed) + # assign a color based upon Organism type + self.OrganismGraphic.setFill('orange') + self.OrganismGraphic.draw(window) + self.prey = ['Plant', 'Omnivore', 'Herbivore', 'Carnivore'] + + def draw(self,window): + super().draw(window) + # assign a color based upon Organism type + self.OrganismGraphic.setFill('orange') + self.OrganismGraphic.draw(window) + +class Carnivore(Organism): + def __init__(self, window, x = None, y = None, size = None, speed = None): + ''' + Can generate a random Carnivore. + Return Value: A randomly generated Organism object. + ''' + if(x == None): + x = random.uniform(-(windowSize * .9), windowSize * .9) + if(y == None): + y = random.uniform(-(windowSize * .9), windowSize * .9) + if(size == None): + size = random.uniform(1, 7) + if(speed == None): + speed = random.uniform(5, 10) + + Organism.__init__(self, window, x, y, size, speed) + # assign a color based upon Organism type + self.OrganismGraphic.setFill('red') + self.OrganismGraphic.draw(window) + self.prey = ['Omnivore', 'Herbivore', 'Carnivore'] + + def draw(self,window): + super().draw(window) + # assign a color based upon Organism type + self.OrganismGraphic.setFill('red') + self.OrganismGraphic.draw(window) + +class Plant(Organism): + def __init__(self, window, x = None, y = None, size = None, speed = None): + ''' + Can generate a random Plant. + Return Value: A randomly generated Organism object. + ''' + if(x == None): + x = random.uniform(-(windowSize * .9), windowSize * .9) + if(y == None): + y = random.uniform(-(windowSize * .9), windowSize * .9) + if(size == None): + size = random.uniform(1, 7) + if(speed == None): + speed = random.uniform(5, 10) + + Organism.__init__(self, window, x, y, size, speed) + # assign a color based upon Organism type + self.OrganismGraphic.setFill(color_rgb(10, 117, 31)) + self.OrganismGraphic.draw(window) + + def draw(self,window): + pass + + #Plants gather energy from sun + def updateEnergy(self): + self.energy += self.energyEfficiency(energyFactor, 10, 100*energyFactor)*(self.size**2) + + def reproduce (self, window, OrganismsList): + ''' + It teproduces the organism at the parent location + the mutation variable helps to create some genetic variations + they should be coded in the child functions + ''' + x_offset = random.randint(-15, 15) + y_offset = random.randint(-15, 15) + random.seed(time) + + if self.pos[0] + self.size + x_offset >= windowSize or self.pos[0] - self.size + x_offset <= -windowSize: + x_offset *= -1 + elif self.pos[1] + self.size + y_offset >= windowSize or self.pos[1] - self.size + y_offset <= -windowSize: + x_offset *= -1 + + newpos = [self.pos[0] + x_offset, self.pos[1] + y_offset] + OrganismsList.append(self.__class__(window, newpos[0], newpos[1], self.size, self.speed)) diff --git a/organisms.pyc b/organisms.pyc new file mode 100644 index 0000000..554c252 Binary files /dev/null and b/organisms.pyc differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ec11f86 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,66 @@ +apturl==0.5.2 +asn1crypto==0.24.0 +Brlapi==0.6.6 +certifi==2018.1.18 +chardet==3.0.4 +chrome-gnome-shell==0.0.0 +command-not-found==0.3 +cryptography==2.1.4 +cupshelpers==1.0 +cycler==0.10.0 +defer==1.0.6 +dell-recovery==0.0.0 +distro-info===0.18ubuntu0.18.04.1 +httplib2==0.9.2 +idna==2.6 +keyring==10.6.0 +keyrings.alt==3.0 +kiwisolver==1.3.1 +language-selector==0.1 +launchpadlib==1.10.6 +lazr.restfulclient==0.13.5 +lazr.uri==1.0.3 +louis==3.5.0 +macaroonbakery==1.1.3 +Mako==1.0.7 +MarkupSafe==1.0 +matplotlib==3.3.3 +netifaces==0.10.4 +numpy==1.19.5 +oauth==1.0.1 +olefile==0.45.1 +pexpect==4.2.1 +Pillow==8.1.0 +progressbar==2.3 +protobuf==3.0.0 +pycairo==1.16.2 +pycrypto==2.6.1 +pycups==1.9.73 +pygobject==3.26.1 +pymacaroons==0.13.0 +PyNaCl==1.1.2 +pyparsing==2.4.7 +pyRFC3339==1.0 +python-apt==1.6.5+ubuntu0.5 +python-dateutil==2.8.1 +python-debian==0.1.32 +pytz==2018.3 +pyxdg==0.25 +PyYAML==3.12 +reportlab==3.4.0 +requests==2.18.4 +requests-unixsocket==0.1.5 +screen-resolution-extra==0.0.0 +SecretStorage==2.3.1 +simplejson==3.13.2 +six==1.15.0 +system-service==0.3 +systemd-python==234 +ubuntu-drivers-common==0.0.0 +ufw==0.36 +unattended-upgrades==0.1 +urllib3==1.22 +usb-creator==0.3.3 +wadllib==1.3.2 +xkit==0.0.0 +zope.interface==4.3.2