diff --git a/Src/CLT/CLT/main.swift b/Src/CLT/CLT/main.swift index de52867..e03d772 100644 --- a/Src/CLT/CLT/main.swift +++ b/Src/CLT/CLT/main.swift @@ -1,10 +1,3 @@ -// -// main.swift -// CLT -// -// Created by BREUIL Yohann on 17/01/2023. -// - import Foundation import Model @@ -21,17 +14,35 @@ func insertPiece(id : Int, column : Int, _ board : inout Board) { print("Board \(column) is full") case .unknown: print("Unknown") + case .negativeOrOutOfBound: + print("Row or column must be posittive") + case .alreadyTake: + print("Column already take") } default: print("Rien") } + print(board) } -if var board = Board(withGrid: [[1,2,1], [2,1,nil], [nil,nil,nil]]){ +if var board = Board(withNbRows: 6, withNbColumns: 7) { print(board) insertPiece(id: 1, column: 0, &board) insertPiece(id: 1, column: 0, &board) insertPiece(id: 1, column: 0, &board) - insertPiece(id: 1, column: 0, &board) - insertPiece(id: 1, column: 0, &board) + insertPiece(id: 2, column: 3, &board) + insertPiece(id: 2, column: 1, &board) + insertPiece(id: 2, column: 0, &board) + insertPiece(id: 2, column: 0, &board) + insertPiece(id: 2, column: 0, &board) + insertPiece(id: 2, column: 0, &board) + insertPiece(id: 2, column: 0, &board) + insertPiece(id: 2, column: 0, &board) + insertPiece(id: 2, column: 0, &board) + + _ = board.removePiece(column: 0) + _ = board.removePiece(column: 0) + _ = board.removePiece(column: 8) + + print(board) } diff --git a/Src/Model/.swiftpm/xcode/xcshareddata/xcschemes/Model.xcscheme b/Src/Model/.swiftpm/xcode/xcshareddata/xcschemes/Model.xcscheme new file mode 100644 index 0000000..94fa614 --- /dev/null +++ b/Src/Model/.swiftpm/xcode/xcshareddata/xcschemes/Model.xcscheme @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Src/Model/Sources/Model/Board/Board.swift b/Src/Model/Sources/Model/Board/Board.swift index 6c722fe..ac94a0f 100644 --- a/Src/Model/Sources/Model/Board/Board.swift +++ b/Src/Model/Sources/Model/Board/Board.swift @@ -17,9 +17,10 @@ public struct Board : CustomStringConvertible { for row in grid.reversed(){ for cell in row { - string.append("\(String(describing: Board.descriptionMapper[cell] ?? "-")) ") + string.append("|") + string.append("\(String(describing: Board.descriptionMapper[cell] ?? "-"))") } - string.append("\n") + string.append("|\n") } return string @@ -95,11 +96,11 @@ public struct Board : CustomStringConvertible { /// - Parameter column : The column to add the piece /// - Returns: The result of the insertion in a `BoardResult` private mutating func insertPiece(id : Int, row : Int, column : Int) -> BoardResult { - guard row >= 0 && row < nbRows && column >= 0 && column < nbColumns else{ - return .failed(reason: .unknown) + guard row >= 0 && row < nbRows && column >= 0 && column < nbColumns else { + return .failed(reason: .negativeOrOutOfBound) } guard grid[row][column] == nil else{ - return .failed(reason: .unknown) + return .failed(reason: .alreadyTake) } grid[row][column] = id return .ok @@ -111,30 +112,35 @@ public struct Board : CustomStringConvertible { /// - Parameter column : The column to add the piece /// - Returns: The result of the insertion in a `BoardResult` public mutating func insertPiece(id : Int, column:Int) -> BoardResult { + guard column >= 0 && column < nbColumns else { + return .failed(reason: .negativeOrOutOfBound) + } + for row in 0.. Bool { - guard row >= 0 && row < nbRows && column >= 0 && column < nbColumns else{ + public mutating func removePiece(column : Int) -> Bool { + guard column >= 0 && column < nbRows else { return false } - guard grid[row][column] == id else { - return false + + for row in stride(from: nbRows - 1, through: 0, by: -1) { + if grid[row][column] != nil{ + grid[row][column] = nil + return true + } } - grid[row][column] = nil return true } } diff --git a/Src/Model/Sources/Model/Board/BoardResult.swift b/Src/Model/Sources/Model/Board/BoardResult.swift index c4eff1e..d45997c 100644 --- a/Src/Model/Sources/Model/Board/BoardResult.swift +++ b/Src/Model/Sources/Model/Board/BoardResult.swift @@ -3,7 +3,7 @@ import Foundation /// The result of an action on board /// /// - Author: Yohann BREUIL -public enum BoardResult { +public enum BoardResult : Equatable { case unknow case ok case failed(reason : FailedResult) @@ -14,6 +14,8 @@ public enum BoardResult { /// - Author: Yohann BREUIL public enum FailedResult { case unknown + case negativeOrOutOfBound + case alreadyTake case columnFull case boardFull } diff --git a/Src/Model/Sources/Model/Game/Game.swift b/Src/Model/Sources/Model/Game/Game.swift new file mode 100644 index 0000000..a3fd8da --- /dev/null +++ b/Src/Model/Sources/Model/Game/Game.swift @@ -0,0 +1,27 @@ +import Foundation + +public class Game { + private var rules : Rules + + private var board : Board? + + private var players : [Player] = [] + + /// Initialize game with a rule + public init(rules: Rules) { + self.rules = rules + self.board = createBoard(rules: rules) + } + + /// + public func createBoard(rules: Rules) -> Board? { + Board(withNbRows: type(of: rules).nbRows, withNbColumns: type(of: rules).nbColumns) ?? nil + } + + /// + public func insertPiece(player: Player) { + var col = player.chooseColumn() + + board?.insertPiece(id: player.id, column: col) + } +} diff --git a/Src/Model/Sources/Model/Player/AI.swift b/Src/Model/Sources/Model/Player/AI.swift index a1840dc..7faf6d0 100644 --- a/Src/Model/Sources/Model/Player/AI.swift +++ b/Src/Model/Sources/Model/Player/AI.swift @@ -4,5 +4,15 @@ import Foundation /// /// - Author: Yohann BREUIL public class AI : Player { + /// Initialize the artificial intelligence + /// + /// Artificial intelligence nickname was "AI" by default + public init() { + super.init(nickname: "AI") + } + /// + public override func chooseColumn() -> Int { + return 1 + } } diff --git a/Src/Model/Sources/Model/Player/Human.swift b/Src/Model/Sources/Model/Player/Human.swift index d66d16e..c79314c 100644 --- a/Src/Model/Sources/Model/Player/Human.swift +++ b/Src/Model/Sources/Model/Player/Human.swift @@ -4,5 +4,28 @@ import Foundation /// /// - Author: Yohann BREUIL public class Human : Player { + private var scanner : () -> Int + /// Initialize human player with a nickname and a scanner method + /// + /// - Parameter nickname : nickname to add to player + /// - Parameter scanner : scanner method which return an integer + public init(nickname: String, scanner: @escaping () -> Int) { + self.scanner = scanner + super.init(nickname: nickname) + } + + /// Initialize human player with a scanner method + /// + /// Human nickname was "Player" by default + /// + /// - Parameter scanner : scanner method which return an integer + public convenience init(scanner: @escaping () -> Int) { + self.init(nickname: "Player", scanner: scanner) + } + + /// + public override func chooseColumn() -> Int { + return 1 + } } diff --git a/Src/Model/Sources/Model/Player/Player.swift b/Src/Model/Sources/Model/Player/Player.swift index cb99468..ebf6384 100644 --- a/Src/Model/Sources/Model/Player/Player.swift +++ b/Src/Model/Sources/Model/Player/Player.swift @@ -4,12 +4,18 @@ import Foundation /// /// - Author: Yohann BREUIL public class Player { + private static var idCounter : Int = 1 + public var id : Int public var nickname : String - public init(id: Int, nickname: String) { - self.id = id + public init(nickname: String) { + self.id = Player.idCounter + 1 self.nickname = nickname } + + public func chooseColumn() -> Int { + return 1 + } } diff --git a/Src/Model/Sources/Model/Rules/ClassicRules.swift b/Src/Model/Sources/Model/Rules/ClassicRules.swift index 421469b..3e9aff7 100644 --- a/Src/Model/Sources/Model/Rules/ClassicRules.swift +++ b/Src/Model/Sources/Model/Rules/ClassicRules.swift @@ -1,21 +1,23 @@ /// Cclassic rules of Connect 4 game -public class ClassicRules { +public class ClassicRules : Rules { public static var nbRows: Int = 6 public static var nbColumns: Int = 7 public static var nbAlignedPieces: Int = 4 public static var nbTrials: Int = 3 + + public var winCoord : [(Int, Int)] = [] - public init() {} + public func createBoard() -> Board? { + Board(withNbRows: Self.nbRows, withNbColumns: Self.nbColumns) ?? nil + } + + /// + public func isGameOver() -> (Bool, Int, GameResult) { + return (true, 2, .lose); + } -// public func createBoard() -> Board { -// -// } -// -// public func isGameOver() -> (Bool, Int, GameResult) { -// <#code#> -// } -// -// public func getNextPlayer() -> Int { -// <#code#> -// } + /// + public func getNextPlayer(board: Board) -> Int { + return 9; + } } diff --git a/Src/Model/Sources/Model/Rules/Rules.swift b/Src/Model/Sources/Model/Rules/Rules.swift index 77f3227..8ac6da3 100644 --- a/Src/Model/Sources/Model/Rules/Rules.swift +++ b/Src/Model/Sources/Model/Rules/Rules.swift @@ -17,9 +17,7 @@ public protocol Rules { static var nbTrials : Int {get} /// Create board - /// - /// - Returns:`Board` created from rows and columns - func createBoard() -> Board + func createBoard() -> Board? /// Defines if game is over /// @@ -28,6 +26,10 @@ public protocol Rules { /// Returns id of next palyer to play /// + /// The player who must play is the one who has the fewest tokens + /// + /// - Parameter board : board game + /// /// - Returns: id of next palyer to play - func getNextPlayer() -> Int + func getNextPlayer(board: Board) -> Int } diff --git a/Src/Model/Tests/ModelTests/Board/BoardTests.swift b/Src/Model/Tests/ModelTests/Board/BoardTests.swift index 5f872ae..883af08 100644 --- a/Src/Model/Tests/ModelTests/Board/BoardTests.swift +++ b/Src/Model/Tests/ModelTests/Board/BoardTests.swift @@ -71,9 +71,46 @@ final class BoardTest: XCTestCase { } XCTAssertNotNil(board) - //XCTAssertEqual(board?.insertPiece(id: idPlayer, column: column), boardResult) + XCTAssertEqual(board?.insertPiece(id: idPlayer, column: column), boardResult) } - - expect(grid: [[1,nil], [2,nil]], idPlayer: 2, column: 0, boardResult: .ok, notNil: true) + + let grid : [[Int?]] = Array.init(repeating: Array.init(repeating: nil, count: 3), count: 3) + + expect(grid: grid, idPlayer: 2, column: 0, boardResult: .ok, notNil: true) + expect(grid: grid, idPlayer: 2, column: 0, boardResult: .ok, notNil: true) + expect(grid: grid, idPlayer: 2, column: 0, boardResult: .ok, notNil: true) + //expect(grid: grid, idPlayer: 2, column: 0, boardResult: .failed(reason: <#T##FailedResult#>), notNil: true) + + } + + func testRemovePiece() { + func expect(grid : [[Int?]]) { + + } + } + + func testDescriptionBoard() { + func expect(grid : [[Int?]], result : String) { + let board = Board(withGrid: grid) + + XCTAssertEqual(board?.description, result) + } + + expect(grid: [[1, 1], [1, 1]], result: "|X|X|\n|X|X|\n") + expect(grid: [[1, 2], [1, 1]], result: "|X|X|\n|X|O|\n") + expect(grid: [[nil, 1], [nil, 1]], result: "|-|X|\n|-|X|\n") + expect(grid: [[nil, nil], [nil, nil]], result: "|-|-|\n|-|-|\n") + } + + func testFullBoard() { + func expect(grid : [[Int?]], isFull : Bool) { + let board = Board(withGrid: grid) + + XCTAssertEqual(board?.isFull(), isFull) + } + + expect(grid: [[1, 1, 2], [1, 1, 1], [2, 2, 1]], isFull: true) + expect(grid: [[nil, nil, nil], [nil, nil, nil], [nil, nil, nil]], isFull: false) + expect(grid: [[nil, 2, nil], [1, 1, nil], [2, 2, nil]], isFull: false) } } diff --git a/Src/Model/Tests/ModelTests/Human/HumanTests.swift b/Src/Model/Tests/ModelTests/Human/HumanTests.swift deleted file mode 100644 index f78ef31..0000000 --- a/Src/Model/Tests/ModelTests/Human/HumanTests.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// HumanTests.swift -// -// -// Created by BREUIL Yohann on 11/02/2023. -// - -import XCTest - -final class HumanTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - 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. - // Any test you write for XCTest can be annotated as throws and async. - // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. - // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. - } - - func testPerformanceExample() throws { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/Src/Model/Tests/ModelTests/Player/AITests.swift b/Src/Model/Tests/ModelTests/Player/AITests.swift new file mode 100644 index 0000000..805cb8c --- /dev/null +++ b/Src/Model/Tests/ModelTests/Player/AITests.swift @@ -0,0 +1,17 @@ +import XCTest +import Model + +final class AITests: XCTestCase { + func testInit() { + func expect(nickname: String) { + let ai = AI() + + XCTAssertEqual(ai.nickname, nickname) + } + + expect(nickname: "yobreuil") + expect(nickname: "cebouhou") + expect(nickname: "macheval") + } + +} diff --git a/Src/Model/Tests/ModelTests/Player/HumanTests.swift b/Src/Model/Tests/ModelTests/Player/HumanTests.swift new file mode 100644 index 0000000..e6557eb --- /dev/null +++ b/Src/Model/Tests/ModelTests/Player/HumanTests.swift @@ -0,0 +1,18 @@ +import XCTest +import Model + +final class HumanTests: XCTestCase { + func testInit() { + func expect(nickname : String) { + func scan() -> Int { return 1 } + + let human = Human(nickname: nickname, scanner: scan) + + XCTAssertEqual(human.nickname, nickname) + } + + expect(nickname: "yobreuil") + expect(nickname: "cebouhou") + expect(nickname: "macheval") + } +} diff --git a/Src/Model/Tests/ModelTests/Rules/ClassicRulesTests.swift b/Src/Model/Tests/ModelTests/Rules/ClassicRulesTests.swift new file mode 100644 index 0000000..e21a138 --- /dev/null +++ b/Src/Model/Tests/ModelTests/Rules/ClassicRulesTests.swift @@ -0,0 +1,12 @@ +import XCTest +import Model + +final class ClassicRulesTests: XCTestCase { + func testStaticValues() { + XCTAssertEqual(6, ClassicRules.nbRows) + XCTAssertEqual(7, ClassicRules.nbColumns) + XCTAssertEqual(4, ClassicRules.nbAlignedPieces) + XCTAssertEqual(3, ClassicRules.nbTrials) + } + +}