An Akka Actors Ping-Pong example

Here’s a 30-second “ping-pong” demo using Akka Actors:

The source code

If you want the source code, you can get it from the GitHub link shown at the end of this post. First, here’s a quick description of it.

The code is in two files, PingPong.scala and PingPongPanel.scala.

PingPong.scala contains three actors:

  • Ping
  • Pong
  • MainFrameActor

It also kicks the action off in the PingPongTest object, which extends the App trait. Here’s the source code for PingPong.scala:

package com.alvinalexander.akkademos

import akka.actor._
import java.awt._
import javax.swing._
import scala.util.Random

/**
 * Case objects used by the actors.
 */
case object PingMessage
case object PongMessage
case object StartMessage
case object StopMessage

case object DisplayMainFrame
case object DoPing
case object DoPong

/**
 * The Ping actor. Notice that its constructor takes an ActorRef.
 */
class Ping(pong: ActorRef) extends Actor {
  var count = 0
  val MAX_NUM_PINGS = 100
  val mainFrameActor = context.actorSelection("../mainFrameActor")
  def incrementCounter { count += 1 }
  def receive = {
    case StartMessage =>
        incrementCounter
        pong ! PingMessage
    case PongMessage => 
        incrementCounter
        if (count > MAX_NUM_PINGS) {
          sender ! StopMessage
          println("ping stopped")
          context.stop(self)
        } else {
          sender ! PingMessage
          mainFrameActor ! DoPing
          Thread.sleep(125)  // blocking! bad!
        }
  }
}

/**
 * The Pong actor.
 */
class Pong extends Actor {
  val mainFrameActor = context.actorSelection("../mainFrameActor")
  def receive = {
    case PingMessage =>
        sender ! PongMessage
        mainFrameActor ! DoPong
        Thread.sleep(125)  // blocking! bad!
    case StopMessage =>
        println("pong stopped")
        context.stop(self)
  }
}

/**
 * An Actor to handle all the interactions with the JFrame. 
 */
class MainFrameActor extends Actor {

  val WIDTH = 600
  val HEIGHT = 400
  val pingPongPanel = new PingPongPanel
  val mainFrame = new JFrame {
    setMinimumSize(new Dimension(WIDTH, HEIGHT))
    setPreferredSize(new Dimension(WIDTH, HEIGHT))
  }
  configureMainFrame

  def receive = {
    case DisplayMainFrame => showMainFrame
    case DoPing => doAction("PING")
    case DoPong => doAction("PONG")
    case _ =>
  }

  def doAction (action: String) { 
    SwingUtilities.invokeLater(new Runnable {
      def run {
        action match {
          case "PING" => pingPongPanel.doPing
          case "PONG" => pingPongPanel.doPong
        }
      }
    })
  }
  
  def configureMainFrame {
    mainFrame.setTitle("Akka Ping Pong Demo")
    mainFrame.setBackground(Color.BLACK)
    mainFrame.getContentPane.add(pingPongPanel)
    mainFrame.setLocationRelativeTo(null)
  }
  
  def showMainFrame {
    SwingUtilities.invokeLater(new Runnable {
      def run {
        mainFrame.setVisible(true)
      }
    })
  }
}

/**
 * The "main" part of the application.
 */
object PingPongTest extends App {
  
  // create the actor system
  val actorSystem = ActorSystem("PingPongSystem")

  // create the actors
  val mainFrameActor = actorSystem.actorOf(Props[MainFrameActor], name = "mainFrameActor")
  val pong = actorSystem.actorOf(Props[Pong], name = "pong")
  val ping = actorSystem.actorOf(Props(new Ping(pong)), name = "ping")

  // display the main frame (jframe)
  mainFrameActor ! DisplayMainFrame
  
  Thread.sleep(5*1000)

  // start the action
  ping ! StartMessage
  
  // shut down the actor system
  //actorSystem.shutdown
}

Notice that there’s some blocking in that actor code. That’s bad, don’t do that! I had to do it to slow down the actors so I could see the circles. Otherwise the code finishes almost instantly. (I can improve the painting algorithm shown below, but the code still finishes almost instantly without the intentional delays.)

Without much discussion at this time, here’s the code from the PingPongPanel.scala file:

package com.alvinalexander.akkademos

import javax.swing.JPanel
import java.awt.{Color, Graphics}
import scala.util.Random

class PingPongPanel extends JPanel {

  val panelWidth = 600
  val panelHeight = 400
  val ballDiameter = 40
  val halfPanelWidth = panelWidth / 2
  val halfPanelHeight = panelHeight / 2
  var currentX = 0
  var currentY = 0
  var color = Color.GREEN
  private val r = new Random
  
  def doPing {
    var x1 = r.nextInt(halfPanelWidth)
    if (x1 < ballDiameter) x1 = ballDiameter
    if (x1 > (halfPanelWidth-ballDiameter)) x1 = halfPanelWidth-ballDiameter
    currentX = x1
    currentY = getRandomY
    color = Color.GREEN
    repaint()
  }

  def doPong {
    var x1 = r.nextInt(halfPanelWidth) + halfPanelWidth
    if (x1 > (panelWidth-ballDiameter)) x1 = panelWidth-ballDiameter
    currentX = x1
    currentY = getRandomY
    color = Color.YELLOW
    repaint()
  }
  
  def getRandomY = {
    var y = r.nextInt(panelHeight)
    if (y < ballDiameter) y = ballDiameter / 2
    if (y > (panelHeight-ballDiameter)) y = panelHeight-2*ballDiameter
    y
  }

  override def paintComponent(g: Graphics) {
    g.clearRect(0, 0, panelWidth, panelHeight)
    
    // draw mid-court line
    g.setColor(Color.WHITE)
    g.drawLine(halfPanelWidth, 0, halfPanelWidth, panelHeight)

    // draw circle (ball)
    g.setColor(color)
    g.fillOval(currentX, currentY, ballDiameter, ballDiameter)
  }

}

More information

Sorry for the rush, I’m a little short on time today, but you can clone the source code from this GitHub project:

I’ll add more discussion here over time, but right now I’m really running late for something else.