added modal reroll

This commit is contained in:
Rory Hinnen
2026-09-07 10:07:07 -05:00
parent 17f2b0a288
commit a4d1634df7
5 changed files with 153 additions and 18 deletions
+41 -17
View File
@@ -104,11 +104,19 @@ struct MainView: View {
.padding() .padding()
} }
Spacer() Spacer()
VStack{ VStack {
Text("Reroll") Text("Reroll")
Button("🎲", action: { Picker("", selection: $current.saveRerollMode) {
current.dice.roll(howMany: current.total) ForEach(RerollMode.allCases, id: \.self) { mode in
}) Text(mode.label).tag(mode)
}
}
.pickerStyle(.segmented)
.frame(width: 180)
Button("🎲") {
current.applyReroll()
}
.disabled(current.saveRerollMode == .none || current.saveHasRerolled)
.padding() .padding()
.font(.system(size: controlSize)) .font(.system(size: controlSize))
} }
@@ -173,13 +181,21 @@ struct MainView: View {
Text("Modifier") Text("Modifier")
} }
Spacer() Spacer()
VStack{ VStack {
Text("Reroll") Text("Reroll")
Button("🎲", action: { Picker("", selection: $current.woundRerollMode) {
current.dice.roll(howMany: current.total) ForEach(RerollMode.allCases, id: \.self) { mode in
}) Text(mode.label).tag(mode)
.padding() }
.font(.system(size: controlSize)) }
.pickerStyle(.segmented)
.frame(width: 180)
Button("🎲") {
current.applyReroll()
}
.disabled(current.woundRerollMode == .none || current.woundHasRerolled)
.padding()
.font(.system(size: controlSize))
} }
Spacer() Spacer()
}.padding() }.padding()
@@ -223,13 +239,21 @@ struct MainView: View {
.font(.system(size: controlSize)) .font(.system(size: controlSize))
} }
VStack{ VStack {
Text("Reroll") Text("Reroll")
Button("🎲", action: { Picker("", selection: $current.hitRerollMode) {
current.dice.roll(howMany: current.total) ForEach(RerollMode.allCases, id: \.self) { mode in
}) Text(mode.label).tag(mode)
.padding() }
.font(.system(size: controlSize)) }
.pickerStyle(.segmented)
.frame(width: 180)
Button("🎲") {
current.applyReroll()
}
.disabled(current.hitRerollMode == .none || current.hitHasRerolled)
.padding()
.font(.system(size: controlSize))
} }
} }
}.padding() }.padding()
@@ -304,7 +328,7 @@ struct MainView: View {
buttons(color: blue, label: "Pool", value: current.total) buttons(color: blue, label: "Pool", value: current.total)
} }
.sheet(isPresented: $showFillView, .sheet(isPresented: $showFillView,
onDismiss: {current.dice.roll(howMany: current.total)}) { onDismiss: { current.resetPool() }) {
VStack { VStack {
AddToPool() AddToPool()
Button("Accept", action: {showFillView.toggle()}) Button("Accept", action: {showFillView.toggle()})
+19 -1
View File
@@ -279,6 +279,25 @@ struct Dice {
} }
} }
mutating func rerollBelow(cutoff: Int) {
var count = 0
for faceValue in 1..<cutoff {
guard let face = DiceType(rawValue: faceValue) else { continue }
count += value(which: face)
switch face {
case .one: one = 0
case .two: two = 0
case .three: three = 0
case .four: four = 0
case .five: five = 0
case .six: six = 0
}
}
var rerolls = Dice()
rerolls.roll(howMany: count)
add(pool: rerolls)
}
mutating func blowup(dice: DiceType) { mutating func blowup(dice: DiceType) {
var rolls = Dice() var rolls = Dice()
if dice == .six { if dice == .six {
@@ -291,4 +310,3 @@ struct Dice {
} }
} }
+81
View File
@@ -78,6 +78,19 @@ enum Appearance: Int, CaseIterable, Identifiable {
} }
} }
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 // MARK: Structs
struct Event: Identifiable { struct Event: Identifiable {
let id: UUID let id: UUID
@@ -130,6 +143,14 @@ class Roll: ObservableObject {
@Published var currentPage: String @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 // calculated variables
var totalDigits: [Int] { var totalDigits: [Int] {
get { get {
@@ -209,6 +230,22 @@ class Roll: ObservableObject {
} }
} }
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
init() { init() {
if !UserDefaults.standard.bool(forKey: "didLaunchBefore") { if !UserDefaults.standard.bool(forKey: "didLaunchBefore") {
@@ -295,17 +332,61 @@ class Roll: ObservableObject {
case .hit: case .hit:
self.total = hit self.total = hit
self.state = .wound self.state = .wound
woundHasRerolled = false
case .wound: case .wound:
self.total = wound self.total = wound
self.state = .armour self.state = .armour
saveHasRerolled = false
case .armour: case .armour:
self.total = save self.total = save
self.state = .hit self.state = .hit
hitHasRerolled = false
} }
self.dice.roll(howMany: self.total) self.dice.roll(howMany: self.total)
} }
// MARK: extra // 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) { func add(howMuch: Int) {
var newTotal: Int = 0 var newTotal: Int = 0
newTotal = self.total + howMuch newTotal = self.total + howMuch
+2
View File
@@ -45,6 +45,7 @@
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
768C2F3A304F04E6002FEA9A /* Goals.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Goals.md; sourceTree = "<group>"; };
7692E8422BD55CDE005BDD76 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; }; 7692E8422BD55CDE005BDD76 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
980EF453285E2BAD002FE8A0 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; }; 980EF453285E2BAD002FE8A0 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
981AD0BA27C30E31005A6492 /* Bunch O' Dice.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Bunch O' Dice.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 981AD0BA27C30E31005A6492 /* Bunch O' Dice.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Bunch O' Dice.app"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -97,6 +98,7 @@
981AD0B127C30E31005A6492 = { 981AD0B127C30E31005A6492 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
768C2F3A304F04E6002FEA9A /* Goals.md */,
981AD0BC27C30E31005A6492 /* Bunch-O--Dice */, 981AD0BC27C30E31005A6492 /* Bunch-O--Dice */,
981AD0CD27C30E32005A6492 /* Bunch O' DiceTests */, 981AD0CD27C30E32005A6492 /* Bunch O' DiceTests */,
981AD0D727C30E32005A6492 /* Bunch O' DiceUITests */, 981AD0D727C30E32005A6492 /* Bunch O' DiceUITests */,
+10
View File
@@ -0,0 +1,10 @@
- [ ] Update graphics to clarify roll context (to hit / to wound / armor save) #focus/professional
- [ ] Add stacking flat hit/wound modifier input, capped at net ±1 #focus/professional
- [ ] Add reroll mode selector (none / reroll 1s / reroll all failed / reroll all) #focus/professional
- [ ] Add critical-trigger layer keyed off unmodified natural 6s (Sustained Hits, Lethal Hits, Devastating Wounds, Anti-X) #focus/professional
- [ ] Add "ignore all modifiers" flag (Psychic) #focus/professional
- [ ] Add "auto-success, no roll" flag (Torrent) #focus/professional
- Android
- [ ] Android: set up official Swift-on-Android toolchain. #focus/professional #android
- [ ] Android: extract game logic (dice/modifier engine) into shared Swift library #focus/professional #android
- [ ] Android: rewrite UI natively in Jetpack Compose #focus/professional #android