Home > Mobile >  Why don't my randomly generated rooms always connect (Kotlin)
Why don't my randomly generated rooms always connect (Kotlin)

Time:06-19

I'm making a game for Android in Kotlin where infinite rooms are randomly generated as you explore the map(like the Backrooms). When you enter an unexplored room, it generates a random one, generating items and exits and then saving it so the same room loads at the same coordinates when you come back. Each room's exits are designed to connect to each other, so when a new room generates, it checks for the existence of neighboring rooms in all directions and connects its exits to them if they exist. So if the room to the left has an exit on the right, the new room will have an exit on the left leading to that room, and vise versa when the room doesn't have a right side exit. If the room doesn't exist yet, it chooses randomly whether to put an exit there or not. At least, that's how the code is supposed to work, but it doesn't, and I haven't been able to figure out why. I even got suspicious of Random.nextBoolean() being the culprit so I replaced every relevant instance of that with this:

when((1..2).random()) {
                1 -> false
                else -> true
            }

Unsurprisingly, that still didn't work. I've tried so many things and I just can't get it to work right, although I have gotten it to seem like it connects exits more often, but I can't see how anything would only slightly fix the problem, so I'm guessing its just an illusion and I'm just seeing the rooms connect more than I thought I was before. (Or maybe I'm going mad trying to fix this) I also seem to have more success when regenerating a room that's already been generated, but not 100%.

Here's how the rooms are generated:

    if (!rooms.containsKey(playerCoordinate)) {

        val newRoom = Room(playerCoordinate).apply {

        // Next-door rooms
        val roomL = Coordinate(playerCoordinate.x - 1L, playerCoordinate.y, playerCoordinate.z)
        val roomR = Coordinate(playerCoordinate.x   1L, playerCoordinate.y, playerCoordinate.z)
        val roomT = Coordinate(playerCoordinate.x, playerCoordinate.y 1L, playerCoordinate.z)
        val roomB = Coordinate(playerCoordinate.x, playerCoordinate.y-1L, playerCoordinate.z)
        val roomU = Coordinate(playerCoordinate.x, playerCoordinate.y, playerCoordinate.z 1L)
        val roomD = Coordinate(playerCoordinate.x, playerCoordinate.y, playerCoordinate.z-1L)

            // Randomizes the properties and add things like exits or ladders if the room next-door has one leading here.
            exitT = if(rooms.containsKey(roomT)) {
                 Room(roomT).exitB
            } else when((1..2).random()) {
                1 -> false
                else -> true
            }

            exitB = if(rooms.containsKey(roomB)) {
                Room(roomB).exitT
            } else when((1..2).random()) {
                1 -> false
                else -> true
            }

            exitL = if(rooms.containsKey(roomL)) {
                Room(roomL).exitR
            } else when((1..2).random()) {
                1 -> false
                else -> true
            }

            exitR = if(rooms.containsKey(roomR)) {
                Room(roomR).exitL
            } else when((1..2).random()) {
                1 -> false
                else -> true
            }

            upLadder = if(rooms.containsKey(roomU)) {
                Room(roomU).downLadder
            } else when((1..15).random()) {
                in 1..14 -> false
                else -> true
            }

            downLadder = if(rooms.containsKey(roomD)) {
                Room(roomD).upLadder
            } else when((1..15).random()) {
                in 1..14 -> false
                else -> true
            }

            upStairs = if(rooms.containsKey(roomU)) {
                Room(roomU).downStairs
            } else when((1..10).random()) {
                in 1..9 -> false
                else -> true
            }

            downStairs = if(rooms.containsKey(roomD)) {
                Room(roomD).upStairs
            } else when((1..10).random()) {
                in 1..9 -> false
                else -> true
            }

            prizeBox = when((1..30).random()) {
                in 1..29 -> false
                else -> Random.nextBoolean()
            }

            if(playerCoordinate.x == 0L && playerCoordinate.y == 0L && playerCoordinate.z == 0L) {
            exitT = true
            exitB = true
            exitL = true
            exitR = true
            }
        }
        rooms[playerCoordinate] = newRoom
    }

Heres my data classes, defining things like Room():

    data class Coordinate(
    val x: Long = 0,
    val y: Long = 0,
    val z: Long = 0
) {

    fun moveX(amount: Long) = copy(x = x   amount)
    fun moveY(amount: Long) = copy(y = y   amount)
    fun moveZ(amount: Long) = copy(z = z   amount)
}

class Room(val coordinate: Coordinate) {
    var exitT: Boolean = true
    var exitB: Boolean = true
    var exitL: Boolean = true
    var exitR: Boolean = true
    var upLadder: Boolean = true
    var downLadder: Boolean = true
    var upStairs: Boolean = true
    var downStairs: Boolean = true
    var prizeBox: Boolean = false
}

enum class Movement {
    None, Start, Left, Right, Top, Bottom, UpLadder, DownLadder, UpStairs, DownStairs
}

I also mapped out a few of these rooms by cropping and combining screenshots from testing it on my phone, and you can see how the exits don't always connect, along with other odd patterns. It is not supposed to generate those big walls on the edges like that, with the odds being 50:50, theoretically around half of those walls on the edges of the map should have exits, it's an infinite map. Here is that map

If it helps, I had a similar stack overflow post from a couple days ago on generating these rooms, and if you need more of the code, I have it on GitHub here.

CodePudding user response:

You are creating new rooms instead of retrieving the rooms you already created. For example, this:

exitT = if(rooms.containsKey(roomT)) {
    Room(roomT).exitB
} else when((1..2).random()) {
    1 -> false
    else -> true
}

should be changed to:

exitT = if(rooms.containsKey(roomT)) {
    rooms.getValue(roomT).exitB
} else when((1..2).random()) {
    1 -> false
    else -> true
}

Actually, since Random.nextBoolean() was not the issue, it should be changed to:

exitT = if(rooms.containsKey(roomT)) {
    rooms.getValue(roomT).exitB
} else {
    Random.nextBoolean()
}

You can also use the Map's [] accessor with an Elvis operator to simplify the expression. [] returns the value for that key or null if there is no value, so you can use the Elvis operator to provide the alternative when the value isn't in the map.

exitT = rooms[roomT]?.exitB ?: Random.nextBoolean()

By the way, if you want to choose a Boolean with a 1 in 10 chance, this:

when((1..10).random()) {
    in 1..9 -> false
    else -> true
}

can be more simply expressed like this:

Random.nextFloat() < 0.1f

which evaluates to true if the random number between 0 and 1 is less than 0.1. Or for a 1:15 chance, use < 1/15f.

  • Related