From f7abf021853793f9bb2a6c3fc1c2e6d1907454cf Mon Sep 17 00:00:00 2001 From: Mathieu GROUSSEAU Date: Wed, 4 Jun 2025 09:46:42 +0200 Subject: [PATCH] WIP VMs --- App/App.xcodeproj/project.pbxproj | 24 +++++++ App/App/View/NewGameView.swift | 66 ++++++++++--------- App/App/ViewModel/NewGameVM.swift | 26 ++++++++ .../xcshareddata/WorkspaceSettings.xcsettings | 5 ++ 4 files changed, 89 insertions(+), 32 deletions(-) create mode 100644 App/App/ViewModel/NewGameVM.swift create mode 100644 Connect 4 Swift UI.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/App/App.xcodeproj/project.pbxproj b/App/App.xcodeproj/project.pbxproj index 4cc985b..ee07b57 100644 --- a/App/App.xcodeproj/project.pbxproj +++ b/App/App.xcodeproj/project.pbxproj @@ -14,6 +14,8 @@ F001A05E2DD48FAE00809561 /* AppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F001A05D2DD48FAE00809561 /* AppTests.swift */; }; F001A0682DD48FAE00809561 /* AppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F001A0672DD48FAE00809561 /* AppUITests.swift */; }; F001A06A2DD48FAE00809561 /* AppUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F001A0692DD48FAE00809561 /* AppUITestsLaunchTests.swift */; }; + F0143C2B2DF018F20086CAAA /* GameScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0143C2A2DF018F20086CAAA /* GameScene.swift */; }; + F0143C2D2DF01E120086CAAA /* NewGameVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0143C2C2DF01E120086CAAA /* NewGameVM.swift */; }; F0F59E492DD4958800BE32D6 /* C4.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = F0F59E432DD492B400BE32D6 /* C4.xcframework */; }; F0F59E4A2DD4958800BE32D6 /* C4Persistance.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = F0F59E412DD492B400BE32D6 /* C4Persistance.xcframework */; }; F0F59E4B2DD4958800BE32D6 /* C4Players.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = F0F59E422DD492B400BE32D6 /* C4Players.xcframework */; }; @@ -56,6 +58,8 @@ F001A0632DD48FAE00809561 /* AppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F001A0672DD48FAE00809561 /* AppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUITests.swift; sourceTree = ""; }; F001A0692DD48FAE00809561 /* AppUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUITestsLaunchTests.swift; sourceTree = ""; }; + F0143C2A2DF018F20086CAAA /* GameScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameScene.swift; sourceTree = ""; }; + F0143C2C2DF01E120086CAAA /* NewGameVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewGameVM.swift; sourceTree = ""; }; F0F59E412DD492B400BE32D6 /* C4Persistance.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = C4Persistance.xcframework; path = ../precompiled/xcframeworks/C4Persistance.xcframework; sourceTree = ""; }; F0F59E422DD492B400BE32D6 /* C4Players.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = C4Players.xcframework; path = ../precompiled/xcframeworks/C4Players.xcframework; sourceTree = ""; }; F0F59E432DD492B400BE32D6 /* C4.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = C4.xcframework; path = ../precompiled/xcframeworks/C4.xcframework; sourceTree = ""; }; @@ -124,6 +128,8 @@ F001A04A2DD48FAB00809561 /* App */ = { isa = PBXGroup; children = ( + F0143C292DF018B70086CAAA /* SpriteKit */, + F0143C282DE729CB0086CAAA /* ViewModel */, F0F59E4D2DD4986C00BE32D6 /* View */, F001A04B2DD48FAB00809561 /* AppApp.swift */, F001A04F2DD48FAD00809561 /* Assets.xcassets */, @@ -169,6 +175,22 @@ name = Frameworks; sourceTree = ""; }; + F0143C282DE729CB0086CAAA /* ViewModel */ = { + isa = PBXGroup; + children = ( + F0143C2C2DF01E120086CAAA /* NewGameVM.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + F0143C292DF018B70086CAAA /* SpriteKit */ = { + isa = PBXGroup; + children = ( + F0143C2A2DF018F20086CAAA /* GameScene.swift */, + ); + path = SpriteKit; + sourceTree = ""; + }; F0F59E4D2DD4986C00BE32D6 /* View */ = { isa = PBXGroup; children = ( @@ -318,8 +340,10 @@ F0F59E592DE6EB2A00BE32D6 /* PlayerVSPage.swift in Sources */, F001A04E2DD48FAB00809561 /* MainMenuView.swift in Sources */, F001A04C2DD48FAB00809561 /* AppApp.swift in Sources */, + F0143C2D2DF01E120086CAAA /* NewGameVM.swift in Sources */, F0F59E532DDDC35100BE32D6 /* NewGameView.swift in Sources */, F0F59E5B2DE6F68800BE32D6 /* ScoreboardView.swift in Sources */, + F0143C2B2DF018F20086CAAA /* GameScene.swift in Sources */, F0F59E572DE6D6E600BE32D6 /* SavedGamesView.swift in Sources */, F0F59E552DDDED1D00BE32D6 /* IngameView.swift in Sources */, ); diff --git a/App/App/View/NewGameView.swift b/App/App/View/NewGameView.swift index b2f2113..b171e24 100644 --- a/App/App/View/NewGameView.swift +++ b/App/App/View/NewGameView.swift @@ -9,38 +9,47 @@ import SwiftUI import PhotosUI struct NewGameView: View { - @State private var p2: PlayerType = .AISimpleNegaMax - @State private var rules_type: RulesType = .Classic - @State private var width: UInt = 7 - @State private var height: UInt = 7 - @State private var alignedTokens: UInt = 4 + @StateObject + private var vm: NewGameVM = NewGameVM() + @StateObject + private var p1: PlayerSettingsVM = PlayerSettingsVM(type: .Human) + @StateObject + private var p2: PlayerSettingsVM = PlayerSettingsVM(type: .AISimpleNegaMax) var body: some View { VStack { Form { - Section("generic.player1.name") { - PlayerSectionView(initialType: .Human) + Section(header: Label("generic.player1.name", systemImage: "person")) { + PlayerSectionView(settings: p1) } - Section("generic.player2.name") { - PlayerSectionView(initialType: .AISimpleNegaMax) + Section(header: Label("generic.player2.name", systemImage: "person")) { + PlayerSectionView(settings: p2) } HStack { - Picker("newGame.rules.title", systemImage: "globe", selection: $rules_type) { + Picker("newGame.rules.title", systemImage: "slider.horizontal.3", selection: $vm.rulesType) { Text("generic.rules.classic.name").tag(RulesType.Classic) } - Image(systemName: "globe") + Button(action: { + // TODO + }) { + Image(systemName: "questionmark.circle") + } } - Section("newGame.dimensions") { - Stepper("newGame.dimensions.width \(width)", value: $width) - Stepper("newGame.dimensions.height \(height)", value: $height) - Stepper("newGame.dimensions.alignedTokens \(alignedTokens)", value: $alignedTokens) + Section(header: Label("newGame.dimensions", systemImage: "crop")) { + Stepper("newGame.dimensions.width \(vm.width)", value: $vm.width) + Stepper("newGame.dimensions.height \(vm.height)", value: $vm.height) + Stepper("newGame.dimensions.alignedTokens \(vm.alignedTokens)", value: $vm.alignedTokens) } - Button("newGame.play") { + Section(header: Label("newGame.timeLimit", systemImage: "stopwatch")) { + + } + + Button("newGame.play", systemImage: "play") { // TODO: yes } } @@ -49,19 +58,21 @@ struct NewGameView: View { } private struct PlayerSectionView: View { - @State private var type: PlayerType - @State private var name: String + @ObservedObject + private var settings: PlayerSettingsVM + + @State private var name: String = "" @State private var photo: PhotosPickerItem? = nil var body: some View { - Picker("newGame.player.type", selection: $type) { + Picker("newGame.player.type", selection: $settings.type) { Text("generic.player.type.human").tag(PlayerType.Human) Text("generic.player.type.aiRandom").tag(PlayerType.AIRandom) Text("generic.player.type.aiFinnishHim").tag(PlayerType.AIFinnishHim) Text("generic.player.type.aiSimpleNegaMax").tag(PlayerType.AISimpleNegaMax) } - let binding: Binding = if (self.type == .Human) { + let binding: Binding = if (settings.type == .Human) { $name } else { // FIXME: make text field readonly @@ -74,27 +85,18 @@ private struct PlayerSectionView: View { //} // TODO: actual photo support - Image(systemName: "globe").overlay { + Image(systemName: "camera.viewfinder").overlay { PhotosPicker(selection: $photo) { Text("newGame.player.photo.picker") } } } - init(initialType type: PlayerType) { - self.type = type - self.name = "TODO default name" + init(settings: PlayerSettingsVM) { + self.settings = settings } } -private enum PlayerType { - case Human, AIRandom, AIFinnishHim, AISimpleNegaMax -} - -private enum RulesType { - case Classic -} - #Preview { NewGameView() } diff --git a/App/App/ViewModel/NewGameVM.swift b/App/App/ViewModel/NewGameVM.swift new file mode 100644 index 0000000..a8d2b5c --- /dev/null +++ b/App/App/ViewModel/NewGameVM.swift @@ -0,0 +1,26 @@ +import Foundation + +class NewGameVM : ObservableObject { + @Published var rulesType: RulesType = .Classic + @Published var width: UInt = 7 + @Published var height: UInt = 7 + @Published var alignedTokens: UInt = 4 +} + +// Didn't manage to nest player settings under NewGameVM +class PlayerSettingsVM : ObservableObject, Identifiable { + @Published var type: PlayerType + @Published var name: String = "" + + init(type: PlayerType ) { + self.type = type + } +} + +enum PlayerType { + case Human, AIRandom, AIFinnishHim, AISimpleNegaMax +} + +enum RulesType { + case Classic +} diff --git a/Connect 4 Swift UI.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/Connect 4 Swift UI.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/Connect 4 Swift UI.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,5 @@ + + + + +