CyberTank

Data Shared by the Entire Application

Game Mode

Our game is going to operate in a number of different modes:

Map Building
At some point, we need to be able to build a map. This will mean deciding on its size and setting up the terrain types and the features. Eventually, we’ll need to be able to save the data to our database so that we can use it to play.
Set Up
The start of every game will be to select the units of the defenders and to place them on the map.
Play the Game
Once the setup is complete, we can start playing the game. Here the player will be able to move units and fire upon the CyberTank. The CyberTank will move and fire on the units.
enum class GameMode { MAP_BUILD, SETUP, PLAYING }

Location

All of the Tiles have a row and a column even though the rows are zigzag. We’re not going to try to store our Tiles or Tile related data into a two dimensional Array because dealing with that eventually becomes a huge pain for handling groups of Tiles at a time. You end up with nested loops all over the place and it’s messy.

Instead, we’ll store the location of the Tile in the Tile data, and then store the Tile data in a simple List, We can use the find() method in Kotlin to locate a Tile or groups of Tiles. It’ll work just a well, and makes processing easier.

So we need a data structure for the Tile locations:

data class Location(val column: Int, val row: Int) {
   fun equals(otherLocation: Location?): Boolean = otherLocation?.let {
      (this.row == otherLocation.row) && (this.column == otherLocation.column)
   } ?: false

   override fun toString(): String {
      return "$column:$row"
   }
}

This class will grow, for sure. We’ll probably need functions to calculate distances, and neighbours by direction and other things like that. For now, we just need equals() and toString().

Game Model

To start with, our GameModel is fairly simple and, to be honest, it probably won’t get much more complicated. Remember that the GameModel is a “Presentation Model”, so it contains only information that’s used by the GUI. 99% of the complicated stuff in our application is going to be game-play logic, and that doesn’t need data included in the GUI.

class GameModel {
   val hexWidth: DoubleProperty = SimpleDoubleProperty(60.0)
   val hexHeight: ObservableDoubleValue
   val tileModels: ObservableList<TileModel> = FXCollections.observableArrayList()
   val gameMode: ObjectProperty<GameMode> = SimpleObjectProperty(GameMode.SETUP)
   val setupModel = SetupModel()

   init {
      hexHeight = Bindings.createDoubleBinding({ sin(Math.PI / 3) * hexWidth.value }, hexWidth)
   }

   fun consumeTile(location: Location, tileConsumer: Consumer<TileModel>) {
      tileModels.find { tileModel -> tileModel.locationProperty.value.equals(location) }?.run {
         tileConsumer.accept(this)
      }
   }
}

The only thing that’s a bit tricky here is the consumeTile() function. It allows the calling method to pass a code snippet which will act upon a single Tile. The caller just needs to specify the Location of the Tile, and the GameModel will find it and run the code snippet on it.