Friday 23 July 2010

Ayo game using scala actors


Disclaimer:
This post is an introduction to Scala Actors. It's written in such a way that you'll need very little knowledge of scala and you don't really have to know how to play the game 'ayo' either (a variation of this game is also called mancala - I think). But if you want to know more about the game before starting visit this bbc post. If you want an introduction to scala, you are better off visiting scala language's home page




First off, the scala eclipse ide (for scala 2.8) has taken a HUGE leap forward since the last time I tried it. It's now good enough, I think, to be used in place of intellij's idea - the only thing I have against idea is that it is plain ugly. Even NetBeans looks like a pretty bride in mini-skirt beside it. In any case let's get serious...


Actors are scala response to multi-threaded programming. I hear they were borrowed from haskell or some other fancy language but if that's why you are here... there's an... a google for it. The cool thing about Actors is that it takes away the abstraction of threads and leaves you with fancy-dandy sending and receiving of messages.


package actor {

import scala.actors.Actor
import Actor._


The first thing to note about actors is the classes to import. Basically you import scala.actor.Actor and then you import the methods and classes within the 'Actor' class itself.


//messages to be sent to Actors
case object StartGame
case class Play(board:List[Int])
case class Stoned(count:Int)
case class Move(startAt:Int, player:Player)
case class GameOver(message:String)


Next, we declare the messages that we'll be sending to and from Actors. The usual thing is to use case classes/objects as Messages because the plumbing for pattern matching is built into them (Any class - string, int, etc - that supports case matching would work though). So we've got, for instance, 'StartGame' that represents a message we'll be sending to Referee to start the game. The others should be easy to guess. Alternatively, we might talk about them later.


class Player(val name:String) extends Actor{
var stones = 0

def act = loop {
react {
case Play(board) => reply(Move(getUserInput(board), this))
case Stoned(count) => stones += count
}
}

def getUserInput(board:List[Int]):Int = {
val choices = board.zipWithIndex.filter(_._1 != 0).map(_._2)
print(
"Player "+name+"'s choice "+choices.toString+": ")
val choice = readInt

//verify that the user hasn't select a 'zero' hole
if(!choices.filter(_ == choice).isEmpty)
choice
else{
println(
"Invalid choice :(")
getUserInput(board)
}
}
}


This perhaps is where the real fun begins - We define a Player. A player is used to getting user input and then sending a message to the Referee containing the player's input. The player would of course only get input when a Referee demands it. To turn a player into an Actor we simply extend 'Actor'. The next thing is that we have to write an 'act' function. In the 'act' function you would need a loop of some sort so that the 'Actor' doesn't quit listening after processing just one message. Fortunately, there's a built in 'loop' construct (note, I'm saying construct because I don't know if it's a function or an object or some other voodoo scala thingy) that we can use. In side the 'loop' you define a 'react' function with matching cases for each message that you want the actor to handle. In 'Player' we want to handle messages to increase the number of stones that the player has captured (Stoned) and we want the 'Player' to know when to make a choice on where to start his next move from (Play).


'reply' is a function that sends a response back to the Sender of a message. So we don't really have to know who sent the message, we can simply process the message and use the 'reply' function to send a response back to the whoever sent the message in the first place. For instance, when the 'Player' gets a 'Play' message from the 'Ref' the 'Player' simply calls 'reply(Move(getUserInput(board), ...))' so that a 'Move' message, containing the player's choice, is sent back to the 'Ref'.


As an aside, you'll notice that there's a 'getUserInput' function that actually does the job of getting the user's input. This method receives a list of stones corresponding to holes that the user currently owns (no pun intended). Basically, we keep requesting for the user's input until the user enters a choice that's valid. This way we get players that are honest to a fault - no Thierry Henry scoring hand goals or Luis Suarez' stopping clear cut goals with their hands - and apparently we can have even Graham Poll as the ref since we've removed the risk of a player receiving 1, talk less of 3, yellow cards.


Below we have the Referee or 'Ref'. I'll include comments within the class definition (remember that you can hide all the other with that link above) just so I don't lose my mind before this is all done.


object Ref extends Actor {
import scala.collection.mutable.Buffer
var players:List[Player] = List(
new Player("one"), new Player("two")) //always assumes 2 players
var holes:Buffer[(Int, Player)] = {0 to 11}.map(i => (4, players((i/6).toInt))).toList.toBuffer //always assume players have half the board to start with



We declare/define the total number of players as well as the holes (no pun intended... again!) here. Nothing special there, move along.



def act = {
loop {
react {
case Move(startAt, player) => { //TODO logic to carry out actual move and to keep score
move(startAt, -1, player)
val p = nextPlayer(player)
p ! Play(board(p))
}
case StartGame => {
players.foreach(_.start)
val p = nextPlayer(
null)
p ! Play(board(p))
}
case GameOver(s) => exit
}
}
}




So, the Ref is also an 'Actor'. We got that by having the 'Ref' extend an 'Actor'. This also means the 'Ref' has to have an 'act' method which we have duely done above. The most important thing here is that the Ref can respond to three messages - StartGame (which the application itself sends to it), GameOver - which the Ref sends to himself (ahhh... the thought of female referes), and Move - which is the response a player sends to the 'Ref' after you, the end user, have made up your mind what hole to start your move from.


It's easy at this point to see that this game is not complete yet. What's important is to keep the goal before your eyes and not succomb to the whole moving target chirade. In our case, the goal is understanding 'Actor' concept. Having a complete game is a side effect - a bonus at best! That said, the game - work!, it should! But I digress... again

    
/****
*
@params stonesLeft -1 means start counting, 0 means stop counting, > 0 means keep counting
****/

def move(startAt:Int, stonesLeft:Int, player:Player){
val hole = holes(startAt)
val next = (startAt + 1)%12

if(stonesLeft == 0){
var start =
if(startAt - 1 < 0) 11 else startAt - 1
var h = holes(start)

if(h._1 == 1)
Unit
else if(h._1 == 4){
player ! Stoned(4)
holes(start) = (0, player)
}
else
move(start, -1, player)
}
else if(stonesLeft == -1){
val stones = hole._1
holes(startAt) = (0, hole._2)
move(next, stones, player)
}
else{
holes(startAt) = (hole._1 + 1, hole._2)
move(next, stonesLeft - 1, player)
}
}



As far as the game is concerned, 'move' is the most important function of the game. It contains logic about carrying out the counting of the game, and capturing houses or holes (now that I think about it houses is a better word that holes but the damage is done).

stonesLeft is actually the number stones the player currently has in his/her hands. So if the user has 0 stones in her hands it's time to take stock

  • if where she stopped has 4 stones then she captures that hole as well as the 4 stones in that hole

  • if where she stopped had no stones then her move is complete and the next player gets to play

  • if where she stopped has neither 0 nor 4 stones then she pick up the stones from where she stopped and starting another round of counting


Actually, the explanation above is a little misleading because it's the Ref that does the counting on behalf of the 'Player'. The 'Ref' can send messages back to the Player like 'Stoned(4)' to tell the player that she has captured 4 more stones.



def nextPlayer(player:Player) = {
draw
scores
players.filter(_ != player)(0)
}

//board is a list of user stones in holes i.e. if the user doesn't own a hole, zero stones appear
def board(player:Player):List[Int] = holes.toList.map(x => { if(x._2 == player && x._1 != 0) x._1 else 0 })

def draw(){
holes.takeRight(6).reverse.foreach(hole => print(hole._1 +
"\t"))
println
holes.take(6).foreach(hole => print(hole._1 +
"\t"))
println
}

def scores(){
players.foreach(p => println(
"Player "+p.name+" has "+p.stones+" stones"))
println
}
}

object Ayo
extends Application{
Ref.start
Ref ! StartGame
}
}



One of the most important bits to the whole 'Actor' magic is the 'start' function call. You've got to call start on an 'Actor' in order to get the party rolling. In the main class 'Ayo' above we not only start the 'Ref' actor using 'start' but we also send a 'StartGame' message to the 'Ref' actor. When the 'Ref' receives the 'StartGame' message, it in turn calls 'start' on all the 'Player' instances and then sends a 'Play' message to the first Player instance. All in all, Actors sure bits Threads in java. One last thing though, all the messages we've been sending were done using the ! (bang). Messages sent in this way are asynchronous. In other words, the 'Ref' doesn't block for a response from the 'Player'. Instead he continues doing whatever else he can do (in our case mostly nothing) until he receives the response from the 'Player'. To send synchronous messages use !? instead of !.




And that's all folks.

I'm convinced that I should create a second blog for posts like this but I'm too lazy to maintain 2 blogs. Time (and hits - anything more than 5 hits and I'm creating a new blog) would tell - this here blog is kind of sacred to me, you know ;)

No comments: