Saturday 28 February 2009

Ayo and Scala 3.5

Ayo is a game that played in various forms across parts of west africa. I learnt it as a kid growing up in Benin City but with a different set of rules from what the way it's played in large parts of the yoruba people. The Yorubas call it Ayo but you've probably heard of it by the more popular name of mencala. Needless to say I'm implementing the game as explained here. Find out more about the game here.

Last time around I gave a quick overview of the scala implementation. Today I'll be looking at each component in a little more detail starting with the board. The board of ayo is made up of 12 holes, 6 on each side. Each one of these 12 holes has 4 seeds to start with. Each player also 'owns' all the houses (6) on one side of the board.

Here's a list of what I've design the board to be able to do:

- draw itself out to the console
- carry out a complete sequence of moves

So how do we implement this in scala?


object Board {


A scala object is a singleton class. So one rather crude and incorrect way of looking at a scala object is that you are looking at a java static class.


Declarations in scala are done using the def, val, and var keywords among other stuff. 'def' is used to define a method. In the case where a method takes no parameters you can leave the () brackets off the definition of the method. 'val' defines what can be called, in java parlance, a constant. Variables defined with 'val' can never be reassigned to another value. Then there's the 'var' keyword that defines regular good old fashion variables. These can be reassigned and all that...

There'd be no point in using scala if it didnt have a good enough library of containers among other things. The cool thing about scala is that it not only has libraries of its own but it also can leverage java libraries transparently - kind of like what python is to c/c++. In scala though, and largely because of its cool functionaly idiosyncresis, there're 2 types of container objects - mutable and immutable (actually there are 3 packages but that's besides the point here). A very popular example of a mutable object is java string class. Nothing can be done to the class itself that doesn't result in the creation of a brand new string. Well in scala you have lists and iterable elements that have this characteristic (we'll talk about this a little more later). I've digressed... let's take a look at the code again.


def roll(index:Int, stoneBuffer:Int, list:Array[Int]):(List[Int],Int) =
if(stoneBuffer==0)
(list.toList, position(index-1))
else{
list(position(index)) += 1; draw(list.toList)
roll(index+1, stoneBuffer-1, list)
}


The roll function is a recursive one. The signature of the method is that had to grasp. Where java would say int i, scala would say i:Int. So this is a method that takes 3 parameters. The method returns a 2 tuple containning an array and an integer. The contents of the tuple actually are the stones as they are arranged on the board after a sequence of moves and the position where the sequence of moves ended.
You'd also notice that the method contains just one if block and that there are no return statements. Well, that's because in scala all expressions are ... ok I dont know how to put it but by default the last expression in a method is returned. So one of the last statement in each of 'if' block's conditional subunits gets returned when the method is called.

What the roll function actually does is to check if there are any stones left to move about, if there isn't it returns the board and the current location. Otherwise, it adds one stone to the current location, reduces the number of stones left to move by one and then calls itself again... simple!

Next stop is the Rules object.

object Rules {
def canMove(plantedSeeds:Int) = plantedSeeds!=0
def canKeep(plantedSeeds:Int) = plantedSeeds==4
def hasHouse(houses:Set[Int]) = !houses.isEmpty
def gameOver(list:List[Int]) = list.reduceLeft(_+_) < 4
}

One thing you may have noticed is that I appear to be passing around a lot of excess luggage from function to function. Well that, I've heard, is one of the features of functional programming. Functional programming avoids side effects - which means I only deal with what the function returns and not with any changes that the function may have made to the parameters that I passed to it. One way of implementing this is to make sure that only immutable objects are used and passed along from method to method. That way I'm sure that if I sent you a parameter you wont be able to change it and all i'll have to worry about is what you've passed back to me. Consequently it's becomes pretty easy to test methods as everything is easily compartmentalized.

The Rules object basically just contains the rules of the game. For instance a player can only start a move (canMove) when there are seeds in the hole from which he wants to start the move. But this is an incomplete rule because a player cannot start a move from a house he doesn't own (Perhaps this is a design flaw).

Now it makes perfect sense to use singletons for both the Rules and the Board classes because both classes dont really have any fields. In essence, I'm trying my best to make this a fully working functional game (I think I wont succeed though, it's so paaaaainful). The other methods in the Rules object appear to be straight forward or in the words of one of my fav college teachers - "think small" (go figure)

Now let's talk interesting stuff

There's a class that stores the current configuration of the game. It's called Config. This class is a case class (what?). Well, in scala there's this cool type of class you can create that will authomatically create a toString method and comparison (equals/compareTo) method that works off of the toString method (I may be wrong here but's that how I understand it). The major advantage of this is that you can do some pattern matching on this class and generally write less code. So we define a case class that actually stores the seeds won by each player, the houses won by each player, the player whose turn it is to play and the arrangement of the stones left over on the board.
Now you cant change the values of the fields of a case class without really running into trouble since the toString class is created at class initialization (I need to verify this... but when I started learning scala way back on 1.6 or thereabout this appears to have been the case). In any case, each method of this config class that appears to modify any of its fields actually creates and returns a brand new Config object (cool eh?).

The constructor of a class in scala is the same of what appears to be the parameter list of the class itself, so in our case stones:List[Int], houses:List[Set[Int]], turn:Int, list:List[Int] are the parameters of the constructor of the Config class. The methods of this class are pretty straight forward so I wont be talking about them at all.


case class Config(stones:List[Int], houses:List[Set[Int]], turn:Int, list:List[Int]){

def plusStone(count:Int):Config = plusStone(count, turn)
def plusStone(count:Int, player:Int) = {
val s = stones.toArray
s(player) += count
Config(s.toList, houses, turn, list)
}
.
.
.
.


Ha! That's all for this week folks. I'll talk about how it all comes together when I look at the last part of the game (an object called 'Game') next week maybe.

Sunday 22 February 2009

Ayo in Scala 3

I can't believe it's been more than 6 months since last.... I guess that's one of the perks of having nobody read your blog. In any case, I've figured that for once in my life (cue Harry Connick Jnr singing for once in my life, I have someone...) I'll get something done completely. So each day of the week, last week, I took that old Ayo program from 2 years ago and gradually tweaked it. I'm at a stage now that I can say I'm done. I've practically completely what I had in mind 2 years ago. Of course there are now things that I'll love to add but these will be another project for another day. The game plan for now is to talk about a component of the program each day of this week (note I'm not saying I'll post everyday of the week...) and then start a new project next week of implementing a fancy interface using either LIFT or JAVAFX (I've never done anything previously in either so that would be fun).
So here's a general overview of what I'll be going over this week. I've divided AYO into about 5 components.
  1. The board - This represents the wood (or eWood since this wood is intelligent). The wood can carry out arbitrary operations during the game like counting and moving (sowing) seeds.
  2. The Rules - This represents a kind of 10 commandments. The board references it when moving seeds and other components reference it as need be
  3. The Config - This represents the configuration of the game at any point in time. It stores the number of seeds each player has, the player whose current turn it is to play, how many houses a player has won, etc. Obviously the game starts with a new config
  4. The Game - This actually is a misnomer. It should be called the referee or umpire. This component starts the game, receives input from the user, and calls the other components as needed.
  5. I said about 5 didn't I?

Here's the complete source code (buggy and quaint) as it is right now.




object Game {Mankala

def start = {
val s = List(0,0)
val h = List(Set(0,1,2,3,4,5), Set(6,7,8,9,10,11))
val l = List(4,4,4,4,4,4,4,4,4,4,4,4)

move(Config(s, h, 0, l))
}

def move(cfg:Config):Unit = {
if(!Rules.hasHouse(cfg.getHouses)){
//print message alerting player of change of turn
move(cfg.changeTurn)
}

Board.draw(cfg.list)

println("\nPlayer "+(cfg.turn+1)+"\n"+cfg)
//get current player's choice... player can only select his own house and if it has seeds
val choice = getInput(cfg.getHouses.filter(cfg.list(_) != 0))
val (list, index) = plant(choice, cfg.list) //playout the choice

val ncfg =
if(Rules.canKeep(list(index)))
cfg.setList(list).plusStone(4).updateList(index,0)
else
cfg.setList(list)

val ocfg = houseKeeping(0, ncfg)
val pcfg = houseKeeping(1, ocfg)

if(!Rules.gameOver(pcfg.list))
move(pcfg.changeTurn)
else
println("Game Over: \n"+pcfg)
}

/**
* get current player's choice
* if the choice is not valid display message and prompt user again
*/
def getInput(restrictions:Set[Int]):Int = {
print("Please start move ["+restrictions.foldLeft("")(_+","+_)+"]: ")
val x = readInt

if(restrictions.contains(Board.position(x)))
Board.position(x)
else
getInput(restrictions)
}

def plant(pos:Int, l:List[Int]):(List[Int],Int)={
val (listn, index) =
Board.roll(pos+1, l(pos), {l.slice(0,pos):::List(0):::l.slice(pos+1,l.size)}.toArray)

if(Rules.canMove(listn(index)-1) && !Rules.canKeep(listn(index)))
plant(index, listn)
else
(listn, index)
}

def houseKeeping(turn:Int, cfg:Config, houses:List[Int]):Config={
//Board.draw(cfg.list)
//println(">>> "+houses)

if(houses.isEmpty)
return cfg
else{
var nConfig =
if(Rules.canKeep(cfg.list(houses.head)))
cfg.updateList(houses.head, 0).plusStone(4, turn)
else
cfg

houseKeeping(turn, nConfig, houses.tail)Mankala
}
}

def houseKeeping(turn:Int, cfg:Config):Config = houseKeeping(turn, cfg, cfg.houses(turn).toList)
}

case class Config(stones:List[Int], houses:List[Set[Int]], turn:Int, list:List[Int]){
def this() = this(List(0,0), List(Set(0,1,2,3,4,5), Set(6,7,8,9,10,11)), 0, List(4,4,4,4,4,4,4,4,4,4,4,4))

def plusStone(count:Int):Config = plusStone(count, turn)
def plusStone(count:Int, player:Int) = {
val s = stones.toArray
s(player) += count
Config(s.toList, houses, turn, list)
}

def updateList(l:List[Int], index:Int, value:Int):List[Int] =
l.slice(0,index):::List(0):::l.slice(index+1,l.size)

def updateList(index:Int, value:Int):Config =Mankala
Config(stones, houses, turn, updateList(list, index, value))

def setList(l:List[Int]) =
Config(stones, houses, turn, l)

def changeTurn =
Config(stones, houses, (turn+1)%2, list)

def getHouses = houses(turn)
def getHouses(nTurn:Int) = houses(nTurn%2)
}

object Board {
def draw(list:List[Int]):Unit = {
println
list.slice(6,list.size).reverse.foreach(x => print(x+"\t"))
println
list.slice(0, 6).foreach(x => print(x+"\t"))
println
}

def position(i:Int) = (i+12)%12

def roll(index:Int, stoneBuffer:Int, list:Array[Int]):(List[Int],Int) =
if(stoneBuffer==0)
(list.toList, position(index-1))
else{
list(position(index)) += 1; draw(list.toList)
roll(index+1, stoneBuffer-1, list)
}

}

object Rules {
def canMove(plantedSeeds:Int) = plantedSeeds!=0
def canKeep(plantedSeeds:Int) = plantedSeeds==4
def hasHouse(houses:Set[Int]) = !houses.isEmpty
def gameOver(list:List[Int]) = list.reduceLeft(_+_) < 4
}

object Ayo {
def main(args : Array[String]) : Unit = {
Game.start
}
}