-1

I translated this tutorial on BSP into swift. In the tutorial there's this ActionScript function.

public function getRoom():Rectangle
{
    // iterate all the way through these leafs to find a room, if one exists.
    if (room != null)
        return room;
    else
    {
        var lRoom:Rectangle;
        var rRoom:Rectangle;
        if (leftChild != null)
        {
            lRoom = leftChild.getRoom();
        }
        if (rightChild != null)
        {
            rRoom = rightChild.getRoom();
        }
        if (lRoom == null && rRoom == null)
            return null;
        else if (rRoom == null)
            return lRoom;
        else if (lRoom == null)
            return rRoom;
        else if (FlxG.random() > .5)
            return lRoom;
        else
            return rRoom;
    }
}

I translated this function into Swift (to the best of my ability) I must have written it wrong because the function is returning a nil value when it shouldn't be.

My version in Swift:

// left/right child are initialized as follows:
// leftChild:Room?
// rightChild:Room?

public func getRoom() -> Room? {
    if room != nil {
        return room
    } else {
        var lRoom:Room?
        var rRoom:Room?

        if leftChild != nil {
            lRoom = leftChild!.getRoom()!
        }
        if rightChild != nil {
            rRoom = rightChild!.getRoom()!
        }
        if lRoom == nil && rRoom == nil {
            return nil
        } else if rRoom == nil {
            return lRoom
        } else if lRoom == nil {
            return rRoom
        } else if  Double.random(in: 0..<1.0) > 0.5 {
            return lRoom
        } else {
            return rRoom

        }
    }
}

Where Room is a basic class I made to help me handle the rooms.

class Room {
    var x1:Int
    var x2:Int
    var y1:Int
    var y2:Int
    var center:CGPoint

    init(X: Int, Y: Int, W: Int, H: Int) {
        x1 = X
        x2 = X + W
        y1 = Y
        y2 = Y + H
        center = CGPoint(x: (x1 + x2) / 2, y: (y1 + y2) / 2)
    }
}

I'm getting a nil value when I shouldn't be. I think I translated the function wrong. Rectangle would be CGRect in Swift but I replaced it with my Room class in other places in the code, so I know it'll work with the Room class here.

How would this function be written in Swift?

E. Huckabee
  • 1,788
  • 1
  • 13
  • 29
  • You are force unwrapping the result of `getRoom` which can return `nil`. – Paulw11 Oct 05 '18 at 11:22
  • I have to force-unwrap it or it won't let me use the function. I think I translated the function wrong but I'm not sure where. The duplicate question is not my question. – E. Huckabee Oct 05 '18 at 11:23
  • No, you must conditionally unwrap it. Your traversal legitimately returns `nil` at a leaf node. Force unwrapping `nil` gives an exception. The duplicate explains why you are getting a crash. – Paulw11 Oct 05 '18 at 11:32
  • My function is written wrong. The ActionScript function works differently than the version I have written. I'm trying to figure out what I wrote wrong, not why the function is returning nil. I edited the question to clarify my issue. – E. Huckabee Oct 05 '18 at 11:48
  • Aside from the force unwrapping issue your code is reasonable translation of the ActionScript. – Paulw11 Oct 05 '18 at 12:01

1 Answers1

2

Your problem is that you are force unwrapping the result of getRoom - This function returns an optional and can legitimately return nil when your traversal hits a leaf node. Force unwrapping nil results in a crash

By using conditional unwrapping correctly you can not only make your code more readable, you can eliminate the crash.

public func getRoom() -> Room? {

    if let _ = room {
        return room
    }

    let lRoom = leftChild?.getRoom()
    let rRoom = rightChild?.getRoom()

    switch (lRoom != nil, rRoom != nil) {
    case (false,false):
        return nil
    case (true,false):
        return lRoom
    case (false,true):
        return rRoom
    case (true,true):
        return arc4random_uniform(2) == 1 ? lRoom: rRoom
    }
}
Paulw11
  • 108,386
  • 14
  • 159
  • 186
  • It gave me a nil sometimes. I need a value every time so I ran it through an if-statement to test if there was a value there. It's functioning now. – E. Huckabee Oct 05 '18 at 12:02
  • It can only return `nil` if there are no rooms in your graph. – Paulw11 Oct 05 '18 at 12:05
  • exactly, the if-statement checks to see if there is something there: `if leftChild!.getRoom() != nil` then runs the other code afterwards. – E. Huckabee Oct 05 '18 at 12:06
  • You don't need that `if` statement; The checks are contained in the `switch` – Paulw11 Oct 05 '18 at 12:11
  • I tried it with just calling the function and I was still getting a crash. If I put that if-statement there, it doesn't crash. The function returns a nil sometimes. I need a value every time there is one. I am using the ActionScript function to carve out hallways between each of the rooms. I need a hallway every time one pops up. – E. Huckabee Oct 05 '18 at 12:17
  • Because you are still force-unwrapping; Don't do that. You need to conditionally unwrap the optional. The function can legitimately return `nil` when there are no rooms in the subtree. It will never return `nil` if there is a room in the subtree. `!= nil` is just the slightly ugly way of unwrapping the optional. – Paulw11 Oct 05 '18 at 12:20
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/181348/discussion-between-e-huckabee-and-paulw11). – E. Huckabee Oct 05 '18 at 12:27