#!/usr/bin/python # -*- coding: UTF-8 -*- # $Id: Wator.py 623 2007-09-25 18:07:17Z lance $ # """This is a simulation of sharks and fish wageing an ecological war on the toroidal planet Wa-Tor. It comes from an article published by Dewdney in December 1984 issue of Scientific American.""" __details__ = """ Environment: 1. The world is a toroid that is displayed as a rectangular grid. a. The top and bottom edges are joined to each other: moving off the top edge puts a shark or fish on the bottom edge. b. The left and right edges are joined to each other: moving off the right edge puts a shark or fish on the left edge. 2. An initial number of shark and fish are randomly placed in the world. 3. Sharks and fish have a gestation period which is the number of turns before reproduction. 4. Sharks have a starvation period which is the number of turns must eat before the shark dies. Behavior Rules (each round): 1. Fish a. A fish moves in a random direction: north, south, east, or west if the location is clear. b. If a fish moves and gestation period has expired then a new fish is left in the previous location. c. If a fish reproduced then the gestation clock is reset. 2. Shark a. A shark randomly chooses a location to move first from locations that have fish, and if no fish from empty locations. b. If the location has a fish then the shark eats the fish and the hunger clock is reset. c. If a shark moves and gestation period has expired then a new shark is left in the previous location. d. If a shark reproduced then the gestation clock is reset. e. If a shark's hunger clock expires then the shark dies.""" __references__ = """ References: Dewdney, A. K. (1984). Sharks and fish wage an ecological war on the toroidal planet Wa-Tor. Scientific American, December 1984, 14--20. @Article{Dewdney:1984:CRS, author = "A. K. Dewdney", title = "Computer Recreations: {Sharks} and fish wage an ecological war on the toroidal planet Wa-Tor", journal = "Scientific American", volume = "251", number = "6", pages = "14--22", month = dec, year = "1984", CODEN = "SCAMAC", ISSN = "0036-8733", bibsource = "Ai/alife.bib", note = "Description of program for simulating predator-prey dynamics.", keywords = "models-computer", }""" __author__ = ('Lance Finn Helsten',) __version__ = '%s (build: %s)' % ('1.0', "$Revision: 60 $".split()[1]) __copyright__ = """Copyright (C) 2007 Lance Finn Helsten (helsten@acm.org)""" __license__ = """ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA""" __dependencies__ = ('Standard python, http://www.python.org',) import sys import os import math import random class Wator(object): """Build an instance of the Wator world with a dictionary of options. Required Parameters: worldSize Number of locations in the world. sharks Number of initial sharks in the world. fish Number of initial fish in the world. sharkGestation Shark gestation timer. fishGestation Fish gestation timer. sharkStarve Shark starvation timer. """ INIT_KEYS = ['worldSize', 'fish', 'fishGestation', 'sharks', 'sharkGestation', 'sharkStarve'] def __init__(self, **properties): #if properties['sharks'] + properties['fish'] > properties['worldSize']: # raise ValueError, 'Sharks and fish will not fit in grid.' self.empty = Entity(self) self.properties = properties self.reset() def reset(self): self.rows = int(math.sqrt(self.properties['worldSize'])) self.cols = int(self.properties['worldSize'] / self.rows) self.world = list([None] * self.cols for x in range(0, self.rows)) self.fish = 0 self.sharks = 0 for x in range(0, self.properties['sharks']): self.__place(Shark(self, self.properties['sharkGestation'], self.properties['sharkStarve'])) for x in range(0, self.properties['fish']): self.__place(Fish(self, self.properties['fishGestation'])) for r in range(0, self.rows): for c in range(0, self.cols): if self.world[r][c] == None: self.world[r][c] = self.empty def nextRound(self): for r in range(0, self.rows): for c in range(0, self.cols): self.world[r][c].moved = False for r in range(0, self.rows): for c in range(0, self.cols): e = self.world[r][c] if e != self.empty and not e.moved: e.move(r, c) def __str__(self): ret = [] for r in self.world: row = '|' + ''.join([str(c) for c in r]) + '|' ret.append(row) return '\n'.join(ret) def __repr__(self): return "" % hash(self) def __place(self, ent): while True: r = random.randint(0, self.rows - 1) c = random.randint(0, self.cols - 1) if self.world[r][c] == None: self.world[r][c] = ent break class Entity(object): """Defines an entity that lives in Wator. Required Parameters: wator World where the entity lives. breed Number of rounds before the entity breeds: -1 never breeds. starve Number of rounds before the entity starves: -1 never starves. """ def __init__(self, wator, breed=-1, starve=-1): self.wator = wator self.breedTime = breed self.breedClock = breed self.starveTime = starve self.starveClock = starve self.moved = False def eatable(self): return False def move(self, r, c): """Determine how the entity should move and do the move. Parameters: r The row number of this entity. c The column number of this entity. """ loc = self.move0(( self.__getCell(r - 1, c), self.__getCell(r + 1, c), self.__getCell(r, c - 1), self.__getCell(r, c + 1))) if (loc != None): r0, c0, e = loc self.wator.world[r0][c0] = self.wator.world[r][c] self.wator.world[r][c] = self.wator.empty self.moved = True self.breed(r, c) self.starve(r0, c0) else: self.starve(r, c) def __getCell(self, r, c): if r < 0: r = self.wator.rows - 1 if r >= self.wator.rows: r = 0 if c < 0: c = self.wator.cols - 1 if c >= self.wator.cols: c = 0 e = self.wator.world[r][c] return (r, c, e) def move0(self, dirs): """Let sub-class choose which direction to move. The parameters up, down, left, and right is a tuple of the row number, col number, and entity in the location. """ return None def breed(self, r, c): """Determine if the entity should breed. Parameters: r The row number to place a new entity. c The column number to place a new entity. """ if self.breedClock > 0: self.breedClock = self.breedClock - 1 if self.breedClock == 0: e = self.breed0() self.wator.world[r][c] = e self.breedClock = self.breedTime def breed0(self): return self.wator.empty def starve(self, r, c): if self.starveClock > 0: self.starveClock = self.starveClock - 1 if self.starveClock == 0: self.starve0(r, c) def starve0(self, r, c): pass def __str__(self): return " " class Shark(Entity): """Defines a Wator shark and its behavior. Required Parameters: wator World where the shark lives. """ def __init__(self, wator, gestate, starve): Entity.__init__(self, wator, gestate, starve) wator.sharks = wator.sharks + 1 def move0(self, dirs): ret = None fish = [x for x in dirs if x[2].eatable()] if len(fish) > 0: ret = random.choice(fish) if ret != None: self.wator.fish = self.wator.fish - 1 self.starveClock = self.starveTime else: empty = [x for x in dirs if x[2] == self.wator.empty] if len(empty) > 0: ret = random.choice(empty) return ret def breed0(self): return Shark(self.wator, self.breedTime, self.starveTime) def starve0(self, r, c): self.wator.world[r][c] = self.wator.empty self.wator.sharks = self.wator.sharks - 1 def __str__(self): return "S" class Fish(Entity): """Defines a Wator fish and its behavior. Required Parameters: wator World where the fish lives. """ def __init__(self, wator, gestate): Entity.__init__(self, wator, gestate) wator.fish = wator.fish + 1 def eatable(self): return True def move0(self, dirs): ret = None empty = [x for x in dirs if x[2] == self.wator.empty] if len(empty) > 0: random.shuffle(empty) ret = empty[0] return ret def breed0(self): return Fish(self.wator, self.breedTime) def __str__(self): return "F" def _test(): pass if __name__ == "__main__": from optparse import OptionParser parser = OptionParser( description=__doc__, version='%%prog %s' % (__version__,)) parser.add_option('', '--size', action='store', dest='worldSize', type='int', default=1008, help='Define the number of grid locations in the world.') parser.add_option('', '--sharks', action='store', dest='sharks', type='int', default=75, help='Define the initial number of sharks in the world.') parser.add_option('', '--fish', action='store', dest='fish', type='int', default=800, help='Define the initial number of fish in the world.') parser.add_option('', '--shark-gestation', action='store', dest='sharkGestation', type='int', default=10, help='Define the sharks gestation period.') parser.add_option('', '--fish-gestation', action='store', dest='fishGestation', type='int', default=3, help='Define the fish gestation period.') parser.add_option('', '--shark-starve', action='store', dest='sharkStarve', type='int', default=3, help='Define the sharks starvation period.') parser.add_option('', '--details', action='store_true', dest='details', default=False, help='Display program details.') parser.add_option('', '--test', action='store_true', dest='test', default=False, help='Run the internal unit testing.') parser.add_option('', '--copyright', action='store_true', dest='copyright', default=False, help='Display copyright and licence information.') options, args = parser.parse_args() parser.destroy() if options.details: print __doc__ print __details__ print __references__ elif options.copyright: print __copyright__ print __license__ elif options.test: _test() else: try: wator = Wator(worldSize=options.worldSize, fish=options.fish, fishGestation=options.fishGestation, sharks=options.sharks, sharkGestation=options.sharkGestation, sharkStarve=options.sharkStarve) print wator print while wator.fish > 0 and wator.sharks > 0: wator.nextRound() print wator print except KeyboardInterrupt: print >> sys.stderr,'User interruption' sys.exit(3)