Two dimensional arrays with nulls I'm new with Kotlin, and this is my first attempt of a two dimensional array.

 // private var roomGrid = arrayOfNulls&lt;arrayOfNulls&lt;Room&gt;&gt;(4)
private var roomGrid = Array(roomCountX) {arrayOfNulls&lt;Room&gt;(roomCountY)}

 init {
    for (gridY in 0 until roomCountY) {
        for (gridX in 0 until roomCountX) {
            val pixelX = gridX * roomWidth
            val pixelY = gridY * roomHeight
            val room = Room(pixelX.toFloat(), pixelY.toFloat(), s)
            roomGrid[gridX][gridY] = room
        }
    }

     // neighbor relations
    for (gridY in 0 until roomCountY) {
        for (gridX in 0 until roomCountX) {
            val room = roomGrid[gridX][gridY]
            if (gridY &gt; 0)
                roomGrid[gridX][gridY - 1]?.let { room?.setNeighbor(Room.SOUTH, it) }
            if (gridY &lt; roomCountY - 1)
                roomGrid[gridX][gridY + 1]?.let { room?.setNeighbor(Room.NORTH, it) }
            if (gridX &gt; 0)
                roomGrid[gridX - 1][gridY]?.let { room?.setNeighbor(Room.WEST, it) }
            if (gridX &lt; roomCountX - 1)
                roomGrid[gridX + 1][gridY]?.let { room?.setNeighbor(Room.EAST, it) }
        }
    }
}


Even though it works I don't like the look of these:

roomGrid[gridX][gridY + 1]?.let { room?.setNeighbor(Room.NORTH, it) }


They are harder to read, and I'm not really sure what .let and it are.
I would like some simpler syntax/solution to a two dimensional array.

How might I improve this?
let

If you use let, you ask Kotlin to create an invisible function with a parameter.

1.let { println(5) }
    //changes to:
    fun func(it : Int) {
        println(5)
    }
    func(1)


Do you see the it? That's the name of the parameter.
Therefor, you can use it inside let.

1.let { println(it) }
//changes to
fun func(it : Int) {
    println(it)
}
func(1)


Now, the function returns something too, namely the result of the last statement. 
In this case, it's just Unit, or void in Java (emptyNess):

fun func(it : Int) : Unit {
    return println(it)
}
func(1)


If we take another example, it will be clearer:

val a = 5.let { 3 + it }
//changes to
fun func(it: Int) : Int {
     return 3 + it
}
val a = func(5)


?.let

?. is very simple:

val b = a?.foo()
//changes to:
val b = if (a == null) a else a.foo()


?.let is exactly the same:

val b = a?.let { it+3 }
//changes to
fun func(it: Int){
   it + 3
}
val b = if (a == null) null else func(a)


inline

Kotlin is smart. it actually uses an inline function for let.
An inline function is a function that actually doesn't exist.
Instead, the code is copy pasted into the real code.
The parameters are just variables with random names.

so

val b = a?.let { it + 3 }
//is actually changed to
val b = if (a == null) null else {
    val ac432cd = a
    ac432cd + 3
}


simplyfication

roomGrid[gridX][gridY - 1]?.let { room?.setNeighbor(Room.SOUTH, it) }


step 1

// I would check room first:
if (room != null) {
   roomGrid[gridX][gridY - 1]?.let { room.setNeighbor(room.SOUTH, it) }
}


step 2

// flip the rooms (and directions)
if (room != null) {
   roomGrid[gridX][gridY - 1]?.let{ it.setNeighbor(room.SOUTH, room) }
}


step 3

//remove let
if (room != null) {
   roomGrid[gridX][gridY - 1]?.setNeighbor(room.SOUTH, room)
}


full code until now

 // neighbor relations
for (gridY in 0 until roomCountY) {
    for (gridX in 0 until roomCountX) {
        val room = roomGrid[gridX][gridY]
        if (room != null) {
            if (gridY &gt; 0)
                roomGrid[gridX][gridY - 1]?.setNeighbor(Room.NORTH, room)
            if (gridY &lt; roomCountY - 1)
                roomGrid[gridX][gridY + 1]?.setNeighbor(Room.SOUTH, room)
            if (gridX &gt; 0)
                roomGrid[gridX - 1][gridY]?.setNeighbor(Room.EAST, room)
            if (gridX &lt; roomCountX - 1)
                roomGrid[gridX + 1][gridY]?.setNeighbor(Room.WEST, room)
        }
    }
}


step 4

At the moment, we test in the beginning if room isn't null.
If it is, it skips the if-statement and continues with the next iteration.
We can tell to go to the next iteration immediately using the keyword continue.

// neighbor relations
for (gridY in 0 until roomCountY) {
    for (gridX in 0 until roomCountX) {
        val room = roomGrid[gridX][gridY]
        if (room == null) continue
        if (gridY &gt; 0)
            roomGrid[gridX][gridY - 1]?.setNeighbor(Room.NORTH, room)
        ...
    }
}


step 5

last, kotlin has another language feature: ?:

val a = foo() ?: bar()
//will be rewritten to
val tmp = foo()
val a = if (tmp != null) tmp else bar()


Just as return continue skips immediately to the next variable.
This means that we can rewrite the code we wrote in step 4.

// neighbor relations
for (gridY in 0 until roomCountY) {
    for (gridX in 0 until roomCountX) {
        val room = roomGrid[gridX][gridY] ?: continue
        if (gridY &gt; 0)
            roomGrid[gridX][gridY - 1]?.setNeighbor(Room.NORTH, room)
        ...
    }
}


full code

 // neighbor relations
for (gridY in 0 until roomCountY) {
    for (gridX in 0 until roomCountX) {
        val room = roomGrid[gridX][gridY] ?: continue
        if (gridY &gt; 0)
            roomGrid[gridX][gridY - 1]?.setNeighbor(Room.NORTH, room)
        if (gridY &lt; roomCountY - 1)
            roomGrid[gridX][gridY + 1]?.setNeighbor(Room.SOUTH, room)
        if (gridX &gt; 0)
            roomGrid[gridX - 1][gridY]?.setNeighbor(Room.EAST, room)
        if (gridX &lt; roomCountX - 1)
            roomGrid[gridX + 1][gridY]?.setNeighbor(Room.WEST, room)
    }
}