From 2f7ff9bbdc773c4f830008c8751cb4f5795f479d Mon Sep 17 00:00:00 2001 From: Mathieu GROUSSEAU Date: Mon, 10 Feb 2025 13:16:55 +0100 Subject: [PATCH] Check, fix, check ser-de --- CLI/CLI/main.swift | 45 ++++++++++++--------- Model/Sources/Model/Board.swift | 5 ++- Model/Sources/Model/Game.swift | 15 +++++-- Model/Sources/Model/Players/Player.swift | 3 +- Model/Sources/Model/Rules/Rules.swift | 18 ++++++++- Persistance/Sources/Persistance/Rules.swift | 6 +-- 6 files changed, 62 insertions(+), 30 deletions(-) diff --git a/CLI/CLI/main.swift b/CLI/CLI/main.swift index 89c78f5..16ef892 100644 --- a/CLI/CLI/main.swift +++ b/CLI/CLI/main.swift @@ -31,27 +31,28 @@ enum PlayerType: String, CaseIterable { case Human = "Player" } +func playerPrompt(allowed_moves: [Move.Action], board: Board) -> Move.Action { + for (i, action) in allowed_moves.enumerated() { + let text = switch action { + case .InsertAt(let at): "Place piece at \(at.col):\(at.row)" + case .InsertOnSide(let side, let offset): "Fall piece from \(side), offset \(offset)" + } + print("\(i + 1). \(text)") + } + + guard let rawInput = readLine(strippingNewline: true), + let index = Int(rawInput), index >= 1, index <= allowed_moves.count + else { + fatalError("Invalid move number") + } + + return allowed_moves[index - 1] +} + func createPlayer(type: PlayerType, playing pieces: PieceType, named name: String) -> Player { switch type { case .Human: - HumanPlayer(name: name, piece_type: pieces, callback: { - allowed_moves, board in - for (i, action) in allowed_moves.enumerated() { - let text = switch action { - case .InsertAt(let at): "Place piece at \(at.col):\(at.row)" - case .InsertOnSide(let side, let offset): "Fall piece from \(side), offset \(offset)" - } - print("\(i + 1). \(text)") - } - - guard let rawInput = readLine(strippingNewline: true), - let index = Int(rawInput), index >= 1, index <= allowed_moves.count - else { - fatalError("Invalid move number") - } - - return allowed_moves[index - 1] - }) + HumanPlayer(name: name, piece_type: pieces, callback: playerPrompt) case .AIRandom: RandomPlayer(name: name, piece_type: pieces) } @@ -68,7 +69,7 @@ let players: [Player] = PieceType.allCases.map { type in return createPlayer(type: ptype, playing: type, named: playerName) } -let rules: Rules = switch gameType { +let rules: any Rules = switch gameType { case .FourInARow: FourInARowRules(players: players)! case .TicTacToe: TicTacToeRules(players: players)! } @@ -104,3 +105,9 @@ encoder.userInfo[.gameDecodingContext] = CodableContext() let data = try encoder.encode(CodableGameWrapper(of: game)) print(String(data: data, encoding: .utf8)!) + +var decoder = JSONDecoder() +decoder.userInfo[.gameDecodingContext] = CodableContext(creatingCallbacks: { name, type in playerPrompt }) +let game2 = (try decoder.decode(CodableGameWrapper.self, from: data)).game + +assert(game == game2, "Games are not equals!") diff --git a/Model/Sources/Model/Board.swift b/Model/Sources/Model/Board.swift index 04a0d8a..4d0b59d 100644 --- a/Model/Sources/Model/Board.swift +++ b/Model/Sources/Model/Board.swift @@ -42,10 +42,13 @@ public struct Board: Equatable { } } + /// Iterate in the same order as coords public var cells: any Sequence { - self.grid.lazy.flatMap { (row: [Piece?]) -> LazySequence<[Piece?]> in row.lazy } + // self.grid.lazy.flatMap { (row: [Piece?]) -> LazySequence<[Piece?]> in row.lazy } + self.coords.map { self[$0] } } + /// Iterate row by row, from 0 to width public var coords: any Sequence { (0.. Void public typealias GameStateChangedListener = (GameState) -> Void -public class Game { +public class Game: Equatable { public var boardChanged = Event() public var gameStateChanged = Event() public var moveIsInvalid = Event() public let players: [Player] - public let rules: Rules + public let rules: any Rules /// Has to be public for serialization purposes public var board: Board /// Has to be public for serialization purposes public var state: GameState - public init(players: [Player], rules: Rules) { + public init(players: [Player], rules: any Rules) { self.players = players self.rules = rules @@ -23,7 +23,7 @@ public class Game { self.state = rules.gameState(board: board, last_turn: nil) } - public init?(players: [Player], rules: Rules, board: Board, state: GameState?) { + public init?(players: [Player], rules: any Rules, board: Board, state: GameState?) { guard rules.isValid(board: board) else { return nil } self.state = state ?? rules.gameState(board: board, last_turn: nil) @@ -96,6 +96,13 @@ public class Game { board[finalCoords] = Piece(owner: move.player) } + + public static func == (lhs: Game, rhs: Game) -> Bool { + lhs.players == rhs.players + && RulesUtils.equals(lhs: lhs.rules, rhs: rhs.rules) + && lhs.board == rhs.board + && lhs.state == rhs.state + } } @dynamicCallable diff --git a/Model/Sources/Model/Players/Player.swift b/Model/Sources/Model/Players/Player.swift index d5a7b2d..17bd622 100644 --- a/Model/Sources/Model/Players/Player.swift +++ b/Model/Sources/Model/Players/Player.swift @@ -19,8 +19,7 @@ public class Player : Equatable { } public static func == (lhs: Player, rhs: Player) -> Bool { - // TODO: name equality or reference equality? - lhs === rhs + lhs === rhs || (lhs.type == rhs.type && lhs.name == rhs.name && lhs.piece_type == rhs.piece_type) } } diff --git a/Model/Sources/Model/Rules/Rules.swift b/Model/Sources/Model/Rules/Rules.swift index 1f0a506..d1eaea1 100644 --- a/Model/Sources/Model/Rules/Rules.swift +++ b/Model/Sources/Model/Rules/Rules.swift @@ -1,4 +1,4 @@ -public protocol Rules { +public protocol Rules: Equatable { var type: RulesTypes { get } func createBoard() -> Board @@ -14,6 +14,22 @@ public protocol Rules { func gameState(board: Board, last_turn: Player?) -> GameState } +public struct RulesUtils { + public static func equals(lhs: any Rules, rhs: any Rules) -> Bool { + let t1 = lhs.type + let t2 = rhs.type + + if t1 != t2 { return false } + + return switch t1 { + case .FourInARow: + (lhs as! FourInARowRules) == (rhs as! FourInARowRules) + case .TicTacToe: + (lhs as! TicTacToeRules) == (rhs as! TicTacToeRules) + } + } +} + public enum GameState: Equatable { case Playing(turn: Player) case Win(winner: Player, board: Board, cells: [Coords]) diff --git a/Persistance/Sources/Persistance/Rules.swift b/Persistance/Sources/Persistance/Rules.swift index 24bfa3b..802c6fc 100644 --- a/Persistance/Sources/Persistance/Rules.swift +++ b/Persistance/Sources/Persistance/Rules.swift @@ -1,9 +1,9 @@ import Model public struct CodableRulesWrapper: Codable { - public let rules: Rules + public let rules: any Rules - public init(of rules: Rules) { + public init(of rules: any Rules) { self.rules = rules } @@ -30,7 +30,7 @@ extension RulesTypes: CodingKey, Codable { try CodableEnum(of: self).encode(to: encoder) } - public func decodeRules(from decoder: any Decoder) throws -> Rules { + public func decodeRules(from decoder: any Decoder) throws -> any Rules { switch self { case .FourInARow: try FourInARowRules(from: decoder) case .TicTacToe: try TicTacToeRules(from: decoder)