From ec776daed0c0cb2016fc3654451ad29d5a6be22f Mon Sep 17 00:00:00 2001 From: Mathieu GROUSSEAU Date: Wed, 11 Jun 2025 11:58:37 +0200 Subject: [PATCH] WIP cells --- App/App.xcodeproj/project.pbxproj | 20 ++++++++++++++ App/App/SpriteKit/GameScene.swift | 39 +++++++++++++++++++------- App/App/SpriteKit/PieceNode.swift | 46 +++++++++++++++++++++++++++++++ App/App/Utils/PlayerType.swift | 7 +++++ App/App/View/IngameView.swift | 10 +++++-- App/App/View/MainMenuView.swift | 8 ++++-- App/App/ViewModel/InGameVM.swift | 5 ++++ App/App/ViewModel/NewGameVM.swift | 6 ---- 8 files changed, 121 insertions(+), 20 deletions(-) create mode 100644 App/App/SpriteKit/PieceNode.swift create mode 100644 App/App/Utils/PlayerType.swift create mode 100644 App/App/ViewModel/InGameVM.swift diff --git a/App/App.xcodeproj/project.pbxproj b/App/App.xcodeproj/project.pbxproj index ee07b57..88cdf7a 100644 --- a/App/App.xcodeproj/project.pbxproj +++ b/App/App.xcodeproj/project.pbxproj @@ -16,6 +16,9 @@ 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 */; }; + F0143C2F2DF96B690086CAAA /* InGameVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0143C2E2DF96B690086CAAA /* InGameVM.swift */; }; + F0143C312DF9813C0086CAAA /* PieceNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0143C302DF9813C0086CAAA /* PieceNode.swift */; }; + F0143C342DF987490086CAAA /* PlayerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0143C332DF987490086CAAA /* PlayerType.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 */; }; @@ -60,6 +63,9 @@ 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 = ""; }; + F0143C2E2DF96B690086CAAA /* InGameVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InGameVM.swift; sourceTree = ""; }; + F0143C302DF9813C0086CAAA /* PieceNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PieceNode.swift; sourceTree = ""; }; + F0143C332DF987490086CAAA /* PlayerType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerType.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 = ""; }; @@ -128,6 +134,7 @@ F001A04A2DD48FAB00809561 /* App */ = { isa = PBXGroup; children = ( + F0143C322DF987360086CAAA /* Utils */, F0143C292DF018B70086CAAA /* SpriteKit */, F0143C282DE729CB0086CAAA /* ViewModel */, F0F59E4D2DD4986C00BE32D6 /* View */, @@ -179,6 +186,7 @@ isa = PBXGroup; children = ( F0143C2C2DF01E120086CAAA /* NewGameVM.swift */, + F0143C2E2DF96B690086CAAA /* InGameVM.swift */, ); path = ViewModel; sourceTree = ""; @@ -187,10 +195,19 @@ isa = PBXGroup; children = ( F0143C2A2DF018F20086CAAA /* GameScene.swift */, + F0143C302DF9813C0086CAAA /* PieceNode.swift */, ); path = SpriteKit; sourceTree = ""; }; + F0143C322DF987360086CAAA /* Utils */ = { + isa = PBXGroup; + children = ( + F0143C332DF987490086CAAA /* PlayerType.swift */, + ); + path = Utils; + sourceTree = ""; + }; F0F59E4D2DD4986C00BE32D6 /* View */ = { isa = PBXGroup; children = ( @@ -337,14 +354,17 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F0143C312DF9813C0086CAAA /* PieceNode.swift in Sources */, 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 */, + F0143C2F2DF96B690086CAAA /* InGameVM.swift in Sources */, F0F59E5B2DE6F68800BE32D6 /* ScoreboardView.swift in Sources */, F0143C2B2DF018F20086CAAA /* GameScene.swift in Sources */, F0F59E572DE6D6E600BE32D6 /* SavedGamesView.swift in Sources */, + F0143C342DF987490086CAAA /* PlayerType.swift in Sources */, F0F59E552DDDED1D00BE32D6 /* IngameView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/App/App/SpriteKit/GameScene.swift b/App/App/SpriteKit/GameScene.swift index 86aa604..a4ea54c 100644 --- a/App/App/SpriteKit/GameScene.swift +++ b/App/App/SpriteKit/GameScene.swift @@ -1,22 +1,41 @@ import SpriteKit -public class GameScene : SKScene { - public override init(size: CGSize) { - super.init(size: size) +public class GameScene: SKScene { + private var boardNode: SKShapeNode + private var cells: [PieceNode] + + public init(s: (w: Int, h: Int)) { + let boardHeight = CGFloat(s.h) * PieceNode.pieceSize + let gWidth = CGFloat(s.w) * PieceNode.pieceSize + let gHeight = boardHeight + PieceNode.pieceSize - anchorPoint = CGPoint(x: 0.5, y: 0.5) + boardNode = SKShapeNode(rect: CGRect(x: 0, y: 0, width: gWidth, height: boardHeight)) + boardNode.fillColor = .blue + boardNode.strokeColor = .black - backgroundColor = .cyan + cells = (0..<(s.w * s.h)).map({ index in + let piece = PieceNode() + piece.intPosition = (x: index % s.w, y: index / s.w) + piece.piece = switch(index % 3) { + case 1: .init(withOwner: .player1) + case 2: .init(withOwner: .player2) + default: .init(withOwner: .noOne) + } + return piece + }) - let rect = SKShapeNode(rect: CGRect(x: 10, y: 10, width: 100, height: 100)) - rect.strokeColor = .clear - rect.fillColor = .red + super.init(size: CGSize(width: gWidth, height: gHeight)) - addChild(rect) + // anchorPoint = CGPoint(x: 0.5, y: 0.5) + + addChild(boardNode) + for cell in cells { + addChild(cell) + } } public required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) + fatalError("Unreachable") } } diff --git a/App/App/SpriteKit/PieceNode.swift b/App/App/SpriteKit/PieceNode.swift new file mode 100644 index 0000000..9f0afde --- /dev/null +++ b/App/App/SpriteKit/PieceNode.swift @@ -0,0 +1,46 @@ +import Foundation +import SpriteKit +import Connect4Core + +class PieceNode : SKNode { + static let pieceSize: CGFloat = 100 + + private var circle: SKShapeNode + + var piece: Piece = .init(withOwner: .noOne) { + didSet { + switch (piece.owner) { + case .player1: + circle.fillColor = .yellow + circle.strokeColor = .orange + case .player2: + circle.fillColor = .red + circle.strokeColor = .orange + + default: + circle.fillColor = .lightGray + circle.strokeColor = .clear + } + } + } + var intPosition: (x: Int, y: Int) = (0, 0) { + didSet { + position = CGPoint( + x: CGFloat(intPosition.x) * Self.pieceSize + Self.pieceSize / 2, + y: CGFloat(intPosition.y) * Self.pieceSize + Self.pieceSize / 2 + ) + } + } + + override init() { + circle = SKShapeNode(circleOfRadius: Self.pieceSize / 2 * 0.90) + + super.init() + + addChild(circle) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/App/App/Utils/PlayerType.swift b/App/App/Utils/PlayerType.swift new file mode 100644 index 0000000..c1b6c52 --- /dev/null +++ b/App/App/Utils/PlayerType.swift @@ -0,0 +1,7 @@ +import Foundation + +enum PlayerType: CaseIterable, Identifiable { + var id: Self { self } + + case Human, AIRandom, AIFinnishHim, AISimpleNegaMax +} diff --git a/App/App/View/IngameView.swift b/App/App/View/IngameView.swift index fb93d58..ec485cb 100644 --- a/App/App/View/IngameView.swift +++ b/App/App/View/IngameView.swift @@ -20,7 +20,13 @@ struct IngameView: View { Spacer() - SpriteView(scene: self.scene).aspectRatio(contentMode: .fit) + VStack(alignment: .center) { + Spacer() + + SpriteView(scene: self.scene).scaledToFit() + + Spacer() + } Spacer() @@ -33,7 +39,7 @@ struct IngameView: View { } init(settings: NewGameVM, player1: PlayerSettingsVM, player2: PlayerSettingsVM) { - self.scene = GameScene(size: CGSize(width: 700, height: 600)) + self.scene = GameScene(s: (Int(settings.width), Int(settings.height))) // TODO actual game initialization } diff --git a/App/App/View/MainMenuView.swift b/App/App/View/MainMenuView.swift index f0dfc55..2bff8c2 100644 --- a/App/App/View/MainMenuView.swift +++ b/App/App/View/MainMenuView.swift @@ -12,9 +12,13 @@ struct MainMenuView: View { VStack(spacing: 20) { Label("appNameBannerTitle", systemImage: "globe") - NavigationLink("mainMenu.button.newGame", destination: NewGameView().navigationTitle("newGame.title")) + NavigationLink(destination: NewGameView().navigationTitle("newGame.title")) { + Label("mainMenu.button.newGame", systemImage: "play") + } - NavigationLink("mainMenu.button.scoreboard", destination: SavedGamesView().navigationTitle("savedGames.title")) + NavigationLink(destination: SavedGamesView().navigationTitle("savedGames.title")) { + Label("mainMenu.button.scoreboard", systemImage: "rosette") + } } } } diff --git a/App/App/ViewModel/InGameVM.swift b/App/App/ViewModel/InGameVM.swift new file mode 100644 index 0000000..fb38a2a --- /dev/null +++ b/App/App/ViewModel/InGameVM.swift @@ -0,0 +1,5 @@ +import Foundation + +class InGameVM: ObservableObject { + +} diff --git a/App/App/ViewModel/NewGameVM.swift b/App/App/ViewModel/NewGameVM.swift index 11ead28..6c70c5c 100644 --- a/App/App/ViewModel/NewGameVM.swift +++ b/App/App/ViewModel/NewGameVM.swift @@ -17,12 +17,6 @@ class PlayerSettingsVM : ObservableObject, Identifiable { } } -enum PlayerType: CaseIterable, Identifiable { - var id: Self { self } - - case Human, AIRandom, AIFinnishHim, AISimpleNegaMax -} - enum RulesType { case Classic }