"We've been playing games since humanity had civilization - there is something primal about our desire and our ability to play games"
Jane McGonigal
Is a mathematical model of computation used to design both computer programs and sequential logic circuits. It is conceived as an abstract machine that can be in one of a finite number of states.
Wikipedia
Actors are lightweight programmable queues of immutable messages which are processed asynchronously and in a non-concurrent fashion. They can communicate with other actors via immutable messages.
Also known as:Any => Unit
sealed trait Message
case object Ping extends Message
case object Pong extends Message
class PingPongActor extends Actor {
def ping(times: Int): Receive = {
case Ping =>
println(s"Actor Received Ping, transition $times")
context.become(pong(times + 1))
}
def pong(times: Int): Receive = {
case Pong =>
println(s"Actor Received Pong, transition $times")
context.become(ping(times + 1))
}
override def receive: Receive = ping(0)
}
val pingPongActor = system.actorOf(Props[PingPongActor], "PingPongActor")
pingPongActor ! Ping
pingPongActor ! Ping
pingPongActor ! Pong
pingPongActor ! Ping
>Received Ping, transition 0
>Received Pong, transition 1
>Received Ping, transition 2
A subtype of Actors that model a Finite-State Machine. It's described as a set of relations of:State(S) x Event(E) -> Actions(A), State(S')
sealed trait Message
case object Ping extends Message
case object Pong extends Message
sealed trait State
case object WaitingPing extends State
case object WaitingPong extends State
case class Data(times: Int)
class PingPongFSM extends Actor with FSM[State, Data] {
startWith(WaitingPing, stateData = new Data(0))
when(WaitingPing) {
case Event(Ping, data:Data) =>
println(s"Received Ping, transition ${data.times}")
goto(WaitingPong) using new Data(data.times + 1)
}
when(WaitingPong) {
case Event(Pong, data:Data) =>
println(s"Received Pong, transition ${data.times}")
goto(WaitingPing) using new Data(data.times + 1)
}
initialize()
}
val pingPongFSM = system.actorOf(Props[PingPongFSM], "PingPongFSM")
pingPongFSM ! Ping
pingPongFSM ! Ping
pingPongFSM ! Pong
pingPongFSM ! Ping
>Received Ping, transition 0
>[WARN] ... unhandled event Ping in state WaitingPong
>Received Pong, transition 1
>Received Ping, transition 2
"A PingPongFSM actor" must {
"behave in Ping Pong fashion" in {
val pingPongFSM = TestFSMRef(new PingPongFSM)
assert(pingPongFSM.stateName === WaitingPing)
pingPongFSM ! Ping
assert(pingPongFSM.stateName === WaitingPong)
pingPongFSM ! Ping
assert(pingPongFSM.stateName === WaitingPong)
pingPongFSM ! Pong
assert(pingPongFSM.stateName === WaitingPing)
pingPongFSM ! Ping
assert(pingPongFSM.stateName === WaitingPong)
}
}
It depends...
sealed trait BattleshipState
case object WaitingForPlayers extends BattleshipState
case object PlacingShips extends BattleshipState
case object WaitingForNextPlayer extends BattleshipState
case object CheckingShot extends BattleshipState
case object HitShip extends BattleshipState
case object EndGame extends BattleshipState
sealed trait BattleshipMessages
case class PlaceShip(playerId: Int, id: Short, coord: Coord,
size: Short, vertical: Boolean) extends BattleshipMessages
case object ShipsPlaced
case object NextPlayer
case class PlaceShot(playerId: Int, coord: Coord)
extends BattleshipMessages
case object Miss extends BattleshipMessages
case object Hit extends BattleshipMessages
case object ShipsAlive extends BattleshipMessages
case object AllShipsSunk extends BattleshipMessages
case class Message(msg: String)
class BattleShipActor(player1: ActorRef, player2: ActorRef)
extends ActorLogging with FSM[BattleshipState, BattleshipData] {
...
when(WaitingForPlayers) {
case Event(PlaceShip(playerId, shipId, coord, size, vertical), data)=>
if (data.canShipBePlaced(playerId, shipId, coord, size, vertical))
sender ! Message("Placed")
else sender ! Message("Wrong Position")
goto(PlacingShips) using
data.placeShip(playerId, shipId, coord, size, vertical)
}
...
}
when(PlacingShips) {
case Event(PlaceShip(playerId, shipId, coord, size, vertical), data)=>
if (data.canShipBePlaced(playerId, shipId, coord, size, vertical))
if (data.canShipBePlaced(playerId, shipId, coord, size, vertical))
sender ! Message("Placed")
else sender ! Message("Wrong Position")
stay using data.placeShip(playerId, shipId, coord, size, vertical)
else stay
case Event(ShipsPlaced, data) =>
players.foreach(_ ! Message("Ships placed"))
goto(WaitingForNextPlayer)
}
onTransition {
case _ -> PlacingShips =>
if (nextStateData.shipsToPlace.forall(_.isEmpty)) self ! ShipsPlaced
}
def onTransition(transitionHandler: TransitionHandler): Unit
Set handler which is called upon each state transition, i.e. not when staying in the same state.
when(PlacingShips) {
case Event(PlacedShip, _) =>
goto(CheckingPlacedShips)
}
when(CheckingPlacedShips) {
case Event(PlaceShip(playerId, shipId, coord, size, vertical), data)=>
if (data.canShipBePlaced(playerId, shipId, coord, size, vertical))
sender ! Message("Placed")
else sender ! Message("Wrong Position")
goto(PlacingShips)
using data.placeShip(playerId, shipId, coord, size, vertical)
case Event(ShipsPlaced, _) =>
players.foreach(_ ! Message("Ships placed"))
goto(WaitingForNextPlayer)
}
case _ -> PlacingShips => self ! PlacedShip
case _ -> CheckingPlacedShips =>
if (nextStateData.shipsToPlace.forall(_.isEmpty)) self ! ShipsPlaced
when(WaitingForNextPlayer) {
case Event(NextPlayer, data) =>
stay using data.copy(currentPlayer = data.opponent)
case Event(PlaceShot(playerId, x, y), data) =>
goto(CheckingShot) using data.copy(pendingShot = Some(x, y))
}
when(CheckingShot) {
case Event(Miss, data) =>
goto(WaitingForNextPlayer) using data.copy(pendingShot = None)
case Event(Hit, data) =>
goto(HitShip) using data.shoot.copy(pendingShot = None)
}
when(HitShip) {
case Event(ShipsAlive, data) =>
goto(WaitingForNextPlayer) using data
case Event(AllShipsSunk, _) => goto(EndGame)
}
case _ -> WaitingForNextPlayer =>
players.foreach(_ ! Message(s"End of ${nextStateData.currentPlayer} turn"))
self ! NextPlayer
case _ -> CheckingShot =>
if (nextStateData.wouldBeAShot) {
players.foreach(_ ! Message(s"Ship Hit at ${nextStateData.pendingShot}!"))
self ! Hit
}
else {
players.foreach(_ ! Message(s"Missed at ${nextStateData.pendingShot}!"))
self ! Miss
}
case _ -> HitShip =>
if (nextStateData.areShipsAlive(nextStateData.opponent))
self ! ShipsAlive
else self ! AllShipsSunk
case _ -> EndGame => players.foreach(_ ! Message("Game is Over"))
when(WaitingForNextPlayer, stateTimeout = 30 seconds) {
case Event(NextPlayer, data) =>
stay using data.copy(currentPlayer = data.opponent)
case Event(PlaceShot(playerId, x, y), data) =>
goto(CheckingShot) using data.copy(pendingShot = Some(x, y))
case Event(StateTimeout, data) =>
stay using data.copy(currentPlayer = data.opponent)
}
def getBattleshipFSM() = {
val player1Probe = TestProbe()
val player2Probe = TestProbe()
TestFSMRef(new BattleShipActor(player1Probe.ref, player2Probe.ref))
}
"A Battleship actor" should {
"start in WaitingForPlayers state and be initialized" in {
val battleshipFSM = getBattleshipFSM()
assert(battleshipFSM.stateName === WaitingForPlayers)
val stateData = battleshipFSM.stateData
assert(stateData.currentPlayer === 1)
assert(stateData.grids.forall(_.isEmpty))
assert(stateData.pendingShot === None)
}
}
"A Battleship actor" when {
"in WaitingForPlayer state" can {
"go to CheckingPlacedShips state" in {
val battleshipFSM = getBattleshipFSM()
assert(battleshipFSM.stateName === WaitingForPlayers)
battleshipFSM ! PlaceShip(playerId = 1, id = 2,
Coord(x = 1, y = 1), size = 3, vertical = true)
assert(battleshipFSM.stateName === CheckingPlacedShips)
assert(battleshipFSM.stateData.isOccupied(playerIdx = 1,
Coord(x = 1, y = 1)))
}
}
}
"A Battleship actor" when {
"in CheckingPlacedShips state" can {
"go to WaitingForNextPlayer" in {
val battleshipFSM = getBattleshipFSM()
battleshipFSM.setState(CheckingPlacedShips, battleshipFSM.stateData)
assert(battleshipFSM.stateName === CheckingPlacedShips)
battleshipFSM ! ShipsPlaced
assert(battleshipFSM.stateName === WaitingForNextPlayer)
assert(battleshipFSM.isStateTimerActive)
}
}
}
Code will appear in GitHub repo soon...