読者です 読者をやめる 読者になる 読者になる

Scalaでライフゲーム

Scalaライフゲームを実装した。

import scala.collection.mutable.ArrayBuffer
import scala.swing._
import scala.swing.event._
import java.awt.{Color,Graphics2D,BasicStroke}
import java.awt.geom._
import java.awt.image.BufferedImage
import scala.collection.mutable._
import scala.math._

class Environment(val sqnum:Int){
  val cells=Array.ofDim[Int](sqnum,sqnum)
  val cells0=Array.ofDim[Int](sqnum,sqnum)
  val queue = new Queue[(Int,Int)]
  def apply(x:Int,y:Int)=cells(x)(y)
  def next={
    for (i<- 0 until sqnum){
      for (j<- 0 until sqnum){
        cells0(i)(j)=cells(i)(j)
      }
    }
    for (i <- 0 until sqnum){
      for (j <- 0 until sqnum){
        val surroundLives=getSurrounding(i,j).map({case(x,y)=>cells0(x)(y)}).sum
        if (cells0(i)(j)==1){
          if (surroundLive<=1)cells(i)(j)=0
          else if (surroundLive<=3)cells(i)(j)=1
          else cells(i)(j)=0
        }else{
          if(surroundLives==3){
            cells(i)(j)=1
          }
          else cells(i)(j)=0
        }
        if (cells0(i)(j)!=cells(i)(j)){
          queue.enqueue((i,j))
        }
      }
    }
    def getSurrounding(x:Int,y:Int):List[(Int,Int)]={
      val sur=Array.fill[Boolean](3,3){true}
      sur(1)(1)=false
      if(y==sqnum-1){
        for (i<- 0 until 3) sur(i)(2)=false
      }
      if(y==0){
        for (i<- 0 until 3) sur(i)(0)=false
      }
      if(x==sqnum-1){
        for (i<- 0 until 3) sur(2)(i)=false
      }
      if(x==0){
        for (i<- 0 until 3) sur(0)(i)=false
      }
      (for (i <- 0 until 3) yield (for (j<- 0 until 3 if sur(i)(j)) yield (x+i-1,y+j-1)).toList).reduceLeft(_:::_)  
    }
  }
  def randoms={
    for (i<-0 until sqnum){
      for (j<-0 until sqnum){
        if(floor(random*3).toInt%4==0){
          cells(i)(j)=1
        }
      }
    }
  }
  def bornOfNewCell(x:Int,y:Int)=cells(x)(y)=1
  def terminationOfAll=for (i<- 0 until sqnum) {for (j<- 0 until sqnum) {cells(i)(j)=0;queue.enqueue((i,j))}}
}

class Canvas (val env:Environment) extends Component{
  val imgBuffer = new BufferedImage(320,320,BufferedImage.TYPE_INT_RGB)
  preferredSize = new Dimension(320,320)
  listenTo(mouse.clicks)
  reactions+={
    case MouseClicked(_,p,_,_,_)=>mouseClick(p.x,p.y)
  }
  private def correspondCanvasToEnv:(Int,Int,Int,Int)={
    val d = size
    val squareSide=d.height min d.width
    val x0 = (d.width-squareSide)/2
    val y0 = (d.height-squareSide)/2
    (squareSide,x0,y0,squareSide/env.sqnum)
  }
  private def mouseClick(cx:Int,cy:Int){
    val (_,cx0,cy0,wid)=correspondCanvasToEnv
    val (ex,ey)=((cx-cx0)/wid,(cy-cy0)/wid)
    env.bornOfNewCell(ex,ey)
  }
  override def paintComponent(g:Graphics2D){
    val bg=imgBuffer.getGraphics
    val (_,cx0,cy0,wid)=correspondCanvasToEnv
    g.setRenderingHint(
        java.awt.RenderingHints.KEY_ANTIALIASING,
        java.awt.RenderingHints.VALUE_ANTIALIAS_ON)
    while(!env.queue.isEmpty){
      val (x,y)=env.queue.dequeue()
      if (env(x,y)==1)bg.setColor(Color.GREEN)
      else bg.setColor(Color.BLACK)
      bg.fillRect(cx0+x*wid,cy0+y*wid,wid,wid)
    }
    g.drawImage(imgBuffer,0,0,null)
  }
}
class Lifegame (val env:Environment) extends MainFrame{
  title="LIFEGAME"
  resizable=false
  val canvas=new Canvas(env)
  contents=new BoxPanel(Orientation.Vertical){
    contents+=canvas
    contents+=new BoxPanel(Orientation.Horizontal){
      contents+=Button("All Clear"){env.terminationOfAll}
      contents+=Button("Random"){env.randoms}
      contents+=Button("Next"){env.next;canvas.repaint}
    }
  }
  Timer(300){
    env.next
    canvas.repaint
  }
}
object Timer{
  def apply(interval:Int,repeats:Boolean=true)(op: => Unit){
    val timeOut=new javax.swing.AbstractAction(){
      def actionPerformed(e:java.awt.event.ActionEvent)=op
    }
    val t=new javax.swing.Timer(interval,timeOut)
    t.setRepeats(repeats)
    t.start()
  }
}
object Main{
 def main(arg:Array[String]){
   val env=new Environment(40)
   env.terminationOfAll
   val ui=new Lifegame(env)
   ui.visible=true
 }
}


[追記]
後にScalaでのライフゲームの実行例を調べてみたらこのような実装があった。
qiita.com


リストの境界部分へとアクセスするときにIndexOutOfBoundsExceptionを使って処理していてとても参考になった。
例外を上手に活用したことがないので今後は意識していきたい。