使用scala akka actor模型调用生活游戏中的邻居演员

时间:2014-07-16 06:01:35

标签: scala akka actor

我是scala的初学者,但在Java和C ++方面经验丰富,现在我想使用akka actor模型来实现并行Conway的生命游戏。我的想法是创建一个50 * 50的网格,每个单元格都是一个actor,并在actor之间传递消息进行更新。这是我创建演员的方式:

class World(row: Int, col: Int, step: Int) extends Actor {



val random = new Random() //generate the alive or dead cell for the game
  val cellgrid = new World(row, col, step)
  def receive = {

case Start => { 

  for(gridrow <- 0 until row) {
    for(gridcol <- 0 until col) {
      context.actorOf(Props(new Grid(random.nextBoolean, gridrow, gridcol, row, col, step)))
    }
  }

  for (gridrow <- 0 until row) {
    for (gridcol <- 0 until col) {
      sender ! Compare()
    }
  }

}

case Done(gridrow, gridcol, alive) => {
  for(gridrow <- 0 until row) {
    for(gridcol <- 0 until col) {
      if(alive) print("* ")
      else print("  ")
    }
    print("\n")
  }
  sender ! Stop
}

case Stop => {context.stop(self); println("Stopped the actor system!")}
case _ => context.stop(self)


 }
}

但这会导致问题。由于我创建了很多Grid类,因此我很难调用邻居actor。这是Grid类:

class Grid(var life: Boolean, gridrow: Int, gridcol: Int, row: Int, col: Int, step: Int) extends Actor { 



val alive = life
  var numNeighbors = 0  
  var currentStep = 0
  val desiredSteps = step
  val count = Array(0,0)
  val countsuround = Array(0,0)

  for (neighbourX <- gridrow - 1 to gridrow + 1) {
    if (neighbourX >= 0 && neighbourX < col) {
      for (neighbourY <- gridcol - 1 to gridcol + 1) {
        if (neighbourY >= 0 && neighbourY < row && (neighbourX != row && neighbourY != col))
          numNeighbors = numNeighbors + 1
      }
    }
  }

  def receive = {

case Compare() => {
  for (neighbourX <- gridrow - 1 to gridrow + 1) {
    if (neighbourX >= 0 && neighbourX < col) {
      for (neighbourY <- gridcol - 1 to gridcol + 1) {
        if (neighbourY >= 0 && neighbourY < row && (neighbourX != row && neighbourY != col)) 
          sender ! Record(life, currentStep) //Here I need to pass messages to my neighbors
      }
    }
  }

}

case Record(alive,step) => {
  if(alive == true){
    count(step%2) += 1
  }
  countsuround(step%2) += 1
  self ! Check()
}

case Check() => {
  if(countsuround(currentStep%2) == numNeighbors) {

    if(count(currentStep%2) ==3 ||life == true && count(currentStep%2) == 2)
      life = true
    else
      life = false

    count(currentStep%2) =0; countsuround(currentStep%2) = count(currentStep%2)
    currentStep += 1

    if(desiredSteps <= currentStep + 1)
      sender ! Stop
    else {
      sender ! Done(gridrow, gridcol, alive)
      //context.stop(self)
    }
  }
}

  }

}

请查看接收功能中的比较案例,最后,我需要向邻居发送消息记录,但我找不到正确的谈话方式,我不会&#39 ; t有任何邻居索引(如(neighborX,neighborY).Record(life,currentStep))。请帮助我,我已经在这里坚持了几个星期。感谢!!!

1 个答案:

答案 0 :(得分:1)

这是一个包含一些注释的完整工作示例。

import akka.actor._
import scala.concurrent.duration._


object GameOfLife extends App {
    val system = ActorSystem("game-of-life")

    implicit val ec = system.dispatcher // implicit ExecutionContext for scheduler

    val Width  = 20
    val Height = 20

    // create view so we can send results to
    val view = system.actorOf(Props(classOf[View], Width, Height), "view")

    // create map of cells, key is coordinate, a tuple (Int, Int)
    val cells = (for { i <- 0 until Width; j <- 0 until Height } yield {
        val cellRef = system.actorOf(Props[Cell], s"cell_$i-$j") // correct usage of Props, see docs for details
        ((i,j), cellRef)
    }).toMap

    // we need some helpers to work with grid

    val neighbours = (x:Int, y:Int) => Neighbours(
        for (i <- x - 1 to x + 1; j <- y - 1 to y + 1; if ((i,j) != (x,y))) yield {
            cells( ( (i + Width) % Width, (j + Height) % Height) )
        }
    )

    for { i <- 0 until Width; j <- 0 until Height } { // notice that this loop doesn't have yield, so it is foreach loop
        cells((i,j)) ! neighbours(i,j) // send cell its' neighbours
    }

    // now we need to synchronously update all cells,
    // this is the main reason why suggested model (one actor - one cell) is probably not the most adequate

    for { i <- 0 until Width; j <- 0 until Height } {
      cells((i,j)) ! SetState(alive = util.Random.nextBoolean, x = i, y = j)
    }

    // for simplicity I assume that update will take less then update time
    val refreshTime = 100.millis

    system.scheduler.schedule(1.second, refreshTime) {
      view ! Run
      for { i <- 0 until Width; j <- 0 until Height } { 
        cells((i,j)) ! Run 
      }
    }


}


class View(w:Int, h:Int) extends Actor {

  var actorStates: Map[(Int,Int), Boolean] = Map()

  def receive:Receive = {
    case Run => actorStates = Map.empty
    case UpdateView(alive, x, y) =>
      actorStates = actorStates + (((x,y), alive))
      if (actorStates.size == w * h) {
        for { j <- 0 until h } {
          for(i <- 0 until w) {
            if(actorStates((i,j))) {
              print("x ")
            } else {
              print(". ")
            }
          }
          println()
        }
      }
  }
}

class Cell extends Actor {

    var neighbours:Seq[ActorRef] = Seq()
    var neighbourStates: Map[ActorRef, Boolean] = Map() //  Map.empty[Map[ActorRef, Boolean]] is better
    var alive:Boolean = false
    var previousState:Boolean = false
    var x:Int = 0
    var y:Int = 0

    def receive : Receive = {
      case Run =>
        neighbourStates = Map.empty
        previousState = alive
        neighbours.foreach(_ ! QueryState)
      case SetState(alive,x,y) =>
        this.alive = alive
        this.x = x
        this.y = y
      case Neighbours(xs) =>
        neighbours = xs

      case QueryState =>
        sender ! NeighbourState(alive = previousState)
      case NeighbourState(alive) =>
        neighbourStates = neighbourStates + ((sender, alive))
        // this is tricky when all senders has send you their states it doesn't mean that you can mutate your own, 
        // they could still may request your internal state, will use hackish previousState 
        if (neighbourStates.size == 8) { // total of 8 neighbours sent their states, we are complete with update
          val aliveMembers = neighbourStates.values.filter(identity).size
          aliveMembers match {
            case n if n < 2 => this.alive = false
            case 3 => this.alive = true
            case n if n > 3 => this.alive = false;
            case _ => // 2, state doesn't change
          }

          context.actorSelection("/user/view") ! UpdateView(this.alive, x, y)
        }
    }
}


case class SetState(alive:Boolean, x:Int, y:Int)
case class Neighbours(xs:Seq[ActorRef])
case object QueryState
case object Run
case class NeighbourState(alive:Boolean)
case class UpdateView (alive:Boolean, x:Int, y:Int)