Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Tree: 6ad339650f
Fetching contributors…

Cannot retrieve contributors at this time

326 lines (265 sloc) 9.564 kB
package net.fishbulb.app
import java.util.Random
import org.lwjgl.input.Keyboard
import org.lwjgl.opengl.Display
import org.lwjgl.opengl.DisplayMode
import org.lwjgl.opengl.GL11
import de.matthiasmann.twl.renderer.lwjgl.LWJGLRenderer
import de.matthiasmann.twl.theme.ThemeManager
import de.matthiasmann.twlscala.Action
import de.matthiasmann.twlscala.Button
import de.matthiasmann.twlscala.ButtonClicked
import de.matthiasmann.twlscala.DialogLayout
import de.matthiasmann.twlscala.KeyPressed
import de.matthiasmann.twlscala.Widget
import de.matthiasmann.twl.Alignment
import de.matthiasmann.twl.FPSCounter
import de.matthiasmann.twl.GUI
import net.fishbulb.tile.Layer
import net.fishbulb.tile.ArrayLayer
import net.fishbulb.tile.MapLayer
import net.fishbulb.tile.Tile
import net.fishbulb.tile.TileDisplay
import net.fishbulb.tile.TileLayer
import net.fishbulb.tile.TileTexture
import net.fishbulb.tile.Tileset
object Life extends Widget with App { root =>
theme = "life"
val width = 48
val height = 36
val tileWidth = 16
val tileHeight = 16
val displayMode = new DisplayMode(1000, 600)
val tiletex = new TileTexture("/textures/dftiles.png", "PNG", 16, 16, tileWidth, tileHeight, false)
val tileset = new Tileset(tiletex, Map(
"cell" -> Tile(4, 0), "blank" -> Tile(0, 0), "o_cursor" -> Tile(15, 4)
))
var cursorX = 25
var cursorY = 18
val cellLayer = new TileLayer(new ArrayLayer[Tile](width, height, null), tileset)
val uiLayer = new TileLayer(new MapLayer[Tile](width, height, null), tileset)
val console = new TileDisplay(Seq(cellLayer, uiLayer))
val generations = Seq.fill(2) { new ArrayLayer[Boolean](width, height, false) }
var currentLayer = 0
var targetFPS = 30
var quitRequested = false
var paused = false
lazy val gui = {
val renderer = new LWJGLRenderer
val g = new GUI(this, renderer)
val theme = ThemeManager.createThemeManager(getClass.getResource("/life.xml"), renderer)
g.applyTheme(theme)
g
}
acceptKeyboardFocus = true;
setDepthFocusTraversal(false)
actions = ({
case KeyPressed("quit") => doQuit
case KeyPressed("clear") => doClear
case KeyPressed("repopulate") => doRepopulate
case KeyPressed("togglePause") => doTogglePause
case KeyPressed("increaseFPS") => doIncreaseFPS
case KeyPressed("decreaseFPS") => doDecreaseFPS
case KeyPressed("step") => doStep
case KeyPressed("left") => doCursorLeft
case KeyPressed("right") => doCursorRight
case KeyPressed("up") => doCursorUp
case KeyPressed("down") => doCursorDown
case KeyPressed("reconfigureUI") => doReconfigureUI
}: PartialFunction[Action, Unit]) andThen {
// above functions return true unconditionally
_ => true
} orElse {
// use the return value from these actions
case KeyPressed("toggleCell") => doToggleCell
}
object UI {
abstract sealed class Configuration
case object Horizontal extends Configuration {}
case object Vertical extends Configuration {}
val configs: Seq[Configuration] = Seq(Horizontal, Vertical)
var currentConfig: Configuration = Horizontal
def nextConfig: Configuration = {
configs((configs.indexOf(currentConfig) + 1) % configs.length)
}
def configure(config: Configuration) {
buttons.configure(config)
display.configure(config)
currentConfig = config
}
val buttons = new DialogLayout {
acceptKeyboardFocus = false
theme = "life";
val btnPause = new Button("Pause") with Unfocusable
val btnStep = new Button("Step") with Unfocusable
val btnClear = new Button("Clear") with Unfocusable
val btnRandomize = new Button("Randomize") with Unfocusable
val fpsCounter = new FPSCounter
listenTo(btnPause)
listenTo(btnStep)
listenTo(btnClear)
listenTo(btnRandomize)
reactions += {
case ButtonClicked(`btnPause`) => doTogglePause
case ButtonClicked(`btnStep`) => doStep
case ButtonClicked(`btnClear`) => doClear
case ButtonClicked(`btnRandomize`) => doRepopulate
}
def configure(config: Configuration) {
config match {
// Horizontal layout lays out buttons in a vertical direction
// (The "horizontal" refers to the top level)
case Horizontal =>
horizontalGroup = (btnPause || btnStep || btnClear || btnRandomize || fpsCounter)
verticalGroup = (btnPause -- btnStep -- btnClear -- btnRandomize -- Filler -- fpsCounter)
case Vertical =>
horizontalGroup = (btnPause -- btnStep -- btnClear -- btnRandomize -- Filler -- fpsCounter)
verticalGroup = (btnPause || btnStep || btnClear || btnRandomize || fpsCounter)
}
}
}
val display = new DialogLayout {
theme = "life";
def configure(config: Configuration) {
config match {
case Horizontal =>
horizontalGroup = (buttons -- Filler -- console -- Filler)
verticalGroup = (buttons || console)
case Vertical =>
horizontalGroup = (buttons || console)
verticalGroup = (buttons -- console)
}
setWidgetAlignment(console, Alignment.CENTER)
}
}
def layout() {
buttons.adjustSize()
display.setSize(Display.getWidth, Display.getHeight)
}
}
add(UI.display)
UI.configure(UI.Vertical)
run()
def placeCursor(x: Int, y: Int, force: Boolean = false) {
if (!(uiLayer.enabled || force)) return
uiLayer.clear(cursorX, cursorY)
uiLayer(x, y) = tileset("o_cursor")
cursorX = x
cursorY = y
}
def run() {
Display.setDisplayMode(displayMode)
Display.create()
Display.setTitle("Life")
onResize(Display.getWidth, Display.getHeight)
Keyboard.enableRepeatEvents(true)
populateCells(generations(0))
GL11.glEnable(GL11.GL_TEXTURE_2D)
configureConsole()
mainLoop()
Display.destroy()
sys.exit(0)
}
def mainLoop() {
if (Display.isCloseRequested)
doQuit()
if (!paused) step()
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
gui.update()
Display.update()
Display.sync(if (paused) 10 else targetFPS)
if (!quitRequested) mainLoop
}
override def layout() = UI.layout()
def configureConsole() {
cellLayer.color = (0.5f, 0.5f, 1, 1.0f)
uiLayer.color = (1, 1, 0.5f, 0.25f)
console.layers = console.layers :+ uiLayer
placeCursor(cursorX, cursorY, true)
uiLayer.enabled = false
}
def populateCells(layer: Layer[Boolean]) {
val rand = new Random
layer foreach { layer(_, _) = rand.nextBoolean }
}
private def mod(a: Int, b: Int): Int = (a % b + b) % b
def getCell(layer: Layer[Boolean], x: Int, y: Int): Boolean = {
layer(mod(x, width), mod(y, height))
}
def neighborhood(layer: Layer[Boolean], x: Int, y: Int): Int = {
var count = 0
for (yy <- y - 1 to y + 1; xx <- x - 1 to x + 1)
if (getCell(layer, xx, yy)) count += 1
// don't count the current cell
if (getCell(layer, x, y)) count -= 1
count
}
def rule(cell: Boolean, neighbors: Int) = neighbors == 3 || cell && (neighbors == 2)
def generation(src: Layer[Boolean], dest: Layer[Boolean]): Unit = {
src foreach { (x, y) =>
dest(x, y) = rule(src(x, y), neighborhood(src, x, y))
}
}
def updateConsole(layer: Layer[Boolean]) = {
val cellTile = tileset("cell")
val newCell = cellTile.withColor(0, 1, 1, 1)
val blankTile = tileset("blank")
layer foreach { (x, y) =>
{
val isnew = (generations(0)(x, y) != generations(1)(x, y))
cellLayer(x, y) = if (layer(x, y)) (if (isnew) newCell else cellTile) else blankTile
}
}
}
def onResize(width: Int, height: Int) {
val w = width
val h = if (height == 0) 1 else height
GL11.glViewport(0, 0, w, h)
GL11.glMatrixMode(GL11.GL_PROJECTION)
GL11.glLoadIdentity()
GL11.glOrtho(0, w, h, 0, 1, -1) // Top Left origin standard to 2d stuff like TWL
GL11.glMatrixMode(GL11.GL_MODELVIEW)
GL11.glLoadIdentity()
layout()
}
def step() {
val src = generations(currentLayer)
val dest = generations(1 - currentLayer)
generation(src, dest)
updateConsole(dest)
currentLayer = 1 - currentLayer
}
////////////////////////////////////////
// Actions
def doQuit() = quitRequested = true
def doRepopulate() = populateCells(generations(currentLayer))
def doClear() {
val layer = generations(currentLayer);
layer foreach { layer(_, _) = false }
updateConsole(layer)
}
def doTogglePause() {
paused = !paused
uiLayer.enabled = paused
}
def doDecreaseFPS() = targetFPS = (targetFPS - 5) max 5
def doIncreaseFPS() = targetFPS = (targetFPS + 5) min 300
def doCursorUp() = placeCursor(cursorX, (cursorY - 1) max 0)
def doCursorDown() = placeCursor(cursorX, (cursorY + 1) min (height - 1))
def doCursorLeft() = placeCursor((cursorX - 1) max 0, cursorY)
def doCursorRight() = placeCursor((cursorX + 1) min (width - 1), cursorY)
def doToggleCell(): Boolean = {
if (!uiLayer.enabled) return false
val layer = generations(currentLayer)
layer(cursorX, cursorY) = !layer(cursorX, cursorY)
updateConsole(layer)
true
}
def doStep() = step()
def doReconfigureUI() = {
UI.configure(UI.nextConfig)
}
}
trait Unfocusable { self: de.matthiasmann.twl.Widget =>
override val canAcceptKeyboardFocus = false
}
Jump to Line
Something went wrong with that request. Please try again.