Extended Example: Simulation of an Ocean Environment

This is a writeup of the extended example that I did in class on 1/25/96.

  1. Problem Statement

    We started out stating what the problem was that we were going after. Someone on WebCaMILE had suggested a simulation of an ocean environment, where some animals eat others, that animals move around, etc. We decided to tackle this problem, but to constrain it as we went along to something that was do-able in the 90 minute class.

  2. Brainstorming the Classes

    First step was to brainstorm the "persons, places, and things" which would be relevant for our problem. Here's the list that we developed:

  3. Filtering the Classes

    Next, we iterated on the problem definition. We decided that we'd focus on the ocean ecosystem: Fish swimming around, eating one another, eating plants. We wouldn't try to model weather or other influences on the fish -- just let them move around for now. So we eliminated (in italics) some of the items we identified earlier.

  4. Laying out the Classes and their Relations in Graphical Notation

    Our next step was to more formally define the classes and their relationships, using the graphical notation. We went through several iterations on this process.

    While not quite Coad & Nicola notation, here's what we decided upon.

  5. Figuring out the code to run the system

    We decided that the next step was to figure out what the code would look like that would run the system from the System Workspace.

    | shark1 shark2 fish1 fish2 fish3 ocean queue plant1|
    ocean := Ocean new initialize.
    fish1 := Fish new initialize: ocean at: 1 at: 5.
    fish2 := Fish new initialize: ocean at: 3 at: 7.
    fish3 := Fish new initialize: ocean at: 8 at: 9.
    shark1 := Fish new initialize: ocean at: 12 at: 15.
    shark2 := Fish new initialize: ocean at: 18 at: 1.
    plant1 := LivingOrganism new initialize: ocean at: 10 at: 10.
    queue:= OceanQueue new initialize.
    queue addFish: shark1; addFish: shark2; addFish: fish1; addFish: fish2; addFish: fish3.
    shark1 canEat: fish1; canEat: fish2.
    shark2 canEat: fish1; canEat: fish2; canEat: fish3.
    fish1 canEat: plant1. fish2 canEat: plant1. fish3 canEat: plant1.
    10 timesRepeat: [queue activate.].
    

  6. Building the LivingOrganism Class

    The LivingOrganism class was the most important in the system, so we decided to build that first:

    " Defining the Class "
    Object subclass: #LivingOrganism
    	instanceVariableNames: 'ocean live x y '
    	classVariableNames: ''
    	poolDictionaries: ''
    	category: 'FishWorld'
    
    LivingOrganism methodsFor: 'initialization'
    
    initialize: anOcean at: anX at: anY
    	"Bring the organism to life in the ocean at a given place"
    	live := true.
    	ocean := anOcean.
    	x := anX.
    	y := anY.
    	ocean addToOcean: self.
    
    LivingOrganism methodsFor: 'accessing'
    
    positionX
    	^x.
    
    positionY
    	^y.
    
    LivingOrganism methodsFor: 'food processing'
    
    beEaten
    	"Kill off the organism"
    	live := false.
    	ocean removeFromOcean: self.
    
    

  7. Building the Fish Class

    Next, we decided to build the Fish. The Fish were tricky, in terms of figuring out how to move. We decided to just use a uniform distribution from the Random class so that the Fish would just as likely go up as down as left as right.

    " Defining the class "
    LivingOrganism subclass: #Fish
    	instanceVariableNames: 'food '
    	classVariableNames: ''
    	poolDictionaries: ''
    	category: 'FishWorld'
    	
    Fish methodsFor: 'initialization'
    
    initialize: anOcean at: anX at: anY
    	"Initialize food besides everything else"
    	food := OrderedCollection new.
    	super initialize: anOcean at: anX at: anY.
    
    Fish methodsFor: 'live processing'
    
    activate
    	live ifTrue: [self move. self eat.]
    
    eat
    	"Is there food where I am?  If not (returns nil), leave.
    	If so, EAT IT!"
    	
    	| localfood |
    	localfood := ocean isFood: food at: x at: y.
    	(localfood isNil) ifTrue: [^false].
    	localfood beEaten.
    
    move
    	| randomizer arandom  |
    	randomizer := Random new.
    	arandom := randomizer next.
    	(arandom >  0.75) ifTrue: [x := x + 1]
    		ifFalse: [(arandom >  0.5) ifTrue: [y := y + 1]
    			ifFalse: [(arandom >  0.25) ifTrue: [ x:= x - 1]
    				ifFalse: [y:= y - 1]
    			]
    		].
    
    Fish methodsFor: 'food processing'
    
    canEat: something
    	food add: something
    

  8. Building the OceanQueue class

    Of the two remaining classes, OceanQueue seemed easier, so we did that one next.

    " Defining the class "
    Object subclass: #OceanQueue
    	instanceVariableNames: 'fish time '
    	classVariableNames: ''
    	poolDictionaries: ''
    	category: 'FishWorld'
    
    OceanQueue methodsFor: 'initialization'
    
    initialize
    	fish := OrderedCollection new.
    	time := 0.
    
    OceanQueue methodsFor: 'live processing'
    
    activate
    	fish do: [:aFish | aFish activate].
    	time := time + 1.
    
    OceanQueue methodsFor: 'connections'
    
    addFish: aFish
    	fish add: aFish.
    

  9. Building the Ocean class

    The tricky part of the Ocean class was defining what was food or not. In reality, that wasn't too hard.

    Object subclass: #Ocean
    	instanceVariableNames: 'occupants '
    	classVariableNames: ''
    	poolDictionaries: ''
    	category: 'FishWorld'
    
    Ocean methodsFor: 'initialization'
    
    initialize
    	occupants := OrderedCollection new.
    
    Ocean methodsFor: 'food processing'
    
    isFood: food at: anX at: anY
    	"Check all food's locations.  If find one at same place, return it.
    	 Otherwise, return nil."
    	
    	food do: [:aLife |
    		((aLife positionX = anX) and: [aLife positionY = anY])
    			ifTrue:  [^aLife]].
    	^nil.
    	
    Ocean methodsFor: 'connections'
    
    addToOcean: aLife
    	occupants add: aLife.
    
    removeFromOcean: aLife
    	occupants remove: aLife ifAbsent: [].
    

  10. Run the System

    At this point, we could run the system. We wouldn't see anything happen, because we haven't done the interface yet, but we'd get errors if we'd screwed things up. Some of the things that I did wrong when I entered the code:

    But eventually, it executed without error. It just didn't do anything.

  11. Designing an Interface

    I didn't really care about input into the system, but I wanted someway of seeing output. I decided to implement the dirt-cheapest mechanism of display: An ASCII-only display of part of the ocean. Notice that we never set any limits on where animals might move. By displaying part of the ocean, we'd run the risk of animals moving outside the visible range, but I decided to accept that as a simple interface.

    I also decided to put when time changed and when food was eaten, as additional useful feedback that something was happening.

  12. Modifying the Workspace code

    The first step was to modify the workspace code, so that I'd have an idea of where I was going. I decided that each object would keep track of its representative character/icon, and I would display the ocean before and after the simulation run.

    | shark1 shark2 fish1 fish2 fish3 ocean queue plant1|
    ocean := Ocean new initialize.
    fish1 := Fish new initialize: ocean at: 1 at: 5; display: 'F'.
    fish2 := Fish new initialize: ocean at: 3 at: 7; display: 'F'.
    fish3 := Fish new initialize: ocean at: 8 at: 9; display: 'F'.
    shark1 := Fish new initialize: ocean at: 12 at: 15; display: 'S'.
    shark2 := Fish new initialize: ocean at: 18 at: 1; display: 'S'.
    plant1 := LivingOrganism new initialize: ocean at: 10 at: 10; display: 'P'.
    queue:= OceanQueue new initialize.
    queue addFish: shark1; addFish: shark2; addFish: fish1; addFish: fish2; addFish: fish3.
    shark1 canEat: fish1; canEat: fish2.
    shark2 canEat: fish1; canEat: fish2; canEat: fish3.
    fish1 canEat: plant1. fish2 canEat: plant1. fish3 canEat: plant1.
    ocean display.
    10 timesRepeat: [queue activate.].
    ocean display.
    

  13. Modifying the Ocean class

    Next, I needed a method to display the ocean range I chose -- 20x20.

    Ocean methodsFor: 'display'
    
    display
    	"Walk through a 20x20 space.  If there's a life there, have it display
    	 itself.  If not, just put a marker."
    	| aLife |
    	Transcript cr; show: 'OCEAN:'; cr; cr.
    	1 to: 20 do: [:x |
    		1 to: 20 do: [:y | 
    			aLife := self isFood: occupants at: x at: y.
    			(aLife isNil) ifTrue: [Transcript show: '='.]
    				ifFalse: [aLife display]].
    		Transcript cr.]
    

  14. Modifying the LivingOrganism class to display itself

    LivingOrganisms had to track their display icon, be able to set it, and be able to present it when requested. Tracking the display icon involved re-defining the class, but that was really easy to do.

    Object subclass: #LivingOrganism
    	instanceVariableNames: 'ocean live x y display '
    	classVariableNames: ''
    	poolDictionaries: ''
    	category: 'FishWorld'
    	
    LivingOrganism methodsFor: 'display'
    
    display
    	Transcript show: display.
    
    display: displayCharacter
    	display := displayCharacter.
    

  15. Tweaking the OceanQueue and LivingOrganism Classes

    We also wanted to show when time passed and when organisms were eaten.

    LivingOrganism methodsFor: 'food processing'
    
    beEaten
    	"Kill off the organism"
    	live := false.
    	ocean removeFromOcean: self.
    	Transcript cr; show: 'Someone has been eaten!!'
    
    OceanQueue methodsFor: 'live processing'
    
    activate
    	fish do: [:aFish | aFish activate].
    	time := time + 1.
    	Transcript cr; show: 'AT TIME:  ', time printString.
    

  16. Run the Whole System with Interface

    Finally, I executed the new system workspace code to run the system. It worked! Here's a transcript of a run (since

    Random
    is random, your mileage may vary.)
    
    OCEAN:
    
    ====F===============
    ====================
    ======F=============
    ====================
    ====================
    ====================
    ====================
    ========F===========
    ====================
    =========P==========
    ====================
    ==============S=====
    ====================
    ====================
    ====================
    ====================
    ====================
    S===================
    ====================
    ====================
    
    AT TIME:  1
    AT TIME:  2
    AT TIME:  3
    AT TIME:  4
    AT TIME:  5
    AT TIME:  6
    Someone has been eaten!
    AT TIME:  7
    AT TIME:  8
    Someone has been eaten!
    AT TIME:  9
    AT TIME:  10
    OCEAN:
    
    ====================
    ====================
    ======F=============
    ====================
    ========F===========
    ====================
    ====================
    ====================
    ====================
    ==========F=========
    ====================
    ====================
    ====================
    ==================S=
    ====================
    ====================
    ====================
    ====================
    ====================
    ====================