Created
February 24, 2013 15:28
-
-
Save rladstaetter/5024214 to your computer and use it in GitHub Desktop.
Conway's Game of Life - in 3D
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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