u = ABM.Util; Shapes = ABM.Shapes; Maps = ABM.ColorMaps
log = (arg) -> console.log arg
class MyModel extends ABM.ModelThis is a general template for creating models.
To build your own model, extend class ABM.Model supplying the
two abstract methods setup and step. @foo signifies
an instance variable or method.
See CoffeeScript docs
for explanation of context of a class and its constructor.
We do not provide a constructor of our own.
CoffeeScript automatically calls Model.constructor <args>
and setup will be called by Model.constructor. See:
model = new MyModel "layers", 13, -16, 16, -16, 16
below, which passes all its arguments to Model
ABM.Util, ABM.Shapes, ABM.ColorMaps aliases
u = ABM.Util; Shapes = ABM.Shapes; Maps = ABM.ColorMaps
log = (arg) -> console.log arg
class MyModel extends ABM.Modelstartup initializes resources used by setup and step.
This is called by the constructor which waits for all files
processed by starup. Useful for large files, but here just
for an example, not needed by simple models.
startup: -> # called by constructorAdd new shapes.
Shapes.add "bowtie", true, (c) ->
Shapes.poly c, [[-.5,-.5],[.5,.5],[-.5,.5],[.5,-.5]]The following two example lines don’t work when opened locally in Chrome (they work fine from a webserver). Uncomment if you’d like to add custom images.
if window.location.protocol is "file:"
console.log "Warning: file:// protocol used!"
console.log "This prevents two user defined image shapes from loading!"
console.log "Use http:// protocol to enable image shapes."
else
Shapes.add "cup", true, u.importImage("data/coffee.png")
Shapes.add "redfish", false, u.importImage("data/redfish64t.png")
Shapes.add "twitter", false, u.importImage("data/twitter.png")Initialize our model via the setup abstract method.
This model simply creates population turtles with
arbitrary shapes with size size and speed velocity.
We also periodically change the patch colors to random gray values.
setup: -> # called by Model.constructorFirst, we initialize our model instance variables. Most instance variables are parameters we would like an external UI to setup for us.
@population = 100
@size = 2.0 # size in patch coords
@speed = .5 # move forward this amount in patch coords
@wiggle = u.degToRad(30) # degrees/radians to wiggle
@startCircle = true # initialize turtles randomly or in circleSet the default turtle size; save storage over setting size for each turtle
@turtles.setDefault "size", @sizeSet the turtle to convert shape to bitmap for better performance.
@turtles.setUseSprites()Set animation to 30fps, without multiple steps per draw:
@anim.setRate 30, falseThe patch grid will have been setup for us. Here we initialize patch variables, either built-in ones or any new patch variables our model needs. In this case, we set the built-in color to a random gray value.
for p in @patches
p.color = Maps.randomGray(0,100)Set x,y axes different color
p.color = "blue" if p.x is 0 or p.y is 0Our empty @turtles AgentSet will have been created. Here we
add population Turtles we use in our model.
We set the build-in Turtle variables size and shape
and layout the turtles randomly or in a circle depending
on our modeel’s startCircle variable.
for a in @turtles.create @population
a.shape = u.oneOf Shapes.names() # random shapes
if @startCircle
a.forward @patches.maxX/2 # start in circle
else
a.setXY @patches.randomPt()... # set random locationPrint number of turtles and patches to the console. Note CoffeeScript string interpolation
log "total turtles: #{@turtles.length}, total patches: #{@patches.length}"Print number of turtles with each shape
for s in Shapes.names()
num = @turtles.getPropWith("shape", s).length
log "#{num} #{s}"
console.log "Patch(0,0): ", @patches.patchXY 0,0Update our model via the second abstract method, step
step: -> # called by Model.animateFirst, update our turtles via updateTurtles below
@updateTurtles(a) for a in @turtlesEvery 100 steps, update our patches, print stats to the console, and use the Model refresh flag to redraw the patches. Otherwise don’t refresh.
if @anim.ticks % 100 is 0
@updatePatches(p) for p in @patches
@reportInfo()
@refreshPatches = trueAdd use of our first pull request:
@setSpotlight @turtles.oneOf() if @anim.ticks is 300
@setSpotlight null if @anim.ticks is 600
else
@refreshPatches = falseStop the animation at 1000. Restart by ABM.model.start() in console.
if @anim.ticks is 1000
log "..stopping, restart by app.start()"
@stop()Three of our own methods to manage turtles & patches and report model state.
updateTurtles: (a) -> # a is turtleHave our turtle “wiggle” by changing
our heading by +/- wiggle/2 radians
a.rotate u.randomCentered @wiggleThen move forward by our speed.
a.forward @speed
updatePatches: (p) -> # p is patchUpdate patch colors to be a random gray. u.randomGray(p.color) if p.x isnt 0 and p.y isnt 0 # aviod GC, reuse color Avoid Garbage collection by using a colormap
p.color = Maps.randomColor() if p.x isnt 0 and p.y isnt 0
reportInfo: ->Report the average heading, in radians and degrees
headings = @turtles.getProp "heading"
avgHeading = (headings.reduce (a,b)->a+b) / @turtles.length
Note: multiline strings. block strings also available.
log "
average heading of turtles:
#{avgHeading.toFixed(2)} radians,
#{u.radToDeg(avgHeading).toFixed(2)} degrees"Now that we’ve build our class, we call it with Model’s constructor arguments:
divName, patchSize, minX, maxX, minY, maxY,
isTorus = false, hasNeighbors = true
Note: Netlogo defaults 13, -16, 16, -16, 16
for patchSize, minX, maxX, minY, maxY
model = new MyModel({
div: "layers",
size: 13,
minX: -16,
maxX: 16,
minY: -16,
maxY: 16,
isTorus: true,
hasNeighbors: false
})
.debug() # Debug: Put Model vars in global name space
.start() # Run model immediately after startup initialization