update view, still work to do
This commit is contained in:
@@ -151,6 +151,11 @@ class Roll: ObservableObject {
|
||||
@Published var woundHasRerolled = false
|
||||
@Published var saveHasRerolled = false
|
||||
|
||||
// Snapshot of dice before the current phase's reroll was applied.
|
||||
// Restored when the user switches reroll modes, so the new mode
|
||||
// always applies to the original un-rerolled results.
|
||||
@Published var preRerollDice: Dice? = nil
|
||||
|
||||
@Published var blowupUsed = false
|
||||
@Published var torrent = false
|
||||
@Published var psychic = false
|
||||
@@ -209,17 +214,7 @@ class Roll: ObservableObject {
|
||||
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
|
||||
}
|
||||
return Dice.DiceType(rawValue: woundCutoff) ?? .four
|
||||
case .armour:
|
||||
return Dice.DiceType(rawValue: self.armourSave) ?? .one
|
||||
}
|
||||
@@ -376,6 +371,7 @@ class Roll: ObservableObject {
|
||||
self.total = max(0, hit - autoWoundsCarried) + sustained
|
||||
self.state = .wound
|
||||
woundHasRerolled = false
|
||||
preRerollDice = nil
|
||||
case .wound:
|
||||
let mortals = critWoundCount
|
||||
mortalWoundsTotal = mortals
|
||||
@@ -383,10 +379,12 @@ class Roll: ObservableObject {
|
||||
autoWoundsCarried = 0
|
||||
self.state = .armour
|
||||
saveHasRerolled = false
|
||||
preRerollDice = nil
|
||||
case .armour:
|
||||
self.total = save
|
||||
self.state = .hit
|
||||
hitHasRerolled = false
|
||||
preRerollDice = nil
|
||||
blowupUsed = false
|
||||
mortalWoundsTotal = 0
|
||||
}
|
||||
@@ -397,6 +395,7 @@ class Roll: ObservableObject {
|
||||
func resetPool() {
|
||||
dice.roll(howMany: total)
|
||||
hitHasRerolled = false
|
||||
preRerollDice = nil
|
||||
blowupUsed = false
|
||||
}
|
||||
|
||||
@@ -414,8 +413,6 @@ class Roll: ObservableObject {
|
||||
}
|
||||
|
||||
func applyReroll() {
|
||||
guard !currentHasRerolled else { return }
|
||||
|
||||
let mode: RerollMode
|
||||
let cutoff: Int
|
||||
|
||||
@@ -431,9 +428,19 @@ class Roll: ObservableObject {
|
||||
cutoff = invulnerable ? armourSave : (armourSave + armourPenetration)
|
||||
}
|
||||
|
||||
guard mode != .none else { return }
|
||||
|
||||
// If a reroll was already applied this phase, restore the original dice first
|
||||
// so the new mode always operates on the un-rerolled results.
|
||||
if let snapshot = preRerollDice {
|
||||
dice = snapshot
|
||||
} else {
|
||||
preRerollDice = dice
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case .none:
|
||||
return
|
||||
break
|
||||
case .ones:
|
||||
dice.reroll(dice: .one)
|
||||
case .failed:
|
||||
@@ -449,6 +456,18 @@ class Roll: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
// Restores dice to pre-reroll state when the user selects None.
|
||||
func undoReroll() {
|
||||
guard let snapshot = preRerollDice else { return }
|
||||
dice = snapshot
|
||||
preRerollDice = nil
|
||||
switch state {
|
||||
case .hit: hitHasRerolled = false
|
||||
case .wound: woundHasRerolled = false
|
||||
case .armour: saveHasRerolled = false
|
||||
}
|
||||
}
|
||||
|
||||
func add(howMuch: Int) {
|
||||
var newTotal: Int = 0
|
||||
newTotal = self.total + howMuch
|
||||
|
||||
@@ -31,3 +31,9 @@ extension CaseIterable where Self: Equatable {
|
||||
self = allCases[nextIndex == allCases.endIndex ? allCases.startIndex : nextIndex]
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
func disabledWithOpacity(_ condition: Bool) -> some View {
|
||||
self.disabled(condition).opacity(condition ? 0.4 : 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,49 @@ struct MainView: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func rerollPicker(mode: Binding<RerollMode>) -> some View {
|
||||
let actionBinding = Binding<RerollMode>(
|
||||
get: { mode.wrappedValue },
|
||||
set: { newValue in
|
||||
mode.wrappedValue = newValue
|
||||
if newValue != .none {
|
||||
current.applyReroll()
|
||||
} else {
|
||||
current.undoReroll()
|
||||
}
|
||||
}
|
||||
)
|
||||
HStack {
|
||||
Text("Reroll")
|
||||
Picker("", selection: actionBinding) {
|
||||
ForEach(RerollMode.allCases, id: \.self) { m in
|
||||
Text(m.label).tag(m)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
@ViewBuilder private func statStepper(
|
||||
top: String,
|
||||
value: String,
|
||||
bottom: String,
|
||||
increment: @escaping () -> Void,
|
||||
decrement: @escaping () -> Void
|
||||
) -> some View {
|
||||
VStack {
|
||||
Text(top)
|
||||
Group {
|
||||
Text("▲").gesture(TapGesture().onEnded { increment() })
|
||||
Text(value)
|
||||
Text("▼").gesture(TapGesture().onEnded { decrement() })
|
||||
}
|
||||
.font(.system(size: controlSize))
|
||||
Text(bottom)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder func buttons(color: CGColor, label: String, value: Int) -> some View {
|
||||
let frameSize = 120.0
|
||||
let fontSize = 35.0
|
||||
@@ -79,25 +122,13 @@ struct MainView: View {
|
||||
VStack {
|
||||
HStack(alignment: .top) {
|
||||
Spacer()
|
||||
VStack {
|
||||
Text("Armour")
|
||||
Group {
|
||||
Text("▲")
|
||||
.gesture(TapGesture() .onEnded {
|
||||
current.addPenetration()
|
||||
})
|
||||
Text(
|
||||
(current.armourPenetration > 0 ?
|
||||
"+" : "") +
|
||||
String(current.armourPenetration))
|
||||
Text("▼")
|
||||
.gesture(TapGesture() .onEnded {
|
||||
current.losePenetration()
|
||||
})
|
||||
}
|
||||
.font(.system(size: controlSize))
|
||||
Text("Penetration")
|
||||
}
|
||||
statStepper(
|
||||
top: "Armour",
|
||||
value: (current.armourPenetration > 0 ? "+" : "") + String(current.armourPenetration),
|
||||
bottom: "Penetration",
|
||||
increment: { current.addPenetration() },
|
||||
decrement: { current.losePenetration() }
|
||||
)
|
||||
Spacer()
|
||||
VStack {
|
||||
Text("Invulnerable")
|
||||
@@ -106,21 +137,7 @@ struct MainView: View {
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
HStack {
|
||||
Text("Reroll")
|
||||
Picker("", selection: $current.saveRerollMode) {
|
||||
ForEach(RerollMode.allCases, id: \.self) { mode in
|
||||
Text(mode.label).tag(mode)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
Button("🎲") {
|
||||
current.applyReroll()
|
||||
}
|
||||
.disabled(current.saveRerollMode == .none || current.saveHasRerolled)
|
||||
.font(.system(size: controlSize))
|
||||
}
|
||||
.padding(.horizontal)
|
||||
rerollPicker(mode: $current.saveRerollMode)
|
||||
if current.mortalWoundsTotal > 0 {
|
||||
HStack {
|
||||
Text("Mortal wounds: \(current.mortalWoundsTotal) (bypassed saves)")
|
||||
@@ -149,74 +166,32 @@ struct MainView: View {
|
||||
}
|
||||
HStack(alignment: .top) {
|
||||
Spacer()
|
||||
VStack {
|
||||
Text("Attack")
|
||||
Group {
|
||||
Text("▲")
|
||||
.gesture(TapGesture() .onEnded {
|
||||
current.addStrength()
|
||||
})
|
||||
Text(String(current.strength))
|
||||
Text("▼")
|
||||
.gesture(TapGesture() .onEnded {
|
||||
current.loseStrength()
|
||||
})
|
||||
}.font(.system(size: controlSize))
|
||||
Text("Strength")
|
||||
}
|
||||
statStepper(
|
||||
top: "Attack",
|
||||
value: String(current.strength),
|
||||
bottom: "Strength",
|
||||
increment: { current.addStrength() },
|
||||
decrement: { current.loseStrength() }
|
||||
)
|
||||
Spacer()
|
||||
VStack {
|
||||
Text("Toughness")
|
||||
Group {
|
||||
Text("▲")
|
||||
.gesture(TapGesture() .onEnded {
|
||||
current.addToughness()
|
||||
})
|
||||
Text(String(current.toughness))
|
||||
Text("▼")
|
||||
.gesture(TapGesture() .onEnded {
|
||||
current.loseToughness()
|
||||
})
|
||||
}.font(.system(size: controlSize))
|
||||
Text(" ")
|
||||
}
|
||||
statStepper(
|
||||
top: "Toughness",
|
||||
value: String(current.toughness),
|
||||
bottom: " ",
|
||||
increment: { current.addToughness() },
|
||||
decrement: { current.loseToughness() }
|
||||
)
|
||||
Spacer()
|
||||
VStack {
|
||||
Text("Wound")
|
||||
.multilineTextAlignment(.center)
|
||||
Group {
|
||||
Text("▲")
|
||||
.gesture(TapGesture() .onEnded {
|
||||
current.woundModifier = current.woundModifier.add()
|
||||
})
|
||||
Text(
|
||||
(current.woundModifier.rawValue > 0 ?
|
||||
"+" : "") +
|
||||
String(current.woundModifier.rawValue))
|
||||
Text("▼")
|
||||
.gesture(TapGesture() .onEnded {
|
||||
current.woundModifier = current.woundModifier.remove()
|
||||
})
|
||||
}.font(.system(size: controlSize))
|
||||
Text("Modifier")
|
||||
}
|
||||
statStepper(
|
||||
top: "Wound",
|
||||
value: (current.woundModifier.rawValue > 0 ? "+" : "") + String(current.woundModifier.rawValue),
|
||||
bottom: "Modifier",
|
||||
increment: { current.woundModifier = current.woundModifier.add() },
|
||||
decrement: { current.woundModifier = current.woundModifier.remove() }
|
||||
)
|
||||
Spacer()
|
||||
}
|
||||
HStack {
|
||||
Text("Reroll")
|
||||
Picker("", selection: $current.woundRerollMode) {
|
||||
ForEach(RerollMode.allCases, id: \.self) { mode in
|
||||
Text(mode.label).tag(mode)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
Button("🎲") {
|
||||
current.applyReroll()
|
||||
}
|
||||
.disabled(current.woundRerollMode == .none || current.woundHasRerolled)
|
||||
.font(.system(size: controlSize))
|
||||
}
|
||||
.padding(.horizontal)
|
||||
rerollPicker(mode: $current.woundRerollMode)
|
||||
VStack(spacing: 8) {
|
||||
HStack {
|
||||
Toggle("Devastating Wounds", isOn: $current.devastatingWounds)
|
||||
@@ -240,26 +215,14 @@ struct MainView: View {
|
||||
VStack {
|
||||
HStack(alignment: .top) {
|
||||
Spacer()
|
||||
VStack {
|
||||
Text("Hit")
|
||||
.multilineTextAlignment(.center)
|
||||
Group {
|
||||
Text("▲")
|
||||
.gesture(TapGesture() .onEnded {
|
||||
current.modifier = current.modifier.add()
|
||||
})
|
||||
Text((current.modifier.rawValue > 0 ? "+" : "") +
|
||||
String(current.modifier.rawValue))
|
||||
Text("▼")
|
||||
.gesture(TapGesture() .onEnded {
|
||||
current.modifier = current.modifier.remove()
|
||||
})
|
||||
}
|
||||
.font(.system(size: controlSize))
|
||||
Text("Modifier")
|
||||
}
|
||||
.disabled(current.torrent || current.psychic)
|
||||
.opacity((current.torrent || current.psychic) ? 0.4 : 1.0)
|
||||
statStepper(
|
||||
top: "Hit",
|
||||
value: (current.modifier.rawValue > 0 ? "+" : "") + String(current.modifier.rawValue),
|
||||
bottom: "Modifier",
|
||||
increment: { current.modifier = current.modifier.add() },
|
||||
decrement: { current.modifier = current.modifier.remove() }
|
||||
)
|
||||
.disabledWithOpacity(current.torrent || current.psychic)
|
||||
Spacer()
|
||||
VStack {
|
||||
Text("Blow Up")
|
||||
@@ -276,45 +239,38 @@ struct MainView: View {
|
||||
.padding()
|
||||
.font(.system(size: controlSize))
|
||||
}
|
||||
.disabled(current.blowupUsed || current.torrent)
|
||||
.opacity((current.blowupUsed || current.torrent) ? 0.4 : 1.0)
|
||||
.disabledWithOpacity(current.blowupUsed || current.torrent)
|
||||
Spacer()
|
||||
}
|
||||
rerollPicker(mode: $current.hitRerollMode)
|
||||
.disabledWithOpacity(current.torrent)
|
||||
HStack {
|
||||
Text("Reroll")
|
||||
Picker("", selection: $current.hitRerollMode) {
|
||||
ForEach(RerollMode.allCases, id: \.self) { mode in
|
||||
Text(mode.label).tag(mode)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
Button("🎲") {
|
||||
current.applyReroll()
|
||||
}
|
||||
.disabled(current.hitRerollMode == .none || current.hitHasRerolled)
|
||||
.font(.system(size: controlSize))
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.disabled(current.torrent)
|
||||
.opacity(current.torrent ? 0.4 : 1.0)
|
||||
HStack {
|
||||
Toggle("Torrent", isOn: $current.torrent)
|
||||
Spacer()
|
||||
Toggle("Psychic", isOn: $current.psychic)
|
||||
.disabled(current.torrent)
|
||||
.opacity(current.torrent ? 0.4 : 1.0)
|
||||
VStack {
|
||||
Text("Torrent")
|
||||
Toggle("", isOn: $current.torrent).labelsHidden()
|
||||
}
|
||||
Spacer()
|
||||
VStack {
|
||||
Text("Psychic")
|
||||
Toggle("", isOn: $current.psychic).labelsHidden()
|
||||
.disabledWithOpacity(current.torrent)
|
||||
}
|
||||
Spacer()
|
||||
VStack {
|
||||
Text("Lethal Hits")
|
||||
Toggle("", isOn: $current.lethalHits).labelsHidden()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
VStack(spacing: 8) {
|
||||
HStack {
|
||||
Toggle("Lethal Hits", isOn: $current.lethalHits)
|
||||
Spacer()
|
||||
Toggle("Sust. Hits", isOn: $current.sustainedHits)
|
||||
}
|
||||
if current.sustainedHits {
|
||||
HStack {
|
||||
.fixedSize()
|
||||
Spacer()
|
||||
if current.sustainedHits {
|
||||
Text("X:")
|
||||
Spacer()
|
||||
Text("▲")
|
||||
.gesture(TapGesture().onEnded {
|
||||
if current.sustainedHitsX < 6 { current.sustainedHitsX += 1 }
|
||||
@@ -329,8 +285,9 @@ struct MainView: View {
|
||||
}
|
||||
HStack {
|
||||
Toggle("Anti-X", isOn: $current.antiX)
|
||||
.fixedSize()
|
||||
Spacer()
|
||||
if current.antiX {
|
||||
Spacer()
|
||||
Text("▲")
|
||||
.gesture(TapGesture().onEnded {
|
||||
if current.antiXThreshold < 6 { current.antiXThreshold += 1 }
|
||||
@@ -1,20 +0,0 @@
|
||||
//
|
||||
// dice.swift
|
||||
// Bunch O' Dice
|
||||
//
|
||||
// Created by Rory Hinnen on 6/10/23.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct dice: View {
|
||||
var body: some View {
|
||||
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
|
||||
}
|
||||
}
|
||||
|
||||
struct dice_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
dice()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user