Arrays of protocol Types

Protocols > Classes in swift, after the rather amazing protocol orientated programming video Swift developers everywhere got far more disciplined when it came to favouring composition using protocols over inheritance and base classes. Taking arguments into functions based on what protocols the class or struct adhere to is core Swift functionality. Creating functions in this manner hugely increases code reusability and creates cleaner architecture.

Welcome to Pirates Vs Ninjas a pointless logic game in which pirates and ninjas fight other pirates and ninjas. Being a well disciplined Swift programmer after I finish pretending i'm a pirate for a few hours I sit down and get ready to start my application and the first thing I do is define a 'Fighter' protocol

protocol Fighter {
    var attackStrength: Int {get}
    var health: Int {get set}
    mutating func attackedByFighter(fighter: Fighter)
}

Each fighter has an attack strength a variable health and the ability to be attacked by another fighter. Maybe not quite the structure I would use in real life but it will let me demonstrate my point quite nicely. This being said I now create my pirate and ninja structs

struct Ninja: Fighter {
    let attackStrength: Int
    var health = 100
    
    init(attackStrength: Int) {
        self.attackStrength = attackStrength
    }
    
    mutating func attackedByFighter(fighter: Fighter) {
        health -= fighter.attackStrength
        if health < 0 {
            smokeBomb()
        }
    }

    mutating func smokeBomb() {
        print("The ninja escaped, to the forest to recover")
    }
}

struct Pirate: Fighter {
    let attackStrength: Int
    var health = 100
    
    init(attackStrength: Int) {
        self.attackStrength = attackStrength
    }
    
    mutating func attackedByFighter(fighter: Fighter) {
        health -= fighter.attackStrength
        if health < 0 {
            drinkRum()
        }
    }
    
    mutating func drinkRum() {
        print("Glug glug glug....the pirate passed out, he will need a good nights sleep")
        health = 100
    }
}

So far so good, everything is nice and abstracted and everything is extremely testable! All Good! Now let use this code to run the following scenario...1 Ninja, 1 Pirate, the ninja attacks the pirate, the pirate is confused by the ambush and attacks himself then the ninja finishes him off.

var pirateDave = Pirate(attackStrength: 10)
var ninjaSteve = Ninja(attackStrength: 50)

pirateDave.attackedByFighter(ninjaSteve)
pirateDave.attackedByFighter(pirateDave)
pirateDave.attackedByFighter(ninjaSteve)        //"Glug glug glug....the pirate passed out, he will need a good nights sleep"

This compiles runs and works well as we all know and expect, the poor pirate drinks away his pain, we can pass both pirate and ninja to the attackedByFighter function with out a problem because we are specifying by type. This allows us to keep or structs and not need any inheritance or nasty base classes.

Our pirate angry at his defeat has gone to gather his crew....

var pirateLarry = Pirate(attackStrength: 10)
var pirateLaura = Pirate(attackStrength: 10)
var pirateLinda = Pirate(attackStrength: 10)
var pirateLionel = Pirate(attackStrength: 10)

let pirateCrew = [pirateDave, pirateLarry, pirateLaura, pirateLinda, pirateLionel]

The crew our out to find ninjaSteve. They now plan to attack as a group and exact some sweet piratey revenge! In order to see the exciting conclusion I will update my protocol to allow a Fighter to be attacked by a group of other Fighters

protocol Fighter {
    init(attackStrength: Int)
    var attackStrength: Int {get}
    var health: Int {get set}
    mutating func attackedByFighter(fighter: Fighter)
    mutating func attackedByFighterGang(fighterGang: [Fighter])
}

and I will add the following implementation to my Ninja struct

mutating func attackedByFighterGang(fighterGang: [Fighter]) {
    for fighter in fighterGang {
        attackedByFighter(fighter)
    }
}

Everything is all all set for the attack which we can initialise like so.....

ninjaSteve.attackedByFighterGang(pirateCrew)   //Error

Or not we get a compiler error saying cannot cast type of [Pirate] to type of [Fighter]. This worked fine for individual elements but when we create an Array they Type of the array cannot be automatically inferred to a common protocol. We could make this code work by informing the compiler that the array is an array of fighters

let pirateCrew: [Fighter] = [pirateDave, pirateLarry, pirateLaura, pirateLinda, pirateLionel]
ninjaSteve.attackedByFighterGang(pirateCrew)  //All Good

This now works, but at a cost we have to sacrifice our pirate crews identities as pirates I can now no longer call drinkRum for every pirate in pirate crew as pirate crew can only guarantee that they are fighters, this is upsetting for me as I bloody love rum!

So lets try and solve the issue using Generics, at present the best solution I have found involves me updating the method signature of my attackedByFighterGang function to the following

mutating func attackedByFighterGang <T: Fighter>(fighterGang: Array<T>) 

This solves my compiler issues and holds on to type safety with out casting everywhere.