Game Loop

For those of you who haven’t read the source material, or have never made a game before, all turn based games tend to follow the same loop:

  • While game isn’t over
    • Display information
    • Accept input
    • Respond to user input

Here, we’ll be setting up the basic screen interface that all of our future screens will implement.

Interfaces state that anything that implements them will have certain attributes and methods available to them. How they work will likely vary by the class that is implementing the interface, but you can always count on the method being there, taking a set of parameters, and returning the same type of value.

Screens

Here’s our basic screen interface:

1
2
3
4
5
6
7
8
9
10
package roglin.screens

import asciiPanel.AsciiPanel
import java.awt.event.KeyEvent

interface Screen {
    fun displayOutput(terminal: AsciiPanel)

    fun respondToUserInput(key: KeyEvent): Screen
}

This is the first function we’ve done in Kotlin that has a return type. In traditional Java, it would look like:

public Screen respondToUserInput(KeyEvent key)

The type of the return (or the type of an attribute) is declared after the method/object, separated by a :.

Additionally, there was a design decision in Kotlin to make all methods public by default, and all classes final by default. This will come into play later when we have private methods for modifying data and classes that serve as parents to other classes.

Now for the fun part, implementing each of the screens that we’ll need. The game needs a start screen (to start everything off), a play screen (for when you’re playing!), a win screen (probably the least used screen), and the lose screen (because you die a lot in roguelikes).

Start Screen

Our start screen is the entry point to the game and should look like :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package roglin.screens

import asciiPanel.AsciiPanel
import java.awt.event.KeyEvent

class StartScreen() : Screen {
    override fun displayOutput(terminal: AsciiPanel) {
        terminal.write("rl tutorial", 1, 1)
        terminal.writeCenter("-- press [enter] to start --",22)
    }

    override fun respondToUserInput(key: KeyEvent): Screen {
        return if (key.keyCode == KeyEvent.VK_ENTER)  PlayScreen() else this
    }
}

This is where we actually define how displayOutput and respondToUserIntput actually function (at least for this class).

Notice here that the if structure differs from other languages and does not actually use a ternary operator. From the KotlinLang site: In Kotlin, if is an expression, i.e. it returns a value. Therefore there is no ternary operator (condition ? then : else), because ordinary if works fine in this role.

Play Screen

Here’s an all important screen: Where you play!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package roglin.screens

import asciiPanel.AsciiPanel
import java.awt.event.KeyEvent


class PlayScreen() : Screen {
    override fun displayOutput(terminal: AsciiPanel) {
        terminal.write("You are having fun", 1, 1)
        terminal.writeCenter("-- Press [esc] to lose, or [enter] to win --", 22)
    }

    override fun respondToUserInput(key: KeyEvent): Screen {
        when (key.keyCode) {
            KeyEvent.VK_ESCAPE -> return LoseScreen()
            KeyEvent.VK_ENTER -> return WinScreen()
            else -> return this
        }
    }

}

Because our Play screen will need to react in different ways to different input, we need to introudce the where structure. This takes place of your switch structure in the traditional C-style languages. else is our default branch that is always executed if nothing else happens.

Lose Screen

The all important lose screen! While we will be seeing this often, it is more of the same code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package roglin.screens

import asciiPanel.AsciiPanel
import java.awt.event.KeyEvent


class LoseScreen():Screen{
    override fun displayOutput(terminal: AsciiPanel) {
        terminal.write("You lose.",1,1)
        terminal.writeCenter("-- press [enter] to restart --",22)
    }

    override fun respondToUserInput(key: KeyEvent): Screen {
        return if(key.keyCode == KeyEvent.VK_ENTER) PlayScreen() else this
    }

}

Win Screen

The Win screen! Hopefully someone sees this from time to time!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package roglin.screens

import asciiPanel.AsciiPanel
import java.awt.event.KeyEvent


class WinScreen(): Screen{
    override fun displayOutput(terminal: AsciiPanel) {
        terminal.write("You win!",1,1)
        terminal.writeCenter("-- press [enter] to play again --",22)
    }

    override fun respondToUserInput(key: KeyEvent): Screen {
        return if(key.keyCode == KeyEvent.VK_ENTER) PlayScreen() else this
    }

}

Great! Those are all implemented. You can see how they look here.

Updating the main flow

Now that we’ve implemented screens, we need to make our main program respond to input and display our different screens!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package roglin

import asciiPanel.AsciiPanel
import roglin.screens.Screen
import roglin.screens.StartScreen
import java.awt.event.KeyEvent
import java.awt.event.KeyListener
import javax.swing.JFrame

fun main(args: Array<String>){
    val app = ApplicationMain()
    app.defaultCloseOperation = JFrame.EXIT_ON_CLOSE
    app.isVisible = true
}

class ApplicationMain(): JFrame(), KeyListener {
    override fun keyTyped(e: KeyEvent?) {}

    override fun keyPressed(key: KeyEvent) {
        screen = screen.respondToUserInput(key)
        repaint()
    }

    override fun keyReleased(e: KeyEvent?) {}

    private val terminal: AsciiPanel
    private lateinit var screen: Screen

    init {
        terminal = AsciiPanel()
        add(terminal)
        pack()
        screen = StartScreen()
        addKeyListener(this)
        repaint()
    }

    override fun repaint(){
        terminal.clear()
        screen.displayOutput(terminal)
        super.repaint()
    }

}

The big changes here, are that we now implement the KeyListener interface. This tells the JVM where to send our key presses! Our application needs a new var (because it’s mutable!) to keep track of the which screen should be shown. Additionally, since we now implement the KeyListener, we’ve signed a contract and need to make the functions available to handle the keys!

Additionally, we see the introduction of lateinit. This allows us to have a non-null variable that is not immediately initialized.

Since we’re changing the logic of JFrame’s repaint, in a way, we need to do that here as well. We override the original method, make it clear the terminal, then have the screen display to that terminal, followed by calling the original JFrame’s repaint method.

Final git commit and structure can be found here