How to randomize value in a dictionary for Swift

Viewed 67

I've been trying to build a simple little random fighting game. The premise is to keep it simple as I try to expand and understand swift.

What I have been trying to do is have warriorAction/mageAction value be a random number each time the action is pressed. I feel that I have approached this wrong as despite declaring the key and values I go ahead and change them in the switch down below.

Is there anyway to create a dictionary so that each time the value is called that it will generate a random number or is there a better and less verbose way I could be doing this?

import UIKit

class ViewController: UIViewController {
    
    @IBOutlet weak var warriorHealthBar: UIProgressView!
    @IBOutlet weak var warriorMaxHitPoints: UILabel!
    @IBOutlet weak var warriorCurrentHitPoints: UILabel!
    
    
    @IBOutlet weak var mageHealthBar: UIProgressView!
    @IBOutlet weak var mageMaxHitPoints: UILabel!
    @IBOutlet weak var mageCurrentHitPoints: UILabel!
    
    @IBOutlet weak var fighterMessageUpdate: UILabel!
    
    let warriorAction: [String : Int] = ["Punch": 10, "Kick": 12, "Special": 25, "Heal": 15]
    let warriorMaxHealth = Int.random(in: 99...151)
    
    
    var mageAction: [String : Int] = ["Punch": 10, "Kick": 12, "Special": 25, "Heal": 15]
    let mageMaxHealth = Int.random(in: 89...121)
    
    var actionRandomizer = Int.random(in: 0...15)

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        warriorMaxHitPoints.text = String(warriorMaxHealth)
        warriorCurrentHitPoints.text = warriorMaxHitPoints.text
        
        mageMaxHitPoints.text = String(mageMaxHealth)
        mageCurrentHitPoints.text = mageMaxHitPoints.text
        
    }

    @IBAction func warriorActionPressed(_ sender: UIButton) {
        
        var warriorActionDamage = warriorAction[sender.currentTitle!]
        
        switch sender.currentTitle {
        case "Punch":
            warriorActionDamage = Int.random(in: 8...11)
        case "Kick":
            warriorActionDamage = Int.random(in: 10...14)
        case "Special":
            warriorActionDamage = Int.random(in: 5...20)
        case "Heal":
            warriorActionDamage = Int.random(in: 4...10)
        default:
            print ("Error")
        }
        
        print("You used \(String(describing: sender.currentTitle!)) and did \(String(describing: warriorActionDamage!)) damage to the mage")
        
        fighterMessageUpdate.text = "You used \(String(describing: sender.currentTitle!)) and did \(String(describing: warriorActionDamage!)) damage to the mage"
    }
    
    @IBAction func mageActionPressed(_ sender: UIButton) {
        
        var mageActionDamage = mageAction[sender.currentTitle!]
        
        switch sender.currentTitle {
        case "Punch":
            mageActionDamage = Int.random(in: 8...11)
        case "Kick":
            mageActionDamage = Int.random(in: 10...14)
        case "Special":
            mageActionDamage = Int.random(in: 5...20)
        case "Heal":
            mageActionDamage = Int.random(in: 4...10)
        default:
            print ("Error")
        }
        
        print("You used \(String(describing: sender.currentTitle!)) and did \(String(describing: mageActionDamage!)) damage to the warrior")
        
        fighterMessageUpdate.text = "You used \(String(describing: sender.currentTitle!)) and did \(String(describing: mageActionDamage!)) damage to the warrior"
    }
    
    
}
1 Answers

Your approach doesn't seem bad, you just want to extract your repeated configuration into a variable.

let damageRange = [
    "Punch": 8...11,
    "Kick": 10...14,
    "Special": 5...20,
    "Heal": 4...10,
]

if let range = damageRange[sender.currentTitle] {
    warriorActionDamage = Int.random(in: range)
}

You could make this even cleaner by extracting all your duplicated code into a function. Something along these lines:

func actionPressed(for action: String, to target: String) {
    if let range = damageRange[action] {
        let damage = Int.random(in: range)
        let string = "You used \(action) and did \(damage) damage to the \(target)"
        print(string)
        fighterMessageUpdate.text = string
    } else {
        print("Error")
    }
}

@IBAction func warriorActionPressed(_ sender: UIButton) {
    actionPressed(for: sender.currentTitle!, to: "mage")
}

@IBAction func mageActionPressed(_ sender: UIButton) {
    actionPressed(for: sender.currentTitle!, to: "warrior")
}

For a simple game like this, you're exactly right to keep it simple and not worry too much. As you happen to see duplication, just pull that out into functions. You can often tell you're duplicating something worth making into a function when you find yourself pressing Cmd-C.

I do notice that you have a "Heal" action, which I assume does negative damage to yourself rather than positive damage to your opponent. You absolutely can take care of this with a special case with an if statement. The simplest if statement would be if you make healing negative:

let damageRange = [
    "Punch": 8...11,
    "Kick": 10...14,
    "Special": 5...20,
    "Heal": -4...-10, // <<-----
]

And then you could look at the damage result and decide who to apply it to. And that's really simple, and definitely something to consider.


But what if attacks were fancier? Maybe you have some "energy drain" attack that takes points from your opponent and gives them to you. How would you handle that? Well, if you're a game designer, if statements... :D But if you want to do fancier things, here's how you might approach it:

struct Player {
    var health: Int = 100
    var name: String
}

// An Action applies effects to a source and/or a target
protocol Action {
    var name: String { get }
    func apply(source: inout Player, target: inout Player)
}

struct Kick: Action {
    let name = "Kick"
    func apply(source: inout Player, target: inout Player) {
        target.health -= .random(in: 10...14)
    }
}

struct Heal: Action {
    let name = "Heal"
    func apply(source: inout Player, target: inout Player) {
        source.health += .random(in: 4...10)
    }
}

struct Drain: Action {
    let name = "Drain"
    func apply(source: inout Player, target: inout Player) {
        let amount = Int.random(in: 4...10)
        source.health += amount
        target.health -= amount
    }
}

var warrior = Player(name: "warrior")
var mage = Player(name: "mage")

let actions: [Action] = [Kick(), Heal(), Drain()]

// And now to use them

let buttonTitle = "Drain"
let action = actions.first(where: { $0.name == buttonTitle })! // Or handle a missing action

action.apply(source: &warrior, target: &mage)
warrior.health  // 106
mage.health     // 94

But these are fancier techniques that you shouldn't let get in the way of building a fun game. They're just tools to help you get there as your game gets a little more complicated.

Related