update view, still work to do

This commit is contained in:
Rory Hinnen
2026-09-08 17:49:59 -05:00
parent 241a753879
commit b1d4094f78
9 changed files with 201 additions and 204 deletions
+33 -14
View File
@@ -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
+6
View File
@@ -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 }
-20
View File
@@ -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()
}
}