Roguelike Tutorial 6
Roguelike in Go - Part 6 (Corridors)
All of the code for this tutorial can be found here.
Now that we have rooms, and our player is appropriately spawning in one, let’s connect them into a real dungeon map. All changes for this tutorial will be in level.go.
The first thing we will do is fix a small bug introduced in the last tutorial. The calls to get the x and y values in GenerateLevelTiles were not working quite as I expected them to upon several runs. Therefore, let’s change it from this:
x := GetDiceRoll(gd.ScreenWidth-w-1) - 1
y := GetDiceRoll(gd.ScreenHeight-h-1) - 1
To this:
x := GetDiceRoll(gd.ScreenWidth - w - 1)
y := GetDiceRoll(gd.ScreenHeight - h - 1)
That additional -1 was causing some issues.
Next, I was surprised to find out that Golang seems to have no Min and Max function. So let’s create them for our ease of use:
// Max returns the larger of x or y.
func max(x, y int) int {
if x < y {
return y
}
return x
}
// Min returns the smaller of x or y.
func min(x, y int) int {
if x > y {
return y
}
return x
}
Next, we will write a function which will carve a tunnel between two given points horizontally. I think everything in this function is pretty self-evident as to what’s going on.
func (level *Level) createHorizontalTunnel(x1 int, x2 int, y int) {
gd := NewGameData()
for x := min(x1, x2); x < max(x1, x2)+1; x++ {
index := level.GetIndexFromXY(x, y)
if index > 0 && index < gd.ScreenWidth*gd.ScreenHeight {
level.Tiles[index].Blocked = false
floor, _, err := ebitenutil.NewImageFromFile("assets/floor.png")
if err != nil {
log.Fatal(err)
}
level.Tiles[index].Image = floor
}
}
}
We also want to do the same thing for the vertical tunnels:
func (level *Level) createVerticalTunnel(y1 int, y2 int, x int) {
gd := NewGameData()
for y := min(y1, y2); y < max(y1, y2)+1; y++ {
index := level.GetIndexFromXY(x, y)
if index > 0 && index < gd.ScreenWidth*gd.ScreenHeight {
level.Tiles[index].Blocked = false
floor, _, err := ebitenutil.NewImageFromFile("assets/floor.png")
if err != nil {
log.Fatal(err)
}
level.Tiles[index].Image = floor
}
}
}
All that is left is some changes to the GenerateLevelTiles function: At the top of the function, where we are declaring the variables, add this one:
contains_rooms := false
For the rest of our changes, look for this code within the function:
if okToAdd {
level.createRoom(new_room)
We will be putting all changes inside this block, below the CreateRoom call. Change this block so that it looks like the following:
if okToAdd {
level.createRoom(new_room)
if len(level.Rooms) == 0 {
if contains_rooms {
newX, newY := new_room.Center()
prevX, prevY := level.Rooms[len(level.Rooms)-1].Center()
coinflip := GetDiceRoll(2)
if coinflip == 2 {
level.createHorizontalTunnel(prevX, newX, prevY)
level.createVerticalTunnel(prevY, newY, newX)
} else {
level.createHorizontalTunnel(prevX, newX, newY)
level.createVerticalTunnel(prevY, newY, prevX)
}
}
level.Rooms = append(level.Rooms, new_room)
contains_rooms = true
}
}
Ok, yeah. This is kind of nuts to look at. Let’s address it and explain what’s going on here. Each time we create a room, if its not the first room, we will determine if we can add a hallway between it and another room. We will always tunnel from center to center, so we grab those. Then, depending on the coinflip, we create the tunnels. This gives us a bit more randomness in our tunnels, giving the level some character.
Your screen should now have a complete map: .
As always, if you have any questions, feel free to contact me at fatoldyeti@gmail.com or @idiotcoder on the gophers slack.