swiftstructswift4local-variablesmutating-function

Struct mutating func calling a func in another instance of the same struct


I was learning about structures and classes in swift today and decided to try and use my newly found knowledge to streamline a combat calculator I had built previously. Now I understand that the properties called in a func are local variable by nature and even more so ‘let’ constants. So I know why the below code has an error but what I can’t figure out is how to accomplish my goal without adding a whole lot more complexity to my code using the nil coalescing operator (??).

Any advice would be very much appreciated.

import Foundation
import Glibc


struct Unit {
   enum UnitType: String {
       case sniper
       case shocktrooper
       case infantry
       case support
   }
   let name: String
   let type: UnitType 
   var hitPoints: Int
   let attackStrength: Int

   //attack another unit
   mutating func attack(target: Unit) {
       print("\(self.name) is attacking \(target.name)...")
       if self.attackStrength > target.attackStrength {
           print("\(self.name) hit \(target.name) for
\(self.attackStrength) points of damage!")
           target.hit(target, self.attackStrength) /*error: cannont use
muatating member on imutable value: 'target is a 'let' constant */
       } else {
           self.repelled(by: target.attackStrength)
       }
   }
   //take damage from defender
   mutating func repelled(by damage: Int) {
       self.hitPoints -= damage
       print("\(name) was repelled and took \(damage) points of damage!")
   }
   //take damage from attack
   mutating func hit(for damage: Int) {
       self.hitPoints -= damage
   }
}

//declaring two seperate units
var player1 = Unit(name: "Player 1", type: .sniper, hitPoints: 10,
attackStrength: 3)
var player2 = Unit(name: "Player 2", type: .shocktrooper, hitPoints: 15,
attackStrength: 2)


func score() {
   print("The current hitpoints are: \(player1.name): \(player1.hitPoints)
& \(player2.name): \(player2.hitPoints)")
}

player1.attack(target: player2)
score()

Solution

  • You just need to use inout keyword to be able to mutate your player2:

    struct Unit {
        enum Kind: String {
            case sniper, shocktrooper, infantry, support
        }
        let name: String
        let kind: Kind
        var hitPoints: Int
        let attackStrength: Int
        mutating func attack(target: inout Unit) {
            print("\(name) is attacking \(target.name)...")
            if attackStrength > target.attackStrength {
                print("\(name) hit \(target.name) for \(attackStrength) points of damage!")
                target.hit(for: attackStrength)
            } else {
                repelled(by: target.attackStrength)
            }
        }
        mutating func repelled(by damage: Int) {
            hitPoints -= damage
            print("\(name) was repelled and took \(damage) points of damage!")
        }
        mutating func hit(for damage: Int) {
            hitPoints -= damage
        }
    }
    

    Playground testing

    var player1 = Unit(name: "Player 1", kind: .sniper, hitPoints: 10,  attackStrength: 3)
    var player2 = Unit(name: "Player 2", kind: .shocktrooper, hitPoints: 15, attackStrength: 2)
    func score() {
        print("The current hitpoints are: \(player1.name): \(player1.hitPoints) & \(player2.name): \(player2.hitPoints)")
    }
    player1.attack(target: &player2)
    score()
    

    This will print

    Player 1 is attacking Player 2...

    Player 1 hit Player 2 for 3 points of damage!

    The current hitpoints are: Player 1: 10 & Player 2: 12


    Another option you have is to use a class instead of a struct. You would need to provide a custom initializer to your class and remove the mutating keyword from your methods

    class Unit {
        enum Kind: String {
            case sniper, shocktrooper, infantry, support
        }
        let name: String
        let kind: Kind
        var hitPoints: Int
        let attackStrength: Int
        init(name: String, kind: Kind, hitPoints: Int,  attackStrength: Int){
            self.name = name
            self.kind = kind
            self.hitPoints = hitPoints
            self.attackStrength = attackStrength
        }
        func attack(target: Unit) {
            print("\(name) is attacking \(target.name)...")
            if attackStrength > target.attackStrength {
                print("\(name) hit \(target.name) for \(attackStrength) points of damage!")
                target.hit(for: attackStrength)
            } else {
                repelled(by: target.attackStrength)
            }
        }
        func repelled(by damage: Int) {
            hitPoints -= damage
            print("\(name) was repelled and took \(damage) points of damage!")
        }
        func hit(for damage: Int) {
            hitPoints -= damage
        }
    }
    let player1 = Unit(name: "Player 1", kind: .sniper, hitPoints: 10,  attackStrength: 3)
    let player2 = Unit(name: "Player 2", kind: .shocktrooper, hitPoints: 15, attackStrength: 2)
    func score() {
        print("The current hitpoints are: \(player1.name): \(player1.hitPoints) & \(player2.name): \(player2.hitPoints)")
    }
    player1.attack(target: player2)
    score()