From 025b00619419e08a5046937b14b38bb72a4e5658 Mon Sep 17 00:00:00 2001 From: Rory Hinnen Date: Mon, 7 Sep 2026 10:27:56 -0500 Subject: [PATCH] blowup now locks out subsequent rolls, unit tests added --- Bunch O' DiceTests/Bunch_O__DiceTests.swift | 363 +++++++++++++++++++- Bunch-O--Dice/MainView.swift | 10 +- Bunch-O--Dice/Model/RollState.swift | 17 + Bunch-O--Dice/OnboardingView.swift | 4 +- DieHammer.xcodeproj/project.pbxproj | 26 +- 5 files changed, 381 insertions(+), 39 deletions(-) diff --git a/Bunch O' DiceTests/Bunch_O__DiceTests.swift b/Bunch O' DiceTests/Bunch_O__DiceTests.swift index 9706cc6..35baf54 100644 --- a/Bunch O' DiceTests/Bunch_O__DiceTests.swift +++ b/Bunch O' DiceTests/Bunch_O__DiceTests.swift @@ -5,32 +5,359 @@ // Created by Rory Hinnen on 22/2/20. // -import XCTest +import Testing @testable import Bunch_O__Dice -class Bunch_O__DiceTests: XCTestCase { +// MARK: - Dice Total Calculation - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. +@Suite("Dice Total Calculation") +struct DiceTotalTests { + + // One die showing each face — a clean baseline for cutoff tests. + func oneEach() -> Dice { + var d = Dice() + d.one = 1; d.two = 1; d.three = 1; d.four = 1; d.five = 1; d.six = 1 + return d } - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. + @Test("No modifier counts faces at or above cutoff") + func totalNoModifier() { + let d = oneEach() + #expect(d.total(cutoff: 6, modifier: .none) == 1) + #expect(d.total(cutoff: 5, modifier: .none) == 2) + #expect(d.total(cutoff: 4, modifier: .none) == 3) + #expect(d.total(cutoff: 3, modifier: .none) == 4) + #expect(d.total(cutoff: 2, modifier: .none) == 5) } - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - // Any test you write for XCTest can be annotated as throws and async. - // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. - // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + @Test("Negative modifier effectively raises cutoff by 1") + func totalNegativeModifier() { + let d = oneEach() + #expect(d.total(cutoff: 5, modifier: .negative) == 1) // effectively 6+ + #expect(d.total(cutoff: 4, modifier: .negative) == 2) // effectively 5+ + #expect(d.total(cutoff: 3, modifier: .negative) == 3) // effectively 4+ + #expect(d.total(cutoff: 2, modifier: .negative) == 4) // effectively 3+ + #expect(d.total(cutoff: 1, modifier: .negative) == 5) // effectively 2+ } - func testPerformanceExample() throws { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } + @Test("Positive modifier effectively lowers cutoff by 1") + func totalPositiveModifier() { + let d = oneEach() + #expect(d.total(cutoff: 6, modifier: .positive) == 2) // effectively 5+ + #expect(d.total(cutoff: 5, modifier: .positive) == 3) // effectively 4+ + #expect(d.total(cutoff: 4, modifier: .positive) == 4) // effectively 3+ + #expect(d.total(cutoff: 3, modifier: .positive) == 5) // effectively 2+ + #expect(d.total(cutoff: 2, modifier: .positive) == 5) // already at 2+, no further shift + } +} + +// MARK: - Armour Save Calculation + +@Suite("Armour Save Calculation") +struct ArmourSaveTests { + + func oneEach() -> Dice { + var d = Dice() + d.one = 1; d.two = 1; d.three = 1; d.four = 1; d.five = 1; d.six = 1 + return d + } + + @Test("Counts dice that fail the save") + func normalSaves() { + let d = oneEach() + #expect(d.armour(die: 6, invulnerable: false, save: 6, armourPenetration: 0) == 5) // 1-5 fail + #expect(d.armour(die: 6, invulnerable: false, save: 5, armourPenetration: 0) == 4) // 1-4 fail + #expect(d.armour(die: 6, invulnerable: false, save: 4, armourPenetration: 0) == 3) // 1-3 fail + #expect(d.armour(die: 6, invulnerable: false, save: 3, armourPenetration: 0) == 2) // 1-2 fail + #expect(d.armour(die: 6, invulnerable: false, save: 2, armourPenetration: 0) == 1) // only 1 fails + } + + @Test("AP reduces effective save value") + func armourPenetration() { + let d = oneEach() + // 3+ save with AP 1 → effective 4+: 1-3 fail + #expect(d.armour(die: 6, invulnerable: false, save: 3, armourPenetration: 1) == 3) + // 3+ save with AP 2 → effective 5+: 1-4 fail + #expect(d.armour(die: 6, invulnerable: false, save: 3, armourPenetration: 2) == 4) + } + + @Test("Invulnerable save ignores AP") + func invulnerableSave() { + let d = oneEach() + // 4++ invulnerable with heavy AP should match a plain 4+ with no AP + let withInvuln = d.armour(die: 6, invulnerable: true, save: 4, armourPenetration: 5) + let noAP = d.armour(die: 6, invulnerable: false, save: 4, armourPenetration: 0) + #expect(withInvuln == noAP) + } +} + +// MARK: - Roll Modifiers + +@Suite("Roll Modifiers") +struct RollModifierTests { + + @Test("add() clamps at positive") + func addModifier() { + #expect(Dice.RollModifiers.negative.add() == .none) + #expect(Dice.RollModifiers.none.add() == .positive) + #expect(Dice.RollModifiers.positive.add() == .positive) + } + + @Test("remove() clamps at negative") + func removeModifier() { + #expect(Dice.RollModifiers.positive.remove() == .none) + #expect(Dice.RollModifiers.none.remove() == .negative) + #expect(Dice.RollModifiers.negative.remove() == .negative) + } +} + +// MARK: - Wound Cutoff + +@Suite("Wound Cutoff") +struct WoundCutoffTests { + + @Test("S double or more T: wound on 2+") + func doubleStrength() { + let roll = Roll() + roll.strength = 10; roll.toughness = 5 + #expect(roll.woundCutoff == 2) + roll.strength = 6; roll.toughness = 3 + #expect(roll.woundCutoff == 2) + } + + @Test("S greater than T but less than double: wound on 3+") + func strengthHigher() { + let roll = Roll() + roll.strength = 7; roll.toughness = 5 + #expect(roll.woundCutoff == 3) + roll.strength = 4; roll.toughness = 3 + #expect(roll.woundCutoff == 3) + } + + @Test("S equals T: wound on 4+") + func equal() { + let roll = Roll() + roll.strength = 5; roll.toughness = 5 + #expect(roll.woundCutoff == 4) + roll.strength = 3; roll.toughness = 3 + #expect(roll.woundCutoff == 4) + } + + @Test("T greater than S but less than double: wound on 5+") + func toughnessHigher() { + let roll = Roll() + roll.strength = 3; roll.toughness = 5 + #expect(roll.woundCutoff == 5) + roll.strength = 4; roll.toughness = 5 + #expect(roll.woundCutoff == 5) + } + + @Test("T double or more S: wound on 6+") + func doubleToughness() { + let roll = Roll() + roll.strength = 2; roll.toughness = 5 + #expect(roll.woundCutoff == 6) + roll.strength = 1; roll.toughness = 3 + #expect(roll.woundCutoff == 6) + } +} + +// MARK: - Reroll Logic + +@Suite("Reroll Logic") +struct RerollTests { + + @Test("rerollBelow: no-op when no dice fall below cutoff") + func rerollBelowNoop() { + var d = Dice() + d.four = 2; d.five = 3; d.six = 1 + d.rerollBelow(cutoff: 4) + #expect(d.one == 0) + #expect(d.two == 0) + #expect(d.three == 0) + #expect(d.four == 2) + #expect(d.five == 3) + #expect(d.six == 1) + } + + @Test("rerollBelow: preserves total dice count") + func rerollBelowPreservesCount() { + var d = Dice() + d.one = 3; d.two = 2; d.three = 1; d.four = 1; d.five = 1; d.six = 1 + d.rerollBelow(cutoff: 4) + let total = d.one + d.two + d.three + d.four + d.five + d.six + #expect(total == 9) + } + + @Test("rerollBelow: cutoff 1 is a no-op (empty range)") + func rerollBelowCutoffOne() { + var d = Dice() + d.one = 3; d.six = 2 + d.rerollBelow(cutoff: 1) + #expect(d.one == 3) + #expect(d.six == 2) + } + + @Test("rerollBelow: dice above cutoff are untouched") + func rerollBelowPreservesHighFaces() { + var d = Dice() + d.one = 2; d.two = 2; d.three = 2; d.six = 4 + d.rerollBelow(cutoff: 4) + // The 4 sixes were not in the reroll range and should be unchanged + #expect(d.six == 4) + } + + @Test("applyReroll: none mode leaves hasRerolled false") + func applyRerollNoneMode() { + let roll = Roll() + roll.state = .hit + roll.hitRerollMode = .none + roll.applyReroll() + #expect(roll.hitHasRerolled == false) + } + + @Test("applyReroll: sets hasRerolled flag after use") + func applyRerollSetsFlag() { + let roll = Roll() + roll.state = .hit + roll.hitRerollMode = .ones + roll.dice.one = 3; roll.dice.six = 2 + roll.total = 5 + roll.applyReroll() + #expect(roll.hitHasRerolled == true) + } + + @Test("applyReroll: guard blocks second call") + func applyRerollRespectsFlagAlreadySet() { + let roll = Roll() + roll.state = .hit + roll.hitRerollMode = .all + roll.hitHasRerolled = true // simulate already used + roll.dice.one = 5 // known state + roll.applyReroll() // should be blocked by guard + #expect(roll.dice.one == 5) + #expect(roll.dice.two == 0) + #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") + func applyRerollPerPhaseIsolation() { + let roll = Roll() + roll.state = .wound + roll.woundRerollMode = .ones + roll.applyReroll() + #expect(roll.woundHasRerolled == true) + #expect(roll.hitHasRerolled == false) + #expect(roll.saveHasRerolled == false) + } + + @Test("applyReroll: failed mode preserves total dice count") + func applyRerollFailedModeCount() { + let roll = Roll() + roll.state = .hit + roll.hitCutoff = .four + roll.hitRerollMode = .failed + roll.dice.one = 1; roll.dice.two = 1; roll.dice.three = 1; roll.dice.six = 2 + roll.total = 5 + roll.applyReroll() + let total = roll.dice.one + roll.dice.two + roll.dice.three + + roll.dice.four + roll.dice.five + roll.dice.six + #expect(total == 5) + #expect(roll.dice.six == 2) // dice above cutoff are untouched + } + + @Test("resetPool: clears hitHasRerolled flag") + func resetPoolClearsFlag() { + let roll = Roll() + roll.total = 5 + roll.hitHasRerolled = true + roll.resetPool() + #expect(roll.hitHasRerolled == false) + } + + @Test("resetPool: rolls dice equal to total count") + func resetPoolRollsCorrectCount() { + let roll = Roll() + roll.total = 10 + roll.resetPool() + let total = roll.dice.one + roll.dice.two + roll.dice.three + + roll.dice.four + roll.dice.five + roll.dice.six + #expect(total == 10) + } +} + +// MARK: - Blowup Logic + +@Suite("Blowup Logic") +struct BlowupTests { + + @Test("blowup six: total increases by the original six count") + func blowupSixIncreasesCount() { + var d = Dice() + d.one = 1; d.two = 1; d.six = 3 + let originalTotal = 5 + let originalSix = 3 + d.blowup(dice: .six) + let newTotal = d.one + d.two + d.three + d.four + d.five + d.six + #expect(newTotal == originalTotal + originalSix) + } + + @Test("blowup five: total increases by the original five and six count") + func blowupFiveIncreasesCount() { + var d = Dice() + d.one = 1; d.five = 2; d.six = 3 + let originalTotal = 6 + let originalFiveSix = 5 + d.blowup(dice: .five) + let newTotal = d.one + d.two + d.three + d.four + d.five + d.six + #expect(newTotal == originalTotal + originalFiveSix) + } + + @Test("blowup six: no-op when no sixes in pool") + func blowupSixNoop() { + var d = Dice() + d.one = 3; d.two = 2 + d.blowup(dice: .six) + let total = d.one + d.two + d.three + d.four + d.five + d.six + #expect(total == 5) + } + + @Test("applyBlowup: sets blowupUsed flag") + func applyBlowupSetsFlag() { + let roll = Roll() + roll.dice.six = 3 + roll.applyBlowup(face: .six) + #expect(roll.blowupUsed == true) + } + + @Test("applyBlowup: guard blocks second call") + func applyBlowupGuard() { + let roll = Roll() + roll.dice.six = 2 + roll.blowupUsed = true // already used + let totalBefore = roll.dice.one + roll.dice.two + roll.dice.three + + roll.dice.four + roll.dice.five + roll.dice.six + roll.applyBlowup(face: .six) + let totalAfter = roll.dice.one + roll.dice.two + roll.dice.three + + roll.dice.four + roll.dice.five + roll.dice.six + #expect(totalAfter == totalBefore) + } + + @Test("applyBlowup: using any face disables both") + func blowupAnyFaceDisablesBoth() { + let roll = Roll() + roll.dice.six = 2; roll.dice.five = 2 + roll.applyBlowup(face: .six) + #expect(roll.blowupUsed == true) + // Five blowup should now be blocked + let totalBefore = roll.dice.one + roll.dice.two + roll.dice.three + + roll.dice.four + roll.dice.five + roll.dice.six + roll.applyBlowup(face: .five) + let totalAfter = roll.dice.one + roll.dice.two + roll.dice.three + + roll.dice.four + roll.dice.five + roll.dice.six + #expect(totalAfter == totalBefore) } - } diff --git a/Bunch-O--Dice/MainView.swift b/Bunch-O--Dice/MainView.swift index 9c325f8..853c26e 100644 --- a/Bunch-O--Dice/MainView.swift +++ b/Bunch-O--Dice/MainView.swift @@ -226,14 +226,16 @@ struct MainView: View { VStack { Text("Blow Up") VStack { - Button(action: {current.dice.blowup(dice: .six)}) - {Image(systemName: "die.face.6") + Button(action: { current.applyBlowup(face: .six) }) { + Image(systemName: "die.face.6") .foregroundColor(.primary) } - Button(action: {current.dice.blowup(dice: .five)}) - {Image(systemName: "die.face.5") + .disabled(current.blowupUsed) + Button(action: { current.applyBlowup(face: .five) }) { + Image(systemName: "die.face.5") .foregroundColor(.primary) } + .disabled(current.blowupUsed) } .padding() .font(.system(size: controlSize)) diff --git a/Bunch-O--Dice/Model/RollState.swift b/Bunch-O--Dice/Model/RollState.swift index 0c0a3c1..04a0529 100644 --- a/Bunch-O--Dice/Model/RollState.swift +++ b/Bunch-O--Dice/Model/RollState.swift @@ -150,6 +150,8 @@ class Roll: ObservableObject { @Published var hitHasRerolled = false @Published var woundHasRerolled = false @Published var saveHasRerolled = false + + @Published var blowupUsed = false // calculated variables var totalDigits: [Int] { @@ -341,6 +343,7 @@ class Roll: ObservableObject { self.total = save self.state = .hit hitHasRerolled = false + blowupUsed = false } self.dice.roll(howMany: self.total) } @@ -349,6 +352,20 @@ class Roll: ObservableObject { func resetPool() { dice.roll(howMany: total) hitHasRerolled = false + blowupUsed = false + } + + func applyBlowup(face: Dice.DiceType) { + guard !blowupUsed else { return } + switch face { + case .six: + dice.blowup(dice: .six) + case .five: + dice.blowup(dice: .five) + default: + break + } + blowupUsed = true } func applyReroll() { diff --git a/Bunch-O--Dice/OnboardingView.swift b/Bunch-O--Dice/OnboardingView.swift index d878100..2af3a20 100644 --- a/Bunch-O--Dice/OnboardingView.swift +++ b/Bunch-O--Dice/OnboardingView.swift @@ -8,14 +8,14 @@ import SwiftUI struct OnboardingView: View { - private var courses = DTCourse.pages + private let 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 + ForEach(courses.indices, id: \.self) { idx in VStack(spacing: 0) { Spacer() Text(courses[idx].title) diff --git a/DieHammer.xcodeproj/project.pbxproj b/DieHammer.xcodeproj/project.pbxproj index 7c5431e..dc13e88 100644 --- a/DieHammer.xcodeproj/project.pbxproj +++ b/DieHammer.xcodeproj/project.pbxproj @@ -260,7 +260,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1320; - LastUpgradeCheck = 1530; + LastUpgradeCheck = 2700; TargetAttributes = { 981AD0B927C30E31005A6492 = { CreatedOnToolsVersion = 13.2.1; @@ -411,6 +411,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 5P9XB49J6K; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -433,6 +434,7 @@ MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; @@ -474,6 +476,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 5P9XB49J6K; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -489,6 +492,7 @@ MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; @@ -503,7 +507,6 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Bunch-O--Dice/Preview Content\""; - DEVELOPMENT_TEAM = 5P9XB49J6K; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Bunch-O--Dice-Info.plist"; @@ -514,7 +517,7 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = "$(RECOMMENDED_IPHONEOS_DEPLOYMENT_TARGET)"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -536,7 +539,6 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Bunch-O--Dice/Preview Content\""; - DEVELOPMENT_TEAM = 5P9XB49J6K; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Bunch-O--Dice-Info.plist"; @@ -547,7 +549,7 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = UIInterfaceOrientationPortrait; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = "$(RECOMMENDED_IPHONEOS_DEPLOYMENT_TARGET)"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -564,13 +566,11 @@ 981AD0E227C30E32005A6492 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 5P9XB49J6K; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; + IPHONEOS_DEPLOYMENT_TARGET = "$(RECOMMENDED_IPHONEOS_DEPLOYMENT_TARGET)"; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.chartreusenecktie.Bunch-O--DiceTests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -584,13 +584,11 @@ 981AD0E327C30E32005A6492 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 5P9XB49J6K; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; + IPHONEOS_DEPLOYMENT_TARGET = "$(RECOMMENDED_IPHONEOS_DEPLOYMENT_TARGET)"; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.chartreusenecktie.Bunch-O--DiceTests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -604,11 +602,10 @@ 981AD0E527C30E32005A6492 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 5P9XB49J6K; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = "$(RECOMMENDED_IPHONEOS_DEPLOYMENT_TARGET)"; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.chartreusenecktie.Bunch-O--DiceUITests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -622,11 +619,10 @@ 981AD0E627C30E32005A6492 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 5P9XB49J6K; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = "$(RECOMMENDED_IPHONEOS_DEPLOYMENT_TARGET)"; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.chartreusenecktie.Bunch-O--DiceUITests"; PRODUCT_NAME = "$(TARGET_NAME)";