feat - add game and rules
continuous-integration/drone/push Build is failing Details

main
DJYohann 2 years ago
parent 700d5e804e
commit cc27518657

@ -1,10 +1,3 @@
//
// main.swift
// CLT
//
// Created by BREUIL Yohann on 17/01/2023.
//
import Foundation import Foundation
import Model import Model
@ -21,17 +14,35 @@ func insertPiece(id : Int, column : Int, _ board : inout Board) {
print("Board \(column) is full") print("Board \(column) is full")
case .unknown: case .unknown:
print("Unknown") print("Unknown")
case .negativeOrOutOfBound:
print("Row or column must be posittive")
case .alreadyTake:
print("Column already take")
} }
default: default:
print("Rien") 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) 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: 1, column: 0, &board)
insertPiece(id: 1, column: 0, &board) insertPiece(id: 2, column: 3, &board)
insertPiece(id: 1, column: 0, &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)
} }

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1420"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Model"
BuildableName = "Model"
BlueprintName = "Model"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES"
onlyGenerateCoverageForSpecifiedTargets = "YES">
<CodeCoverageTargets>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Model"
BuildableName = "Model"
BlueprintName = "Model"
ReferencedContainer = "container:">
</BuildableReference>
</CodeCoverageTargets>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ModelTests"
BuildableName = "ModelTests"
BlueprintName = "ModelTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Model"
BuildableName = "Model"
BlueprintName = "Model"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

@ -17,9 +17,10 @@ public struct Board : CustomStringConvertible {
for row in grid.reversed(){ for row in grid.reversed(){
for cell in row { for cell in row {
string.append("|")
string.append("\(String(describing: Board.descriptionMapper[cell] ?? "-"))") string.append("\(String(describing: Board.descriptionMapper[cell] ?? "-"))")
} }
string.append("\n") string.append("|\n")
} }
return string return string
@ -96,10 +97,10 @@ public struct Board : CustomStringConvertible {
/// - Returns: The result of the insertion in a `BoardResult` /// - Returns: The result of the insertion in a `BoardResult`
private mutating func insertPiece(id : Int, row : Int, column : Int) -> BoardResult { private mutating func insertPiece(id : Int, row : Int, column : Int) -> BoardResult {
guard row >= 0 && row < nbRows && column >= 0 && column < nbColumns else { guard row >= 0 && row < nbRows && column >= 0 && column < nbColumns else {
return .failed(reason: .unknown) return .failed(reason: .negativeOrOutOfBound)
} }
guard grid[row][column] == nil else{ guard grid[row][column] == nil else{
return .failed(reason: .unknown) return .failed(reason: .alreadyTake)
} }
grid[row][column] = id grid[row][column] = id
return .ok return .ok
@ -111,30 +112,35 @@ public struct Board : CustomStringConvertible {
/// - Parameter column : The column to add the piece /// - Parameter column : The column to add the piece
/// - Returns: The result of the insertion in a `BoardResult` /// - Returns: The result of the insertion in a `BoardResult`
public mutating func insertPiece(id : Int, column:Int) -> BoardResult { public mutating func insertPiece(id : Int, column:Int) -> BoardResult {
guard column >= 0 && column < nbColumns else {
return .failed(reason: .negativeOrOutOfBound)
}
for row in 0..<nbRows { for row in 0..<nbRows {
if (row == nil) { if (grid[row][column] == nil) {
return insertPiece(id: id, row: row, column: column) return insertPiece(id: id, row: row, column: column)
} }
} }
return .ok return .failed(reason: .alreadyTake)
} }
/// Remove a piece from the grid /// Remove a piece from the grid
/// ///
/// - Parameter id : The id of the player
/// - Parameter column : The column to add the piece
/// - Parameter row : The row to remove the piece /// - Parameter row : The row to remove the piece
/// - Parameter column : The column to remove the piece /// - Parameter column : The column to remove the piece
/// ///
/// - Returns: True if the piece can be removed else false /// - Returns: True if the piece can be removed else false
public mutating func removePiece(id:Int, row:Int, column:Int) -> Bool { public mutating func removePiece(column : Int) -> Bool {
guard row >= 0 && row < nbRows && column >= 0 && column < nbColumns else{ guard column >= 0 && column < nbRows else {
return false
}
guard grid[row][column] == id else {
return false return false
} }
for row in stride(from: nbRows - 1, through: 0, by: -1) {
if grid[row][column] != nil{
grid[row][column] = nil grid[row][column] = nil
return true return true
} }
} }
return true
}
}

@ -3,7 +3,7 @@ import Foundation
/// The result of an action on board /// The result of an action on board
/// ///
/// - Author: Yohann BREUIL /// - Author: Yohann BREUIL
public enum BoardResult { public enum BoardResult : Equatable {
case unknow case unknow
case ok case ok
case failed(reason : FailedResult) case failed(reason : FailedResult)
@ -14,6 +14,8 @@ public enum BoardResult {
/// - Author: Yohann BREUIL /// - Author: Yohann BREUIL
public enum FailedResult { public enum FailedResult {
case unknown case unknown
case negativeOrOutOfBound
case alreadyTake
case columnFull case columnFull
case boardFull case boardFull
} }

@ -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)
}
}

@ -4,5 +4,15 @@ import Foundation
/// ///
/// - Author: Yohann BREUIL /// - Author: Yohann BREUIL
public class AI : Player { 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
}
} }

@ -4,5 +4,28 @@ import Foundation
/// ///
/// - Author: Yohann BREUIL /// - Author: Yohann BREUIL
public class Human : Player { 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
}
} }

@ -4,12 +4,18 @@ import Foundation
/// ///
/// - Author: Yohann BREUIL /// - Author: Yohann BREUIL
public class Player { public class Player {
private static var idCounter : Int = 1
public var id : Int public var id : Int
public var nickname : String public var nickname : String
public init(id: Int, nickname: String) { public init(nickname: String) {
self.id = id self.id = Player.idCounter + 1
self.nickname = nickname self.nickname = nickname
} }
public func chooseColumn() -> Int {
return 1
}
} }

@ -1,21 +1,23 @@
/// Cclassic rules of Connect 4 game /// Cclassic rules of Connect 4 game
public class ClassicRules { public class ClassicRules : Rules {
public static var nbRows: Int = 6 public static var nbRows: Int = 6
public static var nbColumns: Int = 7 public static var nbColumns: Int = 7
public static var nbAlignedPieces: Int = 4 public static var nbAlignedPieces: Int = 4
public static var nbTrials: Int = 3 public static var nbTrials: Int = 3
public init() {} public var winCoord : [(Int, Int)] = []
// public func createBoard() -> Board { public func createBoard() -> Board? {
// Board(withNbRows: Self.nbRows, withNbColumns: Self.nbColumns) ?? nil
// } }
//
// public func isGameOver() -> (Bool, Int, GameResult) { ///
// <#code#> public func isGameOver() -> (Bool, Int, GameResult) {
// } return (true, 2, .lose);
// }
// public func getNextPlayer() -> Int {
// <#code#> ///
// } public func getNextPlayer(board: Board) -> Int {
return 9;
}
} }

@ -17,9 +17,7 @@ public protocol Rules {
static var nbTrials : Int {get} static var nbTrials : Int {get}
/// Create board /// Create board
/// func createBoard() -> Board?
/// - Returns:`Board` created from rows and columns
func createBoard() -> Board
/// Defines if game is over /// Defines if game is over
/// ///
@ -28,6 +26,10 @@ public protocol Rules {
/// Returns id of next palyer to play /// 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 /// - Returns: id of next palyer to play
func getNextPlayer() -> Int func getNextPlayer(board: Board) -> Int
} }

@ -71,9 +71,46 @@ final class BoardTest: XCTestCase {
} }
XCTAssertNotNil(board) 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)
} }
} }

@ -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.
}
}
}

@ -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")
}
}

@ -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")
}
}

@ -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)
}
}
Loading…
Cancel
Save