Finished Rules..?

Rules
Mathieu GROUSSEAU 4 weeks ago
parent e49690cde6
commit e286e7e78a

@ -3,43 +3,68 @@ import Foundation
import Model
import CustomTypes
guard var board = Board(columns: 5, rows: 5) else {
print("Failed to create board.")
exit(EXIT_FAILURE)
var rules = FourInARowRules()
var board = rules.createBoard()
guard rules.isValid(board: board) else {
fatalError("Board is invalid")
}
print(board.debugDescription)
print(board)
print(rules.state)
for i in 0...2 {
for _ in 0...i {
let dropAt = board.getInsertionCoordinates(from: .Top, offset: i)
let landAt = switch board.fallCoordinates(initialCoords: dropAt, direction: .Bottom) {
case .Border(let at), .Piece(let at, _):
at
case .Occupied:
exit(EXIT_FAILURE)
}
board[landAt] = Piece(owner: .A)
}
print("All moves:")
for move in rules.validMoves(board: board) {
print(move)
}
print(board)
print("Moves for \(Player.A):")
for move in rules.validMoves(board: board, for_player: .A) {
print(move)
}
board[0, 2] = Piece(owner: .B)
board[2, 4] = nil
print(board)
for c in 0..<board.rows {
switch board.fallCoordinates(initialCoords: board.getInsertionCoordinates(from: .Right, offset: c), direction: .Left) {
case .Border(let at):
board[at] = Piece(owner: .B)
case .Piece(let at, let touched):
(board[at], board[touched]) = (board[touched], Piece(owner: .B))
while case .Playing(let turn) = rules.state {
let moves = rules.validMoves(board: board, for_player: turn)
let action = moves[Int.random(in: 0..<moves.count)]
guard case .InsertOnSide(let side, let offset) = action else {
fatalError("Unexpected move!")
}
let move = Move(player: turn, action: action)
guard rules.isValid(board: board, move: move) else {
fatalError("Move is in fact invalid???")
}
switch board.fallCoordinates(
initialCoords: board.getInsertionCoordinates(from: side, offset: offset),
direction: .Bottom
) {
case .Border(let at), .Piece(let at, _):
board[at] = Piece(owner: turn)
case .Occupied:
break
fatalError("Occupied???")
}
rules.onMoveDone(move: move, board: board)
print(board)
}
print(board.debugDescription)
print(board)
if case .Finished(let winner) = rules.state {
if let winner = winner {
print("\(winner) is the winner")
} else {
print("Draw.")
}
} else {
fatalError()
}
print("Moves were:")
for move in rules.history {
print("\(move.player) did \(move.action)")
}
exit(EXIT_SUCCESS)

@ -1,4 +1,4 @@
public enum Direction {
public enum Direction: CaseIterable {
case Top, Left, Bottom, Right
var opposite: Self {

@ -1,3 +1,3 @@
public enum Player {
public enum Player: CaseIterable {
case A, B
}

@ -1,12 +1,22 @@
public struct FourInARowRules: Rules {
private(set) public var state: GameState = .Playing(turn: .A)
public static let COLUMNS_MIN: Int = 3
public static let COLUMNS_DEFAULT: Int = 7
public static let ROWS_MIN: Int = 3
public static let ROWS_DEFAULT: Int = 6
// internal for unit testing purposes
internal(set) public var state: GameState = .Playing(turn: .A)
private(set) public var history: [Move] = []
private let columns: Int, rows: Int, minAligned: Int
public init() {
self.init(columns: Self.COLUMNS_DEFAULT, rows: Self.ROWS_DEFAULT)!
}
init?(columns: Int, rows: Int, minAligned: Int = 4) {
guard columns >= 3, rows >= 3 else {
public init?(columns: Int, rows: Int, minAligned: Int = 4) {
guard columns >= Self.COLUMNS_MIN, rows >= Self.ROWS_MIN, minAligned > 1 else {
return nil
}
@ -60,9 +70,9 @@ public struct FourInARowRules: Rules {
}
public func validMoves(board: Board) -> [Move] {
return [ Player.A, Player.B ].flatMap({
return Player.allCases.flatMap({
player in self.validMoves(board: board, for_player: player).map({
action in Move(player: .A, action: action)
action in Move(player: player, action: action)
})
})
}
@ -102,7 +112,7 @@ public struct FourInARowRules: Rules {
fatalError("Illegal move \(move.action)")
}
if countMaxRow(center: pieceCoords, board: board) >= self.minAligned {
if Self.countMaxRow(center: pieceCoords, board: board) >= self.minAligned {
self.state = .Finished(winner: move.player)
} else if board.countPieces() == board.columns * board.rows {
self.state = .Finished(winner: nil)
@ -118,12 +128,12 @@ public struct FourInARowRules: Rules {
}
}
private func countMaxRow(center: Coords, board: Board) -> Int {
internal static func countMaxRow(center: Coords, board: Board) -> Int {
guard let of = board[center]?.owner else { return 0 }
var maxLength = 0
// For each "axis" (described as one direction)
for dir: (dc: Int, dr: Int) in [(1, 0), (0, 1), (1, 1)] {
for dir: (dc: Int, dr: Int) in [(1, 0), (0, 1), (1, 1), (-1, 1)] {
var length = 1
// Run in the two opposite directions of the axis to sum the length
@ -132,7 +142,7 @@ public struct FourInARowRules: Rules {
while true {
pos = Coords(pos.col + dc, pos.row + dr)
if board.isInBounds(pos) && board[pos]?.owner != of { break }
if !board.isInBounds(pos) || board[pos]?.owner != of { break }
length += 1
}
}

@ -1,8 +1,13 @@
public struct Move {
let player: Player
let action: Action
public struct Move: Equatable {
public let player: Player
public let action: Action
public enum Action {
public init(player: Player, action: Action) {
self.player = player
self.action = action
}
public enum Action: Equatable {
case InsertOnSide(side: Direction, offset: Int)
case InsertAt(where: Coords)
}

@ -16,7 +16,7 @@ public protocol Rules {
mutating func onMoveDone(move: Move, board: Board) -> Void
}
public enum GameState {
public enum GameState: Equatable {
case Playing(turn: Player)
case Finished(winner: Player?)

@ -5,7 +5,7 @@ final class EmptyBoardTests: XCTestCase {
private var board: Board!
override func setUpWithError() throws {
super.setUp()
try super.setUpWithError()
guard let board = Board(columns: 5, rows: 6) else {
XCTFail()

@ -0,0 +1,140 @@
import XCTest
@testable import Model
final class FourInARowRulesTests: XCTestCase {
private var rules: FourInARowRules!
private var board: Board!
override func setUpWithError() throws {
try super.setUpWithError()
guard let rules = FourInARowRules(columns: 3, rows: 3, minAligned: 3) else {
XCTFail()
return
}
self.rules = rules
self.board = self.rules.createBoard()
}
func testEmptyHistory() {
XCTAssertTrue(self.rules.history.isEmpty)
}
func testBoardIsValid() {
XCTAssertTrue(self.rules.isValid(board: self.board))
}
func testState() {
XCTAssertEqual(self.rules.state, GameState.Playing(turn: .A))
}
func testMovesAreValid() {
for move in self.rules.validMoves(board: self.board) {
rules.state = .Playing(turn: move.player)
XCTAssertTrue(self.rules.isValid(board: board, move: move), "\(move)")
}
}
func testMovesOfPlayerAreValid() throws {
for player in Player.allCases {
try self.setUpWithError()
try self._testMovesOfPlayerAreValid(player: player)
try self.tearDownWithError()
}
}
private func _testMovesOfPlayerAreValid(player: Player) throws {
for action in self.rules.validMoves(board: self.board, for_player: player) {
rules.state = .Playing(turn: player)
XCTAssertTrue(self.rules.isValid(board: board, move: Move(player: player, action: action)), "\(action)")
}
}
func testOnMoveDone() {
let coord = Coords(1, board.rows - 1)
board[coord] = Piece(owner: .A)
let move = Move(player: .A, action: .InsertOnSide(side: .Top, offset: 1))
self.rules.onMoveDone(move: move, board: self.board)
XCTAssertEqual(self.rules.state, GameState.Playing(turn: .B))
XCTAssertEqual(self.rules.history.last, move)
}
func testOnMoveDoneDraw() {
board[0, 0] = Piece(owner: .A)
board[1, 0] = Piece(owner: .A)
board[2, 0] = Piece(owner: .B)
board[0, 1] = Piece(owner: .B)
board[1, 1] = Piece(owner: .B)
board[2, 1] = Piece(owner: .A)
board[0, 2] = Piece(owner: .A)
board[1, 2] = Piece(owner: .A)
board[2, 2] = Piece(owner: .B)
XCTAssertTrue(rules.isValid(board: board))
let move = Move(player: .B, action: .InsertOnSide(side: .Top, offset: 2))
self.rules.onMoveDone(move: move, board: board)
XCTAssertEqual(rules.state, GameState.Finished(winner: nil))
}
func testOnMoveDoneWin() {
board[0, 0] = Piece(owner: .A)
board[1, 0] = Piece(owner: .A)
board[2, 0] = Piece(owner: .A) //
board[0, 1] = Piece(owner: .B)
board[1, 1] = Piece(owner: .B)
board[2, 1] = Piece(owner: .A)
board[0, 2] = Piece(owner: .A)
board[1, 2] = Piece(owner: .A)
board[2, 2] = Piece(owner: .B)
XCTAssertTrue(rules.isValid(board: board))
let move = Move(player: .A, action: .InsertOnSide(side: .Top, offset: 2))
self.rules.onMoveDone(move: move, board: board)
XCTAssertEqual(rules.state, GameState.Finished(winner: .A))
}
func testCountMaxRow() throws {
for (coords, expected) in [
(Coords(1, 0), 3),
(Coords(0, 1), 2),
(Coords(0, 2), 2),
(Coords(1, 1), 3),
(Coords(2, 2), 3),
(Coords(2, 1), 2),
] {
try self.setUpWithError()
// AAA
// BAB
// BBA
board[0, 0] = Piece(owner: .A)
board[1, 0] = Piece(owner: .A)
board[2, 0] = Piece(owner: .A)
board[0, 1] = Piece(owner: .B)
board[1, 1] = Piece(owner: .A)
board[2, 1] = Piece(owner: .B)
board[0, 2] = Piece(owner: .B)
board[1, 2] = Piece(owner: .B)
board[2, 2] = Piece(owner: .A)
self._testCountMaxRow(coords: coords, expected: expected)
try self.tearDownWithError()
}
}
private func _testCountMaxRow(coords: Coords, expected: Int) {
XCTAssertEqual(FourInARowRules.countMaxRow(center: coords, board: board), expected, "\(coords)")
}
}
Loading…
Cancel
Save