Exit Full View

Cavern Quest 2 / src / commonMain / kotlin / Grid.kt

import uk.co.nickthecoder.kyd.Actor
import uk.co.nickthecoder.kyd.Behaviour
import uk.co.nickthecoder.kyd.util.Vector2
import kotlin.math.round

/**
 * A 2D grid of Actors, which expands as required.
 *
 * To convert between world-coordinates to grid references, just divide by [gridSize] and round to
 * the nearest Int.
 * This means that grid references can be negative.
 *
 * i.e. `myGrid[-1][-1]` is perfectly fine, as is `myGrid[-1][-1] = someActor`.
 *
 * If we start with an empty grid, and set an item : `myGrid[2][1] = someActor`,
 * then the grid expands as required, adding `null` values / empty [Column]s as required.
 *
 * The net result :
 *
 * We can place [Item]s anywhere we like, calculate their grid reference in [Behaviour.onEnteredStage],
 * and place them in the grid. The order they are added is unpredictable, but we don't care.
 *
 * The playing area should be bounded by [Granite], otherwise the player can walk forever,
 * and the grid will grow as it does so.
 *
 * Note, this is a `ragged` array. i.e. the grid is a list of [Column]s and each [Column]
 * can have different sizes.
 *
 */
class Grid(val gridSize: Float) {

    class Column {
        private val items = mutableListOf<Actor?>()

        /**
         * The row reference of the first item in [items] list.
         * e.g. If this column has actors for rows -1 to 3, then [first] == -1, and `size == 5`.
         */
        private var first = 0

        /**
         * Finds the index into [items] for a given [rowReference].
         * The [items] list is extended (either at the front or end), if [rowReference] does not yet
         * refer to an item in [items].
         */
        private fun rowIndex(rowReference: Int): Int {
            if (rowReference < first) {
                val extra = first - rowReference
                // println("Add Start : $extra extra rows for index $index (first=$first)")
                for (i in 0 until extra) {
                    items.add(0, null)
                }
                first = rowReference
            }
            if (rowReference >= items.size + first) {
                val extra = rowReference - items.size - first + 1
                //println("Add End : $extra extra rows for index $index (first=$first)")
                for (i in 0 until extra) {
                    items.add(null)
                }
            }
            return rowReference - first
        }

        operator fun get(rowReference: Int): Actor? {
            return items[rowIndex(rowReference)]
        }

        operator fun set(index: Int, actor: Actor?) {
            items[rowIndex(index)] = actor
        }
    }

    private val rows = mutableListOf<Column>()
    private var first = 0

    private fun columnIndex(columnReference: Int): Int {
        if (columnReference < first) {
            val extra = first - columnReference
            //println("Add Start : $extra extra columns for index $index (first=$first)")
            for (i in 0 until extra) {
                rows.add(0, Column())
            }
            first = columnReference
        }
        if (columnReference >= rows.size + first) {
            val extra = columnReference - rows.size - first + 1
            //println("Add End : $extra extra columns for index $index (first=$first)")
            for (i in 0 until extra) {
                rows.add(Column())
            }
        }
        return columnReference - first
    }

    operator fun get(columnReference: Int): Column {
        return rows[columnIndex(columnReference)]
    }

    fun getAt(world: Vector2): Actor? {
        val gridX = round(world.x / gridSize).toInt()
        val gridY = round(world.y / gridSize).toInt()
        return this[gridX][gridY]
    }

    fun putAt(world: Vector2, actor: Actor?): Pair<Int, Int> {
        val gridX = round(world.x / gridSize).toInt()
        val gridY = round(world.y / gridSize).toInt()
        this[gridX][gridY] = actor
        return Pair(gridX, gridY)
    }
}