diff --git a/Bunch O' Dice.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Bunch O' Dice.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/Bunch O' Dice.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/Bunch O' Dice/ContentView.swift b/Bunch O' Dice/ContentView.swift deleted file mode 100644 index ee76e45..0000000 --- a/Bunch O' Dice/ContentView.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// ContentView.swift -// Bunch O' Dice -// -// Created by Rory Hinnen on 22/2/20. -// - -import SwiftUI - -struct ContentView: View { - var body: some View { - Text("Hello, world!") - .padding() - } -} - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } -} diff --git a/Bunch-O--Dice-Info.plist b/Bunch-O--Dice-Info.plist new file mode 100644 index 0000000..bc11256 --- /dev/null +++ b/Bunch-O--Dice-Info.plist @@ -0,0 +1,8 @@ + + + + + ITSAppUsesNonExemptEncryption + + + diff --git a/Bunch O' Dice/Assets.xcassets/AccentColor.colorset/Contents.json b/Bunch-O--Dice/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from Bunch O' Dice/Assets.xcassets/AccentColor.colorset/Contents.json rename to Bunch-O--Dice/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/1024.png b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100644 index 0000000..97f8547 Binary files /dev/null and b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/120.png b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100644 index 0000000..5c84110 Binary files /dev/null and b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/120.png differ diff --git a/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/152.png b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/152.png new file mode 100644 index 0000000..830c231 Binary files /dev/null and b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/152.png differ diff --git a/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/167.png b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/167.png new file mode 100644 index 0000000..2c89554 Binary files /dev/null and b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/167.png differ diff --git a/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/180.png b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/180.png new file mode 100644 index 0000000..8d61b6b Binary files /dev/null and b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/180.png differ diff --git a/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/20.png b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/20.png new file mode 100644 index 0000000..f84e854 Binary files /dev/null and b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/20.png differ diff --git a/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/29.png b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100644 index 0000000..d38bb67 Binary files /dev/null and b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/29.png differ diff --git a/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/40.png b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 0000000..8120159 Binary files /dev/null and b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/40.png differ diff --git a/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/58.png b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100644 index 0000000..b4cbc66 Binary files /dev/null and b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/58.png differ diff --git a/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/60.png b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100644 index 0000000..6a4514d Binary files /dev/null and b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/60.png differ diff --git a/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/76.png b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/76.png new file mode 100644 index 0000000..78b14bc Binary files /dev/null and b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/76.png differ diff --git a/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/80.png b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100644 index 0000000..061b804 Binary files /dev/null and b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/80.png differ diff --git a/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/87.png b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/87.png new file mode 100644 index 0000000..0c62c22 Binary files /dev/null and b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/87.png differ diff --git a/Bunch O' Dice/Assets.xcassets/AppIcon.appiconset/Contents.json b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 75% rename from Bunch O' Dice/Assets.xcassets/AppIcon.appiconset/Contents.json rename to Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/Contents.json index 9221b9b..ad9ed1b 100644 --- a/Bunch O' Dice/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Bunch-O--Dice/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,91 +1,109 @@ { "images" : [ { + "filename" : "40.png", "idiom" : "iphone", "scale" : "2x", "size" : "20x20" }, { + "filename" : "60.png", "idiom" : "iphone", "scale" : "3x", "size" : "20x20" }, { + "filename" : "58.png", "idiom" : "iphone", "scale" : "2x", "size" : "29x29" }, { + "filename" : "87.png", "idiom" : "iphone", "scale" : "3x", "size" : "29x29" }, { + "filename" : "80.png", "idiom" : "iphone", "scale" : "2x", "size" : "40x40" }, { + "filename" : "120.png", "idiom" : "iphone", "scale" : "3x", "size" : "40x40" }, { + "filename" : "120.png", "idiom" : "iphone", "scale" : "2x", "size" : "60x60" }, { + "filename" : "180.png", "idiom" : "iphone", "scale" : "3x", "size" : "60x60" }, { + "filename" : "20.png", "idiom" : "ipad", "scale" : "1x", "size" : "20x20" }, { + "filename" : "40.png", "idiom" : "ipad", "scale" : "2x", "size" : "20x20" }, { + "filename" : "29.png", "idiom" : "ipad", "scale" : "1x", "size" : "29x29" }, { + "filename" : "58.png", "idiom" : "ipad", "scale" : "2x", "size" : "29x29" }, { + "filename" : "40.png", "idiom" : "ipad", "scale" : "1x", "size" : "40x40" }, { + "filename" : "80.png", "idiom" : "ipad", "scale" : "2x", "size" : "40x40" }, { + "filename" : "76.png", "idiom" : "ipad", "scale" : "1x", "size" : "76x76" }, { + "filename" : "152.png", "idiom" : "ipad", "scale" : "2x", "size" : "76x76" }, { + "filename" : "167.png", "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" }, { + "filename" : "1024.png", "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" diff --git a/Bunch O' Dice/Assets.xcassets/Contents.json b/Bunch-O--Dice/Assets.xcassets/Contents.json similarity index 100% rename from Bunch O' Dice/Assets.xcassets/Contents.json rename to Bunch-O--Dice/Assets.xcassets/Contents.json diff --git a/Bunch-O--Dice/Assets.xcassets/eight.imageset/Contents.json b/Bunch-O--Dice/Assets.xcassets/eight.imageset/Contents.json new file mode 100644 index 0000000..71a66a4 --- /dev/null +++ b/Bunch-O--Dice/Assets.xcassets/eight.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "armor.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Bunch-O--Dice/Assets.xcassets/eight.imageset/armor.png b/Bunch-O--Dice/Assets.xcassets/eight.imageset/armor.png new file mode 100644 index 0000000..ea67799 Binary files /dev/null and b/Bunch-O--Dice/Assets.xcassets/eight.imageset/armor.png differ diff --git a/Bunch-O--Dice/Assets.xcassets/five.imageset/Contents.json b/Bunch-O--Dice/Assets.xcassets/five.imageset/Contents.json new file mode 100644 index 0000000..8aef607 --- /dev/null +++ b/Bunch-O--Dice/Assets.xcassets/five.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "five.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Bunch-O--Dice/Assets.xcassets/five.imageset/five.png b/Bunch-O--Dice/Assets.xcassets/five.imageset/five.png new file mode 100644 index 0000000..a934686 Binary files /dev/null and b/Bunch-O--Dice/Assets.xcassets/five.imageset/five.png differ diff --git a/Bunch-O--Dice/Assets.xcassets/four.imageset/Contents.json b/Bunch-O--Dice/Assets.xcassets/four.imageset/Contents.json new file mode 100644 index 0000000..33add32 --- /dev/null +++ b/Bunch-O--Dice/Assets.xcassets/four.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "four.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Bunch-O--Dice/Assets.xcassets/four.imageset/four.png b/Bunch-O--Dice/Assets.xcassets/four.imageset/four.png new file mode 100644 index 0000000..9ca782a Binary files /dev/null and b/Bunch-O--Dice/Assets.xcassets/four.imageset/four.png differ diff --git a/Bunch-O--Dice/Assets.xcassets/nine.imageset/Contents.json b/Bunch-O--Dice/Assets.xcassets/nine.imageset/Contents.json new file mode 100644 index 0000000..e779c10 --- /dev/null +++ b/Bunch-O--Dice/Assets.xcassets/nine.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "nine.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Bunch-O--Dice/Assets.xcassets/nine.imageset/nine.png b/Bunch-O--Dice/Assets.xcassets/nine.imageset/nine.png new file mode 100644 index 0000000..57c629d Binary files /dev/null and b/Bunch-O--Dice/Assets.xcassets/nine.imageset/nine.png differ diff --git a/Bunch-O--Dice/Assets.xcassets/one.imageset/1024.png b/Bunch-O--Dice/Assets.xcassets/one.imageset/1024.png new file mode 100644 index 0000000..97f8547 Binary files /dev/null and b/Bunch-O--Dice/Assets.xcassets/one.imageset/1024.png differ diff --git a/Bunch-O--Dice/Assets.xcassets/one.imageset/Contents.json b/Bunch-O--Dice/Assets.xcassets/one.imageset/Contents.json new file mode 100644 index 0000000..94202f5 --- /dev/null +++ b/Bunch-O--Dice/Assets.xcassets/one.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "1024.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Bunch-O--Dice/Assets.xcassets/seven.imageset/Contents.json b/Bunch-O--Dice/Assets.xcassets/seven.imageset/Contents.json new file mode 100644 index 0000000..37d9e2e --- /dev/null +++ b/Bunch-O--Dice/Assets.xcassets/seven.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "wound.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Bunch-O--Dice/Assets.xcassets/seven.imageset/wound.png b/Bunch-O--Dice/Assets.xcassets/seven.imageset/wound.png new file mode 100644 index 0000000..cbde385 Binary files /dev/null and b/Bunch-O--Dice/Assets.xcassets/seven.imageset/wound.png differ diff --git a/Bunch-O--Dice/Assets.xcassets/six.imageset/Contents.json b/Bunch-O--Dice/Assets.xcassets/six.imageset/Contents.json new file mode 100644 index 0000000..daffda3 --- /dev/null +++ b/Bunch-O--Dice/Assets.xcassets/six.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "six.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Bunch-O--Dice/Assets.xcassets/six.imageset/six.png b/Bunch-O--Dice/Assets.xcassets/six.imageset/six.png new file mode 100644 index 0000000..7d1689e Binary files /dev/null and b/Bunch-O--Dice/Assets.xcassets/six.imageset/six.png differ diff --git a/Bunch-O--Dice/Assets.xcassets/three.imageset/Contents.json b/Bunch-O--Dice/Assets.xcassets/three.imageset/Contents.json new file mode 100644 index 0000000..463314f --- /dev/null +++ b/Bunch-O--Dice/Assets.xcassets/three.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "three.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Bunch-O--Dice/Assets.xcassets/three.imageset/three.png b/Bunch-O--Dice/Assets.xcassets/three.imageset/three.png new file mode 100644 index 0000000..c80d7de Binary files /dev/null and b/Bunch-O--Dice/Assets.xcassets/three.imageset/three.png differ diff --git a/Bunch-O--Dice/Assets.xcassets/two.imageset/Contents.json b/Bunch-O--Dice/Assets.xcassets/two.imageset/Contents.json new file mode 100644 index 0000000..0f87d53 --- /dev/null +++ b/Bunch-O--Dice/Assets.xcassets/two.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "two.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Bunch-O--Dice/Assets.xcassets/two.imageset/two.png b/Bunch-O--Dice/Assets.xcassets/two.imageset/two.png new file mode 100644 index 0000000..c3402a8 Binary files /dev/null and b/Bunch-O--Dice/Assets.xcassets/two.imageset/two.png differ diff --git a/Bunch-O--Dice/ContentView.swift b/Bunch-O--Dice/ContentView.swift new file mode 100644 index 0000000..8c83786 --- /dev/null +++ b/Bunch-O--Dice/ContentView.swift @@ -0,0 +1,33 @@ +// +// ContentView.swift +// Bunch O' Dice +// +// Created by Rory Hinnen on 22/2/20. +// + +import SwiftUI + +struct ContentView: View { + @EnvironmentObject var current: Roll + @State var preferredColorScheme: ColorScheme? = nil + + var body: some View { + VStack { + switch current.currentPage { + case "OnboardingView": + OnboardingView() + default: + MainView() + } + } + .preferredColorScheme(current.appearance.type) + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + .environmentObject(Roll()) + .previewInterfaceOrientation(.portrait) + } +} diff --git a/Bunch O' Dice/Bunch_O__DiceApp.swift b/Bunch-O--Dice/DieHammerApp.swift similarity index 74% rename from Bunch O' Dice/Bunch_O__DiceApp.swift rename to Bunch-O--Dice/DieHammerApp.swift index a0a986c..ab7a768 100644 --- a/Bunch O' Dice/Bunch_O__DiceApp.swift +++ b/Bunch-O--Dice/DieHammerApp.swift @@ -9,9 +9,12 @@ import SwiftUI @main struct Bunch_O__DiceApp: App { + @StateObject var state = Roll() + var body: some Scene { WindowGroup { ContentView() + .environmentObject(state) } } } diff --git a/Bunch-O--Dice/MainView.swift b/Bunch-O--Dice/MainView.swift new file mode 100644 index 0000000..9101999 --- /dev/null +++ b/Bunch-O--Dice/MainView.swift @@ -0,0 +1,348 @@ +// +// PortraitView.swift +// Bunch O' Dice +// +// Created by Rory Hinnen on 22/6/18. +// + +import SwiftUI + +struct MainView: View { + + enum MenuView: Int { + case history, add, settings + + func icon() -> Image { + switch self { + case .history: + return(Image(systemName: "folder")) + case .settings: + return(Image(systemName: "square.and.pencil")) + case .add: + return(Image(systemName: "folder.badge.plus")) + } + } + } + + @ViewBuilder private func myCircle() -> some View { + Circle() + .fill(Color.gray) + .blur(radius: 2.0) + .frame(width: controlSize*2.5, height: controlSize*2.5, alignment: .center) + } + + @ViewBuilder private func stateView() -> some View { + switch current.state { + case .wound: + woundCalculator(controlSize: controlSize) + case .armour: + armourCalculator(controlSize: controlSize) + default: + hitCalculator(controlSize: controlSize) + } + } + + @ViewBuilder private func sheetContent() -> some View { + switch sheet.state { + case .settings: + SettingsView(isShowing: $sheet.isShowing) + case .history: + HistoryView(isShowing: $sheet.isShowing) + default: + EmptyView() + } + } + + @ViewBuilder func buttons(color: CGColor, label: String, value: Int) -> some View { + let frameSize = 120.0 + let fontSize = 35.0 + let blurSize = 5.0 + + ZStack { + Circle() + .fill(Color(color)) + .frame(width: frameSize, height: frameSize) + .blur(radius: blurSize) + VStack{ + Text(String(value)) + .fontWeight(.black) + Text(label) + .font(.system(size: 20)) + } + } + .foregroundColor(.primary) + .font(.system(size: fontSize)) + } + + //MARK: State Views + @ViewBuilder func armourCalculator(controlSize: Double) -> some View { + 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") + } + Spacer() + VStack{ + Text("Invulnerable") + Toggle("", isOn: $current.invulnerable).labelsHidden() + .padding() + } + Spacer() + VStack{ + Text("Reroll") + Button("🎲", action: { + current.dice.roll(howMany: current.total) + }) + .padding() + .font(.system(size: controlSize)) + } + Spacer() + } + .padding() + } + + @ViewBuilder func woundCalculator(controlSize: Double) -> some 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") + } + + 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(" ") + } + 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") + } + Spacer() + VStack{ + Text("Reroll") + Button("🎲", action: { + current.dice.roll(howMany: current.total) + }) + .padding() + .font(.system(size: controlSize)) + } + Spacer() + }.padding() + } + + @ViewBuilder func hitCalculator(controlSize: Double) -> some View { + HStack(alignment: .top) { + Group { + 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") + } + VStack { + Text("Blow Up") + VStack { + Button(action: {current.dice.blowup(dice: .six)}) + {Image(systemName: "die.face.6") + .foregroundColor(.primary) + } + Button(action: {current.dice.blowup(dice: .five)}) + {Image(systemName: "die.face.5") + .foregroundColor(.primary) + } + } + .padding() + .font(.system(size: controlSize)) + + } + VStack{ + Text("Reroll") + Button("🎲", action: { + current.dice.roll(howMany: current.total) + }) + .padding() + .font(.system(size: controlSize)) + } + } + }.padding() + } + + //MARK: die views + @ViewBuilder func die(whichDie: Dice.DiceType) -> some View { + let size = 30.0 + let displayColorHit = (whichDie.rawValue >= current.cutoff.rawValue) ? Color.red : Color.primary + let displayColorArmor = (whichDie.rawValue <= current.cutoff.rawValue) ? Color.red : Color.primary + let displayColorArmorText = (whichDie.rawValue < current.cutoff.rawValue) ? Color.red : Color.primary + + HStack{ + Text(String(current.dice.value(which: whichDie))) + .foregroundColor(current.state == .armour ? + displayColorArmorText : displayColorHit) + Image(systemName: "die.face." + String(whichDie.rawValue) + ".fill") + .foregroundColor(current.state == .armour ? + displayColorArmor : displayColorHit) + + } + .font(.system(size: size)) + .padding(2) + .gesture(TapGesture().onEnded { + if current.state == .hit { + if whichDie.rawValue != 1 { + current.hitCutoff = whichDie } + } else if current.state == .armour { + current.armourSave = Int(whichDie.rawValue) + } + }) + } + + @ViewBuilder func diePool() -> some View { + VStack(alignment: .leading) { + ForEach(Dice.DiceType.allCases.reversed(), id: \.rawValue) { i in + die(whichDie: i) + } + } + } + + //MARK: Var declarations + @EnvironmentObject var current: Roll + @ObservedObject var sheet = SettingsSheet() + @State var showFillView = false + + let padSize = 60.0 + let controlSize = 30.0 + let red = CGColor(red: 1.0, green: 0.3, blue: 0.3, alpha: 1.0) + let blue = CGColor(red: 0.3, green: 0.3, blue: 1.0, alpha: 1.0) + + var body: some View { + VStack{ + HStack{ + Button(action: {current.state = .hit}) { + ZStack{ + if current.state == .hit {myCircle()} + Text(RollState.hit.print())}} + Button(action: {current.state = .wound}) { + ZStack{ + if current.state == .wound {myCircle()} + Text(RollState.wound.print())}} + Button(action: {current.state = .armour}) { + ZStack{ + if current.state == .armour {myCircle()} + Text(RollState.armour.print())}} + }.font(.system(size: 60)) + VStack{ + HStack { + VStack { + Button(action: {showFillView.toggle()}) { + buttons(color: blue, label: "Pool", value: current.total) + } + .sheet(isPresented: $showFillView, + onDismiss: {current.dice.roll(howMany: current.total)}) { + VStack { + AddToPool() + Button("Accept", action: {showFillView.toggle()}) + .padding(20) + } + } + Spacer() + Button(action: current.incrementState) { + buttons(color: red, label: current.state.text(), value: current.result) + } + + }.padding(.leading, padSize) + Spacer() + diePool().padding(.trailing, padSize) + }.frame(maxHeight: 285) + stateView() + }.padding() + } + .toolbar { + ToolbarItemGroup(placement: .bottomBar) { + HStack { + Button(action: {self.sheet.state = .history}) { + MenuView.history.icon()} + Button(action: {current.history.insert(Event(total: current.result, pool: current.dice, state: current.state), at: 0)}) { + MenuView.add.icon()} + Button(action: {self.sheet.state = .settings}) { + MenuView.settings.icon()} + } + .font(.system(size: controlSize)) + .foregroundColor(.primary) + } + } + .sheet(isPresented: $sheet.isShowing, content: sheetContent)} +} + +struct MainView_Previews: PreviewProvider { + static var previews: some View { + MainView() + .environmentObject(Roll()) + } +} diff --git a/Bunch-O--Dice/Model/Dice.swift b/Bunch-O--Dice/Model/Dice.swift new file mode 100644 index 0000000..00a7cab --- /dev/null +++ b/Bunch-O--Dice/Model/Dice.swift @@ -0,0 +1,294 @@ +// +// Dice.swift +// Bunch O' Dice +// +// Created by Rory Hinnen on 22/2/20. +// + +import Foundation + +struct Dice { + // MARK: Private + private var d6: Int { + get { + return Int.random(in: 1...6) + } + } + + // MARK: Enums + enum DiceType: Int, CaseIterable{ + case one = 1 + case two = 2 + case three = 3 + case four = 4 + case five = 5 + case six = 6 + } + + enum RollModifiers: Int { + case negative = -1 + case none = 0 + case positive = 1 + + func add() -> RollModifiers { + switch self { + case .negative: + return RollModifiers.none + case .none: + return RollModifiers.positive + case .positive: + return RollModifiers.positive + } + } + + func remove() -> RollModifiers { + switch self { + case .negative: + return RollModifiers.negative + case .none: + return RollModifiers.negative + case .positive: + return RollModifiers.none + } + } + } + + // MARK: Variables + var one = 0 + var two = 0 + var three = 0 + var four = 0 + var five = 0 + var six = 0 + + // MARK: Roll controllers + // clear and roll the die + mutating func roll(howMany: Int) { + clear() + var y = howMany + while y > 0 { + switch d6 { + case 1: + one += 1 + case 2: + two += 1 + case 3: + three += 1 + case 4: + four += 1 + case 5: + five += 1 + default: + six += 1 + } + y -= 1 + } + } + + // calculate basic roll + func total(cutoff: Int, modifier: Dice.RollModifiers) -> Int { + var total = six + + switch modifier { + case .none: + switch cutoff { + case 5: + total += five + case 4: + total += four + five + case 3: + total += three + four + five + case 2: + total += two + three + four + five + default: + break + } + case .negative: + switch cutoff { + case 4: + total += five + case 3: + total += four + five + case 2: + total += three + four + five + case 1: + total += two + three + four + five + default: + break + } + case .positive: + switch cutoff { + case 6: + total += five + case 5: + total += four + five + case 4: + total += three + four + five + case 3, 2: + total += two + three + four + five + default: + break + } + } + return total + } + + // Calculate different rolls + func hit(die: Int, cutoff: Int, modifier: Int) -> Int { + return total(cutoff: cutoff, modifier: Dice.RollModifiers(rawValue: modifier) ?? .none) + } + + func wound(die: Int, mortal: Bool, cutoff: Int, modifier: Int) -> Int { + if mortal { + return total(cutoff: 2, modifier: Dice.RollModifiers(rawValue: modifier) ?? .none) + } else { + return total(cutoff: cutoff, modifier: Dice.RollModifiers(rawValue: modifier) ?? .none) + } + } + + func armour(die: Int, invulnerable: Bool, save: Int, armourPenetration: Int) -> Int { + var total = 0 + switch invulnerable ? save : (save - armourPenetration) { + case 7...: + total = six + five + four + three + two + one + case 6: + total = five + four + three + two + one + case 5: + total = four + three + two + one + case 4: + total = three + two + one + case 3: + total = two + one + default: + total = one + } + return total + } + + // MARK: roll utilities + // Utility functions + mutating func clear() { + one = 0 + two = 0 + three = 0 + four = 0 + five = 0 + six = 0 + } + + mutating func add(pool: Dice) { + one += pool.one + two += pool.two + three += pool.three + four += pool.four + five += pool.five + six += pool.six + } + + func value(which: DiceType) -> Int { + switch which { + case .one: + return one + case .two: + return two + case .three: + return three + case .four: + return four + case .five: + return five + case .six: + return six + } + } + + // MARK: Extra + mutating func increment(die: DiceType) { + switch die { + case .one: + one += 1 + case .two: + two += 1 + case .three: + three += 1 + case .four: + four += 1 + case .five: + five += 1 + case .six: + six += 1 + } + } + + mutating func decrement(die: DiceType) { + switch die { + case .one: + if one > 0 { + one -= 1 + } + case .two: + if two > 0 { + two -= 1 + } + case .three: + if three > 0 { + three -= 1 + } + case .four: + if four > 0 { + four -= 1 + } + case .five: + if five > 0 { + five -= 1 + } + case .six: + if six > 0 { + six -= 1 + } + } + } + + mutating func reroll(dice: DiceType) { + var rerolls = Dice() + switch dice { + case .one: + rerolls.roll(howMany: one) + one = 0 + self.add(pool: rerolls) + case .two: + rerolls.roll(howMany: two) + two = 0 + self.add(pool: rerolls) + case .three: + rerolls.roll(howMany: three) + three = 0 + self.add(pool: rerolls) + case .four: + rerolls.roll(howMany: four) + four = 0 + self.add(pool: rerolls) + case .five: + rerolls.roll(howMany: five) + five = 0 + self.add(pool: rerolls) + case .six: + rerolls.roll(howMany: six) + six = 0 + self.add(pool: rerolls) + } + } + + mutating func blowup(dice: DiceType) { + var rolls = Dice() + if dice == .six { + rolls.roll(howMany: six) + self.add(pool: rolls) + } else if dice == .five { + rolls.roll(howMany: six + five) + self.add(pool: rolls) + } + } +} + + diff --git a/Bunch-O--Dice/Model/Onboarding.swift b/Bunch-O--Dice/Model/Onboarding.swift new file mode 100644 index 0000000..7d06315 --- /dev/null +++ b/Bunch-O--Dice/Model/Onboarding.swift @@ -0,0 +1,34 @@ +// +// OnboardingModel.swift +// Bunch O' Dice +// +// Created by Rory Hinnen on 22/6/11. +// + +import Foundation + +struct DTCourse: Identifiable { + var id = UUID() + var title: String + var subtitle: String + var desc: String + var imageName: String = "" +} + +extension DTCourse { + static let title = "DieHammer" + static var pages: [DTCourse] { + [ + DTCourse(title: "DieHammer", subtitle: "A chaos manager for Warhammer or 40k", desc: "Welcome! This app will help you manage simple rolls to hit, wound, or save for Warhammer and Warhammer:40,000. Let's walk through the interface to help you understand how to use it.", imageName: "one"), + DTCourse(title: "State", subtitle: "Types of Rolls", desc: "You can make three kinds of rolls: Hit, Wound or Armor Save. The top of the screen shows you what roll you are making. You can change the roll at any time by tapping the icon.", imageName: "two"), + DTCourse(title: "The Pool", subtitle: "Rolling the Dice", desc: "The pool shows how many dice you are going to roll. Tap on the pool to change the amount.", imageName: "three"), + DTCourse(title: "The Result", subtitle: "How bad is it, Doc?", desc: "Results will count up the number of dice that apply to the roll. We always count damage, so the Armour Save total shows how many wounds to apply. Tapping on results will save the current roll, and move on to the next.", imageName: "four"), + DTCourse(title: "Re-Rolling", subtitle: "Ooo...yeah.", desc: "You can re-roll at any time by tapping the re-roll icon", imageName: "five"), + DTCourse(title: "Hit Settings", subtitle: "Sweet, sweet modifiers", desc: "Tap the die that relates to your hit value, and adjust a hit modifier if you have one. If you have a special rule for Blowing up a hit, tapping the icon will add the new rolls.", imageName: "six"), + DTCourse(title: "Wound", subtitle: "Make them bleed!", desc: "Set the Attackers Strength and the Defenders strength to calculate the new Wound cutoff. Results are always updated in real time.", imageName: "seven"), + DTCourse(title: "Armour Save", subtitle: "This is your last chance!", desc: "Amour Save will show how many dice in the roll fail, counting up the number of wounds to distribute.", imageName: "eight"), + DTCourse(title: "History & Settings", subtitle: "Wait, what happened?", desc: "You can always pull up your history to see the old rolls you've made. You can also add a roll to the history at any time.", imageName: "nine"), + DTCourse(title: title, subtitle: "Have Fun!", desc: "", imageName: "one") + ] + } +} diff --git a/Bunch-O--Dice/Model/RollState.swift b/Bunch-O--Dice/Model/RollState.swift new file mode 100644 index 0000000..e8216aa --- /dev/null +++ b/Bunch-O--Dice/Model/RollState.swift @@ -0,0 +1,317 @@ +// +// 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 + } + } +} + +// 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 + + // 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) + } + } + + // 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 + case .wound: + self.total = wound + self.state = .armour + case .armour: + self.total = save + self.state = .hit + } + self.dice.roll(howMany: self.total) + } + + // MARK: extra + func add(howMuch: Int) { + var newTotal: Int = 0 + newTotal = self.total + howMuch + if newTotal < 0 { + newTotal = 0 + } + self.total = newTotal + } +} diff --git a/Bunch-O--Dice/OnboardingView.swift b/Bunch-O--Dice/OnboardingView.swift new file mode 100644 index 0000000..d878100 --- /dev/null +++ b/Bunch-O--Dice/OnboardingView.swift @@ -0,0 +1,78 @@ +// +// OnboardingView.swift +// Bunch O' Dice +// +// Created by Rory Hinnen on 22/6/11. +// + +import SwiftUI + +struct OnboardingView: View { + private var courses = DTCourse.pages + @State private var selection = 0 + @EnvironmentObject var current: Roll + + var body: some View { + VStack { + TabView(selection: $selection) { + ForEach(courses.indices) { idx in + VStack(spacing: 0) { + Spacer() + Text(courses[idx].title) + .font(.largeTitle) + Rectangle() + .fill(Color.secondary) + .frame(height: 1) + + VStack(alignment: .leading) { + + Image(courses[idx].imageName) + .resizable() + .scaledToFit() + + Text(courses[idx].subtitle) + + Text(courses[idx].desc) + .foregroundColor(.secondary) + + }.padding() + + Spacer() + } + .tag(idx) + } + } + .tabViewStyle(.page(indexDisplayMode: .never)) + HStack { + Spacer() + Button { + withAnimation { + if selection < courses.count - 1 { + selection += 1 + } else { + current.currentPage = "Bunch" + } + } + } label: { + if selection == courses.count - 1 { + Image(systemName: "checkmark.circle") + .font(.largeTitle) + } else { + Image(systemName: "chevron.right.circle") + .font(.largeTitle) + } + } + } + .padding() + .foregroundColor(.primary) + } + } +} + + +struct OnboardingView_Previews: PreviewProvider { + static var previews: some View { + OnboardingView() + .environmentObject(Roll()) + } +} diff --git a/Bunch O' Dice/Preview Content/Preview Assets.xcassets/Contents.json b/Bunch-O--Dice/Preview Content/Preview Assets.xcassets/Contents.json similarity index 100% rename from Bunch O' Dice/Preview Content/Preview Assets.xcassets/Contents.json rename to Bunch-O--Dice/Preview Content/Preview Assets.xcassets/Contents.json diff --git a/Bunch-O--Dice/PrivacyInfo.xcprivacy b/Bunch-O--Dice/PrivacyInfo.xcprivacy new file mode 100644 index 0000000..79bc9e2 --- /dev/null +++ b/Bunch-O--Dice/PrivacyInfo.xcprivacy @@ -0,0 +1,17 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + + diff --git a/Bunch-O--Dice/Utilities/ViewUtils.swift b/Bunch-O--Dice/Utilities/ViewUtils.swift new file mode 100644 index 0000000..7b33c5b --- /dev/null +++ b/Bunch-O--Dice/Utilities/ViewUtils.swift @@ -0,0 +1,33 @@ +// +// ViewUtiles.swift +// Bunch O' Dice +// +// Created by Rory Hinnen on 22/6/12. +// + +import Foundation +import SwiftUI + +class SheetState: ObservableObject { + @Published var isShowing = false + @Published var state: State? { + didSet { isShowing = state != nil } + } +} + +class SettingsSheet: SheetState { + enum State { + case settings, history + } +} + +extension CaseIterable where Self: Equatable { + mutating func next() { + let allCases = Self.allCases + // just a sanity check, as the possibility of a enum case to not be + // present in `allCases` is quite low + guard let selfIndex = allCases.firstIndex(of: self) else { return } + let nextIndex = Self.allCases.index(after: selfIndex) + self = allCases[nextIndex == allCases.endIndex ? allCases.startIndex : nextIndex] + } +} diff --git a/Bunch-O--Dice/Views/Die.swift b/Bunch-O--Dice/Views/Die.swift new file mode 100644 index 0000000..f9491d0 --- /dev/null +++ b/Bunch-O--Dice/Views/Die.swift @@ -0,0 +1,49 @@ +// +// Die.swift +// Bunch O' Dice +// +// Created by Rory Hinnen on 22/6/18. +// + +import SwiftUI + +struct Die: View { + @EnvironmentObject var current: Roll + + @State var whichDie: Dice.DiceType + + var body: some View { + /* let size = 25.0 + let displayColorHit = (whichDie.rawValue >= current.cutoff.rawValue) ? Color.red : Color.primary + let displayColorArmor = (whichDie.rawValue <= current.cutoff.rawValue) ? Color.red : Color.primary + let displayColorArmorText = (whichDie.rawValue < current.cutoff.rawValue) ? Color.red : Color.primary + + HStack{ + Text(String(current.dice.value(which: whichDie))) + .foregroundColor(current.state == .armour ? + displayColorArmorText : displayColorHit) + Image(systemName: "die.face." + String(whichDie.rawValue) + ".fill") + .foregroundColor(current.state == .armour ? + displayColorArmor : displayColorHit) + + } + .font(.system(size: size)) + .padding(2) + .gesture(TapGesture().onEnded { + if current.state == .hit { + if whichDie.rawValue != 1 { + current.hitCutoff = whichDie } + } else if current.state == .armour { + current.armourSave = Int(whichDie.rawValue) + } + }) + } */ + Text("") +} + +struct Die_Previews: PreviewProvider { + static var previews: some View { + Die(whichDie: Dice.DiceType.one) + .environmentObject(Roll()) + } +} diff --git a/Bunch-O--Dice/Views/LandscapeVariants/DieLandscape.swift b/Bunch-O--Dice/Views/LandscapeVariants/DieLandscape.swift new file mode 100644 index 0000000..870bad0 --- /dev/null +++ b/Bunch-O--Dice/Views/LandscapeVariants/DieLandscape.swift @@ -0,0 +1,50 @@ +// +// Die.swift +// Bunch O' Dice +// +// Created by Rory Hinnen on 22/2/20. +// + +import SwiftUI + +struct SingleDie: View { + @EnvironmentObject var current: Roll + @State var whichDie: Dice.DiceType + + var body: some View { + VStack { + Text(String(current.dice.value(which: whichDie))) + Group { + if (current.state != .armour) && (whichDie.rawValue >= current.cutoff.rawValue) { + Text(whichDie.print()) + .foregroundColor(Color.red) + } else if (current.state == .armour) && (whichDie.rawValue < current.cutoff.rawValue) { + Text(whichDie.print()) + .foregroundColor(Color.red) + } else { + Text(whichDie.print()) + .foregroundColor(Color.black) + .background(Color.white) + } + } + .font(.system(size: 45)) + } + .font(/*@START_MENU_TOKEN@*/.title/*@END_MENU_TOKEN@*/) + .padding(.horizontal, 5.0) + .gesture(TapGesture().onEnded { + if current.state == .hit { + current.hitCutoff = whichDie + } + else if current.state == .armour { + current.armourSave = Int(whichDie.rawValue) + } + }) + } +} + +struct Die_Previews: PreviewProvider { + static var previews: some View { + Die(whichDie: .one) + .environmentObject(Roll()) + } +} diff --git a/Bunch-O--Dice/Views/LandscapeVariants/ShowPoolLandscape.swift b/Bunch-O--Dice/Views/LandscapeVariants/ShowPoolLandscape.swift new file mode 100644 index 0000000..9fde3b3 --- /dev/null +++ b/Bunch-O--Dice/Views/LandscapeVariants/ShowPoolLandscape.swift @@ -0,0 +1,29 @@ +// +// SwiftUIView.swift +// Bunch O' Dice +// +// Created by Rory Martin Hinnen on 6/7/22. +// + +import SwiftUI + +struct ShowPoolLandscape: View { + @EnvironmentObject var current: Roll + + var body: some View { + HStack { + Spacer() + ForEach(Dice.DiceType.allCases, id: \.rawValue) { i in + SingleDie(whichDie: i) + } + Spacer() + } + } +} + +struct ShowPool_Previews: PreviewProvider { + static var previews: some View { + ShowPoolLandscape() + .environmentObject(Roll()) + } +} diff --git a/Bunch-O--Dice/Views/Modals/AddToPool.swift b/Bunch-O--Dice/Views/Modals/AddToPool.swift new file mode 100644 index 0000000..a6f3f10 --- /dev/null +++ b/Bunch-O--Dice/Views/Modals/AddToPool.swift @@ -0,0 +1,50 @@ +// +// IncrementDie.swift +// Bunch O' Dice +// +// Created by Rory Martin Hinnen on 6/7/22. +// + +import SwiftUI + +struct AddToPool: View { + @EnvironmentObject var current: Roll + + var body: some View { + VStack { + Spacer() + Text(String(current.total) + " 🎲") + .font(.system(size: 40)) + .padding(15) + HStack { + ForEach(Array(stride(from: 2, through: 0, by: -1)), id: \.self) { place in + VStack{ + Text("▲") + .gesture(TapGesture().onEnded({ + current.incrementDigits(place: place) + })) + Text(String(current.totalDigits[place])) + .padding(4) + .border(.black, width: 1.0) + Text("▼") + .gesture(TapGesture().onEnded({ + current.decrementDigits(place: place) + })) + } + } + } + .font(.system(size: 45)) + .padding(10) + Button("Clear", action: {current.total = 0}) + Spacer() + } + } +} + +struct IncrementDie_Previews: PreviewProvider { + static var previews: some View { + AddToPool() + .environmentObject(Roll()) + + } +} diff --git a/Bunch-O--Dice/Views/Modals/HistoryView.swift b/Bunch-O--Dice/Views/Modals/HistoryView.swift new file mode 100644 index 0000000..ef52704 --- /dev/null +++ b/Bunch-O--Dice/Views/Modals/HistoryView.swift @@ -0,0 +1,30 @@ +// +// HistoryView.swift +// Bunch O' Dice +// +// Created by Rory Hinnen on 22/6/11. +// + +import SwiftUI + +struct HistoryView: View { + @EnvironmentObject var current: Roll + @Binding var isShowing: Bool + + var body: some View { + VStack{ + List(current.history) {event in + Text("\(event.poolTotal) Dice Rolled, \(event.total) \(event.state.print())") + } + Spacer() + Button("Dismiss", action: {isShowing.toggle()}) + } + } +} + +struct HistoryView_Previews: PreviewProvider { + static var previews: some View { + HistoryView(isShowing: .constant(false)) + .environmentObject(Roll()) + } +} diff --git a/Bunch-O--Dice/Views/Modals/SettingsView.swift b/Bunch-O--Dice/Views/Modals/SettingsView.swift new file mode 100644 index 0000000..e7e5892 --- /dev/null +++ b/Bunch-O--Dice/Views/Modals/SettingsView.swift @@ -0,0 +1,41 @@ +// +// SettingsView.swift +// Bunch O' Dice +// +// Created by Rory Hinnen on 22/7/2. +// + +import SwiftUI + +struct SettingsView: View { + @EnvironmentObject var current: Roll + @Binding var isShowing: Bool + + var body: some View { + VStack{ + VStack{ + Text("DieHammer") + .font(.title) + Text("A roll management tool for Warhammer & Warhammer:40,000") + .font(.subheadline) + }.padding() + Group{ + VStack{ + Text("Theme") + Button(String(current.appearance.name)) {current.appearance.next()} + } + Button("Tutorial") {current.currentPage = "OnboardingView"} + .padding() + } + Spacer() + Button("Dismiss", action: {isShowing = false}) + } + } +} + +struct SettingsView_Previews: PreviewProvider { + static var previews: some View { + SettingsView(isShowing: .constant(true)) + .environmentObject(Roll()) + } +} diff --git a/Bunch-O--Dice/Views/PortraitVariants/DieHorizontal.swift b/Bunch-O--Dice/Views/PortraitVariants/DieHorizontal.swift new file mode 100644 index 0000000..26af52e --- /dev/null +++ b/Bunch-O--Dice/Views/PortraitVariants/DieHorizontal.swift @@ -0,0 +1,54 @@ +// +// DieHorizontal.swift +// Bunch O' Dice +// +// Created by Rory Hinnen on 22/6/12. +// + +import SwiftUI + +struct DieHorizontal: View { + @EnvironmentObject var current: Roll + @State var whichDie: Dice.DiceType + + var body: some View { + HStack { + Text(String(current.dice.value(which: whichDie))) + .offset(x: 0.0, y: 2.0) + Group { + if (current.state != .armour) && (whichDie.rawValue >= current.cutoff.rawValue) { + Text(whichDie.print()) + .foregroundColor(Color.red) + } else if (current.state == .armour) && (whichDie.rawValue < current.cutoff.rawValue) { + Text(whichDie.print()) + .foregroundColor(Color.red) + } else { + Text(whichDie.print()) + .foregroundColor(Color.black) + } + } + .font(.system(size: 40)) + } + .font(.system(size: 25)) + .padding(.horizontal, 5.0) + + .gesture(TapGesture().onEnded { + if current.state == .hit { + current.hitCutoff = whichDie + } + else if current.state == .armour { + current.armourSave = Int(whichDie.rawValue) + } + }) + } +} + +struct DieHorizontal_Previews: PreviewProvider { + static var previews: some View { + VStack { + DieHorizontal(whichDie: .four).environmentObject(Roll()) + DieHorizontal(whichDie: .three).environmentObject(Roll()) + } + } + //.environmentObject(Roll()) +} diff --git a/Bunch-O--Dice/Views/PortraitVariants/ShowPoolHorizontal.swift b/Bunch-O--Dice/Views/PortraitVariants/ShowPoolHorizontal.swift new file mode 100644 index 0000000..eb1e92a --- /dev/null +++ b/Bunch-O--Dice/Views/PortraitVariants/ShowPoolHorizontal.swift @@ -0,0 +1,26 @@ +// +// ShowPoolHorizontal.swift +// Bunch O' Dice +// +// Created by Rory Hinnen on 22/6/12. +// + +import SwiftUI + +struct ShowPoolHorizontal: View { + @EnvironmentObject var current: Roll + + var body: some View { + VStack(alignment: .trailing) { + ForEach(Dice.DiceType.allCases.reversed(), id: \.rawValue) { i in + DieHorizontal(whichDie: i) + } + } + } +} + +struct ShowPoolHorizontal_Previews: PreviewProvider { + static var previews: some View { + ShowPoolHorizontal().environmentObject(Roll()) + } +} diff --git a/Bunch-O--Dice/Views/dice.swift b/Bunch-O--Dice/Views/dice.swift new file mode 100644 index 0000000..4b63d97 --- /dev/null +++ b/Bunch-O--Dice/Views/dice.swift @@ -0,0 +1,20 @@ +// +// 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() + } +} diff --git a/Bunch-O--Dice/Views/oldLayout/PortraitScreen.swift b/Bunch-O--Dice/Views/oldLayout/PortraitScreen.swift new file mode 100644 index 0000000..2082a0a --- /dev/null +++ b/Bunch-O--Dice/Views/oldLayout/PortraitScreen.swift @@ -0,0 +1,47 @@ +// +// PortraitScreen.swift +// Bunch O' Dice +// +// Created by Rory Hinnen on 22/6/11. +// + +import SwiftUI + +struct PortraitScreen: View { + @EnvironmentObject var current: Roll + var frameSize: CGFloat // = CGFloat(120) + let fontSize: Double // = 35.0 + let blurSize: Double // = 11.0 + + var body: some View { + Group{ + VStack { + HStack { + Spacer() + Pool(frameSize: frameSize, fontSize: fontSize, blurSize: blurSize) + Spacer() + Total(frameSize: frameSize, fontSize: fontSize, blurSize: blurSize) + Spacer() + } + ShowPoolLandscape() + Group { + switch current.state { + case .wound: + WoundCalculator() + case .armour: + ArmourCalculator() + default: + HitCalculator() + } + } + }.padding() + } + } +} + +struct PortraitScreen_Previews: PreviewProvider { + static var previews: some View { + PortraitScreen(frameSize: 120, fontSize: 35, blurSize: 11) + .environmentObject(Roll()) + } +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e6636cf --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,36 @@ +# Diehammer (Bunch O' Dice) + +A SwiftUI dice-rolling app for Warhammer 40k, currently iOS-only. Core structure: + +- `Bunch-O--Dice/Model/` — `Dice.swift`, `RollState.swift`, `Onboarding.swift`: dice pool and roll-state logic. +- `Bunch-O--Dice/Views/` — SwiftUI views, split into `PortraitVariants/`, `LandscapeVariants/`, and `Modals/` (AddToPool, HistoryView, SettingsView). +- `Bunch-O--Dice/ContentView.swift`, `MainView.swift`, `DieHammerApp.swift` — app entry point and top-level layout. +- `Diehammer - 40k Die Modifier Research.md` — reference doc on 40k 11th Edition die modifiers (see below). + +## Active goals + +### 1. Die modifier support + +Warhammer 40k's die-modifier rules (10th → 11th Edition changes, e.g. Cover now applying -1 to hit instead of a save bonus) are documented in `Diehammer - 40k Die Modifier Research.md` in this repo. Implementation sub-goals: + +- Stacking flat hit/wound modifier input, capped at net ±1. +- Reroll mode selector: none / reroll 1s / reroll all failed / reroll all. +- A critical-trigger layer keyed off *unmodified* natural 6s, for Sustained Hits, Lethal Hits, Devastating Wounds, and Anti-X. +- An "ignore all modifiers" flag (Psychic). +- An "auto-success, no roll" flag (Torrent). + +### 2. Clarify roll context in the UI + +Currently the app lets you roll to hit, to wound, and armor save, but it's not always visually clear which action is being rolled. Update the graphics so the current roll phase (hit / wound / save) is unambiguous. + +### 3. Android port + +Plan: use the official Swift-on-Android toolchain, not a transpiler like Skip. + +- Set up the Swift-on-Android toolchain. +- Extract the game logic (dice/modifier engine from `Model/`) into a shared Swift library, decoupled from SwiftUI. +- Rewrite the UI natively in Jetpack Compose, consuming the shared Swift library. + +## Task tracking + +Day-to-day task tracking for this project lives in the Obsidian vault under `👨🏼‍💻 Professional Development.md`, in the "Game Dev: Diehammer" section. This file is meant as durable project context, not a live task list — check Obsidian for current status. diff --git a/Bunch O' Dice.xcodeproj/project.pbxproj b/DieHammer.xcodeproj/project.pbxproj similarity index 73% rename from Bunch O' Dice.xcodeproj/project.pbxproj rename to DieHammer.xcodeproj/project.pbxproj index 712d2d0..bab07d9 100644 --- a/Bunch O' Dice.xcodeproj/project.pbxproj +++ b/DieHammer.xcodeproj/project.pbxproj @@ -7,13 +7,24 @@ objects = { /* Begin PBXBuildFile section */ - 981AD0BE27C30E31005A6492 /* Bunch_O__DiceApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981AD0BD27C30E31005A6492 /* Bunch_O__DiceApp.swift */; }; + 7692E8432BD55CDF005BDD76 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 7692E8422BD55CDE005BDD76 /* PrivacyInfo.xcprivacy */; }; + 980EF454285E2BAD002FE8A0 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980EF453285E2BAD002FE8A0 /* MainView.swift */; }; + 981AD0BE27C30E31005A6492 /* DieHammerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981AD0BD27C30E31005A6492 /* DieHammerApp.swift */; }; 981AD0C027C30E31005A6492 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981AD0BF27C30E31005A6492 /* ContentView.swift */; }; 981AD0C227C30E32005A6492 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 981AD0C127C30E32005A6492 /* Assets.xcassets */; }; 981AD0C527C30E32005A6492 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 981AD0C427C30E32005A6492 /* Preview Assets.xcassets */; }; 981AD0CF27C30E32005A6492 /* Bunch_O__DiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981AD0CE27C30E32005A6492 /* Bunch_O__DiceTests.swift */; }; 981AD0D927C30E32005A6492 /* Bunch_O__DiceUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981AD0D827C30E32005A6492 /* Bunch_O__DiceUITests.swift */; }; 981AD0DB27C30E32005A6492 /* Bunch_O__DiceUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981AD0DA27C30E32005A6492 /* Bunch_O__DiceUITestsLaunchTests.swift */; }; + 981AD0E927C31208005A6492 /* Dice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981AD0E827C31208005A6492 /* Dice.swift */; }; + 98225DE12A3527160019DE8E /* dice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98225DE02A3527160019DE8E /* dice.swift */; }; + 98325A2E2839BE4B0006DF4B /* RollState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98325A2D2839BE4B0006DF4B /* RollState.swift */; }; + 98745CA62870CC670031166A /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98745CA52870CC670031166A /* SettingsView.swift */; }; + 988B150F2854E9170082C2C5 /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 988B150E2854E9160082C2C5 /* HistoryView.swift */; }; + 988B1517285565610082C2C5 /* Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 988B1516285565610082C2C5 /* Onboarding.swift */; }; + 988B1519285566AD0082C2C5 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 988B1518285566AD0082C2C5 /* OnboardingView.swift */; }; + 98E534D42856540A00D5ABB4 /* ViewUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98E534D32856540A00D5ABB4 /* ViewUtils.swift */; }; + 98F001D5284F86F1004F5140 /* AddToPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F001D4284F86F1004F5140 /* AddToPool.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -34,8 +45,10 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 7692E8422BD55CDE005BDD76 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + 980EF453285E2BAD002FE8A0 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; 981AD0BA27C30E31005A6492 /* Bunch O' Dice.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Bunch O' Dice.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 981AD0BD27C30E31005A6492 /* Bunch_O__DiceApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bunch_O__DiceApp.swift; sourceTree = ""; }; + 981AD0BD27C30E31005A6492 /* DieHammerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DieHammerApp.swift; sourceTree = ""; }; 981AD0BF27C30E31005A6492 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 981AD0C127C30E32005A6492 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 981AD0C427C30E32005A6492 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; @@ -44,6 +57,16 @@ 981AD0D427C30E32005A6492 /* Bunch O' DiceUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Bunch O' DiceUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 981AD0D827C30E32005A6492 /* Bunch_O__DiceUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bunch_O__DiceUITests.swift; sourceTree = ""; }; 981AD0DA27C30E32005A6492 /* Bunch_O__DiceUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bunch_O__DiceUITestsLaunchTests.swift; sourceTree = ""; }; + 981AD0E827C31208005A6492 /* Dice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dice.swift; sourceTree = ""; }; + 98225DE02A3527160019DE8E /* dice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dice.swift; sourceTree = ""; }; + 98325A2D2839BE4B0006DF4B /* RollState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RollState.swift; sourceTree = ""; }; + 98745CA52870CC670031166A /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + 988997FF285814AB00B07E09 /* Bunch-O--Dice-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Bunch-O--Dice-Info.plist"; sourceTree = SOURCE_ROOT; }; + 988B150E2854E9160082C2C5 /* HistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryView.swift; sourceTree = ""; }; + 988B1516285565610082C2C5 /* Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Onboarding.swift; sourceTree = ""; }; + 988B1518285566AD0082C2C5 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; + 98E534D32856540A00D5ABB4 /* ViewUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewUtils.swift; sourceTree = ""; }; + 98F001D4284F86F1004F5140 /* AddToPool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddToPool.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -74,7 +97,7 @@ 981AD0B127C30E31005A6492 = { isa = PBXGroup; children = ( - 981AD0BC27C30E31005A6492 /* Bunch O' Dice */, + 981AD0BC27C30E31005A6492 /* Bunch-O--Dice */, 981AD0CD27C30E32005A6492 /* Bunch O' DiceTests */, 981AD0D727C30E32005A6492 /* Bunch O' DiceUITests */, 981AD0BB27C30E31005A6492 /* Products */, @@ -91,15 +114,22 @@ name = Products; sourceTree = ""; }; - 981AD0BC27C30E31005A6492 /* Bunch O' Dice */ = { + 981AD0BC27C30E31005A6492 /* Bunch-O--Dice */ = { isa = PBXGroup; children = ( - 981AD0BD27C30E31005A6492 /* Bunch_O__DiceApp.swift */, + 7692E8422BD55CDE005BDD76 /* PrivacyInfo.xcprivacy */, + 988997FF285814AB00B07E09 /* Bunch-O--Dice-Info.plist */, + 981AD0BD27C30E31005A6492 /* DieHammerApp.swift */, 981AD0BF27C30E31005A6492 /* ContentView.swift */, + 988B1518285566AD0082C2C5 /* OnboardingView.swift */, + 980EF453285E2BAD002FE8A0 /* MainView.swift */, + 981AD0EC27C3356D005A6492 /* Views */, + 981AD0E727C311DA005A6492 /* Model */, + 98E534D2285653E700D5ABB4 /* Utilities */, 981AD0C127C30E32005A6492 /* Assets.xcassets */, 981AD0C327C30E32005A6492 /* Preview Content */, ); - path = "Bunch O' Dice"; + path = "Bunch-O--Dice"; sourceTree = ""; }; 981AD0C327C30E32005A6492 /* Preview Content */ = { @@ -127,6 +157,43 @@ path = "Bunch O' DiceUITests"; sourceTree = ""; }; + 981AD0E727C311DA005A6492 /* Model */ = { + isa = PBXGroup; + children = ( + 98325A2D2839BE4B0006DF4B /* RollState.swift */, + 981AD0E827C31208005A6492 /* Dice.swift */, + 988B1516285565610082C2C5 /* Onboarding.swift */, + ); + path = Model; + sourceTree = ""; + }; + 981AD0EC27C3356D005A6492 /* Views */ = { + isa = PBXGroup; + children = ( + 98D0CD192856E3850047DC1B /* Modals */, + 98225DE02A3527160019DE8E /* dice.swift */, + ); + path = Views; + sourceTree = ""; + }; + 98D0CD192856E3850047DC1B /* Modals */ = { + isa = PBXGroup; + children = ( + 98F001D4284F86F1004F5140 /* AddToPool.swift */, + 988B150E2854E9160082C2C5 /* HistoryView.swift */, + 98745CA52870CC670031166A /* SettingsView.swift */, + ); + path = Modals; + sourceTree = ""; + }; + 98E534D2285653E700D5ABB4 /* Utilities */ = { + isa = PBXGroup; + children = ( + 98E534D32856540A00D5ABB4 /* ViewUtils.swift */, + ); + path = Utilities; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -191,7 +258,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1320; - LastUpgradeCheck = 1320; + LastUpgradeCheck = 1530; TargetAttributes = { 981AD0B927C30E31005A6492 = { CreatedOnToolsVersion = 13.2.1; @@ -206,7 +273,7 @@ }; }; }; - buildConfigurationList = 981AD0B527C30E31005A6492 /* Build configuration list for PBXProject "Bunch O' Dice" */; + buildConfigurationList = 981AD0B527C30E31005A6492 /* Build configuration list for PBXProject "DieHammer" */; compatibilityVersion = "Xcode 13.0"; developmentRegion = en; hasScannedForEncodings = 0; @@ -231,6 +298,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7692E8432BD55CDF005BDD76 /* PrivacyInfo.xcprivacy in Resources */, 981AD0C527C30E32005A6492 /* Preview Assets.xcassets in Resources */, 981AD0C227C30E32005A6492 /* Assets.xcassets in Resources */, ); @@ -257,8 +325,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 98E534D42856540A00D5ABB4 /* ViewUtils.swift in Sources */, 981AD0C027C30E31005A6492 /* ContentView.swift in Sources */, - 981AD0BE27C30E31005A6492 /* Bunch_O__DiceApp.swift in Sources */, + 988B1519285566AD0082C2C5 /* OnboardingView.swift in Sources */, + 981AD0E927C31208005A6492 /* Dice.swift in Sources */, + 98F001D5284F86F1004F5140 /* AddToPool.swift in Sources */, + 988B150F2854E9170082C2C5 /* HistoryView.swift in Sources */, + 98225DE12A3527160019DE8E /* dice.swift in Sources */, + 981AD0BE27C30E31005A6492 /* DieHammerApp.swift in Sources */, + 980EF454285E2BAD002FE8A0 /* MainView.swift in Sources */, + 98325A2E2839BE4B0006DF4B /* RollState.swift in Sources */, + 988B1517285565610082C2C5 /* Onboarding.swift in Sources */, + 98745CA62870CC670031166A /* SettingsView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -299,6 +377,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; @@ -332,6 +411,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -346,7 +426,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -360,6 +440,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; @@ -393,6 +474,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -401,7 +483,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -418,25 +500,29 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_ASSET_PATHS = "\"Bunch O' Dice/Preview Content\""; + DEVELOPMENT_ASSET_PATHS = "\"Bunch-O--Dice/Preview Content\""; DEVELOPMENT_TEAM = 5P9XB49J6K; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Bunch-O--Dice-Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = DieHammer; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.2; PRODUCT_BUNDLE_IDENTIFIER = "com.chartreusenecktie.Bunch-O--Dice"; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = "Bunch O' Dice"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = 1; }; name = Debug; }; @@ -447,25 +533,29 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_ASSET_PATHS = "\"Bunch O' Dice/Preview Content\""; + DEVELOPMENT_ASSET_PATHS = "\"Bunch-O--Dice/Preview Content\""; DEVELOPMENT_TEAM = 5P9XB49J6K; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Bunch-O--Dice-Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = DieHammer; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.2; PRODUCT_BUNDLE_IDENTIFIER = "com.chartreusenecktie.Bunch-O--Dice"; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_NAME = "Bunch O' Dice"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = 1; }; name = Release; }; @@ -548,7 +638,7 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 981AD0B527C30E31005A6492 /* Build configuration list for PBXProject "Bunch O' Dice" */ = { + 981AD0B527C30E31005A6492 /* Build configuration list for PBXProject "DieHammer" */ = { isa = XCConfigurationList; buildConfigurations = ( 981AD0DC27C30E32005A6492 /* Debug */, diff --git a/DieHammer.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/DieHammer.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..51dcb00 --- /dev/null +++ b/DieHammer.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Bunch O' Dice.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/DieHammer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from Bunch O' Dice.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to DieHammer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/DieHammer.xcodeproj/xcuserdata/rory.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/DieHammer.xcodeproj/xcuserdata/rory.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..beba43c --- /dev/null +++ b/DieHammer.xcodeproj/xcuserdata/rory.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/Bunch O' Dice.xcodeproj/xcuserdata/rory.xcuserdatad/xcschemes/xcschememanagement.plist b/DieHammer.xcodeproj/xcuserdata/rory.xcuserdatad/xcschemes/xcschememanagement.plist similarity index 100% rename from Bunch O' Dice.xcodeproj/xcuserdata/rory.xcuserdatad/xcschemes/xcschememanagement.plist rename to DieHammer.xcodeproj/xcuserdata/rory.xcuserdatad/xcschemes/xcschememanagement.plist diff --git a/DieHammer.xcodeproj/xcuserdata/roryhinnen.xcuserdatad/xcschemes/xcschememanagement.plist b/DieHammer.xcodeproj/xcuserdata/roryhinnen.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..a3a6a1a --- /dev/null +++ b/DieHammer.xcodeproj/xcuserdata/roryhinnen.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + Bunch O' Dice.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/DieHammerIcon.png b/DieHammerIcon.png new file mode 100644 index 0000000..abdab79 Binary files /dev/null and b/DieHammerIcon.png differ diff --git a/DieHammerIcon.psd b/DieHammerIcon.psd new file mode 100644 index 0000000..4c4bd97 Binary files /dev/null and b/DieHammerIcon.psd differ diff --git a/Diehammer - 40k Die Modifier Research.md b/Diehammer - 40k Die Modifier Research.md new file mode 100644 index 0000000..d618cfd --- /dev/null +++ b/Diehammer - 40k Die Modifier Research.md @@ -0,0 +1,48 @@ +# Warhammer 40k Die Modifiers — Research (11th Edition, current as of Aug 2026) + +Reference for the Diehammer app's die-modifier research task. Sourced from the 11th Edition core rules (released 2026) via Goonhammer/Tabletop Battles and ThriftHammer's keyword cheat sheet. + +## Core roll mechanics to support + +- **Flat modifiers to Hit**: +1/-1. Net modifiers to a single hit roll are capped at ±1 regardless of how many sources stack. +- **Flat modifiers to Wound**: +1/-1. Same net cap of ±1 applies. +- **Unmodifiable results**: an unmodified roll of 1 always fails; an unmodified roll of 6 always succeeds (hit and wound rolls). +- **Rerolls**: + - Reroll 1s (to hit or to wound) + - Reroll all failed rolls + - Reroll all rolls ("fishing" — used with Sustained Hits/Devastating Wounds to chase crits) + +## Hit roll keywords + +- **Sustained Hits X**: unmodified hit rolls of 6 generate X additional automatic hits. Bonus hits are not themselves Critical Hits. +- **Lethal Hits**: unmodified hit rolls of 6 auto-wound (skip the wound roll). Not a Critical Wound, so the target still gets a save. +- **Torrent**: no hit roll at all — automatic hits equal to the weapon's Attacks characteristic. +- **Psychic**: ignores all modifiers to the hit roll (cover, stealth, etc. all disregarded). +- **Heavy**: +1 to hit if the firing model moved less than 3" that turn (unit-wide, all-or-nothing). +- **Plunging Fire**: +1 BS when shooting from 3"+ elevation. +- **Indirect Fire**: target always treated as in Cover (-1 to hit), no rerolls to hit allowed. +- **Cover** (changed in 11th): now a straight -1 to the hit roll, not a save bonus. +- **Ignores Cover**: negates the Cover -1 to-hit penalty. +- **Stealth**: grants the Benefit of Cover even in the open (was a direct -1 to hit in 10th). + +## Wound roll keywords + +- **Devastating Wounds**: a Critical Wound (unmodified 6, or lower threshold via Anti) bypasses armor and invulnerable saves entirely. Only Feel No Pain can still apply. +- **Anti-X (keyword) Y+**: against units with the matching keyword, wound rolls of Y+ count as automatic Critical Wounds. +- **Twin-linked**: reroll wound rolls (ranged and melee). +- **Lance**: +1 to wound if the unit charged this turn. +- **Critical Wound**: bypasses all armor/invuln saves outright; only affects one model regardless of Damage stat. + +## Damage-stage modifiers (not hit/wound, but relevant to a full simulator) + +- **Melta X**: +X Damage when firing within half range (roll separately for models in/out of half range; apply after base damage, before damage-reduction effects). +- **Feel No Pain X+**: post-save roll-off to ignore a wound entirely. +- **Hazardous**: post-attack self-damage roll (fail on 1-2 → Mortal Wound(s), no saves). + +## Implications for Diehammer's modifier system + +To "accommodate" this fully, the die-modifier engine likely needs to support, per roll type (hit/wound/save): +1. A stacking flat-modifier input, auto-capped at ±1 net (hit/wound only — saves aren't capped the same way, since AP reduces save target directly rather than via a capped modifier). +2. A reroll mode selector: none / reroll 1s / reroll all failed / reroll all. +3. A "critical trigger" layer separate from raw success — since Sustained Hits, Lethal Hits, and Devastating Wounds all key off *unmodified* natural 6s (or a lowered Anti-X threshold), not the modified result. +4. Flags for "ignore all modifiers" (Psychic) and "auto-success, no roll" (Torrent).