Chapter 17, Gold Challenge Solution

I added a question mark to the init keyword in the Monster class to specify a failable initializer. I also added a guard statement to prevent monsters from having an empty string for a name.

//
//  Monster.swift
//  MonsterTown
//

import Foundation

class Monster {
    static let isTerrifying = true
    class var spookyNoise: String {
        return "Grrr"
    }
    var town: Town?
    var name: String
    var victimPool: Int {
        get {
            return town?.population ?? 0
        }
        set(newVictimPool) {
            town?.population = newVictimPool
        }
    }
    
    required init?(town: Town?, monsterName: String) {
        guard monsterName != "" else {
            return nil
        }
        self.town = town
        name = monsterName
    }
    
    func terrorizeTown() {
        if town != nil {
            print("\(name) is terrorizing a town!")
        } else {
            print("\(name) hasn't found a town to terrorize yet...")
        }
    }
}

The change to the Monster class necessitated changes to the Zombie class: I made all of the initializers failable by adding question marks to them.

//
//  Zombie.swift
//  MonsterTown
//

import Foundation

class Zombie: Monster {
    class override var spookyNoise: String {
        return "Brains..."
    }
    var walksWithLimp: Bool
    private(set) var isFallingApart: Bool
    
    init?(limp: Bool,
         fallingApart: Bool,
         town: Town?,
         monsterName: String) {
        walksWithLimp = limp
        isFallingApart = fallingApart
        super.init(town: town, monsterName: monsterName)
    }
    
    convenience init?(limp: Bool, fallingApart: Bool) {
        self.init(limp: limp,
                  fallingApart: fallingApart,
                  town: nil,
                  monsterName: "Fred")
        if walksWithLimp {
            print("This zombie has a bad knee.")
        }
    }
    
    convenience required init?(town: Town?, monsterName: String) {
        self.init(limp: false,
                  fallingApart: false,
                  town: town,
                  monsterName: monsterName)
    }
    
    deinit {
        print("Zombie \(name) is no longer with us.")
    }
    
    func regenerate() {
        walksWithLimp = false
    }
    
    override func terrorizeTown() {
        if !isFallingApart {
            town?.changePopulation(by: -10)
        }
        super.terrorizeTown()
        regenerate()
    }
}

I added a proof of concept to main.swift where I attempt to create a new zombie instance with an empty string.

//
//  main.swift
//  MonsterTown
//

import Foundation

var myTown = Town(population: 0, stoplights: 6)
myTown?.printDescription()
let myTownSize = myTown?.townSize
print(String(describing: myTownSize))
myTown?.changePopulation(by: 1_000_000)
print("Size: \(String(describing: myTown?.townSize)); population: "
      + "\(String(describing: myTown?.population))")

var fredTheZombie: Zombie? = Zombie(limp: false,
                           fallingApart: false,
                           town: myTown,
                           monsterName: "Fred")
fredTheZombie?.terrorizeTown()
fredTheZombie?.town?.printDescription()

var convenientZombie = Zombie(limp: true, fallingApart: false)

// Silver challenge
var convenientZombieTwo = Zombie(town: myTown, monsterName: "Barney")
convenientZombieTwo?.terrorizeTown()

// Gold challenge
var failableZombie = Zombie(town: myTown, monsterName: "")
failableZombie?.terrorizeTown() // This shouldn't unwrap and terrorize
                                // because it is nil

print("Victim pool: \(String(describing: fredTheZombie?.victimPool))")
fredTheZombie?.victimPool = 500
print("Victim pool: \(String(describing: fredTheZombie?.victimPool))")
print(Zombie.spookyNoise)
if Zombie.isTerrifying {
    print("Run away!")
}
fredTheZombie = nil