// // State.swift // Bunch O' Dice // // Created by Rory Hinnen on 22/5/21. // import Foundation import SwiftUI // MARK: Extensions extension Int { func digits() -> [Int] { var digits: [Int] = [] var num = self digits.append(num % 10) repeat { num = num / 10 digits.append(num % 10) } while num >= 10 while digits.count < 3 { digits.append(0) } return digits } } // MARK: Enums enum RollState: Int { case hit case wound case armour func print() -> String { switch self { case .hit: return "⚔️" case .wound: return "🩸" case .armour: return "🛡" } } func text() -> String { switch self { case .hit: return "Hit" case .wound: return "Wound" case .armour: return "Damage" } } } enum Appearance: Int, CaseIterable, Identifiable { case light, dark, automatic var id: Int { self.rawValue } var name: String { switch self { case .light: return "Light" case .dark: return "Dark" case .automatic: return "Automatic" } } var type: ColorScheme? { switch self { case .light: return ColorScheme.light case .dark: return ColorScheme.dark case .automatic: return nil } } } enum RerollMode: Int, CaseIterable { case none, ones, failed, all var label: String { switch self { case .none: return "None" case .ones: return "1s" case .failed: return "Failed" case .all: return "All" } } } // MARK: Structs struct Event: Identifiable { let id: UUID var total: Int var pool: Dice var state: RollState var poolTotal: Int { get { return pool.one + pool.two + pool.three + pool.four + pool.five + pool.six } } init() { id = UUID() total = 0 pool = Dice() state = .hit } init(total: Int, pool: Dice, state: RollState) { self.id = UUID() self.total = total self.pool = pool self.state = state } } // MARK: Classes class Roll: ObservableObject { //Published @Published var dice = Dice() @Published var total = 0 @Published var state = RollState.hit @Published var history = [Event()] @Published var appearance = Appearance.automatic @Published var firstView = true @Published var hitCutoff = Dice.DiceType.four @Published var modifier = Dice.RollModifiers.none @Published var strength = 7 @Published var toughness = 5 @Published var woundModifier = Dice.RollModifiers.none @Published var mortal = false @Published var armourPenetration = 0 @Published var armourSave = 3 @Published var invulnerable = false @Published var currentPage: String @Published var hitRerollMode = RerollMode.none @Published var woundRerollMode = RerollMode.none @Published var saveRerollMode = RerollMode.none @Published var hitHasRerolled = false @Published var woundHasRerolled = false @Published var saveHasRerolled = false // calculated variables var totalDigits: [Int] { get { return total.digits() } } var result: Int { get { switch self.state { case .hit: return hit case .wound: return wound case .armour: return save } } } var woundCutoff: Int { get { if Float(self.strength) / Float(self.toughness) >= 2.0 { return 2 } else if Float(self.strength) / Float(self.toughness) > 1.0 { return 3 } else if Float(self.strength) / Float(self.toughness) == 1 { return 4 } else if Float(self.strength) / Float(self.toughness) <= 0.5 { return 6 } else { return 5 } } } var cutoff: Dice.DiceType { get { switch self.state { case .hit: return hitCutoff case .wound: if Float(self.strength) / Float(self.toughness) >= 2.0 { return .two } else if Float(self.strength) / Float(self.toughness) > 1.0 { return .three } else if Float(self.strength) / Float(self.toughness) == 1 { return .four } else if Float(self.strength) / Float(self.toughness) <= 0.5 { return .six } else { return .five } case .armour: return Dice.DiceType(rawValue: self.armourSave) ?? .one } } } var hit: Int { get { return self.dice.hit(die: total, cutoff: self.hitCutoff.rawValue, modifier: self.modifier.rawValue) } } var wound: Int { get { return self.dice.wound(die: total, mortal: self.mortal, cutoff: self.woundCutoff, modifier: woundModifier.rawValue) } } var save: Int { get { return self.dice.armour(die: total, invulnerable: self.invulnerable, save: self.armourSave, armourPenetration: self.armourPenetration) } } var currentRerollMode: RerollMode { switch state { case .hit: return hitRerollMode case .wound: return woundRerollMode case .armour: return saveRerollMode } } var currentHasRerolled: Bool { switch state { case .hit: return hitHasRerolled case .wound: return woundHasRerolled case .armour: return saveHasRerolled } } // init init() { if !UserDefaults.standard.bool(forKey: "didLaunchBefore") { UserDefaults.standard.set(true, forKey: "didLaunchBefore") currentPage = "OnboardingView" } else { currentPage = "Bunch" } } // modifier controllers // MARK: wound controllers func addStrength() { self.strength += 1 } func loseStrength() { if self.strength > 1 { self.strength -= 1 } } func addToughness() { self.toughness += 1 } func loseToughness() { if self.toughness > 1 { self.toughness -= 1 } } // MARK: armour controllers func addArmour() { if self.armourSave < 6 { self.armourSave += 1 } } func loseArmour() { if self.armourSave > 1 { self.armourSave -= 1 } } func addPenetration() { self.armourPenetration += 1 } func losePenetration() { self.armourPenetration -= 1 } // MARK: total pool func incrementDigits(place: Int) { if totalDigits[place] < 9 { self.total += Int(pow(10.0, Float(place))) } } func decrementDigits(place: Int) { if totalDigits[place] > 0 { self.total -= Int(pow(10.0, Float(place))) } } // MARK: utility function func incrementState() { var x = 0 switch self.state { case .hit: x = self.hit case .wound: x = self.wound case .armour: x = self.armourSave } if self.history[0].total == 0 { self.history[0] = Event(total: x, pool: self.dice, state: self.state) } else { self.history.insert(Event(total: x, pool: self.dice, state: self.state), at: 0) } switch self.state { case .hit: self.total = hit self.state = .wound woundHasRerolled = false case .wound: self.total = wound self.state = .armour saveHasRerolled = false case .armour: self.total = save self.state = .hit hitHasRerolled = false } self.dice.roll(howMany: self.total) } // MARK: extra func resetPool() { dice.roll(howMany: total) hitHasRerolled = false } func applyReroll() { guard !currentHasRerolled else { return } let mode: RerollMode let cutoff: Int switch state { case .hit: mode = hitRerollMode cutoff = hitCutoff.rawValue case .wound: mode = woundRerollMode cutoff = woundCutoff case .armour: mode = saveRerollMode cutoff = invulnerable ? armourSave : (armourSave - armourPenetration) } switch mode { case .none: return case .ones: dice.reroll(dice: .one) case .failed: dice.rerollBelow(cutoff: cutoff) case .all: dice.roll(howMany: total) } switch state { case .hit: hitHasRerolled = true case .wound: woundHasRerolled = true case .armour: saveHasRerolled = true } } func add(howMuch: Int) { var newTotal: Int = 0 newTotal = self.total + howMuch if newTotal < 0 { newTotal = 0 } self.total = newTotal } }