Skip to content

Instantly share code, notes, and snippets.

@rladstaetter
Created February 24, 2013 15:28
Show Gist options
  • Select an option

  • Save rladstaetter/5024214 to your computer and use it in GitHub Desktop.

Select an option

Save rladstaetter/5024214 to your computer and use it in GitHub Desktop.
Conway's Game of Life - in 3D
package net.ladstatt.apps
import scala.collection.JavaConversions.asScalaBuffer
import scala.collection.JavaConversions.seqAsJavaList
import scala.util.Random
import javafx.animation.Animation
import javafx.animation.KeyFrame
import javafx.animation.Timeline
import javafx.application.Application
import javafx.collections.ObservableList
import javafx.event.ActionEvent
import javafx.event.Event
import javafx.event.EventHandler
import javafx.scene.Group
import javafx.scene.Node
import javafx.scene.Scene
import javafx.scene.input.MouseEvent
import javafx.scene.paint.Color
import javafx.scene.paint.CycleMethod
import javafx.scene.paint.LinearGradient
import javafx.scene.paint.Stop
import javafx.scene.shape.Rectangle
import javafx.stage.Stage
import javafx.util.Duration
import javafx.scene.PerspectiveCamera
import javafx.scene.paint.PhongMaterial
import javafx.scene.PointLight
import javafx.scene.shape.Box
import javafx.scene.transform.Rotate
import javafx.geometry.Point3D
/**
* John Conway's Game of Life - with JavaFX and Scala using JDK8 3D API
*/
object GameOfLife {
def main(args: Array[String]): Unit = {
Application.launch(classOf[GameOfLife], args: _*)
}
}
trait JfxUtils {
def mkEventHandler[E <: Event](f: E => Unit) = new EventHandler[E] { def handle(e: E) = f(e) }
}
class GameOfLife extends javafx.application.Application with JfxUtils {
val canvasWidth = 800
val canvasHeight = 800
val canvasDepth = 800
val cellCount = 20
val lifeSpeed = 4
val gap = 4
// --------------------------------------------------------------------------
val (width, height, depth) = ((canvasWidth / cellCount) - gap, (canvasHeight / cellCount) - gap, (canvasDepth / cellCount) - gap)
var anchorX: Double = _
var anchorY: Double = _
var anchorAngle: Double = _
val pointLight = {
val l = new PointLight(Color.BISQUE)
l.setTranslateZ(-200)
l
}
val spectatorLight = {
val l = new PointLight(Color.AZURE)
l.setTranslateZ(-1500)
l
}
case class Cell(x: Int, y: Int, alive: Boolean = false) extends Box(width, height, depth) {
setUserData(alive)
setTranslateX(x * (width + gap))
setTranslateY(y * (height + gap))
paint()
setOnMousePressed(mkEventHandler((e: MouseEvent) => { killOrResurrect() }))
def paint() =
if (isAlive) {
setTranslateZ(depth * 2)
setMaterial(aliveMaterial)
} else {
setMaterial(deadMaterial)
setTranslateZ(0)
}
def isAlive: Boolean = getUserData().asInstanceOf[Boolean]
def killOrResurrect() = {
setUserData(!isAlive)
paint()
}
}
override def start(stage: Stage): Unit = {
stage.setTitle("Conway's Game of Life")
val cells = new Group((for {
x <- 0 to cellCount
y <- 0 to cellCount
} yield Cell(x, y, Random.nextBoolean)))
val drawingArea = new Group(cells, pointLight)
drawingArea.setTranslateZ(1500)
drawingArea.setRotationAxis(new Point3D(1, 1, 1))
val growTimeline = new Timeline
growTimeline.setRate(lifeSpeed)
growTimeline.setCycleCount(Animation.INDEFINITE)
growTimeline.getKeyFrames().add(
new KeyFrame(Duration.seconds(1),
new EventHandler[ActionEvent]() {
def handle(event: ActionEvent) {
val nextGen = nextGeneration((convert2CellList(cells.getChildren())))
cells.getChildren().clear()
cells.getChildren.addAll(nextGen)
}
}))
growTimeline.play()
val all = new Group(drawingArea, spectatorLight)
val scene = new Scene(all, canvasWidth, canvasHeight, true)
scene.setOnMouseMoved(mkEventHandler((e: MouseEvent) => {
pointLight.setTranslateX(e.getX)
pointLight.setTranslateY(e.getY)
}))
scene.setFill(Color.BLACK)
scene.setOnMousePressed(mkEventHandler((event: MouseEvent) => {
anchorX = event.getSceneX()
anchorAngle = drawingArea.getRotate()
}))
scene.setOnMouseDragged(mkEventHandler((event: MouseEvent) => {
drawingArea.setRotate(anchorAngle + anchorX - event.getSceneX())
}))
val perspectiveCamera = new PerspectiveCamera(false)
scene.setCamera(perspectiveCamera)
stage.setScene(scene)
stage.show()
}
def nextGeneration(allCells: List[Cell]): List[Cell] = {
for (c <- allCells) yield {
if (c.isAlive) {
getAliveNeighbors(c, allCells).size match {
case 1 => c.copy(alive = false)
case 2 => c
case 3 => c
case _ => c.copy(alive = false)
}
} else {
if (getAliveNeighbors(c, allCells).size == 3)
c.copy(alive = true)
else c
}
}
}
def getAliveNeighbors(c: Cell, cells: List[Cell]) = getNeighbors(c, cells).filter(_.isAlive)
def convert2CellList(nodes: ObservableList[Node]): List[Cell] = {
(for (
n <- nodes if (n match {
case c: Cell => true
case _ => false
})
) yield n.asInstanceOf[Cell]).toList
}
def getNeighbors(cell: Cell, cells: List[Cell]) =
cells.filter(c => c != cell && (scala.math.abs(cell.x - c.x) <= 1 && scala.math.abs(cell.y - c.y) <= 1))
def mkMaterial(color: Color): PhongMaterial = {
val m = new PhongMaterial()
m.setDiffuseColor(color)
m.setSpecularColor(Color.WHITESMOKE)
m
}
val aliveMaterial = mkMaterial(Color.RED)
val deadMaterial = mkMaterial(Color.WHITE)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment