include diehammer plan docs

This commit is contained in:
Rory Hinnen
2026-08-08 18:37:59 -05:00
parent ae99e94cd7
commit 17f2b0a288
67 changed files with 1991 additions and 50 deletions
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 547 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@@ -0,0 +1,116 @@
{
"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"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -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
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

@@ -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
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

@@ -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
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

@@ -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
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 KiB

@@ -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
}
}
@@ -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
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

@@ -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
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

@@ -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
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

@@ -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
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

+33
View File
@@ -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)
}
}
+20
View File
@@ -0,0 +1,20 @@
//
// Bunch_O__DiceApp.swift
// Bunch O' Dice
//
// Created by Rory Hinnen on 22/2/20.
//
import SwiftUI
@main
struct Bunch_O__DiceApp: App {
@StateObject var state = Roll()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(state)
}
}
}
+348
View File
@@ -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())
}
}
+294
View File
@@ -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)
}
}
}
+34
View File
@@ -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")
]
}
}
+317
View File
@@ -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
}
}
+78
View File
@@ -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())
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>
</dict>
</plist>
+33
View File
@@ -0,0 +1,33 @@
//
// ViewUtiles.swift
// Bunch O' Dice
//
// Created by Rory Hinnen on 22/6/12.
//
import Foundation
import SwiftUI
class SheetState<State>: ObservableObject {
@Published var isShowing = false
@Published var state: State? {
didSet { isShowing = state != nil }
}
}
class SettingsSheet: SheetState<SettingsSheet.State> {
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]
}
}
+49
View File
@@ -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())
}
}
@@ -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())
}
}
@@ -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())
}
}
@@ -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())
}
}
@@ -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())
}
}
@@ -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())
}
}
@@ -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())
}
@@ -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())
}
}
+20
View File
@@ -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()
}
}
@@ -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())
}
}