changeset 34:6ec51a4ca897

fix crash when asking new questions
author Dennis C. M. <dennis@denniscm.com>
date Sat, 12 Nov 2022 14:23:05 +0100
parents 6d574bd1644f
children 1b36c022f138
files GeoQuiz.xcodeproj/project.pbxproj GeoQuiz.xcodeproj/xcshareddata/xcschemes/GeoQuiz.xcscheme GeoQuiz/Controllers/CityGameController.swift GeoQuiz/Controllers/CountryGameController.swift GeoQuiz/Controllers/GameProtocol+Extension.swift GeoQuizTests/GeoQuizTests.swift
diffstat 6 files changed, 233 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- a/GeoQuiz.xcodeproj/project.pbxproj	Sat Nov 12 11:18:30 2022 +0100
+++ b/GeoQuiz.xcodeproj/project.pbxproj	Sat Nov 12 14:23:05 2022 +0100
@@ -257,6 +257,7 @@
 		957822482918F445005F2D50 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 957822472918F445005F2D50 /* Extensions.swift */; };
 		9590359528E098FF00B24560 /* ProfileModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9590359428E098FF00B24560 /* ProfileModalView.swift */; };
 		95919DBC28F08D0600F21F8F /* SettingsRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95919DBB28F08D0600F21F8F /* SettingsRow.swift */; };
+		959C3DB1291FC193003A3FD1 /* GeoQuizTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 959C3DB0291FC193003A3FD1 /* GeoQuizTests.swift */; };
 		95A4F42B29043DC00018DFAC /* UserImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A4F42A29043DC00018DFAC /* UserImage.swift */; };
 		95AF322A28DF293900023ACC /* GuessTheCountryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95AF322928DF293900023ACC /* GuessTheCountryView.swift */; };
 		95BC392D28EC42570049AB49 /* CityMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BC392C28EC42570049AB49 /* CityMap.swift */; };
@@ -277,6 +278,16 @@
 		95FA409A28D9876B00129B60 /* GuessTheFlagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95FA409928D9876B00129B60 /* GuessTheFlagView.swift */; };
 /* End PBXBuildFile section */
 
+/* Begin PBXContainerItemProxy section */
+		959C3DB2291FC193003A3FD1 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 9539828728C51EDE00B70973 /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 9539828E28C51EDE00B70973;
+			remoteInfo = GeoQuiz;
+		};
+/* End PBXContainerItemProxy section */
+
 /* Begin PBXFileReference section */
 		95030CE928D1BA4D001AA3A1 /* AnswerButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnswerButton.swift; sourceTree = "<group>"; };
 		9509A8DD28E5A19A00CFCDBA /* countries.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = countries.json; sourceTree = "<group>"; };
@@ -529,6 +540,8 @@
 		957822472918F445005F2D50 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
 		9590359428E098FF00B24560 /* ProfileModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileModalView.swift; sourceTree = "<group>"; };
 		95919DBB28F08D0600F21F8F /* SettingsRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsRow.swift; sourceTree = "<group>"; };
+		959C3DAE291FC193003A3FD1 /* GeoQuizTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GeoQuizTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+		959C3DB0291FC193003A3FD1 /* GeoQuizTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoQuizTests.swift; sourceTree = "<group>"; };
 		95A4F42A29043DC00018DFAC /* UserImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserImage.swift; sourceTree = "<group>"; };
 		95AF322928DF293900023ACC /* GuessTheCountryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuessTheCountryView.swift; sourceTree = "<group>"; };
 		95BC392C28EC42570049AB49 /* CityMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CityMap.swift; sourceTree = "<group>"; };
@@ -560,6 +573,13 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		959C3DAB291FC193003A3FD1 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
@@ -827,6 +847,7 @@
 			isa = PBXGroup;
 			children = (
 				9539829128C51EDE00B70973 /* GeoQuiz */,
+				959C3DAF291FC193003A3FD1 /* GeoQuizTests */,
 				9539829028C51EDE00B70973 /* Products */,
 				950C535728F3178B00179C78 /* Frameworks */,
 			);
@@ -836,6 +857,7 @@
 			isa = PBXGroup;
 			children = (
 				9539828F28C51EDE00B70973 /* GeoQuiz.app */,
+				959C3DAE291FC193003A3FD1 /* GeoQuizTests.xctest */,
 			);
 			name = Products;
 			sourceTree = "<group>";
@@ -911,6 +933,14 @@
 			path = Helpers;
 			sourceTree = "<group>";
 		};
+		959C3DAF291FC193003A3FD1 /* GeoQuizTests */ = {
+			isa = PBXGroup;
+			children = (
+				959C3DB0291FC193003A3FD1 /* GeoQuizTests.swift */,
+			);
+			path = GeoQuizTests;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
@@ -934,6 +964,24 @@
 			productReference = 9539828F28C51EDE00B70973 /* GeoQuiz.app */;
 			productType = "com.apple.product-type.application";
 		};
+		959C3DAD291FC193003A3FD1 /* GeoQuizTests */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 959C3DB4291FC193003A3FD1 /* Build configuration list for PBXNativeTarget "GeoQuizTests" */;
+			buildPhases = (
+				959C3DAA291FC193003A3FD1 /* Sources */,
+				959C3DAB291FC193003A3FD1 /* Frameworks */,
+				959C3DAC291FC193003A3FD1 /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				959C3DB3291FC193003A3FD1 /* PBXTargetDependency */,
+			);
+			name = GeoQuizTests;
+			productName = GeoQuizTests;
+			productReference = 959C3DAE291FC193003A3FD1 /* GeoQuizTests.xctest */;
+			productType = "com.apple.product-type.bundle.unit-test";
+		};
 /* End PBXNativeTarget section */
 
 /* Begin PBXProject section */
@@ -941,12 +989,16 @@
 			isa = PBXProject;
 			attributes = {
 				BuildIndependentTargetsInParallel = 1;
-				LastSwiftUpdateCheck = 1340;
+				LastSwiftUpdateCheck = 1410;
 				LastUpgradeCheck = 1340;
 				TargetAttributes = {
 					9539828E28C51EDE00B70973 = {
 						CreatedOnToolsVersion = 13.4.1;
 					};
+					959C3DAD291FC193003A3FD1 = {
+						CreatedOnToolsVersion = 14.1;
+						TestTargetID = 9539828E28C51EDE00B70973;
+					};
 				};
 			};
 			buildConfigurationList = 9539828A28C51EDE00B70973 /* Build configuration list for PBXProject "GeoQuiz" */;
@@ -966,6 +1018,7 @@
 			projectRoot = "";
 			targets = (
 				9539828E28C51EDE00B70973 /* GeoQuiz */,
+				959C3DAD291FC193003A3FD1 /* GeoQuizTests */,
 			);
 		};
 /* End PBXProject section */
@@ -1201,6 +1254,13 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		959C3DAC291FC193003A3FD1 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 /* End PBXResourcesBuildPhase section */
 
 /* Begin PBXSourcesBuildPhase section */
@@ -1254,8 +1314,24 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		959C3DAA291FC193003A3FD1 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				959C3DB1291FC193003A3FD1 /* GeoQuizTests.swift in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 /* End PBXSourcesBuildPhase section */
 
+/* Begin PBXTargetDependency section */
+		959C3DB3291FC193003A3FD1 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 9539828E28C51EDE00B70973 /* GeoQuiz */;
+			targetProxy = 959C3DB2291FC193003A3FD1 /* PBXContainerItemProxy */;
+		};
+/* End PBXTargetDependency section */
+
 /* Begin XCBuildConfiguration section */
 		9539829B28C51EDF00B70973 /* Debug */ = {
 			isa = XCBuildConfiguration;
@@ -1443,6 +1519,52 @@
 			};
 			name = Release;
 		};
+		959C3DB5291FC193003A3FD1 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+				CODE_SIGN_STYLE = Automatic;
+				CURRENT_PROJECT_VERSION = 1;
+				DEVELOPMENT_TEAM = MTX83R5H8X;
+				GENERATE_INFOPLIST_FILE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 16.1;
+				MACOSX_DEPLOYMENT_TARGET = 13.0;
+				MARKETING_VERSION = 1.0;
+				PRODUCT_BUNDLE_IDENTIFIER = io.dennistech.GeoQuizTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SDKROOT = auto;
+				SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
+				SWIFT_EMIT_LOC_STRINGS = NO;
+				SWIFT_VERSION = 5.0;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GeoQuiz.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/GeoQuiz";
+			};
+			name = Debug;
+		};
+		959C3DB6291FC193003A3FD1 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+				CODE_SIGN_STYLE = Automatic;
+				CURRENT_PROJECT_VERSION = 1;
+				DEVELOPMENT_TEAM = MTX83R5H8X;
+				GENERATE_INFOPLIST_FILE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 16.1;
+				MACOSX_DEPLOYMENT_TARGET = 13.0;
+				MARKETING_VERSION = 1.0;
+				PRODUCT_BUNDLE_IDENTIFIER = io.dennistech.GeoQuizTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SDKROOT = auto;
+				SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
+				SWIFT_EMIT_LOC_STRINGS = NO;
+				SWIFT_VERSION = 5.0;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GeoQuiz.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/GeoQuiz";
+			};
+			name = Release;
+		};
 /* End XCBuildConfiguration section */
 
 /* Begin XCConfigurationList section */
@@ -1464,6 +1586,15 @@
 			defaultConfigurationIsVisible = 0;
 			defaultConfigurationName = Release;
 		};
+		959C3DB4291FC193003A3FD1 /* Build configuration list for PBXNativeTarget "GeoQuizTests" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				959C3DB5291FC193003A3FD1 /* Debug */,
+				959C3DB6291FC193003A3FD1 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
 /* End XCConfigurationList section */
 
 /* Begin XCRemoteSwiftPackageReference section */
--- a/GeoQuiz.xcodeproj/xcshareddata/xcschemes/GeoQuiz.xcscheme	Sat Nov 12 11:18:30 2022 +0100
+++ b/GeoQuiz.xcodeproj/xcshareddata/xcschemes/GeoQuiz.xcscheme	Sat Nov 12 14:23:05 2022 +0100
@@ -28,6 +28,17 @@
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
       shouldUseLaunchSchemeArgsEnv = "YES">
       <Testables>
+         <TestableReference
+            skipped = "NO"
+            parallelizable = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "959C3DAD291FC193003A3FD1"
+               BuildableName = "GeoQuizTests.xctest"
+               BlueprintName = "GeoQuizTests"
+               ReferencedContainer = "container:GeoQuiz.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
       </Testables>
    </TestAction>
    <LaunchAction
--- a/GeoQuiz/Controllers/CityGameController.swift	Sat Nov 12 11:18:30 2022 +0100
+++ b/GeoQuiz/Controllers/CityGameController.swift	Sat Nov 12 14:23:05 2022 +0100
@@ -21,7 +21,7 @@
     @Published var userScore = 0
     @Published var userLives = 3
     
-    @Published var correctAnswer = (key: String(), value: T(country: String(), lat: Double(), lon: Double()))
+    @Published var correctAnswer = (key: "", value: T(country: "", lat: Double(), lon: Double()))
     @Published var correctAnswers = [String: T]()
     @Published var wrongAnswers = [String: T]()
     
@@ -45,7 +45,7 @@
         let shuffledCities = data.cities.shuffled()
         var cities = [String: T]()
         
-        for _ in 1...10 {
+        for _ in 1...100 {
             let countryNames = cities.map { $0.value.country }
             let city = shuffledCities.first(where: {
                 !countryNames.contains($0.value.country)
--- a/GeoQuiz/Controllers/CountryGameController.swift	Sat Nov 12 11:18:30 2022 +0100
+++ b/GeoQuiz/Controllers/CountryGameController.swift	Sat Nov 12 14:23:05 2022 +0100
@@ -8,29 +8,28 @@
 import Foundation
 import AVFAudio
 
-class CountryGameController: Game, ObservableObject {
+@MainActor class CountryGameController: Game, ObservableObject {
     
-    // Define type of generics
+    // Define generic type
     typealias T = CountryModel.Country
     
+    // Game
     var data: [String: T]
     var dataAsked = [String: T]()
     
-    @Published var correctAnswer = (
-        key: String(),
-        value: T(flag: String(), currency: String(), population: Int(), capital: String())
-    )
-    
     // User
     @Published var userChoices = [String: T]()
     @Published var userScore = 0
     @Published var userLives = 3
+    
+    @Published var correctAnswer = (key: "", value: T(flag: "", currency: "", population: Int(), capital: ""))
     @Published var correctAnswers = [String: T]()
     @Published var wrongAnswers = [String: T]()
     
     // Alerts
     @Published var alertTitle = String()
     @Published var alertMessage = String()
+    
     @Published var showingEndGameAlert = false
     @Published var showingWrongAnswerAlert = false
     @Published var showingExitGameAlert = false
@@ -44,7 +43,7 @@
     
     init() {
         let data: CountryModel = Bundle.main.decode("countries.json")
-        let shuffledCountries = data.countries.shuffled().prefix(5)
+        let shuffledCountries = data.countries.shuffled().prefix(100)
         var countries = [String: T]()
         
         for shuffledCountry in shuffledCountries {
@@ -53,8 +52,6 @@
         
         self.data = countries
         
-        print(countries)
-        
         let user = UserController()
         userLives = user.data.numberOfLives
         
--- a/GeoQuiz/Controllers/GameProtocol+Extension.swift	Sat Nov 12 11:18:30 2022 +0100
+++ b/GeoQuiz/Controllers/GameProtocol+Extension.swift	Sat Nov 12 14:23:05 2022 +0100
@@ -68,21 +68,16 @@
         
         var userChoices = [String: T]()
         
-        while userChoices.count < 2 {
+        let correctAnswer = data.shuffled().first(where: { !dataAsked.keys.contains($0.key) })!
+        dataAsked[correctAnswer.key] = correctAnswer.value
+        userChoices[correctAnswer.key] = correctAnswer.value
+        
+        while userChoices.count < 3 {
             let choice = data.randomElement()!
             userChoices[choice.key] = choice.value
         }
         
-        let correctKey = data.keys.shuffled().first(where: {
-            !userChoices.keys.contains($0) &&               // Avoid duplicated items
-            !dataAsked.keys.contains($0)                    // Avoid items already asked
-        })!
-        
-        let correctValue = data[correctKey]!
-        
-        userChoices[correctKey] = correctValue
-        dataAsked[correctKey] = correctValue
-        correctAnswer = (key: correctKey, value: correctValue)
+        self.correctAnswer = correctAnswer
         self.userChoices = userChoices
     }
     
@@ -105,10 +100,10 @@
             haptics.error()
             playSound("wrongAnswer")
 
-//            withAnimation(.easeIn(duration: 0.5)) {
-//                livesScaleAmount += 1
-//                userLives -= 1
-//            }
+            withAnimation(.easeIn(duration: 0.5)) {
+                livesScaleAmount += 1
+                userLives -= 1
+            }
             
             wrongAnswers[choice.key] = choice.value
             
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuizTests/GeoQuizTests.swift	Sat Nov 12 14:23:05 2022 +0100
@@ -0,0 +1,71 @@
+//
+//  GeoQuizTests.swift
+//  GeoQuizTests
+//
+//  Created by Dennis Concepción Martín on 12/11/22.
+//
+
+import XCTest
+@testable import GeoQuiz
+
+final class GeoQuizTests: XCTestCase {
+
+    override func setUpWithError() throws {
+        // Put setup code here. This method is called before the invocation of each test method in the class.
+    }
+
+    override func tearDownWithError() throws {
+        // Put teardown code here. This method is called after the invocation of each test method in the class.
+    }
+
+    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.
+    }
+
+    func testPerformanceExample() throws {
+        // This is an example of a performance test case.
+        measure {
+            // Put the code you want to measure the time of here.
+        }
+    }
+}
+
+final class CountryGameControllerTests: XCTestCase {
+    var game: CountryGameController!
+    
+    @MainActor override func setUpWithError() throws {
+        game = CountryGameController()
+    }
+
+    @MainActor func testAsk() throws {
+        for _ in game.data {
+            game.ask()
+            XCTAssertEqual(game.userChoices.count, 3)
+            XCTAssertEqual(Set(game.userChoices.keys).count, 3)
+        }
+        
+        XCTAssertEqual(game.dataAsked.count, game.data.count)
+    }
+}
+
+final class CityGameControllerTests: XCTestCase {
+    var game: CityGameController!
+    
+    @MainActor override func setUpWithError() throws {
+        game = CityGameController()
+    }
+
+    @MainActor func testAsk() throws {
+        for _ in game.data {
+            game.ask()
+            XCTAssertEqual(game.userChoices.count, 3)
+            XCTAssertEqual(Set(game.userChoices.keys).count, 3)
+        }
+        
+        XCTAssertEqual(game.dataAsked.count, game.data.count)
+    }
+}