JavaFx Client
Now let´s implement our game on one more platform. This time JavaFx. This should be easy as Android and JavaFx use Java, and we already have implemented the Java specific stuff.
On JavaFx we will use a Canvas in order to draw our game. This API is similar to the one we use on Android. So we first take the Android drawing logic and move it to the common project as well.
As there is still some platform specific code necessary we could use expect - actual here again to provide it. We want to try a different approach here. We just provide some plain old callbacks which are then implemented on the Android and on the JavaFx side.
class GolCanvas {
private var offsetX = 0.0f
private var offsetY = 0.0f
fun drawBoard(
board: Board,
drawRect: (left: Float, top: Float, size: Float) -> Unit,
clear: () -> Unit = {}
) {
clear()
for (rowIdx in 0 until board.rows) {
for (columnIdx in 0 until board.columns) {
val cell = board.cellAt(column = columnIdx, row = rowIdx)
if (cell.alive) {
val cellSize = board.cellSize
val cellPadding = board.cellPadding
val left = (columnIdx * cellSize) + cellPadding + offsetX
val top = (rowIdx * cellSize) + cellPadding + offsetY
drawRect(left, top, cellSize - cellPadding)
}
}
}
}
fun zoom(board: Board, zoomFactor: Float, xPosition: Float, yPosition: Float) {
val oldCellSize = board.cellSize
board.scale(zoomFactor)
val realScaleFactor = board.cellSize / oldCellSize
val distX = xPosition - offsetX
val distY = yPosition - offsetY
val corrX = distX - distX * realScaleFactor
val corrY = distY - distY * realScaleFactor
offsetX += corrX
offsetY += corrY
}
}
Then we use this code in our Android project.
class BoardView : View {
lateinit var board: Board
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
golCanvas.drawBoard(
board = board,
drawRect = { left, right, size ->
val deviceLeft = left * resources.displayMetrics.density
val deviceRight = right * resources.displayMetrics.density
val deviceSize = size * resources.displayMetrics.density
rect.set(deviceLeft, deviceRight, deviceLeft + deviceSize, deviceRight + deviceSize)
canvas.drawRect(rect, paint)
}
)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
return zoomDetector.onTouchEvent(event)
}
private val zoomListener = object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
golCanvas.zoom(board, detector.scaleFactor, detector.focusX, detector.focusY)
invalidate()
return true
}
}
private val zoomDetector: ScaleGestureDetector = ScaleGestureDetector(context, zoomListener)
}
For the JavaFx client we create a new Gradle project and add our common project as dependency. The implementation of the JavaFx game is straight forward. It consists mainly of the general startup code of a JavaFx implementation, some UI for the canvas, the pause/resume button, and the callbacks needed for drawing and zooming.
class JavaFXExample : Application() {
override fun start(primaryStage: Stage) {
game.afterNextGenerationCalculated = { drawBoard() }
canvas.onZoom = EventHandler { zoomEvent ->
golCanvas.zoom(
board = board,
zoomFactor = zoomEvent.totalZoomFactor.toFloat(),
xPosition = zoomEvent.x.toFloat(),
yPosition = zoomEvent.y.toFloat()
)
drawBoard()
}
val layout = VBox().apply {
val toolbar = HBox().apply {
…. // Some layout code here!!
}
primaryStage.run {
scene = Scene(layout)
show()
}
primaryStage.onCloseRequest = EventHandler {
game.pause()
Platform.exit()
}
game.resume()
}
private fun drawBoard() {
golCanvas.drawBoard(
board = board,
clear = { gc.clearRect(0.0, 0.0, canvasWidth, canvasHeight) },
drawRect = { left, top, size ->
gc.fill = Color.GRAY
gc.fillRect(left.toDouble(), top.toDouble(), size.toDouble(), size.toDouble())
}
)
}
}
Use ./gradlew run
to start the application.