Hello there again.
i am still working on my text-based RPG, and I am having another problem with it.
When I run the game, which currently consists of only one area (I have not programmed in the way the switch areas), things seem to run right, like I can view my equipment, stats, inventory, save, quit, and such, but while playing through the game, it sometimes crashes during battle, while saying that an unwrapped variable is nil, and it always occurs when it is the opponent's turn.
In the battle system, I have four things to choose from, which is attack, defend (does not do anything at this point), use an item, or flee, and I made it so that a random number determines what the enemy does, putting the bounds on the size of the array of choices, so there should never be a nil, yet one tends to come up.
Any ideas on what I need to do to fix this?
According to my thinking, the problem should be in the following function, which takes care of the enemy's turn.
func enemyTurn() {
var choice = RandomGen(number: BattleEvent.allValues.count).number
if (combatants[1].inventory.items.count == 0 && BattleEvent.allValues[choice!] == "item") {
repeat {
choice = RandomGen(number: BattleEvent.allValues.count).number
} while (choice == BattleEvent.allValues.index(of: "item"))
self.event = BattleEvent.allValues[choice!]
self.outcome = self.action(source: combatants[1], target: combatants[0], choice: self.event, itemChoice: nil)
} else if (combatants[1].inventory.items.count > 0 && BattleEvent.allValues[choice!] == "item")
{
var items: [String:Int] = [:]
var itemOptions: [String] = [String]()
for item in combatants[0].inventory.items {
let keyExists = items[item.name] != nil
if (!keyExists) {
items[item.name] = 1
} else {
items[item.name] = items[item.name]!+1
}
}
// loop that is only meant to grab key from dictionary
for (item, _) in items {
itemOptions.append(item)
}
let itemChoice = RandomGen(number: itemOptions.count).number
self.event = BattleEvent.allValues[choice!]
self.outcome = self.action(source: combatants[1], target: combatants[0], choice: self.event, itemChoice: combatants[1].inventory.items[combatants[1].inventory.items.index(where: {$0.name.caseInsensitiveCompare(itemOptions[itemChoice!]) == .orderedSame})!])
} else {
self.event = BattleEvent.allValues[choice!]
self.outcome = self.action(source: combatants[1], target: combatants[0], choice: self.event, itemChoice: nil)
}
} // end function
However, I have tried a bunch of ideas, like checking if the random number is not nil or the array element is not nil, as well as using '??' to make '0' be default, but it tends to put out nil a lot.
If the character attacks, this function runs.
func attack(enemy: Character) -> Int {
var dmg: Int = 0 // variable to hold damage
if (RandomGen(number: 201).number > Int(enemy.evasion)) {
dmg = self.attack
if (self.gear.weapon != nil && self.gear.weapon.count != 0) {
dmg += self.inventory.weapons[self.inventory.weapons.index(where: { $0.name.caseInsensitiveCompare(self.gear.weapon) == .orderedSame})!].damage
}
var cover = 0 // variable for enemy defense
if (enemy.gear.head != nil && self.gear.head.count != 0) {
cover += enemy.inventory.armor[enemy.inventory.armor.index(where: { $0.name.caseInsensitiveCompare(enemy.gear.head) == .orderedSame})!].defense
}
if (enemy.gear.torso != nil && self.gear.torso.count != 0) {
cover += enemy.inventory.armor[enemy.inventory.armor.index(where: { $0.name.caseInsensitiveCompare(enemy.gear.torso) == .orderedSame})!].defense
}
if (enemy.gear.legs != nil && self.gear.legs.count != 0) {
cover += enemy.inventory.armor[enemy.inventory.armor.index(where: { $0.name.caseInsensitiveCompare(enemy.gear.legs) == .orderedSame})!].defense
}
// create 1/32 change of a critical hit, ignoring defense
if (RandomGen(number: 32).number == 0) {
dmg = RandomGen(number: dmg*2).number
} else {
let baseDmg = dmg - cover/2
dmg = RandomGen(number: baseDmg/4).number
if (dmg < 1) {
dmg = RandomGen(number: 2).number
}
}
enemy.hp -= dmg
}
return dmg
} // end function
Since I am now not too sure where to look, here is the code that takes care of the battle.
func activate() -> Bool {
// determine who goes first, player being highest priority
print("HP: " + String(combatants[0].hp) + "/" + String(combatants[0].maxHp)) // make sure player HP is always displayed
if (combatants[0].agility >= combatants[1].agility) {
self.playerTurn() // initiate player turn
// determine what player did and print message based on that action
if (self.event == "attack") {
if (self.outcome > 0) {
print(combatants[0].name + " dealt " + String(self.outcome) + " points of damage to " + combatants[1].name + "'s health.")
} else {
print(combatants[0].name + "'s attack did not connect.")
}
} else if (self.event == "defend") {
print(combatants[0].name + " decided to defend.")
} else if (self.event == "item") {
print(combatants[0].name + " healed themselves by " + String(self.outcome) + " points.")
} else {
if (self.outcome != 0) {
print(combatants[0].name + " fled from battle.")
return false
} else {
print(combatants[0].name + " tried to flee from battle, but " + combatants[1].name + " prevented their escape.")
}
}
// check if opponent is dead
if (!combatants[1].isAlive()) {
return false
}
This function takes care of executing each choice:
func action(source: Character!, target: Character!, choice: String!, itemChoice: Item?) -> Int {
if let selection = BattleEvent(rawValue: choice) {
switch selection {
case .attack: return source.attack(enemy: target)
case .defend: return 0
case .item: return source.useItem(item: itemChoice!)
case .flee: return source.flee()
}
}
return 0
} // end function
Also, as I originally wrote this code on Linux, before being forced to test on a Mac environment. I had to create a custom class to deal with Random number generation, in order to cut down on instances of having to check for Linux, so here is the class for how my random number is being generated.
import Foundation
#if os(Linux)
import Glibc
#else
import Darwin
#endif
class RandomGen {
var number: Int!
init(number: Int) {
#if os(Linux)
self.number = random() % number
#else
self.number = Int(arc4random_uniform(UInt32(number)))
#endif
}
}
This should be all that should be needed, but in case it is not, I decided to break down and upload my code to Github, though I usually only put up stuff I got working there, and all the files can be found on this page.
Most of the code comes from the battle class and the attack function comes from the character class.