プログラミングHaskellをScalaで解く(その5)

引き続き、『プログラミングHaskell』の例題と練習問題をScalaで書いてみる。

Graham Hutton - プログラミングHaskell

プログラミングHaskell
Graham Hutton



P.113 ライフゲーム

『プログラミングHaskell』では、ライフゲームは「第9章 対話プログラム」内の例として挙げられる。

val width:Int = 5
val height:Int = 5

type Pos = (Int, Int)
type Board = List[Pos]

val glider:Board = List((4,2), (2,3), (4,3), (3,4), (4,4))

盤のサイズと、セルの位置を示すためのPos型を定義した。

gliderは『プログラミングHaskell』で定義してある、グライダー配置そのものである。

def goto(p:Pos):Unit = print("\u001B[" + p._2 + ";" + p._1 + "H")  // P.110
                           // \u001B == \ESC

def writeat(p:Pos, xs:String):Unit = {                             // P.110
    goto(p)
    print(xs)
}

Haskellでは、"\ESC"と書けばESCを表わす制御文字となるのだが、Scalaではエスケープシーケンスを上手く処理してくれなかったので、Unicodeで記述した。

def showcells(b:Board):Unit = for(p <- b) writeat(p, "O")

def isAlive(b:Board, p:Pos):Boolean = b.exists(_ == p)

def isEmpty(b:Board, p:Pos):Boolean = !(isAlive(b, p))

def wrap(p:Pos):Pos = ( ((p._1-1)%width)+1, ((p._2-1)%height)+1)

def neighbs(p:Pos):List[Pos] = {
    val (x,y) = p
    List((x-1,y-1),(x,y-1),(x+1,y-1),(x-1,y),
         (x+1,y),(x-1,y+1),(x,y+1),(x+1,y+1)).map(wrap(_))
}

def liveneighbs(b:Board,p:Pos):Int = neighbs(p).filter(isAlive(b, _)).length

def survivors(b:Board):List[Pos] = for(p <- b; if List(2,3).exists(_ == liveneighbs(b,p))) yield p

def births(b:Board):List[Pos] =
    for (x <- (1 to width).toList; y <- (1 to height).toList; if isEmpty(b,(x,y)); if liveneighbs(b,(x,y)) == 3) yield (x,y)

def rmdups[A](l:List[A]):List[A] = {
    l match {
        case Nil => Nil
        case (x::xs) => x :: rmdups(xs.filter(_ != x))
    }
}

def nextgen(b:Board):Board = survivors(b) ::: births(b)

def cls:Unit = print("\u001B[2J")  // P.109

def sleep(n:Int):Unit = Thread.sleep(n)

def life(b:Board):Unit = {
    cls
    showcells(b)
    sleep(1000)
    life(nextgen(b))
}

wait関数として、java.lang.Thread.sleep() を利用した。但し、waitという名前にすると java.lang.Object.wait() として定義されている関数と名前が衝突するので、sleepとした。

scala> life(glider)

今回はここまで。