After an embarrassingly long hiatus, I’ve started fooling with Scala some more and would like to report how I whiled away a few pleasant hours this weekend. You may have heard of the Stupid Pet Tricks and Stupid Human Tricks segments that David Letterman featured on his television show some years ago. The idea was that the pets and people would perform tricks which, while amusing, were not useful for anything. They were just stupid tricks. In this post I will be describing a stupid Scala trick.

If I may continue to reminisce, I remember years ago seeing a Martin Gardner-esqe mathematical puzzle called the four bug problem (although I don’t remember whether what I read was actually written by Gardner). I see that Wolfram Alpha refers to it as the “Mice Problem”. In short, imagine four bugs (or mice) positioned at the four corners of a square. Each one begins walking toward his clockwise neighbor. Since they’re all walking, they’re all following moving targets. Each bug’s path is affected by his neighbor, so they form a sort of feedback loop. The problem is to describe the path that each bug will take. It’s an interesting problem.

You can also describe the path of 5 bugs on a pentagon or 6 on a hexagon, and so forth. But I wondered what might happen if you had 5,000 bugs positioned not on a 5,000-agon but just everywhere, each following one of his randomly chosen fellows. Chaos, no doubt. This is the story of a little Scala app that answers the question. I’ll present the parts of my solution individually as well as the complete source code which you can compile and experiment with.

Domain classes

First, I need to describe the location of a bug. I could use something built-in like java.awt.Point but I didn’t like the look of it because its interface mixes doubles and ints whereas I wanted floating point coordinates. So I started by creating a class called Position that stores x and y as Doubles. And since the whole idea is to move from position to position, I included a move function. Like so:

class Position(val x: Double, val y: Double) {
  def move(xOffset: Double, yOffset: Double) = new Position(x + xOffset, y + yOffset)
}

That’s a good start, but ultimately we want to take one bug’s position and move it toward another bug’s position. So I added a moveToward and moveAwayFrom function as well as a function to calculate the distance between two positions. I won’t describe this code in excruciating detail. It’s pretty apparent what’s going one. Here it is:

class Position(val x: Double, val y: Double) {
  def move(xOffset: Double, yOffset: Double) = new Position(x+xOffset, y+yOffset)
  private def distNumbers(that: Position) = {
    val xDist = that.x - this.x
    val yDist = that.y - this.y
    val dist = scala.math.sqrt(xDist*xDist + yDist*yDist)
    (xDist, yDist, dist)
  }
  def dist(that: Position) = distNumbers(that)._3
  def moveToward(that: Position, dist: Double) = {
    val (xDist, yDist, totalDist) = distNumbers(that)
    def offset(num: Double) = {
      val result = dist * num / totalDist
      if (result.isNaN) 0 else result
    }
    move(offset(xDist), offset(yDist))
  }
}

The private function is there to prevent duplication of logic. This class isn’t super fancy, but it has the following features: It is immutable, it consistently uses Doubles for coordinates and offsets, it allows us to move a position, to find the distance between two positions and to move one position some distance toward or away from a second position.

One improvement I added after a few tests was to check for NaN (not-a-number) in the xOffset and yOffset values. You can see that on line 14 above. Since these bugs are going to walk toward each other they will often actually meet, resulting in a division by zero. This would cause the bugs to go haywire and shoot off into the corner of the screen after a little while. Line 14 prevents this.

That’s it for the Position class. Now to create the Bug class.

class Bug(xCoord: Double, yCoord: Double) {
  private var pos = new Position(xCoord,yCoord)
  private var nextPos = new Position(xCoord,yCoord)

  def getPosition = pos
  def x: Double = pos.x
  def y: Double = pos.y

  def prepareMove(func : (Position) => Position) = {
    nextPos = func(pos)
  }

  def paint(g: Graphics2D) {
    g.drawLine(pos.x.toInt, pos.y.toInt, nextPos.x.toInt, nextPos.y.toInt)
    pos = nextPos
  }
}

This is mostly self-explanatory. A Bug is little more than a position, a mechanism for transitioning from one position to the next, and a way for it to draw itself on the screen. The only thing that might need explanation is the reason for the nextPos member and the prepareMove function. I included pos and nextPos because I wanted to ensure that all the bugs first decided where to move based on the other bugs’ current positions, and then moved. Say bug B is following bug A. First, bug A decides his move, based on whoever he happens to be following. When bug B makes his move, I want his move to be based on A’s original position, not the new position. That is to say, I want the bugs to all decide where to move at once based on their fellows’ current position, and then to move all at once to the positions they had decided on. The next position isn’t copied into current position until just after the bug draws itself.

The prepareMove function take a function parameter. You’ll notice that the Bug class contains no logic about how to follow another bug. I wanted this logic to reside outside Bug itself and get passed in.

Setup

Now, to instantiate a bunch of bugs and assign each of them someone to follow:

  val initWinSize = new Dimension(800, 800)
  val bugs = {
    def random = Random.nextInt(2000) - 1000
    List.fill(5000)(
      new Bug(random + initWinSize.width/2,
              random + initWinSize.height/2))
  }
  var targets = Random.shuffle(bugs)

I chose to distribute the locations across a range from -1000 to +1000 in both the x and y directions, and to create 5000 bugs. These numbers are arbitrarily chosen. I also added an offset so that the bugs would be distributed about the center of the program window, whose dimensions I have decided to set at 800 by 800. The scala.util.Random class makes it trivially easy to create a randomly shuffled list of target bugs. The targets list contains the same Bug instances as the bugs list, only rearranged. So the first item in the bugs list will follow the first item in the targets list, the second bug follows the second target, and so forth.

Bug logic

Now we have our Bug class defined, a population of bugs created, and we’ve assigned them someone to follow. Below is a function called performMoves which will be called over and over in a loop running in a Thread.

      def performMoves = {
        for ((bug,target) <- bugs.zip(targets)) {
          bug.prepareMove{(pos) =>
            pos.moveToward(target.getPosition, 1.0)
          }
        }
      }

This code loops through all the bug/target pairs, and moves the bug one unit toward the target. This is behavior we were looking for and it works! When I ran the complete program using the above logic the bugs start our in a tangled mess of intersecting paths, but they gradually coalesce into a few smooth curved lines. It’s neat to see.

The complex curves of single-file marching bugs begin to smooth out and the bugs gradually get closer and closer to one another. When one bug is very close to another bug and moves one unit towards it, the direction he moves starts to get erratic. This makes the smooth line jittery and jagged. To combat this tendency, I made an enhancement to the movement logic. In my improved version of performMoves, bugs behave in the normal way when they 1.0 units or further from their target bug. When they get closer than 1.0 unit the bug moves away from the center of gravity of the bug system. I experimented with different distances for the bugs to flee the center of gravity, but I got the most pleasing results when they flee at a rate of 1.0 over the square root of the bug’s distance from the center of gravity.

This causes the bugs to spread out a bit when they start getting too close to each other and keeps the paths relatively smooth. It also has the effect of making the whole system more dynamic and interesting to watch. When the bugs start to get close to each other there is an outward impulse which causes little waves in the lines. Also, whenever you get a small circuit of bugs (a loop of a dozen bugs, say) they very quickly close into a tight formation and are propelled away from the center, sometimes in a flattened figure eight. Anyway, here’s the improved bug movement logic:

      def performMoves = {
        centerOfGravity = {
          val big = bugs.foldLeft(new Position(0,0))((a,c) => a.move(c.x, c.y))
          new Position(big.x / bugs.length, big.y / bugs.length)
        }
        for ((cur,target) <- bugs.zip(targets)) {
          val centerDist = centerOfGravity.dist(cur.getPosition)
          cur.prepareMove{(bug) =>
            if (bug.dist(target.getPosition) < 1.0)
              bug.moveToward(centerOfGravity, -1.0 / math.sqrt(centerDist))
            else
              bug.moveToward(target.getPosition, 1.0)
          }
        }
      }

That’s all of the interesting parts of the code. In addition to this stuff, we need to add the user interface bits, mouse operations, drawing, etc. before we have a working program.

Finally, the complete source

I’ve kept you in suspense long enough. Without further ado, below is the complete source code followed by section-by-section description in case you really are interested in the UI. To run it just call BugsGame.main(null).

import swing._
import event._
import util.Random

class Position(val x: Double, val y: Double) {
  def move(xOffset: Double, yOffset: Double) = new Position(x+xOffset, y+yOffset)
  private def distNumbers(that: Position) = {
    val xDist = that.x - this.x
    val yDist = that.y - this.y
    val dist = scala.math.sqrt(xDist*xDist + yDist*yDist)
    (xDist, yDist, dist)
  }
  def dist(that: Position) = distNumbers(that)._3
  def moveToward(that: Position, dist: Double) = {
    val (xDist, yDist, totalDist) = distNumbers(that)
    def offset(num: Double) = {
      val result = dist * num / totalDist
      if (result.isNaN) 0 else result
    }
    move(offset(xDist), offset(yDist))
  }
}

class Bug(xCoord: Double, yCoord: Double) {
  private var pos = new Position(xCoord,yCoord)
  private var nextPos = new Position(xCoord,yCoord)
 
  def getPosition = pos
  def x: Double = pos.x
  def y: Double = pos.y
 
  def prepareMove(func : (Position) => Position) = {
    nextPos = func(pos)
  }
 
  def paint(g: Graphics2D) {
    g.drawLine(pos.x.toInt, pos.y.toInt, nextPos.x.toInt, nextPos.y.toInt)
    pos = nextPos
  }
}

object BugsGame extends SimpleSwingApplication {
  import java.awt.{Dimension, Graphics2D, Color => AWTColor}

  override def top = frame

  var center = new Point(0,0)
  var origCenter = new Point(0,0)
  var clickPt: Point = new Point(0,0)

  var centerOfGravity = new Position(0,0)

  val initWinSize = new Dimension(800, 800)

  val bugs = {
    def random = Random.nextInt(2000) - 1000
    List.fill(5000)(
      new Bug(random + initWinSize.width/2,
              random + initWinSize.height/2))
  }
  var targets = Random.shuffle(bugs)

  val frame = new MainFrame {
    title = "Scala Bugs"
    contents = mainPanel
    lazy val mainPanel = new Panel() {
      focusable = true
      background = AWTColor.white
      preferredSize = initWinSize

      override def paint(g: Graphics2D) {
        g.setColor(AWTColor.white)
        g.fillRect(0, 0, size.width, size.height)
        onPaint(g)
      }
    }

    listenTo(mainPanel.mouse.clicks, mainPanel.mouse.moves)
    reactions += {
      case MousePressed(src, point, i1, i2, b) => {
        clickPt = point
        origCenter = center
      }
      case MouseDragged(src, point, i1) => {
        center = new Point(origCenter.x + point.x - clickPt.x,
                           origCenter.y + point.y - clickPt.y)
      }
      case MouseReleased(src, point, i1, i2, b) => {
        center = new Point(origCenter.x + point.x - clickPt.x,
                           origCenter.y + point.y - clickPt.y)
      }
      case MouseClicked(src, point, i1, i2, b) => {
        targets = scala.util.Random.shuffle(bugs)
      }
    }

    val runner = new Thread(new Runnable {
      def run() = {
        while (true) {
          performMoves
          repaint()
          //Thread.sleep(10);
        }
      }

      def performMoves = {
        centerOfGravity = {
          val big = bugs.foldLeft(new Position(0,0))((a,c) => a.move(c.x, c.y))
          new Position(big.x / bugs.length, big.y / bugs.length)
        }
        for ((cur,target) <- bugs.zip(targets)) {
          val centerDist = centerOfGravity.dist(cur.getPosition)
          cur.prepareMove{(bug) =>
            if (bug.dist(target.getPosition) < 1.0)
              bug.moveToward(centerOfGravity, -1.0 / math.sqrt(centerDist))
            else
              bug.moveToward(target.getPosition, 1.0)
          }
        }
      }
    })
    runner.start
  }

  def onPaint(g: Graphics2D) {
    g.translate(center.x, center.y)

    for ((cur,target) <- bugs.zip(targets)) {
      g.setColor(AWTColor.lightGray)
      g.drawLine(cur.x.toInt,    cur.y.toInt,
        target.x.toInt, target.y.toInt)

      g.setColor(AWTColor.black)
      cur.paint(g)
    }

    g.setColor(AWTColor.red)
    g.drawOval(centerOfGravity.x.toInt - 3, centerOfGravity.y.toInt - 3, 6, 6)
  }

}

I’ll skip over the lines that I think are trivial:

Lines 5-22: The Position class as described above

Lines 24-40: The Bug class as described above

Lines 42: I used a SimpleSwingApplication as my base class. I’m no swing expert, so this may be a rather naive swing app.

Lines 47-49: Points (in screen coordinates) that I’ll use in my mouse operations.

Lines 53-61: The bug list initialization I described earlier.

Lines 63-76: Setting up the frame and main panel.

Lines 78-95: The mouse operations. You can drag the view around the window to see different areas in a large system, or to follow the bugs if they drift out of frame. You can also click in the window to re-randomize the targets list. This is a fun feature.

Lines 97-122: The main thread. It contains an infinite loop that moves the bugs and repaints, over and over. This section contains the performMoves function that I described earlier. This is the section that you’ll want to experiment with to see what kind of interesting behaviors you can get. Also, line 102 is a good place to add a Thread.sleep if you want to slow things down.

Lines 125-139: The onPaint function. First, it shifts the whole picture to the position selected using the mouse (line 89). Then for each bug it draws a light grey line from that bug to the bug it’s following, and calls on the bug to draw itself. Finally, it draws a small red circle to indicate the center of gravity of the bug population.