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
}
}

No comments: