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.

1 comment:

Ricky Clarkson said...

val a = if (b) c else d works because if is an expression. You can have a block anywhere, there's nothing special about if in that:

val x = { val y = Math.random; y * y }