Update(master): début persistance

master
Louis DUFOUR 9 months ago
parent 82a0f40cc2
commit 29dbb7e53f

@ -10,6 +10,7 @@
167C5A152B57F0BC006FB682 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 167C5A142B57F0BC006FB682 /* main.swift */; };
16EE5AFB2B5E6FC500A15871 /* Model in Frameworks */ = {isa = PBXBuildFile; productRef = 16EE5AFA2B5E6FC500A15871 /* Model */; };
16EE5AFD2B5E6FC800A15871 /* Extension in Frameworks */ = {isa = PBXBuildFile; productRef = 16EE5AFC2B5E6FC800A15871 /* Extension */; };
BF99EAEE2B80239500B2CDC3 /* GamePersistance in Frameworks */ = {isa = PBXBuildFile; productRef = BF99EAED2B80239500B2CDC3 /* GamePersistance */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@ -34,6 +35,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
BF99EAEE2B80239500B2CDC3 /* GamePersistance in Frameworks */,
16EE5AFD2B5E6FC800A15871 /* Extension in Frameworks */,
16EE5AFB2B5E6FC500A15871 /* Model in Frameworks */,
);
@ -93,6 +95,7 @@
packageProductDependencies = (
16EE5AFA2B5E6FC500A15871 /* Model */,
16EE5AFC2B5E6FC800A15871 /* Extension */,
BF99EAED2B80239500B2CDC3 /* GamePersistance */,
);
productName = DouShouQiConsole;
productReference = 167C5A112B57F0BC006FB682 /* DouShouQiConsole */;
@ -306,6 +309,10 @@
isa = XCSwiftPackageProductDependency;
productName = Extension;
};
BF99EAED2B80239500B2CDC3 /* GamePersistance */ = {
isa = XCSwiftPackageProductDependency;
productName = GamePersistance;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 167C5A092B57F0BC006FB682 /* Project object */;

@ -8,6 +8,7 @@
import Foundation
import Model
import Extension
import GamePersistance
func olMain(){
@ -58,15 +59,19 @@ func olMain(){
Cell(ofType: .jungle), Cell(ofType: .jungle, withPiece: Piece(withOwner: .player2, andAnimal: .lion))]
]
if let board = Board(withGrid: initialBoardConfiguration) {
do {
let board = try Board(withGrid: initialBoardConfiguration)
// Afficher le plateau de jeu
print(board)
} else {
print("Erreur lors de l'initialisation du plateau de jeu.")
}
// Initialisez un Board avec cette configuration
if var board = Board(withGrid: initialBoardConfiguration) {
} catch {
print("Erreur lors de l'initialisation du plateau de jeu: \(error)")
}
do {
var board = try Board(withGrid: initialBoardConfiguration)
print("Plateau initial:")
print(board) // Affichez l'état initial du plateau
print("Plateau initial:")
print(board) // Affichez l'état initial du plateau
@ -90,11 +95,10 @@ func olMain(){
let insertResult = board.insert(piece: Piece(withOwner: .player1, andAnimal: .lion), atRow: 0, andColumn: 0)
print("Résultat de l'insertion : \(insertResult)")
print(board) // Affichez le plateau après insertion
} else {
print("Erreur lors de l'initialisation du plateau de jeu.")
}
} catch {
print("Erreur lors de l'initialisation du plateau de jeu: \(error)")
}
}
func setupHumanVsComputer() -> (Player, Player) {
guard let randomPlayer = RandomPlayer(withName: "Random Player 1", andId: .player1),
@ -115,35 +119,36 @@ func setupHumanVsHuman() -> (Player, Player) {
}
func main() {
print("Sélectionnez le mode de jeu :")
print("1: Humain contre Ordinateur")
print("2: Humain contre Humain")
guard let userInput = readLine(), let userChoice = Int(userInput) else {
fatalError("Entrée invalide")
}
print("Sélectionnez une option :")
print("1: Charger une partie sauvegardée")
print("2: Nouvelle partie Humain contre Ordinateur")
print("3: Nouvelle partie Humain contre Humain")
var player1: Player
var player2: Player
switch userChoice {
case 1:
let players = setupHumanVsComputer()
player1 = players.0
player2 = players.1
case 2:
let players = setupHumanVsHuman()
player1 = players.0
player2 = players.1
default:
fatalError("Option non valide")
}
guard let userInput = readLine(), let userChoice = Int(userInput) else {
fatalError("Entrée invalide")
}
var game: Game
var game = Game(
rules: VerySimpleRules(),
player1: player1,
player2: player2
)
switch userChoice {
case 1:
if let loadedGame = PersistenceManager.shared.loadGame() {
game = loadedGame
print("Partie chargée avec succès.")
} else {
print("Aucune partie sauvegardée trouvée, début d'une nouvelle partie.")
let players = setupHumanVsComputer()
game = Game(rules: VerySimpleRules(), player1: players.0, player2: players.1)
}
case 2:
let players = setupHumanVsComputer()
game = Game(rules: VerySimpleRules(), player1: players.0, player2: players.1)
case 3:
let players = setupHumanVsHuman()
game = Game(rules: VerySimpleRules(), player1: players.0, player2: players.1)
default:
fatalError("Option non valide")
}
// Configuration des callbacks pour la gestion des événements de jeu
game.onGameStart = {

@ -1,13 +1,3 @@
import XCTest
@testable import Extension
/*
final class ExtensionTests: XCTestCase {
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
XCTAssertEqual(Extension().text, "Hello, World!")
}
}
*/

@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

@ -0,0 +1,29 @@
// swift-tools-version: 5.7
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "GamePersistance",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "GamePersistance",
targets: ["GamePersistance"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(path: "../Model"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "GamePersistance",
dependencies: ["Model"]),
.testTarget(
name: "GamePersistanceTests",
dependencies: ["GamePersistance", "Model"]),
]
)

@ -0,0 +1,3 @@
# GamePersistance
A description of this package.

@ -0,0 +1,29 @@
//
// File.swift
//
//
// Created by Louis Dufour on 16/02/2024.
//
import Foundation
import Model
extension Board: Codable {
private enum CodingKeys: String, CodingKey {
case nbRows, nbColumns, grid
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let decodedGrid = try container.decode([[Cell]].self, forKey: .grid)
try self.init(withGrid: decodedGrid)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(nbRows, forKey: .nbRows)
try container.encode(nbColumns, forKey: .nbColumns)
try container.encode(grid, forKey: .grid)
}
}

@ -0,0 +1,25 @@
import Foundation
import Model
extension Cell: Codable {
private enum CodingKeys: String, CodingKey {
case cellType, initialOwner, piece
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let cellType = try container.decode(CellType.self, forKey: .cellType)
let initialOwner = try container.decode(Owner.self, forKey: .initialOwner)
let piece = try container.decodeIfPresent(Piece.self, forKey: .piece)
self.init(ofType: cellType, ownedBy: initialOwner, withPiece: piece)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(cellType, forKey: .cellType)
try container.encode(initialOwner, forKey: .initialOwner)
try container.encode(piece, forKey: .piece)
}
}

@ -0,0 +1,61 @@
//
// File.swift
//
//
// Created by Louis Dufour on 16/02/2024.
//
import Foundation
import Model
extension Game: Codable {
enum CodingKeys: String, CodingKey {
case rules, board, players, currentPlayerIndex
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(rules, forKey: .rules)
try container.encode(board, forKey: .board)
try container.encode(players.map { $0.toPlayerData() }, forKey: .players)
try container.encode(currentPlayerIndex, forKey: .currentPlayerIndex)
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// Décodez directement les propriétés.
let rules = try container.decode(VerySimpleRules.self, forKey: .rules)
let board = try container.decode(Board.self, forKey: .board)
let currentPlayerIndex = try container.decode(Int.self, forKey: .currentPlayerIndex)
// Décodez chaque PlayerData en une instance de Player.
let playersData = try container.decode([PlayerData].self, forKey: .players)
let players = try playersData.map { data -> Player in
switch data.type {
case "HumanPlayer":
guard let humanPlayer = HumanPlayer.from(data: data) else {
throw DecodingError.dataCorruptedError(forKey: .players,
in: container,
debugDescription: "Cannot decode HumanPlayer")
}
return humanPlayer
case "RandomPlayer":
guard let randomPlayer = RandomPlayer.from(data: data) else {
throw DecodingError.dataCorruptedError(forKey: .players,
in: container,
debugDescription: "Cannot decode RandomPlayer")
}
return randomPlayer
default:
throw DecodingError.dataCorruptedError(forKey: .players,
in: container,
debugDescription: "Unrecognized player type")
}
}
self.init(rules: rules, board: board, players: players, currentPlayerIndex: currentPlayerIndex)
}
}

@ -0,0 +1,16 @@
//
// File.swift
//
//
// Created by Louis Dufour on 16/02/2024.
//
import Foundation
import Model
extension HumanPlayer {
static func from(data: PlayerData) -> HumanPlayer? {
guard data.type == "HumanPlayer" else { return nil }
return HumanPlayer(name: data.name, id: data.id, inputMethod: { _ in nil })
}
}

@ -0,0 +1,36 @@
//
// File.swift
//
//
// Created by Louis Dufour on 16/02/2024.
//
import Foundation
import Model
extension Move: Codable {
private enum CodingKeys: String, CodingKey {
case owner, rowOrigin, columnOrigin, rowDestination, columnDestination
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let owner = try container.decode(Owner.self, forKey: .owner)
let rowOrigin = try container.decode(Int.self, forKey: .rowOrigin)
let columnOrigin = try container.decode(Int.self, forKey: .columnOrigin)
let rowDestination = try container.decode(Int.self, forKey: .rowDestination)
let columnDestination = try container.decode(Int.self, forKey: .columnDestination)
self.init(owner: owner, rowOrigin: rowOrigin, columnOrigin: columnOrigin, rowDestination: rowDestination, columnDestination: columnDestination)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(owner, forKey: .owner)
try container.encode(rowOrigin, forKey: .rowOrigin)
try container.encode(columnOrigin, forKey: .columnOrigin)
try container.encode(rowDestination, forKey: .rowDestination)
try container.encode(columnDestination, forKey: .columnDestination)
}
}

@ -0,0 +1,48 @@
//
// File.swift
//
//
// Created by Louis Dufour on 16/02/2024.
//
import Foundation
import Model
public struct PersistenceManager {
public static let shared = PersistenceManager()
private let fileName = "savedGame.json"
public func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
public func saveGame(game: Game) {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
do {
let data = try encoder.encode(game)
let url = getDocumentsDirectory().appendingPathComponent(fileName)
try data.write(to: url)
print("Game saved successfully.")
} catch {
print("Failed to save game: \(error)")
}
}
public func loadGame() -> Game? {
let url = getDocumentsDirectory().appendingPathComponent(fileName)
do {
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
let game = try decoder.decode(Game.self, from: data)
print("Game loaded successfully.")
return game
} catch {
print("Failed to load game: \(error)")
return nil
}
}
}

@ -0,0 +1,14 @@
//
// File.swift
//
//
// Created by Louis Dufour on 16/02/2024.
//
import Foundation
import Model
extension Player {
func toPlayerData() -> PlayerData {
return PlayerData(id: self.id, name: self.name, type: type(of: self) == HumanPlayer.self ? "HumanPlayer" : "RandomPlayer")
}
}

@ -0,0 +1,15 @@
//
// File.swift
//
//
// Created by Louis Dufour on 16/02/2024.
//
import Foundation
import Model
struct PlayerData: Codable {
let id: Owner
let name: String
let type: String // Pour distinguer HumanPlayer de RandomPlayer
}

@ -0,0 +1,15 @@
//
// File.swift
//
//
// Created by Louis Dufour on 16/02/2024.
//
import Foundation
import Model
extension RandomPlayer {
static func from(data: PlayerData) -> RandomPlayer? {
guard data.type == "RandomPlayer" else { return nil }
return RandomPlayer(withName: data.name, andId: data.id)
}
}

@ -0,0 +1,29 @@
//
// File.swift
//
//
// Created by Louis Dufour on 16/02/2024.
//
import Foundation
import Model
extension VerySimpleRules: Codable {
enum CodingKeys: String, CodingKey {
case occurences, historic
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(occurences, forKey: .occurences)
try container.encode(historic, forKey: .historic)
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let decodedOccurences = try container.decode(Dictionary<Board, Int>.self, forKey: .occurences)
let decodedHistoric = try container.decode([Move].self, forKey: .historic)
self.init(occurences: decodedOccurences, historic: decodedHistoric)
}
}

@ -0,0 +1,3 @@
import XCTest
@testable import GamePersistance

@ -12,14 +12,13 @@ public struct Board : Hashable, Equatable {
public let nbColumns: Int
public private(set) var grid: [[Cell]]
public init?(withGrid grid: [[Cell]]) {
public init(withGrid grid: [[Cell]]) throws {
guard let firstRowLength = grid.first?.count, grid.allSatisfy({ $0.count == firstRowLength }) else {
return nil
throw NSError(domain: "BoardInitializationError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Grid is not valid"])
}
self.nbRows = grid.count
self.nbColumns = firstRowLength
self.grid=grid
self.grid = grid
}
public func getCell(atRow row: Int, atColumn column: Int) -> Cell? {
@ -64,6 +63,4 @@ public struct Board : Hashable, Equatable {
return .failed(reason: .cellEmpty)
}
}
}

@ -25,10 +25,7 @@ public struct Cell : CustomStringConvertible, Hashable, Equatable {
public func hash(into hasher: inout Hasher) {
hasher.combine(cellType)
hasher.combine(initialOwner)
// 'Piece?' est déjà 'Hashable' grâce à la conformité automatique de Swift pour les types optionnels.
hasher.combine(piece)
}
}

@ -7,7 +7,7 @@
import Foundation
public enum Animal : CustomStringConvertible { case rat, cat, dog, wolf, leopard, tiger, lion, elephant
public enum Animal : CustomStringConvertible, Codable { case rat, cat, dog, wolf, leopard, tiger, lion, elephant
public var description: String {
switch self {

@ -7,7 +7,7 @@
import Foundation
public enum CellType : CustomStringConvertible {
public enum CellType : CustomStringConvertible, Codable {
case unknown, jungle, water, trap, den
public var description: String {

@ -7,7 +7,7 @@
import Foundation
public enum Owner : CustomStringConvertible {
public enum Owner : CustomStringConvertible, Codable {
case noOne, player1, player2
public var description: String {

@ -6,10 +6,10 @@
//
public struct Game {
private var rules: VerySimpleRules
private var board: Board
private var players: [Player]
private var currentPlayerIndex: Int = 0
public var rules: VerySimpleRules
public var board: Board
public var players: [Player]
public var currentPlayerIndex: Int = 0
public var onGameStart: (() -> Void)?
public var onPlayerTurn: ((Player) -> Void)?
@ -25,6 +25,13 @@ public struct Game {
self.players = [player1, player2]
}
public init(rules: VerySimpleRules, board: Board, players: [Player], currentPlayerIndex: Int) {
self.rules = rules
self.board = board
self.players = players
self.currentPlayerIndex = currentPlayerIndex
}
public mutating func start() {
onGameStart?()
onBoardChanged?(board)

@ -9,18 +9,13 @@ import Foundation
public protocol Rules {
var occurences: [Board: Int] { get set }
// pas forcément utile si j'utilise pas playedMove
var historic: [Move] { get set }
static func createBoard() -> Board
static func checkBoard( b: Board) throws
func getNextPlayer() -> Owner
// Donne tout les coups autoriser
func getMoves( board: Board, owner: Owner) -> [Move]
// Donne tout les coups autoriser à partir d'une cellule
func getMoves( board: Board, owner: Owner, row: Int, column: Int) -> [Move]
func isMoveValid( board: Board, row: Int, column: Int, rowArrived: Int, columnArrived: Int) -> Bool
@ -28,6 +23,5 @@ public protocol Rules {
func isGameOver( board: Board, lastMove: Move) -> (Bool, Result)
// permet de stocker le coût qui a été fait. (playedMove)
mutating func playedMove(move: Move, to board: inout Board) throws
}

@ -7,7 +7,7 @@
import Foundation
public struct Piece : CustomStringConvertible, Equatable, Hashable{
public struct Piece : CustomStringConvertible, Equatable, Hashable, Codable{
public let owner: Owner
public let animal: Animal
@ -17,6 +17,6 @@ public struct Piece : CustomStringConvertible, Equatable, Hashable{
}
public var description: String {
return "[(owner):(animal)]"
}
return "[(owner):(animal)]"
}
}

@ -11,15 +11,12 @@ public class Player {
public let id: Owner
public let name: String
// Initialiseur de la classe Player.
public init?(name: String, id: Owner) {
self.name = name
self.id = id
}
// Méthode chooseMove qui doit être surchargée dans les classes dérivées.
public func chooseMove(board: Board, rules: Rules) -> Move? {
// Méthode devant être remplacée par des sous-classes
fatalError("Cette méthode doit être implémentée par des sous-classes.")
}
}

@ -8,15 +8,19 @@
import Foundation
public struct VerySimpleRules: Rules {
public var occurences = [Board: Int]()
public var historic = [Move]()
public var occurences: [Board : Int]
public var historic: [Move]
public init() {
self.occurences = [Board: Int]()
self.historic = [Move]()
}
public init(occurences: [Board: Int], historic: [Move]) {
self.occurences = occurences
self.historic = historic
}
public static func createBoard() -> Board {
let initialBoardConfiguration: [[Cell]] = [
// Ligne 1 - Joueur 1
@ -43,12 +47,14 @@ public struct VerySimpleRules: Rules {
Cell(ofType: .den, ownedBy: Owner.player2), Cell(ofType: .jungle, withPiece: Piece(withOwner: .player2, andAnimal: .lion)),
Cell(ofType: .jungle)]
]
if let board = Board(withGrid: initialBoardConfiguration) {
return board
} else {
print("Erreur lors de l'initialisation du plateau de jeu.")
return Board(withGrid: [[]])!
}
do {
let board = try Board(withGrid: initialBoardConfiguration)
return board
} catch {
print("Failed to initialize the board due to an error: \(error)")
let defaultGrid: [[Cell]] = [[Cell(ofType: .jungle)]]
return try! Board(withGrid: defaultGrid)
}
}

@ -7,8 +7,11 @@ class BoardTests: XCTestCase {
override func setUp() {
super.setUp()
// Initialisez un Board avec une configuration de test
testBoard = Board(withGrid: initialTestConfiguration)
do {
testBoard = try Board(withGrid: initialTestConfiguration)
} catch {
XCTFail("Error setting up test: \(error)")
}
}
override func tearDown() {
@ -24,8 +27,6 @@ class BoardTests: XCTestCase {
// Vérifiez que la taille du plateau correspond à la configuration initiale
XCTAssertEqual(testBoard.nbRows, initialTestConfiguration.count)
XCTAssertEqual(testBoard.nbColumns, initialTestConfiguration[0].count)
// Ajoutez d'autres vérifications si nécessaire pour vous assurer que le plateau a été initialisé correctement
}
// Testez countPieces(of:)
@ -127,13 +128,15 @@ class BoardTests: XCTestCase {
class BoardPerformanceTests: XCTestCase {
// Initialisez un Board de test avec une configuration de test
var testBoard: Board!
override func setUp() {
super.setUp()
// Initialisez un Board avec une configuration de test
testBoard = Board(withGrid: initialTestConfiguration)
do {
testBoard = try Board(withGrid: initialTestConfiguration)
} catch {
XCTFail("Error setting up test: \(error)")
}
}
override func tearDown() {
@ -186,13 +189,13 @@ class BoardPerformanceTests: XCTestCase {
}
// Test de performance pour l'initialisateur de Board
func testPerformanceInitializeBoard() {
self.measure {
for _ in 0..<1000 { // Exécutez le test 1000 fois pour mesurer les performances
_ = Board(withGrid: initialTestConfiguration)
}
func testPerformanceInitializeBoard() {
self.measure {
for _ in 0..<1000 { // Exécutez le test 1000 fois pour mesurer les performances
_ = try! Board(withGrid: initialTestConfiguration)
}
}
}
// Exemple de données de test pour la configuration initiale
let initialTestConfiguration: [[Cell]] = [

@ -6,7 +6,7 @@
//
import XCTest
@testable import Model // Remplacez 'YourModuleName' par le nom réel de votre module
@testable import Model
class PlayerTests: XCTestCase {

@ -53,8 +53,14 @@ class VerySimpleRulesTests: XCTestCase {
invalidCells.append(rowCells)
}
let invalidBoard = Board(withGrid: invalidCells)!
XCTAssertThrowsError(try VerySimpleRules.checkBoard(b: invalidBoard))
do {
let invalidBoard = try Board(withGrid: invalidCells)
XCTAssertThrowsError(try VerySimpleRules.checkBoard(b: invalidBoard)) { error in
XCTAssertTrue(error is Error)
}
} catch {
XCTFail("Unexpected error while creating the board: \(error)")
}
}
func testMoveValidation() {
@ -151,5 +157,4 @@ class VerySimpleRulesTests: XCTestCase {
// Tester un mouvement invalide (par exemple, trop loin)
XCTAssertFalse(rules.isMoveValid(board: board, row: 0, column: 0, rowArrived: 3, columnArrived: 3), "Un mouvement invalide a été reconnu comme valide")
}
}

@ -4,6 +4,9 @@
<FileRef
location = "group:DouShouQiConsole/DouShouQiConsole.xcodeproj">
</FileRef>
<FileRef
location = "group:GamePersistance">
</FileRef>
<FileRef
location = "group:Extension">
</FileRef>

Loading…
Cancel
Save