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)
}
}