• turtle.coffee

  • ¶
  • ¶

    Class Turtle instances represent the dynamic, behavioral element of modeling. Each turtle knows the patch it is on, and interacts with that and other patches, as well as other turtles.

    class Turtle
  • ¶

    Constructor & Class Variables:

    • id: unique identifier, promoted by agentset create() factory method
    • breed: the agentset this turtle belongs to
    • x,y: position on the patch grid, in patch coordinates, default: 0,0
    • size: size of turtle, in patch coords, default: 1
    • color: the color of the turtle, default: randomColor
    • shape: the shape name of the turtle, default: “default”
    • label: a text label drawn on my instances
    • labelColor: the color of my label text
    • labelOffset:the x,y offset of my label from my x,y location
    • heading: direction of the turtle, in radians, from x-axis
    • hidden: whether or not to draw this turtle
    • p: patch at current x,y location
    • penDown: true if turtle pen is drawing
    • penSize: size in pixels of the pen, default: 1 pixel
    • sprite: an image of the turtle if non null
    • cacheLinks: if true, keep array of links in/out of me
    • links: array of links in/out of me. Only used if @cacheLinks is true

    These class variables are “defaults” and many are “promoted” to instance variables. To have these be set to a constant for all instances, use breed.setDefault. This can be a huge savings in memory.

      id: null            # unique id, promoted by agentset create factory method
      breed: null         # my agentSet, set by the agentSet owning me
      x: 0; y:0; p: null  # my location and the patch I'm on
      size: 1             # my size in patch coords
      color: null         # default color, overrides random color if set
      strokeColor: null   # color of the border of an turtle
      shape: "default"    # my shape
      hidden: false       # draw me?
      label: null         # my text
      labelColor: [0,0,0] # its color
      labelOffset: [0,0]  # its offset from my x,y
      penDown: false      # if my pen is down, I draw my path between changes in x,y
      penSize: 1          # the pen thickness in pixels
      heading: null       # the direction I'm pointed in, in radians
      sprite: null        # an image of me for optimized drawing
      useSprites: false   # should I use sprites?
      cacheLinks: false   # should I keep links to/from me in links array?.
      links: null         # array of links to/from me as an endpoint; init by ctor
      constructor: -> # called by agentSets create factory, not user
        @x = @y = 0
        @p = @model.patches.patch @x, @y
        @color = u.randomColor() unless @color? # or @useSprites or @hidden
        @heading = u.randomFloat(Math.PI*2) unless @heading?
        @p.turtles.push @ if @p.turtles? # @model.patches.cacheTurtlesHere
        @links = [] if @cacheLinks
  • ¶

    Set turtle color to c scaled by s. Usage: see patch.scaleColor

      scaleColor: (c, s) ->
        u.deprecated "Turtle.scaleColor: use ColorMaps ramps or closestColor"
        @color = ColorMaps.scaleColor(c, s)
  • ¶

    @color = u.clone @color unless @hasOwnProperty “color” # promote color to inst var u.scaleColor c, s, @color

      scaleOpacity: (c, s) ->
        u.deprecated "Turtle.scaleOpacity: use ColorMaps ramps"
        @color = u.scaleOpacity c, s, @color
  • ¶

    @color = u.clone @color unless @hasOwnProperty “color” u.scaleOpacity c, s, @color

  • ¶

    Return a string representation of the turtle.

      toString: -> "{id:#{@id} xy:#{u.aToFixed [@x,@y]} c:#{@color.css} h: #{h=@heading.toFixed 2}/#{Math.round(u.radToDeg(h))}}"
  • ¶

    Place the turtle at the given x,y (floats) in patch coords using patch topology (isTorus)

      setXY: (x, y) -> # REMIND GC problem, 2 arrays
        [x0, y0] = [@x, @y] if @penDown
        [@x, @y] = @model.patches.coord x, y
        p = @p
        @p = @model.patches.patch @x, @y
        if p.turtles? and p isnt @p # @model.patches.cacheTurtlesHere
          u.removeItem p.turtles, @
          @p.turtles.push @
        if @penDown
          drawing = @model.drawing
          drawing.strokeStyle = @color.css # u.colorStr @color
          drawing.lineWidth = @model.patches.fromBits @penSize
          drawing.beginPath()
          drawing.moveTo x0, y0; drawing.lineTo x, y # REMIND: euclidean
          drawing.stroke()
  • ¶

    Place the turtle at the given patch/turtle location

      moveTo: (a) -> @setXY a.x, a.y
  • ¶

    Move forward (along heading) d units (patch coords), using patch topology (isTorus)

      forward: (d) ->
        @setXY @x + d*Math.cos(@heading), @y + d*Math.sin(@heading)
  • ¶

    Change current heading by rad radians which can be + (left) or - (right). Returns new heading.

      rotate: (rad) -> @heading = u.wrap @heading + rad, 0, Math.PI*2
      right: (rad) -> @rotate -rad
      left: (rad) -> @rotate rad
  • ¶

    Draw the turtle, instanciating a sprite if required

      draw: (ctx) ->
        shape = Shapes[@shape]
        rad = if shape.rotate then @heading else 0 # radians
        if @sprite? or @useSprites # @breed.useSprites
          @setSprite() unless @sprite? # lazy evaluation of useSprites
          Shapes.drawSprite ctx, @sprite, @x, @y, @size, rad
        else
          Shapes.draw ctx, shape, @x, @y, @size, rad, @color, @strokeColor
        if @label?
          [x,y] = @model.patches.patchXYtoPixelXY @x, @y
          u.ctxDrawText ctx, @label,
            x+@labelOffset[0], y+@labelOffset[1], @labelColor
  • ¶

    Set an individual turtle’s sprite, synching its color, shape, size

      setSprite: (sprite)->
        if (s=sprite)?
          @sprite = s; @color = s.color; @strokeColor = s.strokeColor
          @shape = s.shape; @size = @model.patches.fromBits(s.size)
        else
  • ¶

    @color = u.randomColor() unless @color? # not needed if lazy evaluation

          @sprite = Shapes.shapeToSprite @shape, @color,
            @model.patches.toBits(@size), @strokeColor
  • ¶

    Draw the turtle on the drawing layer, leaving permanent image.

      stamp: -> @draw @model.drawing
  • ¶

    Return distance in patch coords from me to x,y using patch topology (isTorus)

      distanceXY: (x,y) ->
        if @model.patches.isTorus
        then u.torusDistance @x, @y, x, y, @model.patches.numX, @model.patches.numY
        else u.distance @x, @y, x, y
  • ¶

    Return distance in patch coords from me to given turtle/patch using patch topology.

      distance: (o) -> # o any object w/ x,y, patch or turtle
        @distanceXY o.x, o.y
  • ¶

    Return the closest torus topology point of given x,y relative to myself. Used internally to determine how to draw links between two turtles. See Util.torusPt.

      torusPtXY: (x, y) ->
        u.torusPt @x, @y, x, y, @model.patches.numX, @model.patches.numY
  • ¶

    Return the closest torus topology point of given turtle/patch relative to myself. See Util.torusPt.

      torusPt: (o) ->
        @torusPtXY o.x, o.y
  • ¶

    Set my heading towards given turtle/patch using patch topology.

      face: (o) -> @heading = @towards o
  • ¶

    Return heading towards x,y using patch topology.

      towardsXY: (x, y) ->
        if (ps=@model.patches).isTorus
        then u.torusRadsToward @x, @y, x, y, ps.numX, ps.numY
        else u.radsToward @x, @y, x, y
  • ¶

    Return heading towards given turtle/patch using patch topology.

      towards: (o) -> @towardsXY o.x, o.y
  • ¶

    Return patch ahead of me by given distance and heading. Returns null if non-torus and off patch world. Heading is + for left, - for right.

      patchAtHeadingAndDistance: (h,d) ->
        [dx,dy] = u.polarToXY d, h + @heading
        @patchAt dx,dy
      patchLeftAndAhead: (dh, d) -> @patchAtHeadingAndDistance dh, d
      patchRightAndAhead: (dh, d) -> @patchAtHeadingAndDistance -dh, d
      patchAhead: (d) -> @patchAtHeadingAndDistance 0, d
      canMove: (d) -> @patchAhead(d)?
      patchAt: (dx,dy) ->
        x=@x+dx; y=@y+dy
        if (ps=@model.patches).isOnWorld x,y then ps.patch x,y else null
  • ¶

    Remove myself from the model. Includes removing myself from my agentset(s) and removing any links I may have.

      die: ->
        @breed.remove @
        l.die() for l in @myLinks() by -1
        u.removeItem @p.turtles, @ if @p.turtles?
        null
  • ¶

    Factory: create num new turtles at this turtle’s location. The optional init proc is called on the new turtle after inserting in its agentSet.

      hatch: (num = 1, breed = @breed, init = ->) ->
        breed.create num, (a) => # fat arrow so that @ = this turtle
          a.setXY @x, @y # for side effects like patches.turtlesHere
          a.color = @color
          a[k] = @[k] for k in breed.ownVariables when a[k] is null # hatched turtle inherits parents' breed properties
  • ¶

    possible alternative: a[k] = @[k] for k, v in a when v is null

          init(a); a # Important: init called after object inserted in agentset
  • ¶

    Return the members of the given agentset that are within radius distance from me, using patch topology

      inRadius: (aset, radius) ->
        aset.inRadius(@, radius)
  • ¶

    Return the members of the given agentset that are within distance from me, and within angle radians of my heading using patch topology

      inCone: (aset, distance, angle) ->
        aset.inCone @, distance, angle, @heading
  • ¶

    Return true if world coordinate falls on turtle sprite

      hitTest: (x, y) ->
        @distanceXY(x, y) < @size
  • ¶

    Return other end of link from me

      otherEnd: (l) -> if l.end1 is @ then l.end2 else l.end1
  • ¶

    Return all links linked to me

      myLinks: ->
        @links ? (l for l in @model.links when (l.end1 is @) or (l.end2 is @))
  • ¶

    Return all agents linked to me

      linkNeighbors: ->
        @otherEnd l for l in @myLinks()
  • ¶

    Return links where I am the “to” agent in links.create

      myInLinks: ->
        l for l in @myLinks() when l.end2 is @
  • ¶

    Return other end of myInLinks

      inLinkNeighbors: ->
        l.end1 for l in @myLinks() when l.end2 is @
  • ¶

    Return links where I am the “from” agent in links.create

      myOutLinks: ->
        l for l in @myLinks() when l.end1 is @
  • ¶

    Return other end of myOutinks

      outLinkNeighbors: ->
        l.end2 for l in @myLinks() when l.end1 is @
  • ¶

    use colorMixin to setup colors

    colorMixin(Turtle, "color", null)
    colorMixin(Turtle, "strokeColor", null)
    colorMixin(Turtle, "labelColor", "black")