Introductory Activity:
A lot to do today, so get started by reviewing notes from Part 1 on what makes a good Moose class. Create a moose class and add the properties and their initial values to the class. Outline in comments the methods: canEat and liveForYear in code and discuss what should be in each method.
The function canEat() can be as complicated as you’d like. We wanted to model that a moose may not find itself in the part of the island that has good vegetation for some reason, so we settled on a randomNumber test to see if the moose found itself in the well-vegetated portion of the island, or not. This seems to work well.
Moose Class:
class Moose { var mooseAgeMaturity = 5 // age a moose needs to be to reproduce var mooseLongevityAge = 100 // number of years a moose will live naturally var age = 0 var alive = true // moose is alive or dead var vegetationNeededToSurvive = 0.01 // each moose needs to eat 1% worth of the islands vegetation per year var mooseBirthRate = 0.75 // each moose will create 0.75 moose each year once it’s old enough to mate var starvation = 0 var environment: Environment init(in environment: Environment, age: Int) { mooseAgeMaturity = 5 let deltaLife = Int.random(in: -6 ... 6) // randomize the longevity of a moose - simulation not very sensitive to this feature mooseLongevityAge = 20 + deltaLife self.age = age alive = true vegetationNeededToSurvive = 0.002 // fraction of the region's vegetation a single moose eats per year mooseBirthRate = 0.75 // fraction of a moose another moose gives birth to, assuming it is mature enough starvation = 0 // counter to keep track of how many years a moose has gone without sufficient food self.environment = environment } func canEat() -> Bool { // current vegetation cover in the region is a fraction from 0.0 to 1.0 // randomize the probability that a moose finds itself in the vegetated fraction of the region and can eat sufficiently for a year let randomNumber = Double.random(in: 0.0 ... 1.0) if randomNumber > environment.vegCover { return false } return true } func liveForYear() { // If a moose doesn’t find food for two years in a row, it dies // If a moose survives until mooseLongevityAge, it dies if canEat() { age += 1 environment.vegCover -= vegetationNeededToSurvive } else { starvation += 1 } if starvation >= 2 { alive = false } if age >= mooseLongevityAge { alive = false } } }
Environment Class:
class Environment { var vegCover = 1.0 let vegatationRegrowthRate = 0.1 // rate at which vegetation regrows each year var mooseArray = [Moose]() // create an empty array of moose func updateVegetation() { // grow vegetation cover for a year vegCover += vegatationRegrowthRate if vegCover > 1.0 { vegCover = 1.0 } } // update simulation population func updateMoosePopulation() { var newMooseArray = [Moose]() var newMooseBabies = 0.0 // check and see if there are any dead moose, of so, remove them from the mooseArray for i in mooseArray.indices { let thisMoose = mooseArray[i] // each moose experiences a year thisMoose.liveForYear() // determine how many new moose will be born this year if thisMoose.alive { newMooseArray.append(thisMoose) if thisMoose.age >= thisMoose.mooseAgeMaturity { newMooseBabies += thisMoose.mooseBirthRate } } } // have moose reproduce let numberOfBabies = Int(newMooseBabies) // add new moose babies to the herd for _ in 0 ..< numberOfBabies { newMooseArray.append(Moose(in: self, age: 0)) } mooseArray = newMooseArray } }
Summary Activity:
Ask the students:
In preparation for running our experiments with our model, can you think of a way we can make the Simulation class more generic and useful?
Ideas:
- create an initializer that takes a numberOfYearsToSimulate as an input argument
- create an initializer that takes an initial number of moose, some properties of those moose (longevity, fractionOfVegetationEatenPerYear, maturityAge, etc.)