**SF Rogue Room Connection**by Paul Ledger

# SF Rogue Room Connection

This article continues covering the challenges I faced while creating SF Rogue.

## Challenge 3

Now that we have our random rooms generated, that don't overlap, we need to ensure that rooms that are next to each other are connected with doors.

We will continue using the same playground we created in the previous article, which can be found on GitHub.

One thing we know for sure if we are going to connect rooms, we will need a door to go through. We need to create a new Swift file in the Sources folder called `Door`

...

```
import UIKit
struct Door {
// A connection from a room to another, the connecting room will have an "opposite" door
var connectingRoomNumber: Int // The number of the room this door connects to
var joiningPoint: CGPoint // The position the door appears in the room
var wall: Constants.DoorWall // Which wall the door appears on
}
```

This struct uses a new enum to identify the wall the door appears on. Open the `Constants`

file, add these 2 `Constraints`

values...

```
static let doorSpace = CGFloat(Constraints.tileSize * 3) // Need to allow space for the door plus the 2 walls (if this is a corridor)
static let halfTileSize = Constraints.tileSize / 2 // Half the size for positional calculations
```

and add a new enum for the wall identification...

```
enum DoorWall {
case right
case left
case top
case bottom
}
```

Most of the work for finding the doors and creating them is handled in the `Room`

class, add the following variable to the `Room`

to keep a track of the doors the room has been allocated...

```
var doors = [Door]() // Holds the doors the room has
```

Then create some additional computed properties, place them under the `width`

property, to help with the matching of rooms...

```
var leftEdge: Int {
return Int(self.node.frame.minX)
}
var rightEdge: Int {
return Int(self.node.frame.maxX)
}
var bottomEdge: Int {
return Int(self.node.frame.minY)
}
var topEdge: Int {
return Int(self.node.frame.maxY)
}
```

In order for there to be a door the rooms must be "touching" on one of their sides. Its a bit more complicated than that, because not only do the sides have to be equal, they also need to be in the correct space. Take the following example...

Rooms 1, 3 and 5 all have a touching edges with rooms 2, 4 and 6, but we only want side doors between 1-2, 3-4 and 5-6. First we are going to make a helper function that will allow us to filter rooms that are touching on edges, this will reduce the processing we have to do...

```
public func sameEdge(toRoom: Room) -> Bool {
if self.doors.filter( { $0.connectingRoomNumber == toRoom.number }).count > 0 {
//we already have this connection and therefore we don't need to add another door
return false
}
return self.leftEdge == toRoom.rightEdge ||
self.rightEdge == toRoom.leftEdge ||
self.topEdge == toRoom.bottomEdge ||
self.bottomEdge == toRoom.topEdge
}
```

Now we can create a function, and a couple of further helper functions to do the fine tune comparison, which will determine if we can create an actual door...

```
public func buttingConnection(toRoom: Room) {
if self.doors.filter( { $0.connectingRoomNumber == toRoom.number }).count > 0 {
//we already have this connection
return
}
if self.leftEdge == toRoom.rightEdge, sameVerticalSpace(toRoom) {
createConnectingDoor(toRoom: toRoom, basedOnWall: .left)
return
} else if self.rightEdge == toRoom.leftEdge, sameVerticalSpace(toRoom) {
createConnectingDoor(toRoom: toRoom, basedOnWall: .right)
return
} else if self.topEdge == toRoom.bottomEdge, sameHorizontalSpace(toRoom) {
createConnectingDoor(toRoom: toRoom, basedOnWall: .top)
return
} else if self.bottomEdge == toRoom.topEdge, sameHorizontalSpace(toRoom) {
createConnectingDoor(toRoom: toRoom, basedOnWall: .bottom)
return
}
}
func sameVerticalSpace(_ toRoom: Room) -> Bool {
// check for complete cover first
if self.bottomEdge <= toRoom.bottomEdge, self.topEdge >= toRoom.topEdge {
return true
}
//Is there enough space for the door
return self.frame.intersection(toRoom.frame).height >= Constants.Constraints.doorSpace
}
func sameHorizontalSpace(_ toRoom: Room) -> Bool {
//check for complete cover first
if self.leftEdge <= toRoom.leftEdge && self.rightEdge >= toRoom.rightEdge {
return true
}
return self.frame.intersection(toRoom.frame).width >= Constants.Constraints.doorSpace
}
```

This code will now highlight some errors as we've not written the function `createConnectingDoor`

, it calculates positions for the doors in the room and the connecting room...

```
func createConnectingDoor(toRoom: Room, basedOnWall: Constants.DoorWall) {
// Used to determine what edges we are comparing
let sideDoor = (basedOnWall == .left || basedOnWall == .right)
// Gives an intersection so that we know how much space we have for the door
let intersection = self.frame.intersection(toRoom.frame)
// Calculate the edges, adjusting by a tileSize to allow for the walls
let maxEdge = Int(sideDoor ? intersection.maxY : intersection.maxX) - Constants.Constraints.tileSize
let minEdge = Int(sideDoor ? intersection.minY : intersection.minX) + Constants.Constraints.tileSize
// This gives a count of how many places the dooe could actually be placed
let availablePositions = (maxEdge - minEdge) / Constants.Constraints.tileSize
// Now pick a random position so that not all rooms appear in the same place
let offset = Int.random(in: 0..<availablePositions)
var myJoiningPoint = CGPoint.zero
var toRoomJoiningPoint = CGPoint.zero
// Calculate the tileSize offset of the door for both this room and the room we are connecting to
let doorbaseOffset = (minEdge - (sideDoor ? self.bottomEdge : self.leftEdge)) / Constants.Constraints.tileSize
let toRoomOffset = (minEdge - (sideDoor ? toRoom.bottomEdge : toRoom.leftEdge)) / Constants.Constraints.tileSize
var oppositeWall: Constants.DoorWall = .right
// Calculate the X/Y points for the door in each room.
if sideDoor {
myJoiningPoint.y = CGFloat(doorbaseOffset + offset)
myJoiningPoint.x = (basedOnWall == .left) ? 0 : CGFloat(cols - 1)
toRoomJoiningPoint.y = CGFloat(toRoomOffset + offset)
toRoomJoiningPoint.x = (basedOnWall == .left) ? CGFloat(toRoom.cols - 1) : 0
oppositeWall = (basedOnWall == .left) ? .right : .left
} else {
myJoiningPoint.x = CGFloat(doorbaseOffset + offset)
myJoiningPoint.y = (basedOnWall == .bottom) ? 0 : CGFloat(rows-1)
toRoomJoiningPoint.x = CGFloat(toRoomOffset + offset)
toRoomJoiningPoint.y = (basedOnWall == .bottom) ? CGFloat(toRoom.rows-1 ) : 0
oppositeWall = (basedOnWall == .bottom) ? .top : .bottom
}
// Create a door for this room and for the room its connecting to
self.doors.append(Door(connectingRoomNumber: toRoom.number,
joiningPoint: myJoiningPoint,
wall: basedOnWall))
toRoom.doors.append(Door(connectingRoomNumber: self.number,
joiningPoint: toRoomJoiningPoint,
wall: oppositeWall))
}
```

Thats all the code in place to work out where doors should be within the rooms, we now need to update the Playground to call them. Open the Playground file and add the following function after `arrangeRooms`

...

```
func findConnections() {
// Loop through the rooms, except the last one as there are no rooms further
// down the array and we would of processed any connections already
for count in 0..<rooms.count-1 {
let thisRoom = rooms[count]
// Looking at rooms further in the array, find ones that have touching edges
let touchingRooms = Array(rooms[count+1..<rooms.count])
.filter( { thisRoom.sameEdge(toRoom: $0)})
for touchingRoom in touchingRooms {
// Now for the ones with touching edges, see if we can actually make a connection
thisRoom.buttingConnection(toRoom: touchingRoom)
}
}
}
```

We are close to being able to run, all we have to do is call this method and actually render the doors. Change the `didMove`

code to the following...

```
override func didMove(to view: SKView) {
// Now we have a view to render too, create how many rooms we need for the map
for number in 0..<totalRooms {
let room = Room(number: number)
self.rooms.append(room)
}
var arrangedCount = 0
repeat {
arrangedCount = arrangeRooms()
} while arrangedCount > 0
findConnections()
for room in rooms {
room.render(scene: self)
}
}
```

We've added the `findConnections`

call and moved the rendering of the rooms to the end. Now we just need to draw the doors, in the `Room`

`render`

function...

```
public func render(scene: SKScene) {
drawDoors()
scene.addChild(self.node)
}
func drawDoors() {
for door in self.doors {
// Create a square that represents the door
let doorShape = SKShapeNode(rectOf: CGSize(width: Constants.Constraints.tileSize, height: Constants.Constraints.tileSize))
doorShape.lineWidth = 0
doorShape.fillColor = .blue
// Now calculate the X/Y position
let x = (Int(door.joiningPoint.x) * Constants.Constraints.tileSize) - (Int(width) / 2) + Constants.Constraints.halfTileSize
let y = (Int(door.joiningPoint.y) * Constants.Constraints.tileSize) - (Int(height) / 2) + Constants.Constraints.halfTileSize
doorShape.position = CGPoint(x: x, y: y)
self.node.addChild(doorShape)
}
}
```

The Playground is now ready to run, and should produce something like this in the LiveView...

## Challenge 3 Complete!

Congratulations, we now have a number of random sized rooms, that don't overlap, *with connecting doors*, being rendered in SpriteKit. You'll notice from the above example that some of the rooms don't have any doors and there are also a group of rooms on the right that are "floating", its time to move onto corridors.

The Playground for this article is available on GitHub

If you have any questions or comments you can reach me on Twitter.