JavaFx Client

Source Code

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.

JavaFx