This is a writeup of the extended example that I did in class on 1/25/96.
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.
First step was to brainstorm the "persons, places, and things" which would be relevant for our problem. Here's the list that we developed:
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.
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.
live attribute) and what it's position was in the ocean. We also decided that it would need to know how to be eaten (beEaten service).
live to dead. It also meant removing oneself from the ocean, so the LivingObject needed to know about the ocean.
Plant and Fish subclasses of LivingObject.
move and eat, but Plants didn't really need to know more than any other LivingObject. So, we renamed LivingObject to LivingOrganism.
OceanQueue) to keep track of simulation time and to track which fish can eat. We decided that OceanQueues would know how to activate, which would involve telling all of their fish to activate. They would also keep track of simulation time.
food attribute).
move then eat, if there happened to be something that the Fish considered food at the position it was at -- but only if the Fish were alive.
Ocean which would keep of its occupants. Oceans would be informed when something died (so that it would no longer be an occupant of the ocean), and oceans would inform LivingOrganisms if something which it considers food is available (isFood:at:at:).
ocean so that they could tell the ocean when they had passed on.
While not quite Coad & Nicola notation, here's what we decided upon.
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.].
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.
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
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.
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: [].
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:
time to 0 in the initialize method of OceanQueue, so it complained that time didn't know how to +.
occupants to a new OrderedCollection, so it didn't know how to add:.
Ocean whether something was food, instead of the instance variable ocean (notice the lowercase).
But eventually, it executed without error. It just didn't do anything.
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.
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.
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.]
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.
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.
Finally, I executed the new system workspace code to run the system. It worked! Here's a transcript of a run (since
Randomis 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= ==================== ==================== ==================== ==================== ==================== ====================