Scalaでライフゲームを書いてみた
2024/12/31
2025/01/01
今年『なっとく!関数型プログラミング』という本を読んで、関数型プログラミングについての理解が深まりました。
この本では、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 さんに、改善点を教えていただきました。ありがとうございます。
“初めてのScalaプログラムなので、改善点などがあれば教えてください”https://t.co/2mhXr6sPZ8 とあったので、雑に目につく箇所多少リファクタしたものを作りました (解説文章書くの面倒でコミットメッセージに全部書いた) (本気で書き換えるならもっと色々あるけど) pic.twitter.com/IOJqiuHUYD
— Kenji Yoshida (@xuwei_k) January 1, 2025
fillなど便利な関数があり、更にすっきり記述できるようになりました。 GitHubリポジトリにpushしました。