Kotlin Game Development: Model

We need a model layer to define what objects will exist within our game. We can define any object but since we’re working on the snake game it makes sense to start with the essentials: snake and apple. Feel free to add more objects such as bonuses if you think it would make the game more interesting.

This is the seventh part of the “Kotlin Game Development” series and it’s better if you read it in chronological order:

  1. Part 1 - Introduction
  2. Part 2 - Creating a Scene
  3. Part 3 - Controller Input
  4. Part 4 - Game Loop
  5. Part 5 - Game Factory
  6. Part 6 - Main Menu
  7. Part 7 - Model (you are here)
  8. Part 8 - Game Scene
  9. Part 9 - Finalizing The Game

Snake Model

How can we represent a snake? As you may already know the traditional snake looked like a chain of blocks that constantly moves in a specific direction. Each part of the snake has it’s own position and this position changes when the snake moves.

Let’s create a new class and call it SnakeBodyPart:

1
2
3
4
5
6
package com.bubelov.snake.model

data class SnakeBodyPart (
    var x: Int,
    var y: Int
)

This class represents one piece of the snake. Usually the snake is quite short when the game starts but it grows longer as it eats apples. Each apple consumed by our snake adds one more part to it’s body. The only thing we want to know about each body part is it’s position.

Do we need something else except the list of body parts to describe the snake? It turns out the snake is a bit smarter than the sum of it’s parts. At least, it should have a direction and it should be able to move according to the selected direction.

Let’s create a Snake class that will coordinate the movement of all of the body parts:

 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
package com.bubelov.snake.model

class Snake(
    startX: Int,
    startY: Int,
    var direction: Direction = Direction.RIGHT
) {
    val body = mutableListOf<SnakeBodyPart>()
    val head by lazy { body[0] }

    init {
        body += SnakeBodyPart(startX, startY)
        body += SnakeBodyPart(startX - direction.deltaX(), startY - direction.deltaY())
        body += SnakeBodyPart(startX - direction.deltaX() * 2, startY - direction.deltaY() * 2)
    }

    fun move() {
        for (i in body.size - 1 downTo 1) {
            val current = body[i]
            val (x, y) = body[i - 1]
            current.x = x
            current.y = y
        }

        head.x = head.x + direction.deltaX()
        head.y = head.y + direction.deltaY()
    }
}

This class handles the creation of the snake at the specific location as well as moving it in any specific direction. The only missing part is the Direction enum:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.bubelov.snake.model

enum class Direction {
    UP,
    RIGHT,
    DOWN,
    LEFT;

    fun deltaX(): Int {
        return when (this) {
            LEFT -> -1
            RIGHT -> 1
            else -> 0
        }
    }

    fun deltaY(): Int {
        return when (this) {
            UP -> 1;
            DOWN -> -1;
            else -> 0;
        }
    }
}

Note that we have 2 helper methods to calculate how a particular direction affects x and y coordinates of the snake. For instance, the UP direction will produce deltaY = -1 and deltaX = 0.

Apple Model

The apple model is very simple. The only thing we need to know is the position of an apple which can be described as a pair of integers (x and y):

1
2
3
4
5
6
package com.bubelov.snake.model

data class Apple (
    val x: Int,
    val y: Int
)

Conclusion

Now we have a model which describes all objects that can exist in our game’s world. In the next piece we will place those objects on the game screen and make them interact with the player and with each other.