Bronze & Silver Challenge - Ch 15


#1

I feel like I got a little sloppy with these challenges. Any alternative suggestions are welcome.

Bronze Challenge:

I modeled my code based on the principle that Zombies (or any monsters, for that matter) could potentially get stronger–for example, after it is instantiated somewhere else in the code and it’s power is directly manipulated (if we choose to keep that public), we would still be able to reduce the population of the current town to 0 if the population threshold is exceeded and not go into negative numbers.

super.terrorizeTown() only needs to be called if there is a population left after the Zombie is done terrorizing, so I included that in only the default case in the switch statement.

And lastly, I changed my new zombieTerrorPower and population to be a UInt, because it made more sense to me that the power of a monster would be a positive number, not a negative number (same with the population). Plus, it allows me some compile-time safety so that I know what I’m getting (no negative numbers!). This means a bit of refactoring, adding a check to see if a monster is attacking, but worth the effort.

/// Town.swift
struct Town {
    var population: UInt   = 5422
    var numberOfStoplights = 4
    var isMonsterAttacking = false

    func printDescription() {
        print("Population: \(population), number of stoplights: \(numberOfStoplights)")
    }

    mutating func changePopulation(by amount: UInt) {
        if isMonsterAttacking {
            population -= amount
        } else {
            population += amount
        }
    }
}
/// Zombie.swift
class Zombie: Monster {
    var walksWithLimp           = true
    var zombieTerrorPower: UInt = 10

    final override func terrorizeTown() {
        town?.isMonsterAttacking = true
        if let currentPop = town?.population {
            switch currentPop {
                case 0: // no population--do nothing.
                    print("\(self.name) tried terrorizing but everyone's dead...nobody left to terrorize.")
                case 1 ... zombieTerrorPower: // scales with zombie power
                    town?.population = 0
                    print("\(self.name) terrorized the town, and now everyone is dead.")
                default: // fallback
                    super.terrorizeTown()
                    town?.changePopulation(by: zombieTerrorPower)
            }
        }
        town?.isMonsterAttacking = false
    }
}

Silver Challenge:

Similar in structure to the Zombie, but less cases (more work to do in each case though):

/// Vampire.swift
class Vampire: Monster {
    var convertedVamps           = [Vampire]()
    var vampireTerrorPower: UInt = 1

    final override func terrorizeTown() {
        town?.isMonsterAttacking = true
        if let currentPop = town?.population {
            switch currentPop {
                case 0: // no population--do nothing.
                    print("\(self.name) tried terrorizing but everyone's either dead or a vampire...nobody left to terrorize.")
                    printVampireCount()
                default: // fallback
                    super.terrorizeTown()
                    town?.changePopulation(by: vampireTerrorPower)
                    convertedVamps.append(Vampire())
                    printVampireCount()
            }
        }
        town?.isMonsterAttacking = false
    }

    func printVampireCount() {
        print("There are \(convertedVamps.count) vampires in this town.")
    }
}
/// main.swift (testing the code)
var myTown = Town()
myTown.printDescription()

let dracula = Vampire()
dracula.name = "Dracula"
dracula.town = myTown
dracula.terrorizeTown()
dracula.terrorizeTown()
dracula.terrorizeTown()
dracula.town?.printDescription()

let fredTheZombie = Zombie()
fredTheZombie.town = myTown
fredTheZombie.name = "Zombie"
fredTheZombie.zombieTerrorPower = 10000
fredTheZombie.terrorizeTown()
fredTheZombie.town?.printDescription()

Console Output:

Population: 5422, number of stoplights: 4
Dracula is terrorizing a town!
There are 1 vampires in this town.
Dracula is terrorizing a town!
There are 2 vampires in this town.
Dracula is terrorizing a town!
There are 3 vampires in this town.
Population: 5419, number of stoplights: 4
Zombie terrorized the town, and now everyone is dead.
Population: 0, number of stoplights: 4


#2

Your code looks damn good.

Did you enter html by hand to format it? I am unable to format it like yours by using the widgets provided by the editor.


#3

Thanks!

In the standard editor on this forum, you can output syntax-highlighted code by putting 3 back-ticks above and below the code you want to format (this makes a code-block in their flavor of Markdown).

Like this

You can also do inline code by just surrounding your words with back-ticks, like this.

Hope that makes your code examples nicer!


#4

Roger that. It is three back-ticks above and below.

func print (s:String)  {
   Swift.print (s)
}

print ("Thank you for the three back-ticks!")

#5
What's a back-tick?!?!

#6

The character (`) underneath the tilde (~) key on the keyboard.


#7

For the Bronze challenge, I came up with the following changes to the Zombie class, which seemed to get the job done, but wasn’t as flexible as the model manintacos came up with:

class Zombie: Monster {
  var walksWithLimp = true
  
  final override func terrorizeTown() {
    if let pop = town?.population {
      if pop >= 10 {
        town?.changePopulation(by: -10)
      } else {
        town?.population = 0
      }
    }
    super.terrorizeTown()
  }
}

However, I also tried to use

if town?.population >= 10 {
        town?.changePopulation(by: -10)
      } else {
        town?.population = 0
      }

But that generated an error. Can anyone help me understand why? I thought that was how I chained the optional to get the value…without using the if…let construct.


#8

bthooper, for your second case you need to unwrap the value, so your code would look like:

if (town?.population)! >= 10 {
        town?.changePopulation(by: -10)
      } else {
        town?.population = 0
      }

population is always guaranteed to have a value since population can never be nil so it’s safe to unwrap. Unfortunately in this case you’ll need to do an additional check though in the event that there’s no town. Cause if there isn’t the code would fail during run time since you’re trying to unwrap a value of a population of a town that doesn’t exist. So your code would look like:

        if town != nil {
            if (town?.population)! >= 10 {
                town?.changePopulation(by: -10)
            } else {
                town?.population = 0
            }
            super.terrorizeTown()
        }
    }

In this case, you end up with just as many lines of code.


#9

Thank you for this. I think I was confused about the distinction between the optional and a non-optional variable within the optional. Population is there, if town is there. But town might not be there, so that’s where I need to check for a value…I think.


#10

Hello. Can anyone explain why myTown didn’t change its population?

Vampire.Swift

    var numberOfVampireThrall = 0
    final override func terrorizeTown() {
        if town != nil {
        if (town?.population)! > 1 {
        town?.changePopulation(by: -1)
        numberOfVampireThrall += 1
            print("Number of vampires: \(numberOfVampireThrall)")
        } else {
        town?.population = 0
        }
        }
    }
}

Main.Swift

myTown.changePopulation(by: 500)
let fredTheZombie = Zombie()
fredTheZombie.town = myTown
fredTheZombie.terrorizeTown()
fredTheZombie.town?.printDescription()
let alexTheVampire = Vampire()
alexTheVampire.town = myTown
alexTheVampire.terrorizeTown()
alexTheVampire.terrorizeTown()
alexTheVampire.terrorizeTown()
alexTheVampire.town?.printDescription()```

Output:  <img src="/uploads/bignerdranch/original/2X/e/ee7506432fe8ce77017b47acbe3e0e495bf8985d.png" width="416" height="142">

#11

Can someone explain why myTown’s population went back up after being terrorized by Zombie and before being terrorized by Dracula. How might I change my code to have the population continue to decrease when being terrorized by Zombie and then Dracula?

Vampire.Swift

import Foundation

class Vampire: Monster {
    var drinksYourBlood = true
    var convertedVampires = [Vampire]()
    
    override func terrorizeTown() {
        if let pop = town?.population {
            if pop >= 0 {
                convertedVampires.append(Vampire())
                town?.changePopulation(by: -convertedVampires.count)
                print("There are \(convertedVampires.count) vampires.")
            } else {
                town?.population = 0
            }
        }
        super.terrorizeTown()
    }
}

Zombie.swift

import Foundation

class Zombie: Monster {
    var walksWithLimp = true
    
    final override func terrorizeTown() {
        if let pop = town?.population {
            if pop >= 10 {
                town?.changePopulation(by: -10)
            } else {
                town?.population = 0
            }
        }
        
        super.terrorizeTown()
    }
}

Main.Swift

import Foundation

var myTown = Town()
myTown.printDescription()

let fredTheZombie = Zombie()
fredTheZombie.name = "Zombie"
fredTheZombie.town = myTown
fredTheZombie.terrorizeTown()
fredTheZombie.town?.printDescription()

let dracula = Vampire()
dracula.name = "Dracula"
dracula.town = myTown
dracula.terrorizeTown()
dracula.terrorizeTown()
dracula.town?.printDescription()


Output


#12

I did this a little different than everyone else. Because the instructions for the silver challenge say “an array of Vampires on the Vampire type” I made the Vampires array a static property. This also has the added benefit of not creating a new array each time a Vampire instance is created to be added to the original array.

class Vampire: Monster {
 static var vampires = [Vampire]()
  
  override func terrorizeTown() {
    let vampire = Vampire()
    Vampire.vampires.append(vampire)
    print("Added one to vampire army. \(Vampire.vampires.count) total.")
    town?.changePopulation(by: -1)
  }
}

#14

I believe that the problem arises from the fact that, when, in main.swift, you write

fredTheZombie.town = myTown

(or the equivalent for creating the Vampire town) you are making a copy of myTown and not really setting myTown and fredTheZombie.town to be one and the same.

The book (a bit after Listing 15.12) states “The value semantics of structs means that the terrorTown instance will not be the same as the town instance. Why? Because value types, including structs, are always copied when you pass them around in your code.”

I think this is the explanation for myTown.population not changing when you change the population of fredTheZombie.town with the line:

town?.changePopulation(by: -10)

in the Zombie.swift file. Likewise for the Vampire situation.

I was able to synch myTown.population with fredTheZombie.town?.population and with the Vampire town equivalent by adding the line:

myTown.changePopulation(by: -10)

after the line:

town?.changePopulation(by: -10)

in the Zombie.swift file and a similar line for the Vampire town in the Vampire.swift file.

Thus, the override function in Zombie.swift becomes:

    final override func terrorizeTown() {
        if town != nil {
            if (town?.population)! >= 10 {
                town?.changePopulation(by: -10)
                // add this next line to also change the population of myTown
                myTown.changePopulation(by: -10)
            } else {
            town?.population = 0
            }
        super.terrorizeTown()
        }
    }

So, this seems to work but I wonder if there is a more elegant or appropriate way to keep myTown.population in synch with the other population variables.

Cheers,
Jim


#16

This is my alternative solution for the bronze challenge, I didn’t liked much the idea of modifying directly the value of population, so I obtained it and changePopulation by -population

    final override func terrorizeTown() {
       if let population = town?.population {
           if(population < 10){
               town?.changePopulation(by: -population)
           }else {
               town?.changePopulation(by: -10)
           }
       }
       super.terrorizeTown()
   }

#17

Bronze Challenge:

class Zombie: Monster {
    var walksWithLimp = true
    
    final override func terrorizeTown() { // final indicates that subclasses can't override further.
        if let thisTown = town {
            if thisTown.population > 10 {
                  town?.changePopulation(by: -10) // optional chaining -- makes sure that it is safe to call a function on the "town" instance.
                  super.terrorizeTown() // "super" accesses the superclasses' implementation of a method.
            } else {
                town?.population = 0 // optional chaining.
                print("They're all dead!") 
            }
        }
    }
}

#18

Silver Challenge Code:

Code from Vampire.swift:

class Vampire: Monster {
    var hasFangs = true
    var vampThrall = [Int]()
    var newVamp = 1
    
    final override func terrorizeTown() { // final indicates that subclasses can't override further.
        if let thisTown = town {
            //There is a town to terrorize, so add to the Vampire's "thrall"
            vampThrall.append(newVamp) // add a new one
            if thisTown.population > 0 {
                town?.changePopulation(by: -1) // optional chaining -- makes sure that it is safe to call a function on the "town" instance.
                super.terrorizeTown() // "super" accesses the superclasses' implementation of a method.
                 print("The vampire thrall is: \(vampThrall).") // check the array
            } else {
                town?.population = 0 // optional chaining.
                print("They're all dead!")
               
            }
        }
    }
}

Code from Main:

let fangTheVampire = Vampire()
fangTheVampire.town = myTown
//print("Fang's town is: \(fangTheVampire.town).")

while fangTheVampire.town?.population != 0 {
    fangTheVampire.terrorizeTown()
    fangTheVampire.town?.printDescription()
}

Output:

The vampire thrall is: [1].
Monster is terrorizing a town.
The vampire thrall is: [1, 1].
Monster is terrorizing a town.
The vampire thrall is: [1, 1, 1].
Monster is terrorizing a town.
The vampire thrall is: [1, 1, 1, 1].
Monster is terrorizing a town.
The vampire thrall is: [1, 1, 1, 1, 1].
Monster is terrorizing a town.
The vampire thrall is: [1, 1, 1, 1, 1, 1].
Monster is terrorizing a town.
The vampire thrall is: [1, 1, 1, 1, 1, 1, 1].
Monster is terrorizing a town.
The vampire thrall is: [1, 1, 1, 1, 1, 1, 1, 1].
Monster is terrorizing a town.
The vampire thrall is: [1, 1, 1, 1, 1, 1, 1, 1, 1].
Monster is terrorizing a town.
The vampire thrall is: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1].
Monster is terrorizing a town.
The vampire thrall is: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1].
They're all dead!
Population: 0; number of stop lights: 4.
Program ended with exit code: 0

#19

Bronze & Silver Challenge

I modified the bronze solution somewhat to deal with the negative population issue in the Town class. I think it’s cleaner that way.

Town Struct:

    struct Town {
        var population = 5_422
        var numberOfStoplights = 4
    
        func printDescription() {
            print("Population: \(population); number of stoplights: \(numberOfStoplights)")
        }
    
        mutating func changePopulation(by amount: Int) {
            if ((population + amount) < 0) {
                population = 0
            } else {
                population += amount
            }
        }
     }

Zombie Class:

class Zombie: Monster {
    var walksWithLimp = true
    
    final override func terrorizeTown() {
        town?.changePopulation(by: -10)
        super.terrorizeTown()
    }
}

Vampire Class:

class Vampire: Monster {
    var thralls = [Vampire]()
    
    override func terrorizeTown() {
        let newThrall = Vampire()
        thralls.append(newThrall)
        town?.changePopulation(by: -1)
        super.terrorizeTown()
    }
}

main.swift:

var myTown = Town()
myTown.changePopulation(by: 500)
let fredTheZombie = Zombie()
fredTheZombie.town = myTown
fredTheZombie.terrorizeTown()
fredTheZombie.town?.printDescription()

let dracula = Vampire()
dracula.town = myTown

for _ in 1...10 {
    dracula.terrorizeTown()
}

dracula.town?.printDescription()
print(dracula.thralls.count)

Output:

Monster is terrorizing a town!
Population: 5912; number of stoplights: 4
Monster is terrorizing a town!
Monster is terrorizing a town!
Monster is terrorizing a town!
Monster is terrorizing a town!
Monster is terrorizing a town!
Monster is terrorizing a town!
Monster is terrorizing a town!
Monster is terrorizing a town!
Monster is terrorizing a town!
Monster is terrorizing a town!
Population: 5912; number of stoplights: 4
10

If you want to avoid the population “reset” when different monsters terrorize the town, you could change the Town struct to make it a class instead. Just change the struct keyword to class and remove mutating from the function in there.


#20

I haven’t looked at your solution because I want to figure it out but I’m just not seeing this solution. Seems like every line of code I try causes an error.

I’ve tried many different things. This is what I have at the moment…

import Foundation

class Zombie: Monster {
   var walksWithLimp = true
   var decrementAmount = 10
    
    override func terrorizeTown() {
        if town?.population > 0 {
            town!.changePopulation(by: -10)
        } else if town!.population < decrementAmount {
            town!.population = 0
        }
    }
}

I get the error:
Binary operator ‘>’ cannot be applied to operands of type ‘Int?’ and ‘Int’

This is taking me forever and I don’t seem to be making any progress. I don’t want anyone’s code but any hints would be greatly appreciated.


#21

"population is always guaranteed to have a value since population can never be nil so it’s safe to unwrap. "

Population can’t be nil because we didn’t declare it as an optional right?

and if I would’ve known to unwrap

(town?.population)!

this wouldn’t have taken me nearly as long


#22

I typically recommend to new folks to unwrap optionals safely vs using force unwrapping (also known as the crash operator :wink: