416 lines
9.8 KiB
Swift
416 lines
9.8 KiB
Swift
//
|
|
// 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
|
|
|
|
@Published var blowupUsed = 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
|
|
blowupUsed = false
|
|
}
|
|
self.dice.roll(howMany: self.total)
|
|
}
|
|
|
|
// MARK: extra
|
|
func resetPool() {
|
|
dice.roll(howMany: total)
|
|
hitHasRerolled = false
|
|
blowupUsed = false
|
|
}
|
|
|
|
func applyBlowup(face: Dice.DiceType) {
|
|
guard !blowupUsed else { return }
|
|
switch face {
|
|
case .six:
|
|
dice.blowup(dice: .six)
|
|
case .five:
|
|
dice.blowup(dice: .five)
|
|
default:
|
|
break
|
|
}
|
|
blowupUsed = true
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|