Compare commits

..
7 Commits
Author SHA1 Message Date
Rory Hinnen e4e7c9fa0d next step 2026-09-13 18:08:11 -05:00
Rory Hinnen 2034dbab40 big u/i restructures - still ugly 2026-09-13 11:37:58 -05:00
Rory Hinnen 1507f8b8c6 refactor into smaller units 2026-09-12 11:25:34 -05:00
Rory Hinnen 4eb7494d08 update view, still work to do 2026-09-08 18:06:24 -05:00
Rory Hinnen b1d4094f78 update view, still work to do 2026-09-08 17:49:59 -05:00
Rory Hinnen 241a753879 fix tests 2026-09-08 16:38:09 -05:00
Rory HinnenandClaude Sonnet 4.6 178123c944 fix project file paths and add DieHammerEngine local package dependency
Corrects group path from Bunch-O--Dice to DieHammer after directory rename,
fixes DEVELOPMENT_ASSET_PATHS, adds .gitignore to exclude build artifacts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 15:27:46 -05:00
22 changed files with 968 additions and 585 deletions
+10
View File
@@ -0,0 +1,10 @@
# Swift Package Manager
.build/
.swiftpm/
# Xcode user data
xcuserdata/
*.xcuserstate
# macOS
.DS_Store
+36
View File
@@ -0,0 +1,36 @@
{
"configurations" : [
{
"id" : "3E4A1B2C-5D6E-7F8A-9B0C-1D2E3F4A5B6C",
"name" : "Test Scheme Action",
"options" : {
}
}
],
"defaultOptions" : {
},
"testTargets" : [
{
"target" : {
"containerPath" : "container:DieHammer.xcodeproj",
"identifier" : "981AD0C927C30E32005A6492",
"name" : "Bunch O' DiceTests"
}
},
{
"target" : {
"containerPath" : "container:DieHammer.xcodeproj",
"identifier" : "981AD0D327C30E32005A6492",
"name" : "Bunch O' DiceUITests"
},
"uiTargetApp" : {
"containerPath" : "container:DieHammer.xcodeproj",
"identifier" : "981AD0B927C30E31005A6492",
"name" : "DieHammer"
}
}
],
"version" : 1
}
+97 -26
View File
@@ -3,11 +3,21 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 55; objectVersion = 60;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
7619A1543055ABA300E6F277 /* RerollPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7619A1533055ABA300E6F277 /* RerollPickerView.swift */; };
7619A1563055ABAF00E6F277 /* HitCalculatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7619A1553055ABAF00E6F277 /* HitCalculatorView.swift */; };
7619A1583055ABB400E6F277 /* WoundCalculatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7619A1573055ABB400E6F277 /* WoundCalculatorView.swift */; };
7619A15A3055ABB600E6F277 /* ArmourCalculatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7619A1593055ABB600E6F277 /* ArmourCalculatorView.swift */; };
7619A15C3055AF2400E6F277 /* StatePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7619A15B3055AF2400E6F277 /* StatePickerView.swift */; };
7619A15E3055AF2A00E6F277 /* RollSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7619A15D3055AF2A00E6F277 /* RollSummaryView.swift */; };
7619A1623055AF3300E6F277 /* DiePoolView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7619A1613055AF3300E6F277 /* DiePoolView.swift */; };
7619A1643055AF3900E6F277 /* InfoAreaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7619A1633055AF3900E6F277 /* InfoAreaView.swift */; };
7619A1663055AF3D00E6F277 /* ButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7619A1653055AF3D00E6F277 /* ButtonView.swift */; };
7692E8432BD55CDF005BDD76 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 7692E8422BD55CDE005BDD76 /* PrivacyInfo.xcprivacy */; }; 7692E8432BD55CDF005BDD76 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 7692E8422BD55CDE005BDD76 /* PrivacyInfo.xcprivacy */; };
76AB19823050A4B500BF7B9D /* DieHammerEngine in Frameworks */ = {isa = PBXBuildFile; productRef = 76AB19813050A4B500BF7B9D /* DieHammerEngine */; };
980EF454285E2BAD002FE8A0 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980EF453285E2BAD002FE8A0 /* MainView.swift */; }; 980EF454285E2BAD002FE8A0 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 980EF453285E2BAD002FE8A0 /* MainView.swift */; };
981AD0BE27C30E31005A6492 /* DieHammerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981AD0BD27C30E31005A6492 /* DieHammerApp.swift */; }; 981AD0BE27C30E31005A6492 /* DieHammerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981AD0BD27C30E31005A6492 /* DieHammerApp.swift */; };
981AD0C027C30E31005A6492 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981AD0BF27C30E31005A6492 /* ContentView.swift */; }; 981AD0C027C30E31005A6492 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981AD0BF27C30E31005A6492 /* ContentView.swift */; };
@@ -17,7 +27,6 @@
981AD0D927C30E32005A6492 /* Bunch_O__DiceUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981AD0D827C30E32005A6492 /* Bunch_O__DiceUITests.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 */; }; 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 */; }; 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 */; }; 98325A2E2839BE4B0006DF4B /* RollState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98325A2D2839BE4B0006DF4B /* RollState.swift */; };
98745CA62870CC670031166A /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98745CA52870CC670031166A /* SettingsView.swift */; }; 98745CA62870CC670031166A /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98745CA52870CC670031166A /* SettingsView.swift */; };
988B150F2854E9170082C2C5 /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 988B150E2854E9160082C2C5 /* HistoryView.swift */; }; 988B150F2854E9170082C2C5 /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 988B150E2854E9160082C2C5 /* HistoryView.swift */; };
@@ -45,6 +54,15 @@
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
7619A1533055ABA300E6F277 /* RerollPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RerollPickerView.swift; sourceTree = "<group>"; };
7619A1553055ABAF00E6F277 /* HitCalculatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HitCalculatorView.swift; sourceTree = "<group>"; };
7619A1573055ABB400E6F277 /* WoundCalculatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WoundCalculatorView.swift; sourceTree = "<group>"; };
7619A1593055ABB600E6F277 /* ArmourCalculatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArmourCalculatorView.swift; sourceTree = "<group>"; };
7619A15B3055AF2400E6F277 /* StatePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatePickerView.swift; sourceTree = "<group>"; };
7619A15D3055AF2A00E6F277 /* RollSummaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RollSummaryView.swift; sourceTree = "<group>"; };
7619A1613055AF3300E6F277 /* DiePoolView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiePoolView.swift; sourceTree = "<group>"; };
7619A1633055AF3900E6F277 /* InfoAreaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoAreaView.swift; sourceTree = "<group>"; };
7619A1653055AF3D00E6F277 /* ButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonView.swift; sourceTree = "<group>"; };
768C2F3A304F04E6002FEA9A /* Goals.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Goals.md; sourceTree = "<group>"; }; 768C2F3A304F04E6002FEA9A /* Goals.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Goals.md; sourceTree = "<group>"; };
7692E8422BD55CDE005BDD76 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; }; 7692E8422BD55CDE005BDD76 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
980EF453285E2BAD002FE8A0 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; }; 980EF453285E2BAD002FE8A0 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
@@ -59,10 +77,9 @@
981AD0D827C30E32005A6492 /* Bunch_O__DiceUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bunch_O__DiceUITests.swift; sourceTree = "<group>"; }; 981AD0D827C30E32005A6492 /* Bunch_O__DiceUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bunch_O__DiceUITests.swift; sourceTree = "<group>"; };
981AD0DA27C30E32005A6492 /* Bunch_O__DiceUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bunch_O__DiceUITestsLaunchTests.swift; sourceTree = "<group>"; }; 981AD0DA27C30E32005A6492 /* Bunch_O__DiceUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bunch_O__DiceUITestsLaunchTests.swift; sourceTree = "<group>"; };
981AD0E827C31208005A6492 /* Dice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dice.swift; sourceTree = "<group>"; }; 981AD0E827C31208005A6492 /* Dice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dice.swift; sourceTree = "<group>"; };
98225DE02A3527160019DE8E /* dice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = dice.swift; sourceTree = "<group>"; };
98325A2D2839BE4B0006DF4B /* RollState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RollState.swift; sourceTree = "<group>"; }; 98325A2D2839BE4B0006DF4B /* RollState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RollState.swift; sourceTree = "<group>"; };
98745CA52870CC670031166A /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; }; 98745CA52870CC670031166A /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
988997FF285814AB00B07E09 /* Bunch-O--Dice-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Bunch-O--Dice-Info.plist"; sourceTree = SOURCE_ROOT; }; 988997FF285814AB00B07E09 /* DieHammer.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = DieHammer.plist; sourceTree = SOURCE_ROOT; };
988B150E2854E9160082C2C5 /* HistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryView.swift; sourceTree = "<group>"; }; 988B150E2854E9160082C2C5 /* HistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryView.swift; sourceTree = "<group>"; };
988B1516285565610082C2C5 /* Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Onboarding.swift; sourceTree = "<group>"; }; 988B1516285565610082C2C5 /* Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Onboarding.swift; sourceTree = "<group>"; };
988B1518285566AD0082C2C5 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; }; 988B1518285566AD0082C2C5 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
@@ -75,6 +92,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
76AB19823050A4B500BF7B9D /* DieHammerEngine in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -95,6 +113,29 @@
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
7619A1523055AA4500E6F277 /* Elements */ = {
isa = PBXGroup;
children = (
7619A1533055ABA300E6F277 /* RerollPickerView.swift */,
7619A1553055ABAF00E6F277 /* HitCalculatorView.swift */,
7619A1573055ABB400E6F277 /* WoundCalculatorView.swift */,
7619A1593055ABB600E6F277 /* ArmourCalculatorView.swift */,
7619A15B3055AF2400E6F277 /* StatePickerView.swift */,
7619A15D3055AF2A00E6F277 /* RollSummaryView.swift */,
7619A1613055AF3300E6F277 /* DiePoolView.swift */,
7619A1633055AF3900E6F277 /* InfoAreaView.swift */,
7619A1653055AF3D00E6F277 /* ButtonView.swift */,
);
path = Elements;
sourceTree = "<group>";
};
76AB19803050A4B500BF7B9D /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "<group>";
};
981AD0B127C30E31005A6492 = { 981AD0B127C30E31005A6492 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -102,6 +143,7 @@
981AD0BC27C30E31005A6492 /* Bunch-O--Dice */, 981AD0BC27C30E31005A6492 /* Bunch-O--Dice */,
981AD0CD27C30E32005A6492 /* Bunch O' DiceTests */, 981AD0CD27C30E32005A6492 /* Bunch O' DiceTests */,
981AD0D727C30E32005A6492 /* Bunch O' DiceUITests */, 981AD0D727C30E32005A6492 /* Bunch O' DiceUITests */,
76AB19803050A4B500BF7B9D /* Frameworks */,
981AD0BB27C30E31005A6492 /* Products */, 981AD0BB27C30E31005A6492 /* Products */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
@@ -120,18 +162,16 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
7692E8422BD55CDE005BDD76 /* PrivacyInfo.xcprivacy */, 7692E8422BD55CDE005BDD76 /* PrivacyInfo.xcprivacy */,
988997FF285814AB00B07E09 /* Bunch-O--Dice-Info.plist */, 988997FF285814AB00B07E09 /* DieHammer.plist */,
981AD0BD27C30E31005A6492 /* DieHammerApp.swift */, 981AD0BD27C30E31005A6492 /* DieHammerApp.swift */,
981AD0BF27C30E31005A6492 /* ContentView.swift */,
988B1518285566AD0082C2C5 /* OnboardingView.swift */,
980EF453285E2BAD002FE8A0 /* MainView.swift */,
981AD0EC27C3356D005A6492 /* Views */, 981AD0EC27C3356D005A6492 /* Views */,
981AD0E727C311DA005A6492 /* Model */, 981AD0E727C311DA005A6492 /* Model */,
98E534D2285653E700D5ABB4 /* Utilities */, 98E534D2285653E700D5ABB4 /* Utilities */,
981AD0C127C30E32005A6492 /* Assets.xcassets */, 981AD0C127C30E32005A6492 /* Assets.xcassets */,
981AD0C327C30E32005A6492 /* Preview Content */, 981AD0C327C30E32005A6492 /* Preview Content */,
); );
path = "Bunch-O--Dice"; name = "Bunch-O--Dice";
path = DieHammer;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
981AD0C327C30E32005A6492 /* Preview Content */ = { 981AD0C327C30E32005A6492 /* Preview Content */ = {
@@ -147,7 +187,8 @@
children = ( children = (
981AD0CE27C30E32005A6492 /* Bunch_O__DiceTests.swift */, 981AD0CE27C30E32005A6492 /* Bunch_O__DiceTests.swift */,
); );
path = "Bunch O' DiceTests"; name = "Bunch O' DiceTests";
path = DieHammerTests;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
981AD0D727C30E32005A6492 /* Bunch O' DiceUITests */ = { 981AD0D727C30E32005A6492 /* Bunch O' DiceUITests */ = {
@@ -156,7 +197,8 @@
981AD0D827C30E32005A6492 /* Bunch_O__DiceUITests.swift */, 981AD0D827C30E32005A6492 /* Bunch_O__DiceUITests.swift */,
981AD0DA27C30E32005A6492 /* Bunch_O__DiceUITestsLaunchTests.swift */, 981AD0DA27C30E32005A6492 /* Bunch_O__DiceUITestsLaunchTests.swift */,
); );
path = "Bunch O' DiceUITests"; name = "Bunch O' DiceUITests";
path = DieHammerUITests;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
981AD0E727C311DA005A6492 /* Model */ = { 981AD0E727C311DA005A6492 /* Model */ = {
@@ -172,8 +214,11 @@
981AD0EC27C3356D005A6492 /* Views */ = { 981AD0EC27C3356D005A6492 /* Views */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
7619A1523055AA4500E6F277 /* Elements */,
98D0CD192856E3850047DC1B /* Modals */, 98D0CD192856E3850047DC1B /* Modals */,
98225DE02A3527160019DE8E /* dice.swift */, 988B1518285566AD0082C2C5 /* OnboardingView.swift */,
981AD0BF27C30E31005A6492 /* ContentView.swift */,
980EF453285E2BAD002FE8A0 /* MainView.swift */,
); );
path = Views; path = Views;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -199,9 +244,9 @@
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
981AD0B927C30E31005A6492 /* Bunch O' Dice */ = { 981AD0B927C30E31005A6492 /* DieHammer */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 981AD0DE27C30E32005A6492 /* Build configuration list for PBXNativeTarget "Bunch O' Dice" */; buildConfigurationList = 981AD0DE27C30E32005A6492 /* Build configuration list for PBXNativeTarget "DieHammer" */;
buildPhases = ( buildPhases = (
981AD0B627C30E31005A6492 /* Sources */, 981AD0B627C30E31005A6492 /* Sources */,
981AD0B727C30E31005A6492 /* Frameworks */, 981AD0B727C30E31005A6492 /* Frameworks */,
@@ -211,7 +256,7 @@
); );
dependencies = ( dependencies = (
); );
name = "Bunch O' Dice"; name = DieHammer;
productName = "Bunch O' Dice"; productName = "Bunch O' Dice";
productReference = 981AD0BA27C30E31005A6492 /* Bunch O' Dice.app */; productReference = 981AD0BA27C30E31005A6492 /* Bunch O' Dice.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
@@ -284,11 +329,14 @@
Base, Base,
); );
mainGroup = 981AD0B127C30E31005A6492; mainGroup = 981AD0B127C30E31005A6492;
packageReferences = (
7665121E305077AF0002A498 /* XCLocalSwiftPackageReference "DieHammerEngine" */,
);
productRefGroup = 981AD0BB27C30E31005A6492 /* Products */; productRefGroup = 981AD0BB27C30E31005A6492 /* Products */;
projectDirPath = ""; projectDirPath = "";
projectRoot = ""; projectRoot = "";
targets = ( targets = (
981AD0B927C30E31005A6492 /* Bunch O' Dice */, 981AD0B927C30E31005A6492 /* DieHammer */,
981AD0C927C30E32005A6492 /* Bunch O' DiceTests */, 981AD0C927C30E32005A6492 /* Bunch O' DiceTests */,
981AD0D327C30E32005A6492 /* Bunch O' DiceUITests */, 981AD0D327C30E32005A6492 /* Bunch O' DiceUITests */,
); );
@@ -328,17 +376,25 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
98E534D42856540A00D5ABB4 /* ViewUtils.swift in Sources */, 98E534D42856540A00D5ABB4 /* ViewUtils.swift in Sources */,
7619A1643055AF3900E6F277 /* InfoAreaView.swift in Sources */,
7619A15A3055ABB600E6F277 /* ArmourCalculatorView.swift in Sources */,
981AD0C027C30E31005A6492 /* ContentView.swift in Sources */, 981AD0C027C30E31005A6492 /* ContentView.swift in Sources */,
988B1519285566AD0082C2C5 /* OnboardingView.swift in Sources */, 988B1519285566AD0082C2C5 /* OnboardingView.swift in Sources */,
981AD0E927C31208005A6492 /* Dice.swift in Sources */, 981AD0E927C31208005A6492 /* Dice.swift in Sources */,
7619A1583055ABB400E6F277 /* WoundCalculatorView.swift in Sources */,
98F001D5284F86F1004F5140 /* AddToPool.swift in Sources */, 98F001D5284F86F1004F5140 /* AddToPool.swift in Sources */,
988B150F2854E9170082C2C5 /* HistoryView.swift in Sources */, 988B150F2854E9170082C2C5 /* HistoryView.swift in Sources */,
98225DE12A3527160019DE8E /* dice.swift in Sources */, 7619A1543055ABA300E6F277 /* RerollPickerView.swift in Sources */,
981AD0BE27C30E31005A6492 /* DieHammerApp.swift in Sources */, 981AD0BE27C30E31005A6492 /* DieHammerApp.swift in Sources */,
7619A15E3055AF2A00E6F277 /* RollSummaryView.swift in Sources */,
7619A1623055AF3300E6F277 /* DiePoolView.swift in Sources */,
7619A15C3055AF2400E6F277 /* StatePickerView.swift in Sources */,
980EF454285E2BAD002FE8A0 /* MainView.swift in Sources */, 980EF454285E2BAD002FE8A0 /* MainView.swift in Sources */,
7619A1563055ABAF00E6F277 /* HitCalculatorView.swift in Sources */,
98325A2E2839BE4B0006DF4B /* RollState.swift in Sources */, 98325A2E2839BE4B0006DF4B /* RollState.swift in Sources */,
988B1517285565610082C2C5 /* Onboarding.swift in Sources */, 988B1517285565610082C2C5 /* Onboarding.swift in Sources */,
98745CA62870CC670031166A /* SettingsView.swift in Sources */, 98745CA62870CC670031166A /* SettingsView.swift in Sources */,
7619A1663055AF3D00E6F277 /* ButtonView.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -364,12 +420,12 @@
/* Begin PBXTargetDependency section */ /* Begin PBXTargetDependency section */
981AD0CC27C30E32005A6492 /* PBXTargetDependency */ = { 981AD0CC27C30E32005A6492 /* PBXTargetDependency */ = {
isa = PBXTargetDependency; isa = PBXTargetDependency;
target = 981AD0B927C30E31005A6492 /* Bunch O' Dice */; target = 981AD0B927C30E31005A6492 /* DieHammer */;
targetProxy = 981AD0CB27C30E32005A6492 /* PBXContainerItemProxy */; targetProxy = 981AD0CB27C30E32005A6492 /* PBXContainerItemProxy */;
}; };
981AD0D627C30E32005A6492 /* PBXTargetDependency */ = { 981AD0D627C30E32005A6492 /* PBXTargetDependency */ = {
isa = PBXTargetDependency; isa = PBXTargetDependency;
target = 981AD0B927C30E31005A6492 /* Bunch O' Dice */; target = 981AD0B927C30E31005A6492 /* DieHammer */;
targetProxy = 981AD0D527C30E32005A6492 /* PBXContainerItemProxy */; targetProxy = 981AD0D527C30E32005A6492 /* PBXContainerItemProxy */;
}; };
/* End PBXTargetDependency section */ /* End PBXTargetDependency section */
@@ -506,10 +562,9 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"Bunch-O--Dice/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"DieHammer/Preview Content\"";
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Bunch-O--Dice-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = DieHammer; INFOPLIST_KEY_CFBundleDisplayName = DieHammer;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
@@ -517,13 +572,14 @@
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait;
IPHONEOS_DEPLOYMENT_TARGET = "$(RECOMMENDED_IPHONEOS_DEPLOYMENT_TARGET)"; IPHONEOS_DEPLOYMENT_TARGET = 26.6;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.2; MARKETING_VERSION = 1.2;
PRODUCT_BUNDLE_IDENTIFIER = "com.chartreusenecktie.Bunch-O--Dice"; PRODUCT_BUNDLE_IDENTIFIER = "com.chartreusenecktie.Bunch-O--Dice";
PRODUCT_MODULE_NAME = DieHammer;
PRODUCT_NAME = "Bunch O' Dice"; PRODUCT_NAME = "Bunch O' Dice";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@@ -538,10 +594,9 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"Bunch-O--Dice/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"DieHammer/Preview Content\"";
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Bunch-O--Dice-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = DieHammer; INFOPLIST_KEY_CFBundleDisplayName = DieHammer;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
@@ -549,13 +604,14 @@
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait;
IPHONEOS_DEPLOYMENT_TARGET = "$(RECOMMENDED_IPHONEOS_DEPLOYMENT_TARGET)"; IPHONEOS_DEPLOYMENT_TARGET = 26.6;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.2; MARKETING_VERSION = 1.2;
PRODUCT_BUNDLE_IDENTIFIER = "com.chartreusenecktie.Bunch-O--Dice"; PRODUCT_BUNDLE_IDENTIFIER = "com.chartreusenecktie.Bunch-O--Dice";
PRODUCT_MODULE_NAME = DieHammer;
PRODUCT_NAME = "Bunch O' Dice"; PRODUCT_NAME = "Bunch O' Dice";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@@ -645,7 +701,7 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
981AD0DE27C30E32005A6492 /* Build configuration list for PBXNativeTarget "Bunch O' Dice" */ = { 981AD0DE27C30E32005A6492 /* Build configuration list for PBXNativeTarget "DieHammer" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
981AD0DF27C30E32005A6492 /* Debug */, 981AD0DF27C30E32005A6492 /* Debug */,
@@ -673,6 +729,21 @@
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
7665121E305077AF0002A498 /* XCLocalSwiftPackageReference "DieHammerEngine" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = DieHammerEngine;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
76AB19813050A4B500BF7B9D /* DieHammerEngine */ = {
isa = XCSwiftPackageProductDependency;
package = 7665121E305077AF0002A498 /* XCLocalSwiftPackageReference "DieHammerEngine" */;
productName = DieHammerEngine;
};
/* End XCSwiftPackageProductDependency section */
}; };
rootObject = 981AD0B227C30E31005A6492 /* Project object */; rootObject = 981AD0B227C30E31005A6492 /* Project object */;
} }
@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2700"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "981AD0B927C30E31005A6492"
BuildableName = "Bunch O&apos; Dice.app"
BlueprintName = "DieHammer"
ReferencedContainer = "container:DieHammer.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
shouldUseLaunchSchemeArgsEnv = "YES">
<TestPlans>
<TestPlanReference
reference = "container:Bunch O&apos; Dice.xctestplan"
default = "YES">
</TestPlanReference>
</TestPlans>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
queueDebuggingEnabled = "No">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "981AD0B927C30E31005A6492"
BuildableName = "Bunch O&apos; Dice.app"
BlueprintName = "DieHammer"
ReferencedContainer = "container:DieHammer.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "981AD0B927C30E31005A6492"
BuildableName = "Bunch O&apos; Dice.app"
BlueprintName = "DieHammer"
ReferencedContainer = "container:DieHammer.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -9,6 +9,19 @@
<key>orderHint</key> <key>orderHint</key>
<integer>0</integer> <integer>0</integer>
</dict> </dict>
<key>Bunch O' DiceUITests.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>
<dict>
<key>981AD0D327C30E32005A6492</key>
<dict>
<key>primary</key>
<true/>
</dict>
</dict> </dict>
</dict> </dict>
</plist> </plist>
-509
View File
@@ -1,509 +0,0 @@
//
// 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 {
VStack {
HStack(alignment: .top) {
Spacer()
VStack {
Text("Armour")
Group {
Text("▲")
.gesture(TapGesture() .onEnded {
current.addPenetration()
})
Text(
(current.armourPenetration > 0 ?
"+" : "") +
String(current.armourPenetration))
Text("▼")
.gesture(TapGesture() .onEnded {
current.losePenetration()
})
}
.font(.system(size: controlSize))
Text("Penetration")
}
Spacer()
VStack {
Text("Invulnerable")
Toggle("", isOn: $current.invulnerable).labelsHidden()
.padding()
}
Spacer()
}
HStack {
Text("Reroll")
Picker("", selection: $current.saveRerollMode) {
ForEach(RerollMode.allCases, id: \.self) { mode in
Text(mode.label).tag(mode)
}
}
.pickerStyle(.segmented)
Button("🎲") {
current.applyReroll()
}
.disabled(current.saveRerollMode == .none || current.saveHasRerolled)
.font(.system(size: controlSize))
}
.padding(.horizontal)
if current.mortalWoundsTotal > 0 {
HStack {
Text("Mortal wounds: \(current.mortalWoundsTotal) (bypassed saves)")
.fontWeight(.semibold)
.foregroundColor(.orange)
Spacer()
}
.font(.system(size: 18))
.padding(.horizontal)
}
}
.padding()
}
@ViewBuilder func woundCalculator(controlSize: Double) -> some View {
VStack {
if current.autoWoundsCarried > 0 {
HStack {
Text("Auto-wounds carried: \(current.autoWoundsCarried) (skip wound roll)")
.fontWeight(.semibold)
.foregroundColor(.orange)
Spacer()
}
.font(.system(size: 18))
.padding(.horizontal)
}
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()
}
HStack {
Text("Reroll")
Picker("", selection: $current.woundRerollMode) {
ForEach(RerollMode.allCases, id: \.self) { mode in
Text(mode.label).tag(mode)
}
}
.pickerStyle(.segmented)
Button("🎲") {
current.applyReroll()
}
.disabled(current.woundRerollMode == .none || current.woundHasRerolled)
.font(.system(size: controlSize))
}
.padding(.horizontal)
VStack(spacing: 8) {
HStack {
Toggle("Devastating Wounds", isOn: $current.devastatingWounds)
}
if current.devastatingWounds {
HStack {
Text("Mortals (6s): \(current.critWoundCount)")
.fontWeight(.semibold)
.foregroundColor(.orange)
Spacer()
}
}
}
.font(.system(size: 18))
.padding(.horizontal)
}
.padding()
}
@ViewBuilder func hitCalculator(controlSize: Double) -> some View {
VStack {
HStack(alignment: .top) {
Spacer()
VStack {
Text("Hit")
.multilineTextAlignment(.center)
Group {
Text("▲")
.gesture(TapGesture() .onEnded {
current.modifier = current.modifier.add()
})
Text((current.modifier.rawValue > 0 ? "+" : "") +
String(current.modifier.rawValue))
Text("▼")
.gesture(TapGesture() .onEnded {
current.modifier = current.modifier.remove()
})
}
.font(.system(size: controlSize))
Text("Modifier")
}
.disabled(current.torrent || current.psychic)
.opacity((current.torrent || current.psychic) ? 0.4 : 1.0)
Spacer()
VStack {
Text("Blow Up")
VStack {
Button(action: { current.applyBlowup(face: .six) }) {
Image(systemName: "die.face.6")
.foregroundColor(.primary)
}
Button(action: { current.applyBlowup(face: .five) }) {
Image(systemName: "die.face.5")
.foregroundColor(.primary)
}
}
.padding()
.font(.system(size: controlSize))
}
.disabled(current.blowupUsed || current.torrent)
.opacity((current.blowupUsed || current.torrent) ? 0.4 : 1.0)
Spacer()
}
HStack {
Text("Reroll")
Picker("", selection: $current.hitRerollMode) {
ForEach(RerollMode.allCases, id: \.self) { mode in
Text(mode.label).tag(mode)
}
}
.pickerStyle(.segmented)
Button("🎲") {
current.applyReroll()
}
.disabled(current.hitRerollMode == .none || current.hitHasRerolled)
.font(.system(size: controlSize))
}
.padding(.horizontal)
.disabled(current.torrent)
.opacity(current.torrent ? 0.4 : 1.0)
HStack {
Toggle("Torrent", isOn: $current.torrent)
Spacer()
Toggle("Psychic", isOn: $current.psychic)
.disabled(current.torrent)
.opacity(current.torrent ? 0.4 : 1.0)
}
.padding(.horizontal)
VStack(spacing: 8) {
HStack {
Toggle("Lethal Hits", isOn: $current.lethalHits)
Spacer()
Toggle("Sust. Hits", isOn: $current.sustainedHits)
}
if current.sustainedHits {
HStack {
Text("X:")
Spacer()
Text("▲")
.gesture(TapGesture().onEnded {
if current.sustainedHitsX < 6 { current.sustainedHitsX += 1 }
})
Text(String(current.sustainedHitsX))
.frame(minWidth: 30, alignment: .center)
Text("▼")
.gesture(TapGesture().onEnded {
if current.sustainedHitsX > 1 { current.sustainedHitsX -= 1 }
})
}
}
HStack {
Toggle("Anti-X", isOn: $current.antiX)
if current.antiX {
Spacer()
Text("▲")
.gesture(TapGesture().onEnded {
if current.antiXThreshold < 6 { current.antiXThreshold += 1 }
})
Text("\(current.antiXThreshold)+")
.frame(minWidth: 34, alignment: .center)
Text("▼")
.gesture(TapGesture().onEnded {
if current.antiXThreshold > 2 { current.antiXThreshold -= 1 }
})
}
}
if current.lethalHits || current.sustainedHits || current.antiX {
HStack {
if current.lethalHits || current.sustainedHits {
Text("Crits (6s): \(current.critHitCount)")
.fontWeight(.semibold)
}
Spacer()
if current.antiX {
Text("Anti-X (\(current.antiXThreshold)+): \(current.antiXCount)")
.fontWeight(.semibold)
}
}
.foregroundColor(.orange)
}
}
.font(.system(size: 18))
.padding(.horizontal)
}
.padding()
}
//MARK: die views
@ViewBuilder func die(whichDie: Dice.DiceType) -> some View {
let size = 22.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
VStack(spacing: 2) {
Text(String(current.dice.value(which: whichDie)))
.foregroundColor(current.state == .armour ?
displayColorArmorText : displayColorHit)
.frame(minWidth: 30, alignment: .center)
Image(systemName: "die.face." + String(whichDie.rawValue) + ".fill")
.foregroundColor(current.state == .armour ?
displayColorArmor : displayColorHit)
}
.font(.system(size: size))
.padding(4)
.frame(maxWidth: .infinity)
.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 {
HStack(spacing: 0) {
ForEach(Dice.DiceType.allCases, 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(spacing: 12) {
HStack(spacing: 12) {
Button(action: { showFillView.toggle() }) {
VStack(spacing: 4) {
Text("Dice Pool")
.font(.caption)
Text(String(current.total))
.font(.system(size: 28, weight: .black))
}
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
.background(Color.white)
.clipShape(RoundedRectangle(cornerRadius: 10))
.foregroundColor(.black)
}
.sheet(isPresented: $showFillView,
onDismiss: { current.resetPool() }) {
VStack {
AddToPool()
Button("Accept", action: {showFillView.toggle()})
.padding(20)
}
}
Button(action: current.incrementState) {
VStack(spacing: 4) {
Text(current.state.text())
.font(.caption)
Text(String(current.result))
.font(.system(size: 28, weight: .black))
}
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
.background(Color.white)
.clipShape(RoundedRectangle(cornerRadius: 10))
.foregroundColor(.black)
}
}
.padding()
.background(Color.black)
.clipShape(RoundedRectangle(cornerRadius: 14))
diePool()
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)}
}
#Preview("Hit") {
MainView().environmentObject(Roll())
}
#Preview("Wound") {
MainView().environmentObject({
let r = Roll()
r.state = .wound
return r
}())
}
#Preview("Armour") {
MainView().environmentObject({
let r = Roll()
r.state = .armour
return r
}())
}
+35 -16
View File
@@ -151,6 +151,11 @@ class Roll: ObservableObject {
@Published var woundHasRerolled = false @Published var woundHasRerolled = false
@Published var saveHasRerolled = false @Published var saveHasRerolled = false
// Snapshot of dice before the current phase's reroll was applied.
// Restored when the user switches reroll modes, so the new mode
// always applies to the original un-rerolled results.
@Published var preRerollDice: Dice? = nil
@Published var blowupUsed = false @Published var blowupUsed = false
@Published var torrent = false @Published var torrent = false
@Published var psychic = false @Published var psychic = false
@@ -181,7 +186,7 @@ class Roll: ObservableObject {
case .wound: case .wound:
return wound return wound
case .armour: case .armour:
return save return save + mortalWoundsTotal
} }
} }
} }
@@ -209,17 +214,7 @@ class Roll: ObservableObject {
case .hit: case .hit:
return hitCutoff return hitCutoff
case .wound: case .wound:
if Float(self.strength) / Float(self.toughness) >= 2.0 { return Dice.DiceType(rawValue: woundCutoff) ?? .four
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: case .armour:
return Dice.DiceType(rawValue: self.armourSave) ?? .one return Dice.DiceType(rawValue: self.armourSave) ?? .one
} }
@@ -376,6 +371,7 @@ class Roll: ObservableObject {
self.total = max(0, hit - autoWoundsCarried) + sustained self.total = max(0, hit - autoWoundsCarried) + sustained
self.state = .wound self.state = .wound
woundHasRerolled = false woundHasRerolled = false
preRerollDice = nil
case .wound: case .wound:
let mortals = critWoundCount let mortals = critWoundCount
mortalWoundsTotal = mortals mortalWoundsTotal = mortals
@@ -383,10 +379,12 @@ class Roll: ObservableObject {
autoWoundsCarried = 0 autoWoundsCarried = 0
self.state = .armour self.state = .armour
saveHasRerolled = false saveHasRerolled = false
preRerollDice = nil
case .armour: case .armour:
self.total = save self.total = save + mortalWoundsTotal
self.state = .hit self.state = .hit
hitHasRerolled = false hitHasRerolled = false
preRerollDice = nil
blowupUsed = false blowupUsed = false
mortalWoundsTotal = 0 mortalWoundsTotal = 0
} }
@@ -397,6 +395,7 @@ class Roll: ObservableObject {
func resetPool() { func resetPool() {
dice.roll(howMany: total) dice.roll(howMany: total)
hitHasRerolled = false hitHasRerolled = false
preRerollDice = nil
blowupUsed = false blowupUsed = false
} }
@@ -414,8 +413,6 @@ class Roll: ObservableObject {
} }
func applyReroll() { func applyReroll() {
guard !currentHasRerolled else { return }
let mode: RerollMode let mode: RerollMode
let cutoff: Int let cutoff: Int
@@ -431,9 +428,19 @@ class Roll: ObservableObject {
cutoff = invulnerable ? armourSave : (armourSave + armourPenetration) cutoff = invulnerable ? armourSave : (armourSave + armourPenetration)
} }
guard mode != .none else { return }
// If a reroll was already applied this phase, restore the original dice first
// so the new mode always operates on the un-rerolled results.
if let snapshot = preRerollDice {
dice = snapshot
} else {
preRerollDice = dice
}
switch mode { switch mode {
case .none: case .none:
return break
case .ones: case .ones:
dice.reroll(dice: .one) dice.reroll(dice: .one)
case .failed: case .failed:
@@ -449,6 +456,18 @@ class Roll: ObservableObject {
} }
} }
// Restores dice to pre-reroll state when the user selects None.
func undoReroll() {
guard let snapshot = preRerollDice else { return }
dice = snapshot
preRerollDice = nil
switch state {
case .hit: hitHasRerolled = false
case .wound: woundHasRerolled = false
case .armour: saveHasRerolled = false
}
}
func add(howMuch: Int) { func add(howMuch: Int) {
var newTotal: Int = 0 var newTotal: Int = 0
newTotal = self.total + howMuch newTotal = self.total + howMuch
+50
View File
@@ -31,3 +31,53 @@ extension CaseIterable where Self: Equatable {
self = allCases[nextIndex == allCases.endIndex ? allCases.startIndex : nextIndex] self = allCases[nextIndex == allCases.endIndex ? allCases.startIndex : nextIndex]
} }
} }
extension EnvironmentValues {
@Entry var dieColorSelected: Color = .brown
@Entry var dieColorPrimary: Color = .cyan
}
extension View {
func disabledWithOpacity(_ condition: Bool) -> some View {
self.disabled(condition).opacity(condition ? 0.4 : 1.0)
}
}
struct ToggleChip: View {
let label: String
@Binding var isOn: Bool
var body: some View {
Button(action: { isOn.toggle() }) {
Text(label)
.fontWeight(isOn ? .semibold : .regular)
.padding(.horizontal, 12)
.padding(.vertical, 6)
.foregroundColor(isOn ? Color(UIColor.systemBackground) : .primary)
.background(isOn ? Color.primary : Color.clear)
.clipShape(Capsule())
.overlay(Capsule().stroke(Color.primary, lineWidth: 1))
}
}
}
struct StatStepper: View {
let top: String
let value: String
let bottom: String
let increment: () -> Void
let decrement: () -> Void
var body: some View {
VStack {
Text(top)
Group {
Text("▲").gesture(TapGesture().onEnded { increment() })
Text(value)
Text("▼").gesture(TapGesture().onEnded { decrement() })
}
.font(.system(size: 30))
Text(bottom)
}
}
}
@@ -0,0 +1,39 @@
//
// ArmourCalculatorView.swift
// Bunch O' Dice
//
import SwiftUI
struct ArmourCalculatorView: View {
@EnvironmentObject var current: Roll
var body: some View {
VStack {
RerollPickerView(mode: $current.saveRerollMode)
HStack(alignment: .top) {
Spacer()
StatStepper(
top: "Armour",
value: (current.armourPenetration > 0 ? "+" : "") + String(current.armourPenetration),
bottom: "Penetration",
increment: { current.addPenetration() },
decrement: { current.losePenetration() }
)
Spacer()
ToggleChip(label: "Invulnerable", isOn: $current.invulnerable)
Spacer()
}
}
.padding()
}
}
#Preview {
ArmourCalculatorView()
.environmentObject({
let r = Roll()
r.state = .armour
return r
}())
}
+41
View File
@@ -0,0 +1,41 @@
//
// ButtonView.swift
// Bunch O' Dice
//
import SwiftUI
struct ButtonView: View {
let color: CGColor
let label: String
let value: Int
var body: 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))
}
}
#Preview {
ButtonView(
color: CGColor(red: 1.0, green: 0.3, blue: 0.3, alpha: 1.0),
label: "Hits",
value: 12
)
}
@@ -0,0 +1,54 @@
//
// DiePoolView.swift
// Bunch O' Dice
//
import SwiftUI
struct DiePoolView: View {
var body: some View {
HStack(spacing: 0) {
ForEach(Dice.DiceType.allCases, id: \.rawValue) { i in
DieView(whichDie: i)
}
}
}
}
private struct DieView: View {
let whichDie: Dice.DiceType
@EnvironmentObject var current: Roll
@Environment(\.dieColorSelected) var colorSelected
@Environment(\.dieColorPrimary) var colorPrimary
var body: some View {
let size = 42.0
let displayColorHit = (whichDie.rawValue >= current.cutoff.rawValue) ? colorSelected : colorPrimary
let displayColorArmor = (whichDie.rawValue <= current.cutoff.rawValue) ? colorSelected : colorPrimary
let displayColorArmorText = (whichDie.rawValue < current.cutoff.rawValue) ? colorSelected : colorPrimary
VStack(spacing: 2) {
Text(String(current.dice.value(which: whichDie)))
.foregroundColor(current.state == .armour ? displayColorArmorText : displayColorHit)
.frame(minWidth: 30, alignment: .center)
Image(systemName: "die.face." + String(whichDie.rawValue) + ".fill")
.foregroundColor(current.state == .armour ? displayColorArmor : displayColorHit)
}
.font(.system(size: size))
.padding(4)
.frame(maxWidth: .infinity)
.gesture(TapGesture().onEnded {
if current.state == .hit {
if whichDie.rawValue != 1 {
current.hitCutoff = whichDie
}
} else if current.state == .armour {
current.armourSave = Int(whichDie.rawValue)
}
})
}
}
#Preview {
DiePoolView()
.environmentObject(Roll())
}
@@ -0,0 +1,100 @@
//
// HitCalculatorView.swift
// Bunch O' Dice
//
import SwiftUI
struct HitCalculatorView: View {
@EnvironmentObject var current: Roll
private let controlSize = 30.0
var body: some View {
VStack {
RerollPickerView(mode: $current.hitRerollMode)
.disabledWithOpacity(current.torrent)
HStack(alignment: .top) {
Spacer()
StatStepper(
top: "Hit Modifier",
value: (current.modifier.rawValue > 0 ? "+" : "") + String(current.modifier.rawValue),
bottom: "",
increment: { current.modifier = current.modifier.add() },
decrement: { current.modifier = current.modifier.remove() }
)
.disabledWithOpacity(current.torrent || current.psychic)
Spacer()
VStack {
Text("Blow Up")
VStack {
Button(action: { current.applyBlowup(face: .six) }) {
Image(systemName: "die.face.6")
.foregroundColor(.primary)
}
Button(action: { current.applyBlowup(face: .five) }) {
Image(systemName: "die.face.5")
.foregroundColor(.primary)
}
}
.padding()
.font(.system(size: controlSize))
}
.disabledWithOpacity(current.blowupUsed || current.torrent)
Spacer()
}
HStack(spacing: 6) {
ToggleChip(label: "Torrent", isOn: $current.torrent)
Spacer()
ToggleChip(label: "Psychic", isOn: $current.psychic)
.disabledWithOpacity(current.torrent)
Spacer()
ToggleChip(label: "Lethal", isOn: $current.lethalHits)
}
.padding(.horizontal)
VStack(spacing: 8) {
HStack {
ToggleChip(label: "Sustained Hits", isOn: $current.sustainedHits)
Spacer()
if current.sustainedHits {
Text("X:")
Text("▲")
.gesture(TapGesture().onEnded {
if current.sustainedHitsX < 6 { current.sustainedHitsX += 1 }
})
Text(String(current.sustainedHitsX))
.frame(minWidth: 30, alignment: .center)
Text("▼")
.gesture(TapGesture().onEnded {
if current.sustainedHitsX > 1 { current.sustainedHitsX -= 1 }
})
}
}
HStack {
ToggleChip(label: "Anti-X", isOn: $current.antiX)
Spacer()
if current.antiX {
Text("▲")
.gesture(TapGesture().onEnded {
if current.antiXThreshold < 6 { current.antiXThreshold += 1 }
})
Text("\(current.antiXThreshold)+")
.frame(minWidth: 34, alignment: .center)
Text("▼")
.gesture(TapGesture().onEnded {
if current.antiXThreshold > 2 { current.antiXThreshold -= 1 }
})
}
}
}
.font(.system(size: 18))
.padding(.horizontal)
}
.padding()
}
}
#Preview {
HitCalculatorView()
.environmentObject(Roll())
}
@@ -0,0 +1,49 @@
//
// InfoAreaView.swift
// Bunch O' Dice
//
import SwiftUI
struct InfoAreaView: View {
@EnvironmentObject var current: Roll
var body: some View {
HStack() {
switch current.state {
case .hit:
if current.lethalHits || current.sustainedHits {
Text("Crits (6s): \(current.critHitCount)")
.fontWeight(.semibold)
.foregroundColor(.red)
}
if current.antiX {
Text("Anti-X (\(current.antiXThreshold)+): \(current.antiXCount)")
.fontWeight(.semibold)
.foregroundColor(.red)
}
case .wound:
if current.autoWoundsCarried > 0 {
Text("Auto-wounds carried: \(current.autoWoundsCarried) (skip wound roll)")
.fontWeight(.semibold)
.foregroundColor(.red)
}
if current.devastatingWounds {
Text("Mortal wounds: \(current.critWoundCount)")
.fontWeight(.semibold)
.foregroundColor(.red)
}
case .armour:
EmptyView()
}
}
.font(.system(size: 18))
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal)
}
}
#Preview {
InfoAreaView()
.environmentObject(Roll())
}
@@ -0,0 +1,40 @@
//
// RerollPickerView.swift
// Bunch O' Dice
//
import SwiftUI
struct RerollPickerView: View {
@EnvironmentObject var current: Roll
@Binding var mode: RerollMode
var body: some View {
let actionBinding = Binding<RerollMode>(
get: { mode },
set: { newValue in
mode = newValue
if newValue != .none {
current.applyReroll()
} else {
current.undoReroll()
}
}
)
HStack(spacing: 8) {
Text("Reroll")
Picker("Reroll", selection: actionBinding) {
ForEach(RerollMode.allCases, id: \.self) { m in
Text(m.label).tag(m)
}
}
.pickerStyle(.segmented)
}
.padding(.horizontal)
}
}
#Preview {
RerollPickerView(mode: .constant(.none))
.environmentObject(Roll())
}
@@ -0,0 +1,73 @@
//
// RollSummaryView.swift
// Bunch O' Dice
//
import SwiftUI
struct RollSummaryView: View {
@EnvironmentObject var current: Roll
@State private var showFillView = false
@Environment(\.dieColorSelected) var colorSelected
@Environment(\.dieColorPrimary) var colorPrimary
var body: some View {
VStack{
HStack(spacing: 12) {
VStack {
Text("Dice Pool")
.font(.caption)
Button(action: { showFillView.toggle() }) {
VStack(spacing: 4) {
Text(String(current.total))
.font(.system(size: 28, weight: .black))
}
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
.clipShape(Circle())
.foregroundColor(.black)
.glassEffect(.clear.tint(colorPrimary))
}
}
.sheet(isPresented: $showFillView,
onDismiss: { current.resetPool() }) {
VStack {
AddToPool()
Button("Accept", action: { showFillView.toggle() })
.padding(20)
}
}
VStack {
Text(current.state == .armour ? "Model Damage" : current.state.text() + "s")
.font(.caption)
Button(action: current.incrementState) {
VStack(spacing: 4) {
Text(String(current.result))
.font(.system(size: 28, weight: .black))
if current.state == .armour && current.mortalWoundsTotal > 0 {
Text("mortal: \(current.mortalWoundsTotal)")
.font(.caption)
}
}
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
.clipShape(Circle())
.foregroundColor(.black)
.glassEffect(.clear.tint(colorSelected))
}
}
}
DiePoolView()
}
.padding()
.frame(maxWidth: .infinity)
.background(Color.gray)
}
}
#Preview {
RollSummaryView()
.environmentObject(Roll())
}
@@ -0,0 +1,47 @@
//
// StatePickerView.swift
// Bunch O' Dice
//
import SwiftUI
struct StatePickerView: View {
@EnvironmentObject var current: Roll
private let controlSize = 30.0
var body: some View {
HStack {
Button(action: { current.state = .hit }) {
ZStack {
if current.state == .hit { circle() }
Text(RollState.hit.print())
}
}.padding(10)
Button(action: { current.state = .wound }) {
ZStack {
if current.state == .wound { circle() }
Text(RollState.wound.print())
}
}.padding(10)
Button(action: { current.state = .armour }) {
ZStack {
if current.state == .armour { circle() }
Text(RollState.armour.print())
}
}.padding(10)
}
.font(.system(size: 60))
}
@ViewBuilder private func circle() -> some View {
Circle()
.fill(Color.gray)
.blur(radius: 2.0)
.frame(width: controlSize * 2.5, height: controlSize * 2.5, alignment: .center)
}
}
#Preview {
StatePickerView()
.environmentObject(Roll())
}
@@ -0,0 +1,58 @@
//
// WoundCalculatorView.swift
// Bunch O' Dice
//
import SwiftUI
struct WoundCalculatorView: View {
@EnvironmentObject var current: Roll
var body: some View {
VStack {
RerollPickerView(mode: $current.woundRerollMode)
HStack(alignment: .top) {
Spacer()
StatStepper(
top: "Attack",
value: String(current.strength),
bottom: "Strength",
increment: { current.addStrength() },
decrement: { current.loseStrength() }
)
Spacer()
StatStepper(
top: "Toughness",
value: String(current.toughness),
bottom: " ",
increment: { current.addToughness() },
decrement: { current.loseToughness() }
)
Spacer()
StatStepper(
top: "Wound",
value: (current.woundModifier.rawValue > 0 ? "+" : "") + String(current.woundModifier.rawValue),
bottom: "Modifier",
increment: { current.woundModifier = current.woundModifier.add() },
decrement: { current.woundModifier = current.woundModifier.remove() }
)
Spacer()
}
HStack {
ToggleChip(label: "Devastating Wounds", isOn: $current.devastatingWounds)
}
.font(.system(size: 18))
.padding(.horizontal)
}
.padding()
}
}
#Preview {
WoundCalculatorView()
.environmentObject({
let r = Roll()
r.state = .wound
return r
}())
}
+88
View File
@@ -0,0 +1,88 @@
//
// 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")
}
}
}
@EnvironmentObject var current: Roll
@ObservedObject var sheet = SettingsSheet()
private let controlSize = 30.0
var body: some View {
VStack {
StatePickerView()
VStack(spacing: 8) {
RollSummaryView()
switch current.state {
case .wound: WoundCalculatorView()
case .armour: ArmourCalculatorView()
default: HitCalculatorView()
}
InfoAreaView()
Spacer()
}
}
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
HStack {
Button(action: { 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: { sheet.state = .settings }) {
MenuView.settings.icon()
}
}
.font(.system(size: controlSize))
.foregroundColor(.primary)
}
}
.sheet(isPresented: $sheet.isShowing) {
switch sheet.state {
case .settings: SettingsView(isShowing: $sheet.isShowing)
case .history: HistoryView(isShowing: $sheet.isShowing)
default: EmptyView()
}
}
}
}
#Preview("Hit") {
MainView().environmentObject(Roll())
}
#Preview("Wound") {
MainView().environmentObject({
let r = Roll()
r.state = .wound
return r
}())
}
#Preview("Armour") {
MainView().environmentObject({
let r = Roll()
r.state = .armour
return r
}())
}
-20
View File
@@ -1,20 +0,0 @@
//
// dice.swift
// Bunch O' Dice
//
// Created by Rory Hinnen on 6/10/23.
//
import SwiftUI
struct dice: View {
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
}
}
struct dice_Previews: PreviewProvider {
static var previews: some View {
dice()
}
}
+54 -14
View File
@@ -203,8 +203,8 @@ struct RerollTests {
var d = Dice() var d = Dice()
d.one = 2; d.two = 2; d.three = 2; d.six = 4 d.one = 2; d.two = 2; d.three = 2; d.six = 4
d.rerollBelow(cutoff: 4) d.rerollBelow(cutoff: 4)
// The 4 sixes were not in the reroll range and should be unchanged // Sixes were not in the reroll range; rerolled dice may also produce sixes
#expect(d.six == 4) #expect(d.six >= 4)
} }
@Test("applyReroll: none mode leaves hasRerolled false") @Test("applyReroll: none mode leaves hasRerolled false")
@@ -227,20 +227,30 @@ struct RerollTests {
#expect(roll.hitHasRerolled == true) #expect(roll.hitHasRerolled == true)
} }
@Test("applyReroll: guard blocks second call") @Test("applyReroll: re-applying restores snapshot and rerolls with new mode")
func applyRerollRespectsFlagAlreadySet() { func applyRerollChangeModeRestoresSnapshot() {
let roll = Roll() let roll = Roll()
roll.state = .hit roll.state = .hit
roll.hitCutoff = .four
roll.hitRerollMode = .ones
roll.dice.one = 3; roll.dice.six = 2
roll.total = 5
// First reroll: reroll the 3 ones
roll.applyReroll()
#expect(roll.hitHasRerolled == true)
#expect(roll.preRerollDice != nil)
let afterFirst = roll.dice.one + roll.dice.two + roll.dice.three +
roll.dice.four + roll.dice.five + roll.dice.six
#expect(afterFirst == 5)
// Switch to 'all' mode — should restore snapshot then reroll all
roll.hitRerollMode = .all roll.hitRerollMode = .all
roll.hitHasRerolled = true // simulate already used roll.applyReroll()
roll.dice.one = 5 // known state let afterSecond = roll.dice.one + roll.dice.two + roll.dice.three +
roll.applyReroll() // should be blocked by guard roll.dice.four + roll.dice.five + roll.dice.six
#expect(roll.dice.one == 5) #expect(afterSecond == 5)
#expect(roll.dice.two == 0) #expect(roll.hitHasRerolled == true)
#expect(roll.dice.three == 0)
#expect(roll.dice.four == 0)
#expect(roll.dice.five == 0)
#expect(roll.dice.six == 0)
} }
@Test("applyReroll: only sets flag for the active phase") @Test("applyReroll: only sets flag for the active phase")
@@ -266,7 +276,37 @@ struct RerollTests {
let total = roll.dice.one + roll.dice.two + roll.dice.three + let total = roll.dice.one + roll.dice.two + roll.dice.three +
roll.dice.four + roll.dice.five + roll.dice.six roll.dice.four + roll.dice.five + roll.dice.six
#expect(total == 5) #expect(total == 5)
#expect(roll.dice.six == 2) // dice above cutoff are untouched #expect(roll.dice.six >= 2) // dice above cutoff are untouched; rerolls may also produce sixes
}
@Test("undoReroll: restores dice and clears flag")
func undoRerollRestoresDice() {
let roll = Roll()
roll.state = .hit
roll.hitCutoff = .four
roll.hitRerollMode = .ones
roll.dice.one = 3; roll.dice.six = 2
roll.total = 5
roll.applyReroll()
#expect(roll.hitHasRerolled == true)
roll.undoReroll()
#expect(roll.hitHasRerolled == false)
#expect(roll.preRerollDice == nil)
// Dice should be restored to original state
#expect(roll.dice.one == 3)
#expect(roll.dice.six == 2)
}
@Test("undoReroll: no-op when no reroll has been applied")
func undoRerollNoop() {
let roll = Roll()
roll.state = .hit
roll.dice.one = 3
roll.undoReroll() // nothing to undo
#expect(roll.dice.one == 3)
#expect(roll.hitHasRerolled == false)
} }
@Test("resetPool: clears hitHasRerolled flag") @Test("resetPool: clears hitHasRerolled flag")