From 7ae16d7cc15ab8c6a9c9cbe96d1c43e4a3f150e8 Mon Sep 17 00:00:00 2001 From: Mathieu GROUSSEAU Date: Sun, 22 Jun 2025 15:52:47 +0200 Subject: [PATCH] Themed piece colors --- App/App.xcodeproj/project.pbxproj | 4 ++ App/App/Localizable.xcstrings | 33 ++++++++++ App/App/SpriteKit/GameScene.swift | 8 ++- App/App/SpriteKit/PieceNode.swift | 17 +---- App/App/Utils/Owner.swift | 19 ++++++ App/App/View/IngameView.swift | 32 ++++++--- App/App/View/NewGameView.swift | 65 ++++++++++--------- App/App/View/UIUtilities.swift | 8 +++ App/App/ViewModel/InGameVM.swift | 20 ++++++ App/App/ViewModel/PlayerVM.swift | 1 + .../Piece1.colorset/Contents.json | 56 ++++++++++++++++ .../Piece2.colorset/Contents.json | 38 +++++++++++ .../pieceBorder.colorset/Contents.json | 38 +++++++++++ 13 files changed, 285 insertions(+), 54 deletions(-) create mode 100644 App/App/Utils/Owner.swift create mode 100644 App/Colors.xcassets/Piece1.colorset/Contents.json create mode 100644 App/Colors.xcassets/Piece2.colorset/Contents.json create mode 100644 App/Colors.xcassets/pieceBorder.colorset/Contents.json diff --git a/App/App.xcodeproj/project.pbxproj b/App/App.xcodeproj/project.pbxproj index 783775a..93c107f 100644 --- a/App/App.xcodeproj/project.pbxproj +++ b/App/App.xcodeproj/project.pbxproj @@ -23,6 +23,7 @@ F05DA2112E002AA00094A4A8 /* BoardNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F05DA2102E002AA00094A4A8 /* BoardNode.swift */; }; F05DA2132E01BA270094A4A8 /* PlayerVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = F05DA2122E01BA270094A4A8 /* PlayerVM.swift */; }; F05DA2152E02D1850094A4A8 /* UIUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = F05DA2142E02D1850094A4A8 /* UIUtilities.swift */; }; + F05DA2172E082F5B0094A4A8 /* Owner.swift in Sources */ = {isa = PBXBuildFile; fileRef = F05DA2162E082F5B0094A4A8 /* Owner.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 */; }; @@ -74,6 +75,7 @@ F05DA2102E002AA00094A4A8 /* BoardNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardNode.swift; sourceTree = ""; }; F05DA2122E01BA270094A4A8 /* PlayerVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerVM.swift; sourceTree = ""; }; F05DA2142E02D1850094A4A8 /* UIUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIUtilities.swift; sourceTree = ""; }; + F05DA2162E082F5B0094A4A8 /* Owner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Owner.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 = ""; }; @@ -215,6 +217,7 @@ children = ( F0143C332DF987490086CAAA /* PlayerType.swift */, F0143C352DFA9A000086CAAA /* RulesType.swift */, + F05DA2162E082F5B0094A4A8 /* Owner.swift */, ); path = Utils; sourceTree = ""; @@ -380,6 +383,7 @@ F0F59E5B2DE6F68800BE32D6 /* ScoreboardView.swift in Sources */, F0143C2B2DF018F20086CAAA /* GameScene.swift in Sources */, F0F59E572DE6D6E600BE32D6 /* SavedGamesView.swift in Sources */, + F05DA2172E082F5B0094A4A8 /* Owner.swift in Sources */, F0143C342DF987490086CAAA /* PlayerType.swift in Sources */, F0F59E552DDDED1D00BE32D6 /* IngameView.swift in Sources */, ); diff --git a/App/App/Localizable.xcstrings b/App/App/Localizable.xcstrings index 4171e5d..b05091a 100644 --- a/App/App/Localizable.xcstrings +++ b/App/App/Localizable.xcstrings @@ -285,6 +285,38 @@ } } }, + "inGame.ppButton.pause" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pause" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pause" + } + } + } + }, + "inGame.ppButton.resume" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Resume" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reprendre" + } + } + } + }, "mainMenu.button.newGame" : { "extractionState" : "manual", "localizations" : { @@ -470,6 +502,7 @@ } }, "newGame.timeLimit" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { diff --git a/App/App/SpriteKit/GameScene.swift b/App/App/SpriteKit/GameScene.swift index 4844156..0893c5a 100644 --- a/App/App/SpriteKit/GameScene.swift +++ b/App/App/SpriteKit/GameScene.swift @@ -39,6 +39,13 @@ public class GameScene: SKScene { self.draggablePiece.piece = .init(withOwner: $0) }) self.cancellables.append(vm.$currentBoard.sink(receiveValue: self.updateBoard)) + self.cancellables.append(vm.$paused.sink { paused in + self.draggablePiece.onDragHandler = if paused { + nil + } else { + self.onPieceDragEnd + } + }) vm.game.addGameStartedListener { _ in DispatchQueue.main.async { @@ -77,7 +84,6 @@ public class GameScene: SKScene { ) } private func onPlayerTurn(player: Player) { - print("player \(player)") self.draggablePiece.onDragHandler = if player is HumanPlayer { self.onPieceDragEnd } else { diff --git a/App/App/SpriteKit/PieceNode.swift b/App/App/SpriteKit/PieceNode.swift index 14d58ac..644f90a 100644 --- a/App/App/SpriteKit/PieceNode.swift +++ b/App/App/SpriteKit/PieceNode.swift @@ -13,19 +13,8 @@ class PieceNode : SKNode { var piece: Piece = .init(withOwner: .noOne) { didSet { - switch (piece.owner) { - case .player1: - circle.fillColor = .yellow - circle.strokeColor = .black - case .player2: - circle.fillColor = .red - circle.strokeColor = .black - - default: - // NOTE: should be unreachable - circle.fillColor = .lightGray - circle.strokeColor = .clear - } + circle.fillColor = UIColor(piece.owner.pieceColor) + circle.strokeColor = UIColor(.pieceBorder) } } var boardPosition: (x: Int, y: Int)? = nil { @@ -49,7 +38,7 @@ class PieceNode : SKNode { override init() { circle = SKShapeNode(circleOfRadius: Self.pieceSize / 2 * Self.borderFactor) - + super.init() addChild(circle) diff --git a/App/App/Utils/Owner.swift b/App/App/Utils/Owner.swift new file mode 100644 index 0000000..9585f20 --- /dev/null +++ b/App/App/Utils/Owner.swift @@ -0,0 +1,19 @@ +// +// Owner.swift +// App +// +// Created by etudiant2 on 22/06/2025. +// + +import SwiftUI +import Connect4Core + +extension Owner { + var pieceColor: Color { + switch (self) { + case .player1: .piece1 + case .player2: .piece2 + default: .gray + } + } +} diff --git a/App/App/View/IngameView.swift b/App/App/View/IngameView.swift index 8a3205c..532b6e6 100644 --- a/App/App/View/IngameView.swift +++ b/App/App/View/IngameView.swift @@ -42,11 +42,28 @@ struct IngameView: View { Spacer() - // Button("", systemImage: "globe") { - // + // let style: (key: LocalizedStringKey, img: String) = if vm.paused { + // ("inGame.ppButton.resume", "play.circle") + // } else { + // ("inGame.ppButton.pause", "pause.circle") // } - + // Button(style.key, systemImage: style.img) { + // vm.paused.toggle() + // } + // .`if`(vm.paused, { $0.buttonStyle(.bordered) }) + // .`if`(!vm.paused, { $0.buttonStyle(.borderedProminent) }) + // //For some reason the following crashes the compiler: + // // .either(vm.paused, + // // `true`: { $0.buttonStyle(.borderedProminent) }, + // // `false`: { $0.buttonStyle(.bordered) } + // // ) + // .tint(.accentColor) + // + // Spacer() + Text("inGame.currentRules \(vm.rulesName)") + }.onAppear { + vm.start() } } @@ -56,10 +73,6 @@ struct IngameView: View { self._vm = StateObject(wrappedValue: vm) self.scene = GameScene(viewModel: vm) - - vm.start() - - // TODO actual game initialization } } @@ -116,8 +129,9 @@ private struct PlayerView: View { } } }.padding(.all, 5) - // .background(color, ignoresSafeAreaEdges: Edge.Set()) - // .containerShape(RoundedRectangle(cornerRadius: 5)) + .border(who.id.pieceColor) + .containerShape(RoundedRectangle(cornerRadius: 5)) + .padding(.all, 5) } init(who owner: PlayerVM, dock_left dockLeft: Bool, isTurn: Binding) { diff --git a/App/App/View/NewGameView.swift b/App/App/View/NewGameView.swift index c7f3c7d..68e1037 100644 --- a/App/App/View/NewGameView.swift +++ b/App/App/View/NewGameView.swift @@ -19,13 +19,8 @@ struct NewGameView: View { var body: some View { VStack { Form { - Section(header: Label("generic.player1.name", systemImage: "person")) { - PlayerSectionView(settings: p1) - } - - Section(header: Label("generic.player2.name", systemImage: "person")) { - PlayerSectionView(settings: p2) - } + PlayerSectionView("generic.player1.name", color: .piece1, settings: p1) + PlayerSectionView("generic.player2.name", color: .piece2, settings: p2) HStack { Picker("newGame.rules.title", systemImage: "slider.horizontal.3", selection: $vm.rulesType) { @@ -48,14 +43,13 @@ struct NewGameView: View { Stepper("newGame.dimensions.alignedTokens \(vm.alignedTokens)", value: $vm.alignedTokens) } - Section(header: Label("newGame.timeLimit", systemImage: "stopwatch")) { - - } + // Section(header: Label("newGame.timeLimit", systemImage: "stopwatch")) { + // + // } } }.toolbar { NavigationLink { NavigationLazyView(IngameView(settings: vm, player1: p1, player2: p2)) - // .navigationTitle(Text("Titre" as String)) } label: { Label("newGame.play", systemImage: "play") } @@ -75,35 +69,46 @@ struct NavigationLazyView: View { } private struct PlayerSectionView: View { + private let sectionLabel: LocalizedStringKey + private let pieceColor: Color + @ObservedObject private var settings: PlayerSettingsVM @State private var photo: PhotosPickerItem? = nil var body: some View { - Picker("newGame.player.type", selection: $settings.type) { - ForEach(PlayerType.allCases) { - Text(LocalizedStringKey($0.baseTranslationKey)).tag($0) + Section(header: Label { + Text(sectionLabel) + } icon: { + Circle().fill(self.pieceColor) + }.fixedSize(horizontal: true, vertical: false)) { + Picker("newGame.player.type", selection: $settings.type) { + ForEach(PlayerType.allCases) { + Text(LocalizedStringKey($0.baseTranslationKey)).tag($0) + } } + + if (settings.type == .Human) { + TextField("newGame.player.name", text: $settings.name) + // TODO: MacOS + //.textInputSuggestions(isEnabled: true) { + // + //} + } + + // // TODO: actual photo support + // Image(systemName: "camera.viewfinder").overlay { + // PhotosPicker(selection: $photo) { + // Text("newGame.player.photo.picker") + // } + // } } - - if (settings.type == .Human) { - TextField("newGame.player.name", text: $settings.name) - // TODO: MacOS - //.textInputSuggestions(isEnabled: true) { - // - //} - } - - // // TODO: actual photo support - // Image(systemName: "camera.viewfinder").overlay { - // PhotosPicker(selection: $photo) { - // Text("newGame.player.photo.picker") - // } - // } } - init(settings: PlayerSettingsVM) { + init(_ sectionLabel: LocalizedStringKey, color pieceColor: Color, settings: PlayerSettingsVM) { + self.sectionLabel = sectionLabel + self.pieceColor = pieceColor self.settings = settings } } diff --git a/App/App/View/UIUtilities.swift b/App/App/View/UIUtilities.swift index 3b3f7aa..017bc63 100644 --- a/App/App/View/UIUtilities.swift +++ b/App/App/View/UIUtilities.swift @@ -9,6 +9,14 @@ extension View { self } } + + @ViewBuilder func either(_ condition: Bool, `true`: (Self) -> Content, `false`: (Self) -> Content) -> some View { + if condition { + `true`(self) + } else { + `false`(self) + } + } } extension Binding { diff --git a/App/App/ViewModel/InGameVM.swift b/App/App/ViewModel/InGameVM.swift index a88fca3..e6eeddd 100644 --- a/App/App/ViewModel/InGameVM.swift +++ b/App/App/ViewModel/InGameVM.swift @@ -2,8 +2,11 @@ import Foundation import Connect4Core import Connect4Players import Connect4Rules +import Connect4Persistance class IngameVM: ObservableObject { + let gameName: String + let game: Game // @Published @@ -25,9 +28,26 @@ class IngameVM: ObservableObject { @Published var currentBoard: Board + @Published + var paused: Bool = false { + didSet { + Task.detached(priority: .userInitiated) { + if (self.paused) { + // TODO + } else { + // TODO + } + } + } + } + private var running: Bool = false init?(settings: NewGameVM, player1: PlayerSettingsVM, player2: PlayerSettingsVM) { + let fmt = DateFormatter() + fmt.locale = Locale(identifier: "EN") // No "root" locale? + self.gameName = fmt.string(from: Date.now) + let rulesInit: (Int, Int, Int) -> (any Rules)? = switch (settings.rulesType) { case .Classic: Connect4Rules.init case .TicTacToe: TicTacToeRules.init diff --git a/App/App/ViewModel/PlayerVM.swift b/App/App/ViewModel/PlayerVM.swift index bdbf70e..2f5bb89 100644 --- a/App/App/ViewModel/PlayerVM.swift +++ b/App/App/ViewModel/PlayerVM.swift @@ -7,6 +7,7 @@ class PlayerVM: ObservableObject { var name: String { inner.name } var type: PlayerType + var id: Owner { inner.id } init(inner: Player) { self.inner = inner diff --git a/App/Colors.xcassets/Piece1.colorset/Contents.json b/App/Colors.xcassets/Piece1.colorset/Contents.json new file mode 100644 index 0000000..f543b80 --- /dev/null +++ b/App/Colors.xcassets/Piece1.colorset/Contents.json @@ -0,0 +1,56 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "light" + } + ], + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.313", + "green" : "0.943", + "red" : "0.942" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.274", + "green" : "0.825", + "red" : "0.825" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/App/Colors.xcassets/Piece2.colorset/Contents.json b/App/Colors.xcassets/Piece2.colorset/Contents.json new file mode 100644 index 0000000..e271522 --- /dev/null +++ b/App/Colors.xcassets/Piece2.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.000", + "green" : "0.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.125", + "green" : "0.178", + "red" : "0.811" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/App/Colors.xcassets/pieceBorder.colorset/Contents.json b/App/Colors.xcassets/pieceBorder.colorset/Contents.json new file mode 100644 index 0000000..70e4e10 --- /dev/null +++ b/App/Colors.xcassets/pieceBorder.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.000", + "green" : "0.000", + "red" : "0.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "0.859", + "green" : "0.859", + "red" : "0.851" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +}