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.)