CyberTank

Tiles - The Basic Unit of Display

Our hex map is made up of hexagonal tiles, and each tile is its own little MVCI construct with an hexagonal layout shape.

The Tile Model

The model is pretty simple. We need some data for the location of the Tile and it’s size, the terrain type and feature, and who’s occupying the Tile.

class TileModel(location: Location, val width: ObservableDoubleValue, val height: ObservableDoubleValue) {
  val terrainFeatureProperty: ObjectProperty<Int> = SimpleObjectProperty(0)
  val terrainTypeProperty: ObjectProperty<TerrainType> = SimpleObjectProperty(TerrainType.NONE)
  val selectedProperty: BooleanProperty = SimpleBooleanProperty(false)
  val occupierProperty: ObjectProperty<CounterType> = SimpleObjectProperty(CounterType.NONE)
  val locationProperty: ObjectProperty<Location> = SimpleObjectProperty()
  val column: Int
  get() = locationProperty.value.column
  val row: Int
  get() = locationProperty.value.row

  init {
    this.locationProperty.set(location)
  }
}

We’ve also got a couple of convenience methods to pull the row and column out of the Tile location.

Having the height and width come in as externally provided Observables means that we can rescale the entire map at once by changing those properties as the application level.

The Tile ViewBuilder

All of the layout is handled by a single class TileViewBuilder. Let’s take a look at how it works

CSS and SVG

In order to create the hexagonal shape, we’re going to use the stylesheet parameter -fx-shape. This allows you to specify an SVG path for the shape. The size doesn’t matter, as we’ll scale the tile in the layout and it will just keep the shape.

.hex-tile {
  -fx-shape: "M 300,150 225,280 75,280 0,150 75,20 225,20 z";
  -fx-border-color: -border-colour;
  -fx-border-width: 1px;
}

The Layout

The basic layout is just a StackPane, we’ll see how this makes sense in a little bit:

private fun createStackPane() = StackPane().apply {
      styleClass.add("hex-tile")
      maxHeightProperty().bind(model.height)
      maxWidthProperty().bind(model.width)
      minHeightProperty().bind(model.height)
      minWidthProperty().bind(model.width)
      background = Background(BackgroundFill(model.terrainTypeProperty.value.colour, null, null))
      model.terrainTypeProperty.addListener { _ -> background = Background(BackgroundFill(model.terrainTypeProperty.value.colour, null, null)) }
   }

Here you can see the styleclass added, which gives it its shape, and then it’s size is bound to the height and width as specified in the TileModel. Finally, the background is set to be a BackgroundFill with its colour set to that defined in the TerrainType Enum, and bound to the TileModel.TerrainType.