diff --git a/SOPA.xcodeproj/project.pbxproj b/SOPA.xcodeproj/project.pbxproj index 575c50f..90a4f0f 100644 --- a/SOPA.xcodeproj/project.pbxproj +++ b/SOPA.xcodeproj/project.pbxproj @@ -112,6 +112,20 @@ 64EFCB3E240E732F00714E7C /* LevelDestroyer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EFCB3C240E732F00714E7C /* LevelDestroyer.swift */; }; 64EFCB40240E763A00714E7C /* LevelSolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EFCB3F240E763A00714E7C /* LevelSolver.swift */; }; 64EFCB41240E763A00714E7C /* LevelSolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EFCB3F240E763A00714E7C /* LevelSolver.swift */; }; + A11E00012602000100AAB001 /* StartMenuScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA001 /* StartMenuScene.swift */; }; + A11E00012602000100AAB002 /* StartMenuScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA001 /* StartMenuScene.swift */; }; + A11E00012602000100AAB003 /* CreditsScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA002 /* CreditsScene.swift */; }; + A11E00012602000100AAB004 /* CreditsScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA002 /* CreditsScene.swift */; }; + A11E00012602000100AAB005 /* TutorialScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA003 /* TutorialScene.swift */; }; + A11E00012602000100AAB006 /* TutorialScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA003 /* TutorialScene.swift */; }; + A11E00012602000100AAB007 /* TutorialGameScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA004 /* TutorialGameScene.swift */; }; + A11E00012602000100AAB008 /* TutorialGameScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA004 /* TutorialGameScene.swift */; }; + A11E00012602000100AAB009 /* SopaTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA005 /* SopaTheme.swift */; }; + A11E00012602000100AAB00A /* SopaTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA005 /* SopaTheme.swift */; }; + A11E00012602000100AAB00B /* LabelSizing.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA006 /* LabelSizing.swift */; }; + A11E00012602000100AAB00C /* LabelSizing.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA006 /* LabelSizing.swift */; }; + A11E00012602000100AAB00D /* ButtonTextureFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA007 /* ButtonTextureFactory.swift */; }; + A11E00012602000100AAB00E /* ButtonTextureFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11E00012602000100AAA007 /* ButtonTextureFactory.swift */; }; A1B2C3D424FA000100000001 /* JustPlayServiceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D424FA000100000011 /* JustPlayServiceTest.swift */; }; A1B2C3D424FA000100000002 /* helper/LevelServiceImplUnlockingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D424FA000100000012 /* helper/LevelServiceImplUnlockingTest.swift */; }; A1B2C3D424FA000100000003 /* database/JustPlayHighscorePersistenceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D424FA000100000013 /* database/JustPlayHighscorePersistenceTest.swift */; }; @@ -191,6 +205,13 @@ 64D66B1A21426ADC00595BD3 /* progbot.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = progbot.ttf; sourceTree = ""; }; 64EFCB3C240E732F00714E7C /* LevelDestroyer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LevelDestroyer.swift; sourceTree = ""; }; 64EFCB3F240E763A00714E7C /* LevelSolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LevelSolver.swift; sourceTree = ""; }; + A11E00012602000100AAA001 /* StartMenuScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartMenuScene.swift; sourceTree = ""; }; + A11E00012602000100AAA002 /* CreditsScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditsScene.swift; sourceTree = ""; }; + A11E00012602000100AAA003 /* TutorialScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TutorialScene.swift; sourceTree = ""; }; + A11E00012602000100AAA004 /* TutorialGameScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TutorialGameScene.swift; sourceTree = ""; }; + A11E00012602000100AAA005 /* SopaTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SopaTheme.swift; sourceTree = ""; }; + A11E00012602000100AAA006 /* LabelSizing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelSizing.swift; sourceTree = ""; }; + A11E00012602000100AAA007 /* ButtonTextureFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonTextureFactory.swift; sourceTree = ""; }; A1B2C3D424FA000100000011 /* JustPlayServiceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustPlayServiceTest.swift; sourceTree = ""; }; A1B2C3D424FA000100000012 /* helper/LevelServiceImplUnlockingTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = helper/LevelServiceImplUnlockingTest.swift; sourceTree = ""; }; A1B2C3D424FA000100000013 /* database/JustPlayHighscorePersistenceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = database/JustPlayHighscorePersistenceTest.swift; sourceTree = ""; }; @@ -377,10 +398,17 @@ 64182B18208DCE0A00CF639A /* LevelModeGameScene.swift */, 64D5B8CE20ADE2680007D982 /* LevelModeScoreScene.swift */, 64D5B8D020B46BD70007D982 /* LevelChoiceScene.swift */, + A11E00012602000100AAA001 /* StartMenuScene.swift */, + A11E00012602000100AAA002 /* CreditsScene.swift */, + A11E00012602000100AAA003 /* TutorialScene.swift */, + A11E00012602000100AAA004 /* TutorialGameScene.swift */, 642E2B3E213B0B1E002669E5 /* LevelSelectButton.swift */, 642E2B41213D257C002669E5 /* LevelButtonPositioner.swift */, 646E6AE8213E80F7001D195B /* LevelButtonArea.swift */, 648558C221412BBD00B61C31 /* EffectSpriteButton.swift */, + A11E00012602000100AAA005 /* SopaTheme.swift */, + A11E00012602000100AAA006 /* LabelSizing.swift */, + A11E00012602000100AAA007 /* ButtonTextureFactory.swift */, ); path = LevelMode; sourceTree = ""; @@ -563,6 +591,10 @@ 64EFCB40240E763A00714E7C /* LevelSolver.swift in Sources */, 6493FB652073D6510044B4E0 /* SOPA.xcdatamodeld in Sources */, 64D5B8CF20ADE2680007D982 /* LevelModeScoreScene.swift in Sources */, + A11E00012602000100AAB003 /* CreditsScene.swift in Sources */, + A11E00012602000100AAB001 /* StartMenuScene.swift in Sources */, + A11E00012602000100AAB005 /* TutorialScene.swift in Sources */, + A11E00012602000100AAB007 /* TutorialGameScene.swift in Sources */, 63F661231F9B346E0043ABCF /* AppDelegate.swift in Sources */, E1AA11122602000100ABCDEF /* SceneDelegate.swift in Sources */, 6446C3031FAC80DC00806A10 /* LevelTranslator.swift in Sources */, @@ -574,6 +606,9 @@ 6446C2EF1FAC802900806A10 /* GameServiceImpl.swift in Sources */, 642E2B3F213B0B1E002669E5 /* LevelSelectButton.swift in Sources */, 64206E6C240EBB71001151B4 /* JustPlayGameScene.swift in Sources */, + A11E00012602000100AAB009 /* SopaTheme.swift in Sources */, + A11E00012602000100AAB00B /* LabelSizing.swift in Sources */, + A11E00012602000100AAB00D /* ButtonTextureFactory.swift in Sources */, 6446C3011FAC80DC00806A10 /* LevelFileService.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -632,9 +667,16 @@ 646902C6207F6B7600283F71 /* GameServiceImpl.swift in Sources */, 64847A0B215A382B0012B732 /* LevelModeScoreScene.swift in Sources */, 64206E6D240EBB71001151B4 /* JustPlayGameScene.swift in Sources */, + A11E00012602000100AAB004 /* CreditsScene.swift in Sources */, + A11E00012602000100AAB002 /* StartMenuScene.swift in Sources */, + A11E00012602000100AAB006 /* TutorialScene.swift in Sources */, + A11E00012602000100AAB008 /* TutorialGameScene.swift in Sources */, 646902CE207F6B7D00283F71 /* LevelFileService.swift in Sources */, 646902DA207F6BD500283F71 /* GameScene.swift in Sources */, 642E2B40213B0B1E002669E5 /* LevelSelectButton.swift in Sources */, + A11E00012602000100AAB00A /* SopaTheme.swift in Sources */, + A11E00012602000100AAB00C /* LabelSizing.swift in Sources */, + A11E00012602000100AAB00E /* ButtonTextureFactory.swift in Sources */, 646902C7207F6B7600283F71 /* Level.swift in Sources */, 642EE5BD22423AAF00680612 /* ProportionSet.swift in Sources */, ); diff --git a/SOPA/Assets.xcassets/border/borders.imageset/Contents.json b/SOPA/Assets.xcassets/border/borders.imageset/Contents.json index 82d3b31..3cb9267 100644 --- a/SOPA/Assets.xcassets/border/borders.imageset/Contents.json +++ b/SOPA/Assets.xcassets/border/borders.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "empty.png" + "filename" : "borders.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/border/borders.imageset/borders.png b/SOPA/Assets.xcassets/border/borders.imageset/borders.png new file mode 100644 index 0000000..2327281 Binary files /dev/null and b/SOPA/Assets.xcassets/border/borders.imageset/borders.png differ diff --git a/SOPA/Assets.xcassets/border/bordersFinish.imageset/Contents.json b/SOPA/Assets.xcassets/border/bordersFinish.imageset/Contents.json index 3e8a31a..642f573 100644 --- a/SOPA/Assets.xcassets/border/bordersFinish.imageset/Contents.json +++ b/SOPA/Assets.xcassets/border/bordersFinish.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "border.png" + "filename" : "bordersFinish.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/border/bordersFinish.imageset/bordersFinish.png b/SOPA/Assets.xcassets/border/bordersFinish.imageset/bordersFinish.png new file mode 100644 index 0000000..9291faf Binary files /dev/null and b/SOPA/Assets.xcassets/border/bordersFinish.imageset/bordersFinish.png differ diff --git a/SOPA/Assets.xcassets/border/bordersFinish_filled.imageset/Contents.json b/SOPA/Assets.xcassets/border/bordersFinish_filled.imageset/Contents.json index 3e8a31a..f25dac8 100644 --- a/SOPA/Assets.xcassets/border/bordersFinish_filled.imageset/Contents.json +++ b/SOPA/Assets.xcassets/border/bordersFinish_filled.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "border.png" + "filename" : "bordersFinish_filled.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/border/bordersFinish_filled.imageset/bordersFinish_filled.png b/SOPA/Assets.xcassets/border/bordersFinish_filled.imageset/bordersFinish_filled.png new file mode 100644 index 0000000..155afa9 Binary files /dev/null and b/SOPA/Assets.xcassets/border/bordersFinish_filled.imageset/bordersFinish_filled.png differ diff --git a/SOPA/Assets.xcassets/border/bordersStart.imageset/Contents.json b/SOPA/Assets.xcassets/border/bordersStart.imageset/Contents.json index 3e8a31a..00d1855 100644 --- a/SOPA/Assets.xcassets/border/bordersStart.imageset/Contents.json +++ b/SOPA/Assets.xcassets/border/bordersStart.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "border.png" + "filename" : "bordersStart.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/border/bordersStart.imageset/bordersStart.png b/SOPA/Assets.xcassets/border/bordersStart.imageset/bordersStart.png new file mode 100644 index 0000000..7e8beeb Binary files /dev/null and b/SOPA/Assets.xcassets/border/bordersStart.imageset/bordersStart.png differ diff --git a/SOPA/Assets.xcassets/border/bordersStart_filled.imageset/Contents.json b/SOPA/Assets.xcassets/border/bordersStart_filled.imageset/Contents.json index 3e8a31a..7d11783 100644 --- a/SOPA/Assets.xcassets/border/bordersStart_filled.imageset/Contents.json +++ b/SOPA/Assets.xcassets/border/bordersStart_filled.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "border.png" + "filename" : "bordersStart_filled.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/border/bordersStart_filled.imageset/bordersStart_filled.png b/SOPA/Assets.xcassets/border/bordersStart_filled.imageset/bordersStart_filled.png new file mode 100644 index 0000000..0e0b7fa Binary files /dev/null and b/SOPA/Assets.xcassets/border/bordersStart_filled.imageset/bordersStart_filled.png differ diff --git a/SOPA/Assets.xcassets/tiles/a.imageset/Contents.json b/SOPA/Assets.xcassets/tiles/a.imageset/Contents.json index a39878b..adf2f50 100644 --- a/SOPA/Assets.xcassets/tiles/a.imageset/Contents.json +++ b/SOPA/Assets.xcassets/tiles/a.imageset/Contents.json @@ -9,4 +9,4 @@ "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/tiles/a.imageset/a.png b/SOPA/Assets.xcassets/tiles/a.imageset/a.png index 6dfbddb..d1e26b2 100644 Binary files a/SOPA/Assets.xcassets/tiles/a.imageset/a.png and b/SOPA/Assets.xcassets/tiles/a.imageset/a.png differ diff --git a/SOPA/Assets.xcassets/tiles/a_filled.imageset/Contents.json b/SOPA/Assets.xcassets/tiles/a_filled.imageset/Contents.json index a39878b..aa211cc 100644 --- a/SOPA/Assets.xcassets/tiles/a_filled.imageset/Contents.json +++ b/SOPA/Assets.xcassets/tiles/a_filled.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "a.png" + "filename" : "a_filled.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/tiles/a_filled.imageset/a_filled.png b/SOPA/Assets.xcassets/tiles/a_filled.imageset/a_filled.png new file mode 100644 index 0000000..cf0fa7f Binary files /dev/null and b/SOPA/Assets.xcassets/tiles/a_filled.imageset/a_filled.png differ diff --git a/SOPA/Assets.xcassets/tiles/c.imageset/Contents.json b/SOPA/Assets.xcassets/tiles/c.imageset/Contents.json index f71c70d..9f72339 100644 --- a/SOPA/Assets.xcassets/tiles/c.imageset/Contents.json +++ b/SOPA/Assets.xcassets/tiles/c.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "g.png" + "filename" : "c.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/tiles/c.imageset/c.png b/SOPA/Assets.xcassets/tiles/c.imageset/c.png new file mode 100644 index 0000000..5ea3189 Binary files /dev/null and b/SOPA/Assets.xcassets/tiles/c.imageset/c.png differ diff --git a/SOPA/Assets.xcassets/tiles/c_filled.imageset/Contents.json b/SOPA/Assets.xcassets/tiles/c_filled.imageset/Contents.json index f71c70d..67739fc 100644 --- a/SOPA/Assets.xcassets/tiles/c_filled.imageset/Contents.json +++ b/SOPA/Assets.xcassets/tiles/c_filled.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "g.png" + "filename" : "c_filled.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/tiles/c_filled.imageset/c_filled.png b/SOPA/Assets.xcassets/tiles/c_filled.imageset/c_filled.png new file mode 100644 index 0000000..a4d24d4 Binary files /dev/null and b/SOPA/Assets.xcassets/tiles/c_filled.imageset/c_filled.png differ diff --git a/SOPA/Assets.xcassets/tiles/e.imageset/Contents.json b/SOPA/Assets.xcassets/tiles/e.imageset/Contents.json index f71c70d..3004157 100644 --- a/SOPA/Assets.xcassets/tiles/e.imageset/Contents.json +++ b/SOPA/Assets.xcassets/tiles/e.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "g.png" + "filename" : "e.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/tiles/e.imageset/e.png b/SOPA/Assets.xcassets/tiles/e.imageset/e.png new file mode 100644 index 0000000..608b4b7 Binary files /dev/null and b/SOPA/Assets.xcassets/tiles/e.imageset/e.png differ diff --git a/SOPA/Assets.xcassets/tiles/e_filled.imageset/Contents.json b/SOPA/Assets.xcassets/tiles/e_filled.imageset/Contents.json index f71c70d..0d614b3 100644 --- a/SOPA/Assets.xcassets/tiles/e_filled.imageset/Contents.json +++ b/SOPA/Assets.xcassets/tiles/e_filled.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "g.png" + "filename" : "e_filled.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/tiles/e_filled.imageset/e_filled.png b/SOPA/Assets.xcassets/tiles/e_filled.imageset/e_filled.png new file mode 100644 index 0000000..23b5e47 Binary files /dev/null and b/SOPA/Assets.xcassets/tiles/e_filled.imageset/e_filled.png differ diff --git a/SOPA/Assets.xcassets/tiles/g.imageset/Contents.json b/SOPA/Assets.xcassets/tiles/g.imageset/Contents.json index f71c70d..9831764 100644 --- a/SOPA/Assets.xcassets/tiles/g.imageset/Contents.json +++ b/SOPA/Assets.xcassets/tiles/g.imageset/Contents.json @@ -9,4 +9,4 @@ "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/tiles/g.imageset/g.png b/SOPA/Assets.xcassets/tiles/g.imageset/g.png index 78f7814..804a12b 100644 Binary files a/SOPA/Assets.xcassets/tiles/g.imageset/g.png and b/SOPA/Assets.xcassets/tiles/g.imageset/g.png differ diff --git a/SOPA/Assets.xcassets/tiles/g_filled.imageset/Contents.json b/SOPA/Assets.xcassets/tiles/g_filled.imageset/Contents.json index f71c70d..4dfac92 100644 --- a/SOPA/Assets.xcassets/tiles/g_filled.imageset/Contents.json +++ b/SOPA/Assets.xcassets/tiles/g_filled.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "g.png" + "filename" : "g_filled.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/tiles/g_filled.imageset/g_filled.png b/SOPA/Assets.xcassets/tiles/g_filled.imageset/g_filled.png new file mode 100644 index 0000000..e3d58d8 Binary files /dev/null and b/SOPA/Assets.xcassets/tiles/g_filled.imageset/g_filled.png differ diff --git a/SOPA/Assets.xcassets/tiles/i.imageset/Contents.json b/SOPA/Assets.xcassets/tiles/i.imageset/Contents.json index 98c4b4e..11078e4 100644 --- a/SOPA/Assets.xcassets/tiles/i.imageset/Contents.json +++ b/SOPA/Assets.xcassets/tiles/i.imageset/Contents.json @@ -9,4 +9,4 @@ "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/tiles/i.imageset/i.png b/SOPA/Assets.xcassets/tiles/i.imageset/i.png index 23b4513..2dab43e 100644 Binary files a/SOPA/Assets.xcassets/tiles/i.imageset/i.png and b/SOPA/Assets.xcassets/tiles/i.imageset/i.png differ diff --git a/SOPA/Assets.xcassets/tiles/i_filled.imageset/Contents.json b/SOPA/Assets.xcassets/tiles/i_filled.imageset/Contents.json index 98c4b4e..f8e2ec5 100644 --- a/SOPA/Assets.xcassets/tiles/i_filled.imageset/Contents.json +++ b/SOPA/Assets.xcassets/tiles/i_filled.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "i.png" + "filename" : "i_filled.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/tiles/i_filled.imageset/i_filled.png b/SOPA/Assets.xcassets/tiles/i_filled.imageset/i_filled.png new file mode 100644 index 0000000..3754665 Binary files /dev/null and b/SOPA/Assets.xcassets/tiles/i_filled.imageset/i_filled.png differ diff --git a/SOPA/Assets.xcassets/tiles/o.imageset/Contents.json b/SOPA/Assets.xcassets/tiles/o.imageset/Contents.json index e4460a0..15d73a9 100644 --- a/SOPA/Assets.xcassets/tiles/o.imageset/Contents.json +++ b/SOPA/Assets.xcassets/tiles/o.imageset/Contents.json @@ -9,4 +9,4 @@ "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/tiles/o.imageset/o.png b/SOPA/Assets.xcassets/tiles/o.imageset/o.png index af9eb82..6f082fb 100644 Binary files a/SOPA/Assets.xcassets/tiles/o.imageset/o.png and b/SOPA/Assets.xcassets/tiles/o.imageset/o.png differ diff --git a/SOPA/Assets.xcassets/tiles/u.imageset/Contents.json b/SOPA/Assets.xcassets/tiles/u.imageset/Contents.json index f71c70d..7754ae7 100644 --- a/SOPA/Assets.xcassets/tiles/u.imageset/Contents.json +++ b/SOPA/Assets.xcassets/tiles/u.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "g.png" + "filename" : "u.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/tiles/u.imageset/u.png b/SOPA/Assets.xcassets/tiles/u.imageset/u.png new file mode 100644 index 0000000..6e7529f Binary files /dev/null and b/SOPA/Assets.xcassets/tiles/u.imageset/u.png differ diff --git a/SOPA/Assets.xcassets/tiles/u_filled.imageset/Contents.json b/SOPA/Assets.xcassets/tiles/u_filled.imageset/Contents.json index f71c70d..64e76c4 100644 --- a/SOPA/Assets.xcassets/tiles/u_filled.imageset/Contents.json +++ b/SOPA/Assets.xcassets/tiles/u_filled.imageset/Contents.json @@ -2,11 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "g.png" + "filename" : "u_filled.png" } ], "info" : { "version" : 1, "author" : "xcode" } -} \ No newline at end of file +} diff --git a/SOPA/Assets.xcassets/tiles/u_filled.imageset/u_filled.png b/SOPA/Assets.xcassets/tiles/u_filled.imageset/u_filled.png new file mode 100644 index 0000000..7280c46 Binary files /dev/null and b/SOPA/Assets.xcassets/tiles/u_filled.imageset/u_filled.png differ diff --git a/SOPA/view/LevelMode/ButtonTextureFactory.swift b/SOPA/view/LevelMode/ButtonTextureFactory.swift new file mode 100644 index 0000000..6f96a9d --- /dev/null +++ b/SOPA/view/LevelMode/ButtonTextureFactory.swift @@ -0,0 +1,169 @@ +import SpriteKit +import UIKit + +enum ButtonTextureFactory { + static func makeCircleButtonTexture( + symbolName: String, + side: CGFloat, + circleColor: UIColor = SopaTheme.circleButtonFill, + iconColor: UIColor = SopaTheme.circleButtonIcon, + symbolScale: CGFloat = 0.42, + symbolWeight: UIImage.SymbolWeight = .semibold + ) -> SKTexture { + let textureSize = CGSize(width: side, height: side) + let renderer = UIGraphicsImageRenderer(size: textureSize) + let image = renderer.image { _ in + circleColor.setFill() + UIBezierPath(ovalIn: CGRect(origin: .zero, size: textureSize)).fill() + + let config = UIImage.SymbolConfiguration(pointSize: side * symbolScale, weight: symbolWeight) + if let symbol = UIImage(systemName: symbolName, withConfiguration: config)? + .withTintColor(iconColor, renderingMode: .alwaysOriginal) { + let symbolRect = CGRect( + x: (side - symbol.size.width) / 2.0, + y: (side - symbol.size.height) / 2.0, + width: symbol.size.width, + height: symbol.size.height + ) + symbol.draw(in: symbolRect) + } + } + return SKTexture(image: image) + } + + static func makeMinimalBackTexture(side: CGFloat) -> SKTexture { + let textureSize = CGSize(width: side, height: side) + let renderer = UIGraphicsImageRenderer(size: textureSize) + let image = renderer.image { _ in + let config = UIImage.SymbolConfiguration(pointSize: side * 0.72, weight: .semibold) + if let symbol = UIImage(systemName: "chevron.left", withConfiguration: config)? + .withTintColor(UIColor(red: 160.0 / 255.0, green: 164.0 / 255.0, blue: 170.0 / 255.0, alpha: 0.95), renderingMode: .alwaysOriginal) { + let symbolRect = CGRect( + x: (side - symbol.size.width) / 2.0, + y: (side - symbol.size.height) / 2.0, + width: symbol.size.width, + height: symbol.size.height + ) + symbol.draw(in: symbolRect) + } + } + return SKTexture(image: image) + } + + static func makePageArrowTexture(imageNamed: String, side: CGFloat) -> SKTexture { + let texture = SKTexture(imageNamed: imageNamed) + let imageSize = texture.size() + let ratio = imageSize.width / max(1, imageSize.height) + let targetSize = CGSize(width: side * ratio, height: side) + let renderer = UIGraphicsImageRenderer(size: targetSize) + let image = renderer.image { _ in + UIImage(named: imageNamed)?.draw(in: CGRect(origin: .zero, size: targetSize)) + } + return SKTexture(image: image) + } + + static func makeNeonTextTexture(title: String, size: CGSize) -> SKTexture { + let renderer = UIGraphicsImageRenderer(size: size) + let image = renderer.image { _ in + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.alignment = .center + + let glowShadow = NSShadow() + glowShadow.shadowColor = SopaTheme.neonGlowBlue + glowShadow.shadowBlurRadius = size.height * 0.22 + glowShadow.shadowOffset = .zero + + let font = UIFont(name: "HelveticaNeue-Light", size: size.height * 0.60) + ?? UIFont.systemFont(ofSize: size.height * 0.60, weight: .light) + let textRect = CGRect(x: 0, y: size.height * 0.22, width: size.width, height: size.height * 0.60) + + let glowAttributes: [NSAttributedString.Key: Any] = [ + .font: font, + .foregroundColor: SopaTheme.neonBlue, + .strokeColor: SopaTheme.neonBlue, + .strokeWidth: -1.2, + .paragraphStyle: paragraphStyle, + .shadow: glowShadow + ] + title.draw(in: textRect, withAttributes: glowAttributes) + + let coreAttributes: [NSAttributedString.Key: Any] = [ + .font: font, + .foregroundColor: SopaTheme.neonCoreBlue, + .paragraphStyle: paragraphStyle + ] + title.draw(in: textRect, withAttributes: coreAttributes) + } + return SKTexture(image: image) + } + + static func makeSocialButtonTexture(symbolName: String, size: CGSize, fillBackground: Bool) -> SKTexture { + let renderer = UIGraphicsImageRenderer(size: size) + let image = renderer.image { _ in + let blueColor = UIColor(red: 99.0 / 255.0, green: 177.0 / 255.0, blue: 230.0 / 255.0, alpha: 1.0) + if fillBackground { + let backgroundPath = UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: size.width * 0.18) + blueColor.setFill() + backgroundPath.fill() + } + + let pointSize = fillBackground ? size.width * 0.50 : size.width * 0.75 + let config = UIImage.SymbolConfiguration(pointSize: pointSize, weight: .semibold) + guard let baseSymbol = UIImage(systemName: symbolName, withConfiguration: config) else { + return + } + + let iconColor = fillBackground ? UIColor.white : blueColor + let symbol = baseSymbol.withTintColor(iconColor, renderingMode: .alwaysOriginal) + let symbolRect = CGRect( + x: (size.width - symbol.size.width) / 2.0, + y: (size.height - symbol.size.height) / 2.0, + width: symbol.size.width, + height: symbol.size.height + ) + symbol.draw(in: symbolRect) + } + return SKTexture(image: image) + } + + static func makeTextButtonTexture( + title: String, + size: CGSize, + fontName: String, + textColor: UIColor, + weight: UIFont.Weight = .semibold + ) -> SKTexture { + let renderer = UIGraphicsImageRenderer(size: size) + let image = renderer.image { _ in + let frame = CGRect(origin: .zero, size: size).insetBy(dx: size.width * 0.01, dy: size.height * 0.06) + let backgroundPath = UIBezierPath(roundedRect: frame, cornerRadius: size.height * 0.23) + UIColor(white: 1.0, alpha: 0.10).setFill() + backgroundPath.fill() + UIColor(white: 1.0, alpha: 0.22).setStroke() + backgroundPath.lineWidth = size.height * 0.08 + backgroundPath.stroke() + + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.alignment = .center + let buttonFontSize = LabelSizing.fittedTextFontSize( + text: title, + fontName: fontName, + preferredSize: size.height * 0.38, + weight: weight, + maxWidth: size.width * 0.90 + ) + let attributes: [NSAttributedString.Key: Any] = [ + .font: UIFont(name: fontName, size: buttonFontSize) ?? UIFont.systemFont(ofSize: buttonFontSize, weight: weight), + .foregroundColor: textColor, + .paragraphStyle: paragraphStyle + ] + let textRect = CGRect(x: 0.0, y: size.height * 0.30, width: size.width, height: size.height * 0.5) + title.draw(in: textRect, withAttributes: attributes) + } + return SKTexture(image: image) + } +} + +func makeCircleButtonTexture(symbolName: String, side: CGFloat) -> SKTexture { + ButtonTextureFactory.makeCircleButtonTexture(symbolName: symbolName, side: side) +} diff --git a/SOPA/view/LevelMode/CreditsScene.swift b/SOPA/view/LevelMode/CreditsScene.swift new file mode 100644 index 0000000..63a9359 --- /dev/null +++ b/SOPA/view/LevelMode/CreditsScene.swift @@ -0,0 +1,89 @@ +import Foundation +import SpriteKit +import UIKit + +class CreditsScene: SKScene { + private let sectionColor = SopaTheme.neonBlue + + override init(size: CGSize) { + super.init(size: size) + backgroundColor = SopaTheme.blackBackground + addBackButton() + addTitle() + addCreditsContent() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func addBackButton() { + let side = size.height * 0.08 + let backButton = SpriteButton(texture: ButtonTextureFactory.makeCircleButtonTexture(symbolName: "chevron.left", side: side)) { + ResourcesManager.getInstance().storyService?.loadStartMenuScene() + } + backButton.position = CGPoint(x: size.height * 0.057, y: size.height * 0.91) + addChild(backButton) + } + + private func addTitle() { + let title = SKLabelNode(fontNamed: "Impact") + title.text = "CREDITS" + title.fontSize = size.height * 0.09 + title.fontColor = SopaTheme.titleGray + title.position = CGPoint(x: size.width * 0.5, y: size.height * 0.78) + addChild(title) + } + + private func addCreditsContent() { + let content: [(section: String, entries: [String])] = [ + ("DEVELOPMENT", ["David Schilling - @schillda710", "Raphael Schilling - @ubuntius"]), + ("DESIGN", ["Raphael Schilling - @ubuntius"]), + ("MUSIC", ["Menu - axtoncrolley on opengameart.org"]) + ] + + var currentY = size.height * 0.70 + for block in content { + addSectionLine(text: block.section, y: currentY) + currentY -= size.height * 0.045 + + for entry in block.entries { + addBodyLine(text: entry, y: currentY) + currentY -= size.height * 0.038 + } + currentY -= size.height * 0.028 + } + } + + private func addSectionLine(text: String, y: CGFloat) { + let line = SKLabelNode(fontNamed: "HelveticaNeue-Bold") + line.horizontalAlignmentMode = .left + line.verticalAlignmentMode = .center + line.text = text + line.fontSize = LabelSizing.fittedLabelFontSize( + text: text, + fontName: "HelveticaNeue-Bold", + preferredSize: size.height * 0.032, + maxWidth: size.width * 0.88 + ) + line.fontColor = sectionColor + line.position = CGPoint(x: size.width * 0.06, y: y) + addChild(line) + } + + private func addBodyLine(text: String, y: CGFloat) { + let line = SKLabelNode(fontNamed: "HelveticaNeue") + line.horizontalAlignmentMode = .left + line.verticalAlignmentMode = .center + line.text = text + line.fontSize = LabelSizing.fittedLabelFontSize( + text: text, + fontName: "HelveticaNeue", + preferredSize: size.height * 0.026, + maxWidth: size.width * 0.90 + ) + line.fontColor = SopaTheme.bodyText + line.position = CGPoint(x: size.width * 0.09, y: y) + addChild(line) + } +} diff --git a/SOPA/view/LevelMode/LabelSizing.swift b/SOPA/view/LevelMode/LabelSizing.swift new file mode 100644 index 0000000..fd4e7e6 --- /dev/null +++ b/SOPA/view/LevelMode/LabelSizing.swift @@ -0,0 +1,47 @@ +import SpriteKit +import UIKit + +enum LabelSizing { + static func fittedLabelFontSize( + text: String, + fontName: String, + preferredSize: CGFloat, + maxWidth: CGFloat, + minSize: CGFloat = 10.0 + ) -> CGFloat { + guard !text.isEmpty else { + return preferredSize + } + + let label = SKLabelNode(fontNamed: fontName) + label.text = text + var currentSize = preferredSize + label.fontSize = currentSize + + while label.frame.width > maxWidth && currentSize > minSize { + currentSize -= 1.0 + label.fontSize = currentSize + } + return currentSize + } + + static func fittedTextFontSize( + text: String, + fontName: String, + preferredSize: CGFloat, + weight: UIFont.Weight, + maxWidth: CGFloat, + minSize: CGFloat = 10.0 + ) -> CGFloat { + var currentSize = preferredSize + while currentSize > minSize { + let font = UIFont(name: fontName, size: currentSize) ?? UIFont.systemFont(ofSize: currentSize, weight: weight) + let measuredWidth = (text as NSString).size(withAttributes: [.font: font]).width + if measuredWidth <= maxWidth { + return currentSize + } + currentSize -= 1.0 + } + return minSize + } +} diff --git a/SOPA/view/LevelMode/LevelButtonPositioner.swift b/SOPA/view/LevelMode/LevelButtonPositioner.swift index 35d34c0..167fee2 100644 --- a/SOPA/view/LevelMode/LevelButtonPositioner.swift +++ b/SOPA/view/LevelMode/LevelButtonPositioner.swift @@ -16,7 +16,7 @@ class LevelButtonPositioner { init(size: CGSize) { self.size = size - self.buttonSize = CGSize(width: size.width * 0.3, height: size.width * 0.3) + self.buttonSize = CGSize(width: size.width * 0.24, height: size.width * 0.24) self.drawingHeight = size.height } @@ -26,7 +26,7 @@ class LevelButtonPositioner { let column = idOnPage % 3 let row = idOnPage / 3 let xPos: CGFloat = size.width / 4.0 * CGFloat(1 + column) + (size.width * CGFloat(page)) - let yPos: CGFloat = size.height - (drawingHeight / 5.0 * CGFloat(row + 1)) + let yPos: CGFloat = size.height - (drawingHeight / 5.0 * CGFloat(row + 1)) + size.height * 0.03 let position = CGPoint(x: xPos, y: yPos) return position diff --git a/SOPA/view/LevelMode/LevelChoiceScene.swift b/SOPA/view/LevelMode/LevelChoiceScene.swift index f0f8af7..f422720 100644 --- a/SOPA/view/LevelMode/LevelChoiceScene.swift +++ b/SOPA/view/LevelMode/LevelChoiceScene.swift @@ -10,600 +10,71 @@ import Foundation import SpriteKit import UIKit -func makeCircleButtonTexture(symbolName: String, side: CGFloat) -> SKTexture { - let textureSize = CGSize(width: side, height: side) - let circleColor = UIColor(white: 0.94, alpha: 1.0) - let iconColor = UIColor(red: 90.6 / 255.0, green: 86.7 / 255.0, blue: 70.6 / 255.0, alpha: 1.0) - let renderer = UIGraphicsImageRenderer(size: textureSize) - let image = renderer.image { _ in - circleColor.setFill() - UIBezierPath(ovalIn: CGRect(origin: .zero, size: textureSize)).fill() - - let config = UIImage.SymbolConfiguration(pointSize: side * 0.42, weight: .semibold) - if let symbol = UIImage(systemName: symbolName, withConfiguration: config)? - .withTintColor(iconColor, renderingMode: .alwaysOriginal) { - let symbolRect = CGRect( - x: (side - symbol.size.width) / 2.0, - y: (side - symbol.size.height) / 2.0, - width: symbol.size.width, - height: symbol.size.height - ) - symbol.draw(in: symbolRect) - } - } - return SKTexture(image: image) -} - class LevelChoiceScene: SKScene { private let levelInfos: [LevelInfo] private var levelButtonArea: LevelButtonArea? private var menuButton: SpriteButton? + private var leftArrowButton: SpriteButton? + private var rightArrowButton: SpriteButton? init(size: CGSize, levelService: LevelService) { levelInfos = levelService.getLevelInfos() super.init(size: size) levelButtonArea = LevelButtonArea(size: size, levelInfos: levelInfos, update: update) addChild(levelButtonArea!) + backgroundColor = .black addButtons() - self.backgroundColor = UIColor(red: 90.6 / 255.0, green: 86.7 / 255.0, blue: 70.6 / 255, alpha: 1.0) - - - } - - private func addButtons() { - let side = size.height * 0.08 - menuButton = SpriteButton(texture: makeCircleButtonTexture(symbolName: "chevron.left", side: side), onClick: backToStartMenu) - menuButton?.position = CGPoint(x: size.height * 0.057, y: size.height * 0.91) - addChild(menuButton!) - } - - private func backToStartMenu() { - ResourcesManager.getInstance().storyService?.loadStartMenuScene() - } - - func update() { - // Intentionally empty: level paging is swipe-only. - } - - - - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") + addPageArrows() + update() } -} -class StartMenuScene: SKScene { - private let background = UIColor(red: 90.6 / 255.0, green: 86.7 / 255.0, blue: 70.6 / 255, alpha: 1.0) - private let textColor = UIColor(red: 240.0 / 255.0, green: 239.0 / 255.0, blue: 238.0 / 255.0, alpha: 1.0) - - override init(size: CGSize) { - super.init(size: size) - self.backgroundColor = background - addTitle() - addButtons() - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func addTitle() { - let title = SKLabelNode(fontNamed: "Optima-Bold") - title.text = "SOPA" - title.fontSize = size.height * 0.12 - title.fontColor = textColor - title.position = CGPoint(x: size.width / 2, y: size.height * 0.78) - addChild(title) - - let subtitle = SKLabelNode(fontNamed: "Optima-Bold") - subtitle.text = "Choose a mode" - subtitle.fontSize = size.height * 0.04 - subtitle.fontColor = textColor - subtitle.position = CGPoint(x: size.width / 2, y: size.height * 0.70) - addChild(subtitle) - } - private func addButtons() { - let horizontalPadding = size.width * 0.10 - let interButtonGap = size.width * 0.06 - let maxButtonWidthFromScreen = (size.width - (horizontalPadding * 2.0) - interButtonGap) / 2.0 - let buttonSize = min(size.height * 0.20, maxButtonWidthFromScreen) - let iconTextureSize = CGSize(width: buttonSize, height: buttonSize) - let totalGroupWidth = buttonSize * 2.0 + interButtonGap - let leftCenterX = (size.width - totalGroupWidth) / 2.0 + buttonSize / 2.0 - let rightCenterX = leftCenterX + buttonSize + interButtonGap - let buttonCenterY = size.height * 0.42 - - let levelModeButton = SpriteButton(texture: makeModeTexture(symbolName: "square.grid.2x2", textureSize: iconTextureSize)) { - ResourcesManager.getInstance().storyService?.loadLevelCoiceScene() - } - levelModeButton.position = CGPoint(x: leftCenterX, y: buttonCenterY) - addChild(levelModeButton) - - let levelModeLabel = SKLabelNode(fontNamed: "Optima-Bold") - levelModeLabel.text = "Level Mode" - levelModeLabel.fontSize = size.height * 0.035 - levelModeLabel.fontColor = textColor - levelModeLabel.position = CGPoint(x: 0, y: -buttonSize * 0.70) - levelModeButton.addChild(levelModeLabel) - - let justPlayButton = SpriteButton(texture: makeModeTexture(symbolName: "bolt.fill", textureSize: iconTextureSize)) { - ResourcesManager.getInstance().storyService?.loadJustPlaySceneFromMenuScene() - } - justPlayButton.position = CGPoint(x: rightCenterX, y: buttonCenterY) - addChild(justPlayButton) - - let justPlayLabel = SKLabelNode(fontNamed: "Optima-Bold") - justPlayLabel.text = "Just Play" - justPlayLabel.fontSize = size.height * 0.035 - justPlayLabel.fontColor = textColor - justPlayLabel.position = CGPoint(x: 0, y: -buttonSize * 0.70) - justPlayButton.addChild(justPlayLabel) - - let utilityButtonY = size.height * 0.20 - let utilityButtonSize = CGSize(width: size.width * 0.30, height: size.height * 0.075) - let utilityGap = size.width * 0.04 - let totalUtilityWidth = utilityButtonSize.width * 2.0 + utilityGap - let firstUtilityX = (size.width - totalUtilityWidth) / 2.0 + utilityButtonSize.width / 2.0 - - let tutorialButton = SpriteButton(texture: makeTextButtonTexture(title: "Tutorial", size: utilityButtonSize)) { - ResourcesManager.getInstance().storyService?.loadTutorialSceneFromMenuScene() - } - tutorialButton.position = CGPoint(x: firstUtilityX, y: utilityButtonY) - addChild(tutorialButton) - - let creditsButton = SpriteButton(texture: makeTextButtonTexture(title: "Credits", size: utilityButtonSize)) { - ResourcesManager.getInstance().storyService?.loadCreditsSceneFromMenuScene() - } - creditsButton.position = CGPoint(x: firstUtilityX + utilityButtonSize.width + utilityGap, y: utilityButtonY) - addChild(creditsButton) - } - - private func makeModeTexture(symbolName: String, textureSize: CGSize) -> SKTexture { - let renderer = UIGraphicsImageRenderer(size: textureSize) - let image = renderer.image { _ in - let frame = CGRect(origin: .zero, size: textureSize).insetBy(dx: textureSize.width * 0.05, dy: textureSize.height * 0.05) - let backgroundPath = UIBezierPath(roundedRect: frame, cornerRadius: textureSize.width * 0.18) - UIColor(white: 1.0, alpha: 0.10).setFill() - backgroundPath.fill() - UIColor(white: 1.0, alpha: 0.22).setStroke() - backgroundPath.lineWidth = textureSize.width * 0.03 - backgroundPath.stroke() - - let config = UIImage.SymbolConfiguration(pointSize: textureSize.width * 0.44, weight: .semibold) - if let symbol = UIImage(systemName: symbolName, withConfiguration: config)? - .withTintColor(textColor, renderingMode: .alwaysOriginal) { - let symbolRect = CGRect( - x: (textureSize.width - symbol.size.width) / 2.0, - y: (textureSize.height - symbol.size.height) / 2.0, - width: symbol.size.width, - height: symbol.size.height - ) - symbol.draw(in: symbolRect) - } - } - return SKTexture(image: image) - } - - private func makeTextButtonTexture(title: String, size: CGSize) -> SKTexture { - let renderer = UIGraphicsImageRenderer(size: size) - let image = renderer.image { _ in - let frame = CGRect(origin: .zero, size: size).insetBy(dx: size.width * 0.01, dy: size.height * 0.04) - let backgroundPath = UIBezierPath(roundedRect: frame, cornerRadius: size.height * 0.25) - UIColor(white: 1.0, alpha: 0.10).setFill() - backgroundPath.fill() - UIColor(white: 1.0, alpha: 0.22).setStroke() - backgroundPath.lineWidth = size.height * 0.07 - backgroundPath.stroke() - - let paragraphStyle = NSMutableParagraphStyle() - paragraphStyle.alignment = .center - let attributes: [NSAttributedString.Key: Any] = [ - .font: UIFont(name: "Optima-Bold", size: size.height * 0.40) ?? UIFont.systemFont(ofSize: size.height * 0.40, weight: .semibold), - .foregroundColor: textColor, - .paragraphStyle: paragraphStyle - ] - let textRect = CGRect(x: 0.0, y: size.height * 0.28, width: size.width, height: size.height * 0.5) - title.draw(in: textRect, withAttributes: attributes) - } - return SKTexture(image: image) - } -} - -class CreditsScene: SKScene { - private let background = UIColor(red: 90.6 / 255.0, green: 86.7 / 255.0, blue: 70.6 / 255, alpha: 1.0) - private let textColor = UIColor(red: 240.0 / 255.0, green: 239.0 / 255.0, blue: 238.0 / 255.0, alpha: 1.0) - - override init(size: CGSize) { - super.init(size: size) - self.backgroundColor = background - addBackButton() - addTitle() - addLines() - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func addBackButton() { - let side = size.height * 0.08 - let backButton = SpriteButton(texture: makeCircleButtonTexture(symbolName: "chevron.left", side: side)) { - ResourcesManager.getInstance().storyService?.loadStartMenuScene() - } - backButton.position = CGPoint(x: size.height * 0.057, y: size.height * 0.91) - addChild(backButton) - } - - private func addTitle() { - let title = SKLabelNode(fontNamed: "Optima-Bold") - title.text = "Credits" - title.fontSize = size.height * 0.10 - title.fontColor = textColor - title.position = CGPoint(x: size.width * 0.5, y: size.height * 0.78) - addChild(title) - } - - private func addLines() { - addLine(text: "Design and Development", y: size.height * 0.62, sizeFactor: 0.038) - addLine(text: "David Schilling", y: size.height * 0.55, sizeFactor: 0.048) - addLine(text: "Raphael Schilling", y: size.height * 0.49, sizeFactor: 0.048) - addLine(text: "Gameplay and Product Iteration", y: size.height * 0.39, sizeFactor: 0.038) - addLine(text: "SOPA Team", y: size.height * 0.33, sizeFactor: 0.048) - } - - private func addLine(text: String, y: CGFloat, sizeFactor: CGFloat) { - let line = SKLabelNode(fontNamed: "Optima-Bold") - line.text = text - line.fontSize = fittedFontSize(for: text, preferredSize: size.height * sizeFactor, maxWidth: size.width * 0.86) - line.fontColor = textColor - line.position = CGPoint(x: size.width * 0.5, y: y) - addChild(line) - } - - private func fittedFontSize(for text: String, preferredSize: CGFloat, maxWidth: CGFloat) -> CGFloat { - let label = SKLabelNode(fontNamed: "Optima-Bold") - label.text = text - var currentSize = preferredSize - label.fontSize = currentSize - - while label.frame.width > maxWidth && currentSize > 10.0 { - currentSize -= 1.0 - label.fontSize = currentSize - } - return currentSize - } -} - -class TutorialScene: SKScene { - private let background = UIColor(red: 90.6 / 255.0, green: 86.7 / 255.0, blue: 70.6 / 255, alpha: 1.0) - private let textColor = UIColor(red: 240.0 / 255.0, green: 239.0 / 255.0, blue: 238.0 / 255.0, alpha: 1.0) - private var currentPage = 0 - private let headline = SKLabelNode(fontNamed: "Optima-Bold") - private let textLine1 = SKLabelNode(fontNamed: "Optima-Bold") - private let textLine2 = SKLabelNode(fontNamed: "Optima-Bold") - private let textLine3 = SKLabelNode(fontNamed: "Optima-Bold") - private let tapHint = SKLabelNode(fontNamed: "Optima-Bold") - private var letsGoButton: SpriteButton? - - override init(size: CGSize) { - super.init(size: size) - self.backgroundColor = background - addBackButton() - addTitle() - addContentNodes() - updatePage() - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func addBackButton() { - let side = size.height * 0.08 - let backButton = SpriteButton(texture: makeCircleButtonTexture(symbolName: "chevron.left", side: side)) { - ResourcesManager.getInstance().storyService?.loadStartMenuScene() - } - backButton.position = CGPoint(x: size.height * 0.057, y: size.height * 0.91) - addChild(backButton) - } - - private func addTitle() { - let title = SKLabelNode(fontNamed: "Optima-Bold") - title.text = "Tutorial" - title.fontSize = size.height * 0.10 - title.fontColor = textColor - title.position = CGPoint(x: size.width * 0.5, y: size.height * 0.78) - addChild(title) - } - - private func addContentNodes() { - headline.fontColor = textColor - headline.fontSize = size.height * 0.048 - headline.position = CGPoint(x: size.width * 0.5, y: size.height * 0.62) - addChild(headline) - - textLine1.fontColor = textColor - textLine1.fontSize = size.height * 0.034 - textLine1.position = CGPoint(x: size.width * 0.5, y: size.height * 0.54) - addChild(textLine1) - - textLine2.fontColor = textColor - textLine2.fontSize = size.height * 0.034 - textLine2.position = CGPoint(x: size.width * 0.5, y: size.height * 0.48) - addChild(textLine2) - - textLine3.fontColor = textColor - textLine3.fontSize = size.height * 0.034 - textLine3.position = CGPoint(x: size.width * 0.5, y: size.height * 0.42) - addChild(textLine3) - - tapHint.fontColor = textColor - tapHint.fontSize = size.height * 0.030 - tapHint.position = CGPoint(x: size.width * 0.5, y: size.height * 0.26) - addChild(tapHint) - } - - override func touchesBegan(_ touches: Set, with event: UIEvent?) { - if currentPage < 2 { - currentPage += 1 - updatePage() - } - } - - private func updatePage() { - switch currentPage { - case 0: - setPage( - headlineText: "Goal", - line1: "Connect the start and finish pipes.", - line2: "Swipe rows or columns to move tiles.", - line3: "Only matching pipe segments will connect.", - hint: "Tap anywhere to continue" - ) - letsGoButton?.isHidden = true - case 1: - setPage( - headlineText: "How It Works", - line1: "Horizontal swipe moves a full row.", - line2: "Vertical swipe moves a full column.", - line3: "Try to solve it in as few moves as possible.", - hint: "Tap anywhere to continue" - ) - letsGoButton?.isHidden = true - default: - setPage( - headlineText: "Try It Now", - line1: "Next you play a simple interactive level.", - line2: "Use restart if you want to try again.", - line3: "", - hint: "" - ) - ensureLetsGoButton() - letsGoButton?.isHidden = false - } - } - - private func setPage(headlineText: String, line1: String, line2: String, line3: String, hint: String) { - headline.text = headlineText - headline.fontSize = fittedFontSize(for: headlineText, preferredSize: size.height * 0.048, maxWidth: size.width * 0.90) - - textLine1.text = line1 - textLine1.fontSize = fittedFontSize(for: line1, preferredSize: size.height * 0.034, maxWidth: size.width * 0.92) - textLine2.text = line2 - textLine2.fontSize = fittedFontSize(for: line2, preferredSize: size.height * 0.034, maxWidth: size.width * 0.92) - textLine3.text = line3 - textLine3.fontSize = fittedFontSize(for: line3, preferredSize: size.height * 0.034, maxWidth: size.width * 0.92) - tapHint.text = hint - tapHint.fontSize = fittedFontSize(for: hint, preferredSize: size.height * 0.030, maxWidth: size.width * 0.92) - } - - private func ensureLetsGoButton() { - if letsGoButton != nil { - return - } - - let buttonSize = CGSize(width: size.width * 0.34, height: size.height * 0.10) - let button = SpriteButton(texture: makeTextButtonTexture(title: "Let's Go", size: buttonSize)) { - ResourcesManager.getInstance().storyService?.loadTutorialGameSceneFromTutorialScene() - } - button.position = CGPoint(x: size.width * 0.5, y: size.height * 0.25) - addChild(button) - letsGoButton = button - } - - private func makeTextButtonTexture(title: String, size: CGSize) -> SKTexture { - let renderer = UIGraphicsImageRenderer(size: size) - let image = renderer.image { _ in - let frame = CGRect(origin: .zero, size: size).insetBy(dx: size.width * 0.01, dy: size.height * 0.06) - let backgroundPath = UIBezierPath(roundedRect: frame, cornerRadius: size.height * 0.23) - UIColor(white: 1.0, alpha: 0.10).setFill() - backgroundPath.fill() - UIColor(white: 1.0, alpha: 0.22).setStroke() - backgroundPath.lineWidth = size.height * 0.08 - backgroundPath.stroke() - - let paragraphStyle = NSMutableParagraphStyle() - paragraphStyle.alignment = .center - let attributes: [NSAttributedString.Key: Any] = [ - .font: UIFont(name: "Optima-Bold", size: size.height * 0.42) ?? UIFont.systemFont(ofSize: size.height * 0.42, weight: .semibold), - .foregroundColor: textColor, - .paragraphStyle: paragraphStyle - ] - let textRect = CGRect(x: 0.0, y: size.height * 0.28, width: size.width, height: size.height * 0.5) - title.draw(in: textRect, withAttributes: attributes) - } - return SKTexture(image: image) - } - - private func fittedFontSize(for text: String, preferredSize: CGFloat, maxWidth: CGFloat) -> CGFloat { - if text.isEmpty { - return preferredSize - } - let label = SKLabelNode(fontNamed: "Optima-Bold") - label.text = text - var currentSize = preferredSize - label.fontSize = currentSize - - while label.frame.width > maxWidth && currentSize > 10.0 { - currentSize -= 1.0 - label.fontSize = currentSize - } - return currentSize - } -} - -class TutorialGameScene: GameScene { - private var restartButton: SpriteButton? - private var menuButton: SpriteButton? - private var finishButton: SpriteButton? - private let tutorialHint = SKLabelNode(fontNamed: "Optima-Bold") - private let tutorialSubHint = SKLabelNode(fontNamed: "Optima-Bold") - - override init(size: CGSize, proportionSet: ProportionSet, level: Level) { - super.init(size: size, proportionSet: proportionSet, level: level) - addTutorialHints() - updateHintText() - } - - required init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func addStaticLabels() { - // Tutorial scene keeps UI minimal and avoids overlap with end-of-tutorial CTA. - } - - override func addDynamicLabels() { - // No move counters in tutorial. - } - - override func addButtons() { - let side = proportionSet.levelChoiceSize() - restartButton = SpriteButton(texture: makeCircleButtonTexture(symbolName: "arrow.counterclockwise", side: side), onClick: restartTutorial) - restartButton!.position = CGPoint(x: size.width - size.height * 0.057, y: size.height * 0.91) - addChild(restartButton!) - - menuButton = SpriteButton(texture: makeCircleButtonTexture(symbolName: "chevron.left", side: side), onClick: backToStartMenu) - menuButton!.position = proportionSet.levelChoicePos() + let side = min(size.width * 0.095, size.height * 0.07) + menuButton = SpriteButton(texture: ButtonTextureFactory.makeMinimalBackTexture(side: side), onClick: backToStartMenu) + menuButton?.position = CGPoint(x: size.width * 0.085, y: size.height * 0.90) + menuButton?.zPosition = 20 addChild(menuButton!) } - override func moveLine(horizontal: Bool, rowOrColumn: Int, steps: Int) { - super.moveLine(horizontal: horizontal, rowOrColumn: rowOrColumn, steps: steps) - if !levelSolved { - updateHintText() - } - } + private func addPageArrows() { + let arrowSide = min(size.width * 0.16, size.height * 0.13) + leftArrowButton = SpriteButton(texture: ButtonTextureFactory.makePageArrowTexture(imageNamed: "ArrowLeft", side: arrowSide), onClick: goToPreviousPage) + leftArrowButton?.position = CGPoint(x: size.width * 0.08, y: size.height * 0.21) + leftArrowButton?.zPosition = 20 + addChild(leftArrowButton!) - override func onSolvedGame() { - tutorialHint.text = "Solved" - tutorialHint.fontSize = size.height * 0.06 - tutorialHint.position = CGPoint(x: size.width * 0.5, y: size.height * 0.30) - tutorialSubHint.text = "" - showFinishButton() + rightArrowButton = SpriteButton(texture: ButtonTextureFactory.makePageArrowTexture(imageNamed: "ArrowRight", side: arrowSide), onClick: goToNextPage) + rightArrowButton?.position = CGPoint(x: size.width * 0.92, y: size.height * 0.21) + rightArrowButton?.zPosition = 20 + addChild(rightArrowButton!) } - private func addTutorialHints() { - tutorialHint.fontColor = fontColor - tutorialHint.position = CGPoint(x: size.width * 0.5, y: size.height * 0.24) - tutorialHint.zPosition = 10 - addChild(tutorialHint) - - tutorialSubHint.fontColor = fontColor - tutorialSubHint.position = CGPoint(x: size.width * 0.5, y: size.height * 0.19) - tutorialSubHint.zPosition = 10 - addChild(tutorialSubHint) + private func goToPreviousPage() { + levelButtonArea?.swipeLeft() } - private func updateHintText() { - if gameService.getLevel().movesCounter == 0 { - tutorialHint.text = "Swipe any row or column" - tutorialHint.fontSize = fittedFontSize(for: tutorialHint.text ?? "", preferredSize: size.height * 0.040, maxWidth: size.width * 0.92) - tutorialSubHint.text = "Connect start and finish in this level." - tutorialSubHint.fontSize = fittedFontSize(for: tutorialSubHint.text ?? "", preferredSize: size.height * 0.032, maxWidth: size.width * 0.92) - } else { - tutorialHint.text = "Good" - tutorialHint.fontSize = size.height * 0.044 - tutorialSubHint.text = "Keep going until the path is connected." - tutorialSubHint.fontSize = fittedFontSize(for: tutorialSubHint.text ?? "", preferredSize: size.height * 0.032, maxWidth: size.width * 0.92) - } - } - - private func showFinishButton() { - if finishButton != nil { - finishButton?.isHidden = false - return - } - let buttonSize = CGSize(width: size.width * 0.62, height: size.height * 0.095) - finishButton = SpriteButton(texture: makeTextButtonTexture(title: "Back to Menu", size: buttonSize)) { - ResourcesManager.getInstance().storyService?.loadStartMenuScene() - } - finishButton?.position = CGPoint(x: size.width * 0.5, y: size.height * 0.15) - finishButton?.zPosition = 10 - addChild(finishButton!) - } - - private func restartTutorial() { - ResourcesManager.getInstance().storyService?.loadTutorialGameSceneFromTutorialScene() + private func goToNextPage() { + levelButtonArea?.swipeRight() } private func backToStartMenu() { ResourcesManager.getInstance().storyService?.loadStartMenuScene() } - private func makeTextButtonTexture(title: String, size: CGSize) -> SKTexture { - let renderer = UIGraphicsImageRenderer(size: size) - let image = renderer.image { _ in - let frame = CGRect(origin: .zero, size: size).insetBy(dx: size.width * 0.01, dy: size.height * 0.06) - let backgroundPath = UIBezierPath(roundedRect: frame, cornerRadius: size.height * 0.23) - UIColor(white: 1.0, alpha: 0.10).setFill() - backgroundPath.fill() - UIColor(white: 1.0, alpha: 0.22).setStroke() - backgroundPath.lineWidth = size.height * 0.08 - backgroundPath.stroke() - - let paragraphStyle = NSMutableParagraphStyle() - paragraphStyle.alignment = .center - let buttonFontSize = fittedButtonFontSize(for: title, preferredSize: size.height * 0.38, maxWidth: size.width * 0.90) - let attributes: [NSAttributedString.Key: Any] = [ - .font: UIFont(name: "Optima-Bold", size: buttonFontSize) ?? UIFont.systemFont(ofSize: buttonFontSize, weight: .semibold), - .foregroundColor: fontColor, - .paragraphStyle: paragraphStyle - ] - let textRect = CGRect(x: 0.0, y: size.height * 0.30, width: size.width, height: size.height * 0.5) - title.draw(in: textRect, withAttributes: attributes) + func update() { + guard let area = levelButtonArea else { + leftArrowButton?.isHidden = true + rightArrowButton?.isHidden = true + return } - return SKTexture(image: image) - } - private func fittedButtonFontSize(for text: String, preferredSize: CGFloat, maxWidth: CGFloat) -> CGFloat { - var currentSize = preferredSize - while currentSize > 10.0 { - let font = UIFont(name: "Optima-Bold", size: currentSize) ?? UIFont.systemFont(ofSize: currentSize, weight: .semibold) - let measuredWidth = (text as NSString).size(withAttributes: [.font: font]).width - if measuredWidth <= maxWidth { - return currentSize - } - currentSize -= 1.0 - } - return 10.0 + let isFirstPage = area.currentLevelPage == 0 + let isLastPage = area.currentLevelPage >= area.pageCount - 1 + leftArrowButton?.isHidden = isFirstPage + rightArrowButton?.isHidden = isLastPage || area.pageCount <= 1 } - private func fittedFontSize(for text: String, preferredSize: CGFloat, maxWidth: CGFloat) -> CGFloat { - if text.isEmpty { - return preferredSize - } - let label = SKLabelNode(fontNamed: "Optima-Bold") - label.text = text - var currentSize = preferredSize - label.fontSize = currentSize - - while label.frame.width > maxWidth && currentSize > 10.0 { - currentSize -= 1.0 - label.fontSize = currentSize - } - return currentSize + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") } } diff --git a/SOPA/view/LevelMode/LevelModeGameScene.swift b/SOPA/view/LevelMode/LevelModeGameScene.swift index 059ed47..44bb5ee 100644 --- a/SOPA/view/LevelMode/LevelModeGameScene.swift +++ b/SOPA/view/LevelMode/LevelModeGameScene.swift @@ -12,8 +12,10 @@ class LevelModeGameScene: GameScene { var restartButton: SpriteButton? var levelChoiceButton: SpriteButton? var start: NSDate? + override init(size: CGSize, proportionSet: ProportionSet, level: Level) { super.init(size: size, proportionSet: proportionSet, level: level) + backgroundColor = .black startCounter() } @@ -31,27 +33,100 @@ class LevelModeGameScene: GameScene { required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } + override func addButtons() { - let restartSide = proportionSet.buttonSize() - restartButton = SpriteButton(texture: makeCircleButtonTexture(symbolName: "arrow.counterclockwise", side: restartSide), onClick: restartLevel) - restartButton!.position = proportionSet.restartButtonPos() - addChild(restartButton!) - - let side = proportionSet.levelChoiceSize() - levelChoiceButton = SpriteButton(texture: makeCircleButtonTexture(symbolName: "chevron.left", side: side), onClick: loadLevelChoiceScene) - levelChoiceButton!.position = proportionSet.levelChoicePos() + let backSide = min(size.width, size.height) * 0.09 + levelChoiceButton = SpriteButton(texture: ButtonTextureFactory.makeMinimalBackTexture(side: backSide), onClick: loadLevelChoiceScene) + levelChoiceButton!.position = CGPoint(x: size.width * 0.08, y: size.height * 0.95) addChild(levelChoiceButton!) + let restartSide = min(size.width, size.height) * 0.18 + restartButton = SpriteButton( + texture: ButtonTextureFactory.makeCircleButtonTexture( + symbolName: "arrow.counterclockwise", + side: restartSide, + circleColor: UIColor(white: 0.96, alpha: 1.0), + iconColor: UIColor(white: 0.05, alpha: 1.0), + symbolScale: 0.52, + symbolWeight: .bold + ), + onClick: restartLevel + ) + restartButton!.position = CGPoint(x: size.width * 0.86, y: size.height * 0.12) + addChild(restartButton!) } - + + override func addStaticLabels() { + addChild(movesLabels) + + let headingColor = UIColor(white: 0.90, alpha: 1.0) + let valueColor = UIColor(white: 0.96, alpha: 1.0) + let titleFont = "Impact" + + let minTitle = SKLabelNode(fontNamed: titleFont) + minTitle.text = "Min. Moves" + minTitle.horizontalAlignmentMode = .center + minTitle.verticalAlignmentMode = .center + minTitle.fontSize = min(size.width, size.height) * 0.055 + minTitle.fontColor = headingColor + minTitle.position = CGPoint(x: size.width * 0.14, y: size.height * 0.90) + movesLabels.addChild(minTitle) + + let minValue = SKLabelNode(fontNamed: titleFont) + minValue.text = String(gameService.getLevel().minimumMovesToSolve ?? 0) + minValue.horizontalAlignmentMode = .center + minValue.verticalAlignmentMode = .center + minValue.fontSize = min(size.width, size.height) * 0.15 + minValue.fontColor = valueColor + minValue.position = CGPoint(x: size.width * 0.04, y: size.height * 0.84) + movesLabels.addChild(minValue) + + let currentTitle = SKLabelNode(fontNamed: titleFont) + currentTitle.text = "Current Moves" + currentTitle.horizontalAlignmentMode = .center + currentTitle.verticalAlignmentMode = .center + currentTitle.fontSize = min(size.width, size.height) * 0.055 + currentTitle.fontColor = headingColor + currentTitle.position = CGPoint(x: size.width * 0.84, y: size.height * 0.90) + movesLabels.addChild(currentTitle) + + let levelNumber = SKLabelNode(fontNamed: titleFont) + levelNumber.horizontalAlignmentMode = .left + levelNumber.verticalAlignmentMode = .center + levelNumber.position = CGPoint(x: size.width * 0.04, y: size.height * 0.11) + levelNumber.fontSize = min(size.width, size.height) * 0.15 + levelNumber.text = String(gameService.getLevel().id!) + levelNumber.fontColor = valueColor + addChild(levelNumber) + + let levelLabel = SKLabelNode(fontNamed: titleFont) + levelLabel.text = "Level" + levelLabel.horizontalAlignmentMode = .left + levelLabel.verticalAlignmentMode = .center + levelLabel.fontSize = min(size.width, size.height) * 0.06 + levelLabel.fontColor = headingColor + levelLabel.position = CGPoint(x: size.width * 0.04, y: size.height * 0.045) + addChild(levelLabel) + } + + override func addDynamicLabels() { + currentMovesNode.text = String(gameService.getLevel().movesCounter) + currentMovesNode.horizontalAlignmentMode = .center + currentMovesNode.verticalAlignmentMode = .center + currentMovesNode.fontSize = min(size.width, size.height) * 0.15 + currentMovesNode.position = CGPoint(x: size.width * 0.80, y: size.height * 0.84) + currentMovesNode.fontColor = UIColor(white: 0.96, alpha: 1.0) + movesLabels.addChild(currentMovesNode) + } + func restartLevel() { ResourcesManager.getInstance().storyService?.reloadLevelModeGameScene(levelId: gameService.getLevel().id!) } - - func loadLevelChoiceScene() { + + private func loadLevelChoiceScene() { ResourcesManager.getInstance().storyService?.loadLevelCoiceSceneFromLevelModeScene() } - + override func onSolvedGame() { let time = stopCounter() let level = gameService.getLevel() diff --git a/SOPA/view/LevelMode/LevelSelectButton.swift b/SOPA/view/LevelMode/LevelSelectButton.swift index ee5716f..6af97be 100644 --- a/SOPA/view/LevelMode/LevelSelectButton.swift +++ b/SOPA/view/LevelMode/LevelSelectButton.swift @@ -10,24 +10,21 @@ import Foundation import SpriteKit class LevelSelectButton: SKSpriteNode { - let green = UIColor(red: 169.0 / 255.0, green: 162.0 / 255.0, blue: 121.0 / 255.0, alpha: 1.0) - let grey = UIColor(red: 0.5 , green: 0.5, blue: 0.5, alpha: 1.0) + let numberActive = UIColor(red: 223.0 / 255.0, green: 106.0 / 255.0, blue: 37.0 / 255.0, alpha: 1.0) + let numberUnlocked = UIColor(red: 98.0 / 255.0, green: 100.0 / 255.0, blue: 106.0 / 255.0, alpha: 1.0) + let numberLocked = UIColor(red: 110.0 / 255.0, green: 112.0 / 255.0, blue: 116.0 / 255.0, alpha: 1.0) let levelInfo: LevelInfo + init(levelInfo: LevelInfo, levelButtonPositioner: LevelButtonPositioner) { self.levelInfo = levelInfo + super.init(texture: nil, color: .clear, size: levelButtonPositioner.getLevelSize()) + position = levelButtonPositioner.getLevelPosition(id: levelInfo.levelId) + + let isCurrentTarget = !levelInfo.locked && levelInfo.fewestMoves < 0 + addFrame(locked: levelInfo.locked, highlighted: isCurrentTarget) + addLable(id: levelInfo.levelId, color: labelColor(locked: levelInfo.locked, highlighted: isCurrentTarget)) if !levelInfo.locked { - let texture = SKTexture(imageNamed: "Level") - super.init(texture: texture, color: UIColor.clear, size: levelButtonPositioner.getLevelSize()) - position = levelButtonPositioner.getLevelPosition(id: levelInfo.levelId) addStars(stars: levelInfo.stars) - addLable(id: levelInfo.levelId, color: green) - - } else { - let texture = SKTexture(imageNamed: "LevelSW") - super.init(texture: texture, color: UIColor.clear, size: levelButtonPositioner.getLevelSize()) - position = levelButtonPositioner.getLevelPosition(id: levelInfo.levelId) - addLable(id: levelInfo.levelId, color: grey) - } } @@ -36,14 +33,56 @@ class LevelSelectButton: SKSpriteNode { fatalError("init(coder:) has not been implemented") } + private func addFrame(locked: Bool, highlighted: Bool) { + let inset = size.width * 0.04 + let frameRect = CGRect( + x: -size.width / 2.0 + inset, + y: -size.height / 2.0 + inset, + width: size.width - 2.0 * inset, + height: size.height - 2.0 * inset + ) + let cornerRadius = size.width * 0.20 + + let glowNode = SKShapeNode(rect: frameRect, cornerRadius: cornerRadius) + glowNode.fillColor = .clear + glowNode.lineWidth = size.width * 0.040 + glowNode.strokeColor = highlighted + ? UIColor(red: 245.0 / 255.0, green: 102.0 / 255.0, blue: 34.0 / 255.0, alpha: 0.70) + : UIColor(white: 1.0, alpha: locked ? 0.12 : 0.30) + glowNode.glowWidth = size.width * 0.10 + glowNode.blendMode = .add + glowNode.zPosition = zPosition + addChild(glowNode) + + let frameNode = SKShapeNode(rect: frameRect, cornerRadius: cornerRadius) + frameNode.fillColor = .clear + frameNode.lineWidth = size.width * 0.030 + frameNode.strokeColor = highlighted + ? UIColor(red: 250.0 / 255.0, green: 192.0 / 255.0, blue: 107.0 / 255.0, alpha: 1.0) + : UIColor(white: locked ? 0.55 : 0.90, alpha: 0.95) + frameNode.lineJoin = .round + frameNode.zPosition = zPosition + 1 + addChild(frameNode) + } + + private func labelColor(locked: Bool, highlighted: Bool) -> UIColor { + if highlighted { + return numberActive + } + if locked { + return numberLocked + } + return numberUnlocked + } + func addStars(stars: Int) { let star1: SKSpriteNode = generateStar(achieved: stars >= 1) let star2: SKSpriteNode = generateStar(achieved: stars >= 2) let star3: SKSpriteNode = generateStar(achieved: stars >= 3) - star1.position = CGPoint(x: -size.width * 0.25, y: -size.height * 0.2) - star2.position = CGPoint(x: 0, y: -size.height * 0.27 ) - star3.position = CGPoint(x: size.width * 0.25, y: -size.height * 0.2) + star1.position = CGPoint(x: -size.width * 0.25, y: -size.height * 0.50) + star2.position = CGPoint(x: 0, y: -size.height * 0.56) + star3.position = CGPoint(x: size.width * 0.25, y: -size.height * 0.50) addChild(star1) addChild(star2) @@ -52,11 +91,11 @@ class LevelSelectButton: SKSpriteNode { } func addLable(id: Int, color: UIColor) { - let idLable = SKLabelNode(fontNamed: "Optima-Bold") + let idLable = SKLabelNode(fontNamed: "Impact") idLable.text = String(id) idLable.horizontalAlignmentMode = SKLabelHorizontalAlignmentMode.center idLable.verticalAlignmentMode = SKLabelVerticalAlignmentMode.center - idLable.fontSize = size.height * 0.34 + idLable.fontSize = size.height * 0.32 idLable.fontColor = color idLable.zPosition = zPosition + 1 addChild(idLable) @@ -68,7 +107,7 @@ class LevelSelectButton: SKSpriteNode { if achieved { star = SKSpriteNode(imageNamed: "star") } - star.size = CGSize(width: size.width/3, height: size.height/3) + star.size = CGSize(width: size.width/3.4, height: size.height/3.4) star.zPosition = zPosition + 1 return star } diff --git a/SOPA/view/LevelMode/SopaTheme.swift b/SOPA/view/LevelMode/SopaTheme.swift new file mode 100644 index 0000000..ee57c31 --- /dev/null +++ b/SOPA/view/LevelMode/SopaTheme.swift @@ -0,0 +1,17 @@ +import UIKit + +enum SopaTheme { + static let blackBackground = UIColor.black + static let warmBackground = UIColor(red: 90.6 / 255.0, green: 86.7 / 255.0, blue: 70.6 / 255.0, alpha: 1.0) + + static let titleGray = UIColor(red: 214.0 / 255.0, green: 214.0 / 255.0, blue: 214.0 / 255.0, alpha: 1.0) + static let neonBlue = UIColor(red: 111.0 / 255.0, green: 227.0 / 255.0, blue: 1.0, alpha: 1.0) + static let neonGlowBlue = UIColor(red: 23.0 / 255.0, green: 183.0 / 255.0, blue: 1.0, alpha: 1.0) + static let neonCoreBlue = UIColor(red: 183.0 / 255.0, green: 245.0 / 255.0, blue: 1.0, alpha: 1.0) + + static let bodyText = UIColor(red: 230.0 / 255.0, green: 236.0 / 255.0, blue: 242.0 / 255.0, alpha: 1.0) + static let gameText = UIColor(red: 240.0 / 255.0, green: 239.0 / 255.0, blue: 238.0 / 255.0, alpha: 1.0) + + static let circleButtonFill = UIColor(white: 0.94, alpha: 1.0) + static let circleButtonIcon = UIColor(red: 90.6 / 255.0, green: 86.7 / 255.0, blue: 70.6 / 255.0, alpha: 1.0) +} diff --git a/SOPA/view/LevelMode/StartMenuScene.swift b/SOPA/view/LevelMode/StartMenuScene.swift new file mode 100644 index 0000000..89782b8 --- /dev/null +++ b/SOPA/view/LevelMode/StartMenuScene.swift @@ -0,0 +1,106 @@ +import Foundation +import SpriteKit +import UIKit + +class StartMenuScene: SKScene { + private let twitterUrl = "https://twitter.com/sopagame" + private let shareText = "I played SOPA: https://play.google.com/store/apps/details?id=com.sopaapp" + + override init(size: CGSize) { + super.init(size: size) + backgroundColor = SopaTheme.blackBackground + addTitle() + addButtons() + addSocialButtons() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func addTitle() { + let title = SKLabelNode(fontNamed: "Impact") + title.text = "SOPA" + title.fontSize = size.height * 0.14 + title.fontColor = SopaTheme.titleGray + title.position = CGPoint(x: size.width / 2, y: size.height * 0.77) + addChild(title) + } + + private func addButtons() { + let menuItems: [(title: String, action: () -> Void)] = [ + ("LEVEL MODE", { ResourcesManager.getInstance().storyService?.loadLevelCoiceScene() }), + ("JUST PLAY", { ResourcesManager.getInstance().storyService?.loadJustPlaySceneFromMenuScene() }), + // Keep this wired until a dedicated Settings scene exists on iOS. + ("SETTINGS", { ResourcesManager.getInstance().storyService?.loadTutorialSceneFromMenuScene() }), + ("CREDITS", { ResourcesManager.getInstance().storyService?.loadCreditsSceneFromMenuScene() }) + ] + + let buttonSize = CGSize(width: size.width * 0.76, height: size.height * 0.095) + let firstY = size.height * 0.62 + let verticalGap = size.height * 0.11 + + for (index, item) in menuItems.enumerated() { + let button = SpriteButton(texture: ButtonTextureFactory.makeNeonTextTexture(title: item.title, size: buttonSize), onClick: item.action) + button.position = CGPoint(x: size.width / 2.0, y: firstY - CGFloat(index) * verticalGap) + addChild(button) + } + } + + private func addSocialButtons() { + let buttonSide = min(size.width * 0.18, size.height * 0.10) + + let shareButton = SpriteButton( + texture: ButtonTextureFactory.makeSocialButtonTexture(symbolName: "point.3.connected.trianglepath.dotted", size: CGSize(width: buttonSide, height: buttonSide), fillBackground: true), + onClick: shareApp + ) + shareButton.position = CGPoint(x: size.width * 0.20, y: size.height * 0.14) + addChild(shareButton) + + let twitterButton = SpriteButton( + texture: ButtonTextureFactory.makeSocialButtonTexture(symbolName: "paperplane.fill", size: CGSize(width: buttonSide * 1.15, height: buttonSide * 1.15), fillBackground: false), + onClick: openTwitter + ) + twitterButton.position = CGPoint(x: size.width * 0.74, y: size.height * 0.14) + addChild(twitterButton) + } + + private func openTwitter() { + guard let url = URL(string: twitterUrl) else { + return + } + UIApplication.shared.open(url) + } + + private func shareApp() { + guard let controller = topViewController() else { + return + } + let shareController = UIActivityViewController(activityItems: [shareText], applicationActivities: nil) + shareController.popoverPresentationController?.sourceView = controller.view + controller.present(shareController, animated: true) + } + + private func topViewController(base: UIViewController? = nil) -> UIViewController? { + let initialController: UIViewController? + if let base = base { + initialController = base + } else { + initialController = UIApplication.shared.connectedScenes + .compactMap { $0 as? UIWindowScene } + .flatMap { $0.windows } + .first { $0.isKeyWindow }?.rootViewController + } + + if let navController = initialController as? UINavigationController { + return topViewController(base: navController.visibleViewController) + } + if let tabController = initialController as? UITabBarController { + return topViewController(base: tabController.selectedViewController) + } + if let presented = initialController?.presentedViewController { + return topViewController(base: presented) + } + return initialController + } +} diff --git a/SOPA/view/LevelMode/TutorialGameScene.swift b/SOPA/view/LevelMode/TutorialGameScene.swift new file mode 100644 index 0000000..889b057 --- /dev/null +++ b/SOPA/view/LevelMode/TutorialGameScene.swift @@ -0,0 +1,103 @@ +import Foundation +import SpriteKit +import UIKit + +class TutorialGameScene: GameScene { + private var restartButton: SpriteButton? + private var menuButton: SpriteButton? + private var finishButton: SpriteButton? + private let tutorialHint = SKLabelNode(fontNamed: "Optima-Bold") + private let tutorialSubHint = SKLabelNode(fontNamed: "Optima-Bold") + + override init(size: CGSize, proportionSet: ProportionSet, level: Level) { + super.init(size: size, proportionSet: proportionSet, level: level) + addTutorialHints() + updateHintText() + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func addStaticLabels() { + // Tutorial scene keeps UI minimal and avoids overlap with end-of-tutorial CTA. + } + + override func addDynamicLabels() { + // No move counters in tutorial. + } + + override func addButtons() { + let side = proportionSet.levelChoiceSize() + restartButton = SpriteButton(texture: makeCircleButtonTexture(symbolName: "arrow.counterclockwise", side: side), onClick: restartTutorial) + restartButton!.position = CGPoint(x: size.width - size.height * 0.057, y: size.height * 0.91) + addChild(restartButton!) + + menuButton = SpriteButton(texture: makeCircleButtonTexture(symbolName: "chevron.left", side: side), onClick: backToStartMenu) + menuButton!.position = proportionSet.levelChoicePos() + addChild(menuButton!) + } + + override func moveLine(horizontal: Bool, rowOrColumn: Int, steps: Int) { + super.moveLine(horizontal: horizontal, rowOrColumn: rowOrColumn, steps: steps) + if !levelSolved { + updateHintText() + } + } + + override func onSolvedGame() { + tutorialHint.text = "Solved" + tutorialHint.fontSize = size.height * 0.06 + tutorialHint.position = CGPoint(x: size.width * 0.5, y: size.height * 0.30) + tutorialSubHint.text = "" + showFinishButton() + } + + private func addTutorialHints() { + tutorialHint.fontColor = fontColor + tutorialHint.position = CGPoint(x: size.width * 0.5, y: size.height * 0.24) + tutorialHint.zPosition = 10 + addChild(tutorialHint) + + tutorialSubHint.fontColor = fontColor + tutorialSubHint.position = CGPoint(x: size.width * 0.5, y: size.height * 0.19) + tutorialSubHint.zPosition = 10 + addChild(tutorialSubHint) + } + + private func updateHintText() { + if gameService.getLevel().movesCounter == 0 { + tutorialHint.text = "Swipe any row or column" + tutorialHint.fontSize = LabelSizing.fittedLabelFontSize(text: tutorialHint.text ?? "", fontName: "Optima-Bold", preferredSize: size.height * 0.040, maxWidth: size.width * 0.92) + tutorialSubHint.text = "Connect start and finish in this level." + tutorialSubHint.fontSize = LabelSizing.fittedLabelFontSize(text: tutorialSubHint.text ?? "", fontName: "Optima-Bold", preferredSize: size.height * 0.032, maxWidth: size.width * 0.92) + } else { + tutorialHint.text = "Good" + tutorialHint.fontSize = size.height * 0.044 + tutorialSubHint.text = "Keep going until the path is connected." + tutorialSubHint.fontSize = LabelSizing.fittedLabelFontSize(text: tutorialSubHint.text ?? "", fontName: "Optima-Bold", preferredSize: size.height * 0.032, maxWidth: size.width * 0.92) + } + } + + private func showFinishButton() { + if finishButton != nil { + finishButton?.isHidden = false + return + } + let buttonSize = CGSize(width: size.width * 0.62, height: size.height * 0.095) + finishButton = SpriteButton(texture: ButtonTextureFactory.makeTextButtonTexture(title: "Back to Menu", size: buttonSize, fontName: "Optima-Bold", textColor: fontColor)) { + ResourcesManager.getInstance().storyService?.loadStartMenuScene() + } + finishButton?.position = CGPoint(x: size.width * 0.5, y: size.height * 0.15) + finishButton?.zPosition = 10 + addChild(finishButton!) + } + + private func restartTutorial() { + ResourcesManager.getInstance().storyService?.loadTutorialGameSceneFromTutorialScene() + } + + private func backToStartMenu() { + ResourcesManager.getInstance().storyService?.loadStartMenuScene() + } +} diff --git a/SOPA/view/LevelMode/TutorialScene.swift b/SOPA/view/LevelMode/TutorialScene.swift new file mode 100644 index 0000000..7c7331b --- /dev/null +++ b/SOPA/view/LevelMode/TutorialScene.swift @@ -0,0 +1,139 @@ +import Foundation +import SpriteKit +import UIKit + +class TutorialScene: SKScene { + private var currentPage = 0 + private let headline = SKLabelNode(fontNamed: "Optima-Bold") + private let textLine1 = SKLabelNode(fontNamed: "Optima-Bold") + private let textLine2 = SKLabelNode(fontNamed: "Optima-Bold") + private let textLine3 = SKLabelNode(fontNamed: "Optima-Bold") + private let tapHint = SKLabelNode(fontNamed: "Optima-Bold") + private var letsGoButton: SpriteButton? + + override init(size: CGSize) { + super.init(size: size) + self.backgroundColor = SopaTheme.warmBackground + addBackButton() + addTitle() + addContentNodes() + updatePage() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func addBackButton() { + let side = size.height * 0.08 + let backButton = SpriteButton(texture: makeCircleButtonTexture(symbolName: "chevron.left", side: side)) { + ResourcesManager.getInstance().storyService?.loadStartMenuScene() + } + backButton.position = CGPoint(x: size.height * 0.057, y: size.height * 0.91) + addChild(backButton) + } + + private func addTitle() { + let title = SKLabelNode(fontNamed: "Optima-Bold") + title.text = "Tutorial" + title.fontSize = size.height * 0.10 + title.fontColor = SopaTheme.gameText + title.position = CGPoint(x: size.width * 0.5, y: size.height * 0.78) + addChild(title) + } + + private func addContentNodes() { + headline.fontColor = SopaTheme.gameText + headline.fontSize = size.height * 0.048 + headline.position = CGPoint(x: size.width * 0.5, y: size.height * 0.62) + addChild(headline) + + textLine1.fontColor = SopaTheme.gameText + textLine1.fontSize = size.height * 0.034 + textLine1.position = CGPoint(x: size.width * 0.5, y: size.height * 0.54) + addChild(textLine1) + + textLine2.fontColor = SopaTheme.gameText + textLine2.fontSize = size.height * 0.034 + textLine2.position = CGPoint(x: size.width * 0.5, y: size.height * 0.48) + addChild(textLine2) + + textLine3.fontColor = SopaTheme.gameText + textLine3.fontSize = size.height * 0.034 + textLine3.position = CGPoint(x: size.width * 0.5, y: size.height * 0.42) + addChild(textLine3) + + tapHint.fontColor = SopaTheme.gameText + tapHint.fontSize = size.height * 0.030 + tapHint.position = CGPoint(x: size.width * 0.5, y: size.height * 0.26) + addChild(tapHint) + } + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + if currentPage < 2 { + currentPage += 1 + updatePage() + } + } + + private func updatePage() { + switch currentPage { + case 0: + setPage( + headlineText: "Goal", + line1: "Connect the start and finish pipes.", + line2: "Swipe rows or columns to move tiles.", + line3: "Only matching pipe segments will connect.", + hint: "Tap anywhere to continue" + ) + letsGoButton?.isHidden = true + case 1: + setPage( + headlineText: "How It Works", + line1: "Horizontal swipe moves a full row.", + line2: "Vertical swipe moves a full column.", + line3: "Try to solve it in as few moves as possible.", + hint: "Tap anywhere to continue" + ) + letsGoButton?.isHidden = true + default: + setPage( + headlineText: "Try It Now", + line1: "Next you play a simple interactive level.", + line2: "Use restart if you want to try again.", + line3: "", + hint: "" + ) + ensureLetsGoButton() + letsGoButton?.isHidden = false + } + } + + private func setPage(headlineText: String, line1: String, line2: String, line3: String, hint: String) { + headline.text = headlineText + headline.fontSize = LabelSizing.fittedLabelFontSize(text: headlineText, fontName: "Optima-Bold", preferredSize: size.height * 0.048, maxWidth: size.width * 0.90) + + textLine1.text = line1 + textLine1.fontSize = LabelSizing.fittedLabelFontSize(text: line1, fontName: "Optima-Bold", preferredSize: size.height * 0.034, maxWidth: size.width * 0.92) + textLine2.text = line2 + textLine2.fontSize = LabelSizing.fittedLabelFontSize(text: line2, fontName: "Optima-Bold", preferredSize: size.height * 0.034, maxWidth: size.width * 0.92) + textLine3.text = line3 + textLine3.fontSize = LabelSizing.fittedLabelFontSize(text: line3, fontName: "Optima-Bold", preferredSize: size.height * 0.034, maxWidth: size.width * 0.92) + tapHint.text = hint + tapHint.fontSize = LabelSizing.fittedLabelFontSize(text: hint, fontName: "Optima-Bold", preferredSize: size.height * 0.030, maxWidth: size.width * 0.92) + } + + private func ensureLetsGoButton() { + if letsGoButton != nil { + return + } + + let buttonSize = CGSize(width: size.width * 0.34, height: size.height * 0.10) + let button = SpriteButton(texture: ButtonTextureFactory.makeTextButtonTexture(title: "Let's Go", size: buttonSize, fontName: "Optima-Bold", textColor: SopaTheme.gameText)) { + ResourcesManager.getInstance().storyService?.loadTutorialGameSceneFromTutorialScene() + } + button.position = CGPoint(x: size.width * 0.5, y: size.height * 0.25) + addChild(button) + letsGoButton = button + } +}