Scalaでライフゲームを書いてみた

今年『なっとく!関数型プログラミング』という本を読んで、関数型プログラミングについての理解が深まりました。

この本では、Scalaが使われています。関数型プログラミングを自分でも実践するには、Scalaで何かを作って『なっとく!関数型プログラミング』で学んだ概念を使ってみたいと考えました。

とはいえ、いきなり関数型プログラミングをフル活用したプログラムを書くのは難しいので、まず普通の簡単なプログラムを作ってみました。

Scalaでライフゲームを書いてみた

 

  • src/main/scala/lifegame/Matrix.scala

Booleanの2次元配列を Matrixというcase class を定義しています。

matrixを引数に取り、次の世代のmatrixを返すnextGenerationという関数が、このプログラムの肝の部分です。

package lifegame
import cats.effect.IO
import cats.implicits._

case class Matrix(matrix: List[List[Boolean]])

def createMatrix(x: Int, y: Int, gen: () => Boolean): Matrix = {
    val matrix = Range(0, x).toList.map(_ => 
        Range(0, y).toList.map(_ => gen()).toList
    )
    Matrix(matrix)
}

def getPoint(matrix: Matrix, x: Int, y: Int): Option[Boolean] = {
    for {
        xVal <- matrix.matrix.lift(x)
        yVal <- xVal.lift(y)
    } yield yVal
} 


def aroundLivingCount(matrix: Matrix, x: Int, y: Int): Int = {
    List(
        getPoint(matrix, x - 1, y - 1),
        getPoint(matrix, x - 1, y),
        getPoint(matrix, x - 1, y + 1),
        getPoint(matrix, x, y - 1),
        getPoint(matrix, x, y + 1),
        getPoint(matrix, x + 1, y - 1),
        getPoint(matrix, x + 1, y),
        getPoint(matrix, x + 1, y + 1),
    ).map(_.toList).flatten.filter(_ == true).length
}

def nextValue(current: Option[Boolean], aroundLivings: Int): Boolean = {
    current match {
        case Some(false) => if (aroundLivings == 3) true else false
        case Some(true) => if (aroundLivings <= 1) false
            else if (aroundLivings <= 3) true
            else false
        case None => throw new Exception("bug")
    }
}

def nextGeneration(matrix: Matrix): Matrix = {
    val newMatrix = Range(0, matrix.matrix.length).toList.map(x => 
        Range(0, matrix.matrix.head.length).toList.map(y => {
            val aroundLivings = aroundLivingCount(matrix, x, y)
            nextValue(getPoint(matrix, x, y), aroundLivings)
        }).toList
    )
    Matrix(newMatrix)    
}

 

  • src/main/scala/Main.scala

nextGeneration関数を再帰的に呼び出すメイン関数です。

import scala.annotation.tailrec
import lifegame._

@main def run(): Unit = {
  val matrix = createMatrix(40, 80, () => math.random < 0.5) @tailrec def rec(matrix: Matrix): Unit = { Thread.sleep(100); print("\u001b[H") matrix.matrix.map(line => {
      line.map(if (_) "#" else " ").mkString("")
    }).foreach(println)
    rec(nextGeneration(matrix))
  }
  rec(matrix)
}

動作確認

なかなか速い。

まとめ

まずは、Scalaで何か書いてみるという第一歩でした。関数型プログラミングの利点を活かしたプログラムを書けるようになりたいので、別のものも作りたいです。

初めてのScalaプログラムなので、改善点などがあれば教えてください。

 

追記

 

@xuwei_k さんに、改善点を教えていただきました。ありがとうございます。

fillなど便利な関数があり、更にすっきり記述できるようになりました。 GitHubリポジトリにpushしました。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です


reCaptcha の認証期間が終了しました。ページを再読み込みしてください。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください