Scala swing repaint

1k views Asked by At

So I am very, very new to scala. I am implementing Conways's Game of Life with a GUI. I can not figure out how to get my panel to update when the 2D array changes. Could someone please point me in the right direction? My entire code follows:

import swing._
import java.awt.{Color, Graphics2D, Dimension}


// initialize variables
// infinite plane variable
var infCurrent = scala.collection.mutable.ListBuffer[List[Int]]()
var infNext = scala.collection.mutable.ListBuffer[List[Int]]()
var infNext1 = scala.collection.mutable.ListBuffer[List[Int]]()
var infLifeTester = scala.collection.mutable.MutableList[List[Int]]()


// general variables
var lifeCount = 0
var yes = 1


// displayed variables
val xDim = 20
val yDim = 20
var currentState = Array.ofDim[Int](xDim, yDim)
var colorIndexList = scala.collection.mutable.ListBuffer[List[Int]]()
var colorData = Array.ofDim[Color](xDim, yDim)


// initial value
infCurrent = scala.collection.mutable.ListBuffer(List(6,6), List(6,7), List(6,8), List(5,8), List(4,7))


// this function tests if a CURRENTLY ALIVE CELL on the INF PLANE STAYS ALIVE
def infStayAlive(current: scala.collection.mutable.ListBuffer[List[Int]]): scala.collection.mutable.ListBuffer[List[Int]] = {
  for (i <- current) {
    var a = i(0)
    var b = i(1)
    if (current.contains(List(a - 1, b - 1))) lifeCount += 1
    if (current.contains(List(a - 1, b))) lifeCount += 1
    if (current.contains(List(a - 1, b + 1))) lifeCount += 1
    if (current.contains(List(a, b - 1))) lifeCount += 1
    if (current.contains(List(a, b + 1))) lifeCount += 1
    if (current.contains(List(a + 1, b - 1))) lifeCount += 1
    if (current.contains(List(a + 1, b))) lifeCount += 1
    if (current.contains(List(a + 1, b + 1))) lifeCount += 1
    if (lifeCount == 2) infNext = infNext :+ i
    lifeCount = 0
  }
  return infNext
}


// this function gets ALL NEIGHBORS for what's on the INF PLANE
def infGetNeighbors(current: scala.collection.mutable.ListBuffer[List[Int]]): scala.collection.mutable.MutableList[List[Int]] = {
  for (i <- current) {
    var a = i(0)
    var b = i(1)
    infLifeTester = infLifeTester :+ List(a - 1, b - 1)
    infLifeTester = infLifeTester :+ List(a - 1, b)
    infLifeTester = infLifeTester :+ List(a - 1, b + 1)
    infLifeTester = infLifeTester :+ List(a, b - 1)
    infLifeTester = infLifeTester :+ List(a, b + 1)
    infLifeTester = infLifeTester :+ List(a + 1, b - 1)
    infLifeTester = infLifeTester :+ List(a + 1,b)
    infLifeTester = infLifeTester :+ List(a + 1, b + 1)
  }
  infLifeTester = infLifeTester.distinct
  return infLifeTester
}


// this function determines whether cells on the INF PLANE DIE or COME ALIVE
def infComeAlive(infLifeTester: scala.collection.mutable.MutableList[List[Int]]): scala.collection.mutable.ListBuffer[List[Int]] = {
  for(i <- infLifeTester) {
    var a = i(0)
    var b = i(1)
    if (infCurrent.contains(List(a - 1, b - 1))) lifeCount += 1
    if (infCurrent.contains(List(a - 1, b))) lifeCount += 1
    if (infCurrent.contains(List(a - 1, b + 1))) lifeCount += 1
    if (infCurrent.contains(List(a, b - 1))) lifeCount += 1
    if (infCurrent.contains(List(a, b + 1))) lifeCount += 1
    if (infCurrent.contains(List(a + 1, b - 1))) lifeCount += 1
    if (infCurrent.contains(List(a + 1, b))) lifeCount += 1
    if (infCurrent.contains(List(a + 1, b + 1))) lifeCount += 1
    if (lifeCount == 3) infNext1 = infNext1 :+ i
    lifeCount = 0
  }
  infNext1 = infNext1.distinct
  return infNext1
}


def printGrid(infCurrent: scala.collection.mutable.ListBuffer[List[Int]]): Array[Array[Int]] = {
  for(i <- infCurrent) {
    if(i(0) >= 0) {
      if(i(0) < xDim) {
        if(i(1) >= 0) {
          if(i(1) < yDim) {
            currentState(i(0))(i(1)) = 1
            colorIndexList = colorIndexList :+ i
          }
        }
      }
    }
  }
  return currentState
}


def colorGrid(colorIndexList: scala.collection.mutable.ListBuffer[List[Int]]): Array[Array[Color]] = {
  for (i <- colorIndexList) {
    colorData(i(0))(i(1)) = Color.WHITE
  }
  return colorData
}


// define panel class
class DataPanel(data: Array[Array[Color]]) extends Panel {

  override def paintComponent(g: Graphics2D) {
    val dx = g.getClipBounds.width.toFloat  / data.length
    val dy = g.getClipBounds.height.toFloat / data.map(_.length).max
    for {
      x <- 0 until data.length
      y <- 0 until data(x).length
      x1 = (x * dx).toInt
      y1 = (y * dy).toInt
      x2 = ((x + 1) * dx).toInt
      y2 = ((y + 1) * dy).toInt
    } {
      data(x)(y) match {
        case c: Color => g.setColor(c)
        case _ => g.setColor(Color.BLACK)
        repaint
      }
      g.fillRect(x1, y1, x2 - x1, y2 - y1)
      println("hi")
    }
    println("hey")
  }
}


// make swing app
object Draw extends SimpleSwingApplication {

  val data = colorData

  def top = new MainFrame {
    background = Color.RED
    title = "Shombo's Game of Life"
    val button = new Button {
      text = "Stahhhp!!"
    }
    val life = new DataPanel(data) {
      preferredSize = new Dimension(500, 500)
    }
    contents = new BoxPanel(Orientation.Vertical) {
      contents += life
      contents += button
    }
  }
}



Draw.top.visible = true
while(yes == 1) {

  infLifeTester = infGetNeighbors(infCurrent)
  infNext = infStayAlive(infCurrent)
  infNext1 = infComeAlive(infLifeTester)
  infNext = scala.collection.mutable.ListBuffer.concat(infNext, infNext1)
  infCurrent = infNext

  infNext = scala.collection.mutable.ListBuffer[List[Int]]()
  infNext1 = scala.collection.mutable.ListBuffer[List[Int]]()
  infLifeTester = scala.collection.mutable.MutableList[List[Int]]()

  currentState = printGrid(infCurrent)

  println(currentState.deep.mkString("\n"))
  //println("\n")

  colorData = colorGrid(colorIndexList)
  Draw.top.contents.repaint()
  currentState = Array.ofDim[Int](xDim, yDim)
  colorIndexList = scala.collection.mutable.ListBuffer[List[Int]]()
  colorData = Array.ofDim[Color](xDim, yDim)
  yes = 1

}
1

There are 1 answers

0
0__ On

You have an unclear situation of mixing vars an mutable elements. You should either combine vars with immutable state that is replaced, or use mutable state (e.g. the arrays) but update it in place and do not create new arrays.

In your application, you create an instance of DataPanel which takes as argument the array stored in colorData. But then in your iteration, you are not updating the contents of colorData, but you actually creating a completely new array and replace the value of variable colorData:

colorData = Array.ofDim[Color](xDim, yDim)

Therefore after the first iteration, your global state differs from what is stored in DataPanel.

If you re-use the array in colorData, you need to change colorGrid to assign black color to the inactive cells. You are right now using a null color as black, and a Color.WHITE as white. It would make more sense to define val colorData = Array.ofDim[Boolean](xDim, yDim). In colorGrid you would first set all elements to false and then iterate through colorIndexList.

You should define your iteration as a function that is called in each step. Right now you have an infinite loop while(yes == 1) because yes is never changed. Executing this will freeze your computer.

Also note that calling Draw.top is not good this way, because you defined it as a function; this would create a new window each time you call. Always use lazy val top = to prevent this problem.


Your code is still a big mess, but at least you will have something to move on from. This is a minimum working example, containing only the changes from above:

import scala.collection.mutable.ListBuffer

val colorData = Array.ofDim[Boolean](xDim, yDim)

def colorGrid(colorIndexList: ListBuffer[List[Int]]): Unit = {
  colorData.foreach { row => java.util.Arrays.fill(row, false) } // all 'black'
  for (i <- colorIndexList) {
    colorData(i(0))(i(1)) = true  // some 'white'
  }
}

class DataPanel(data: Array[Array[Boolean]]) extends Panel {

  override def paintComponent(g: Graphics2D): Unit = {
    val dx = g.getClipBounds.width.toFloat  / data.length
    val dy = g.getClipBounds.height.toFloat / data.map(_.length).max
    for {
      x <- 0 until data.length
      y <- 0 until data(x).length
      x1 = (x * dx).toInt
      y1 = (y * dy).toInt
      x2 = ((x + 1) * dx).toInt
      y2 = ((y + 1) * dy).toInt
    } {
      g.setColor(if (data(x)(y)) Color.black else Color.white) // !
      g.fillRect(x1, y1, x2 - x1, y2 - y1)
    }
  }
}

 

def step(): Unit = {
  infLifeTester = infGetNeighbors(infCurrent)
  infNext = infStayAlive(infCurrent)
  infNext1 = infComeAlive(infLifeTester)
  infNext = scala.collection.mutable.ListBuffer.concat(infNext, infNext1)
  infCurrent = infNext

  infNext = scala.collection.mutable.ListBuffer[List[Int]]()
  infNext1 = scala.collection.mutable.ListBuffer[List[Int]]()
  infLifeTester = scala.collection.mutable.MutableList[List[Int]]()

  currentState = printGrid(infCurrent)

  println(currentState.deep.mkString("\n"))
  //println("\n")

  colorGrid(colorIndexList)
  currentState = Array.ofDim[Int](xDim, yDim)
  colorIndexList = scala.collection.mutable.ListBuffer[List[Int]]()
}

 

object Draw extends SimpleSwingApplication {
    lazy val life = new DataPanel(colorData) {
      preferredSize = new Dimension(500, 500)
    }

    lazy val top = new MainFrame {
    background = Color.RED
    title = "Shombo's Game of Life"
    val button = Button("Step") {
      step()             // updates colorData
      life.repaint()     // refreshes view
    }
    contents = new BoxPanel(Orientation.Vertical) {
      contents += life
      contents += button
    }
  }
}

Run: Draw.main(Array())