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.
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
.