@ -1,8 +1,8 @@
|
|||||||
public struct Piece {
|
public struct Piece: Equatable, Sendable {
|
||||||
public let owner: Player
|
public let type: PieceType
|
||||||
|
|
||||||
// Required for public visibility
|
// Required for public visibility
|
||||||
public init(owner: Player) {
|
public init(type: PieceType) {
|
||||||
self.owner = owner
|
self.type = type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
public enum PieceType: Equatable, CaseIterable, Sendable {
|
||||||
|
case A, B
|
||||||
|
}
|
@ -1,3 +0,0 @@
|
|||||||
public enum Player {
|
|
||||||
case A, B
|
|
||||||
}
|
|
@ -0,0 +1,3 @@
|
|||||||
|
public class AIPlayer : Player {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
public typealias MoveCallback = ([Move.Action], Board) -> Move.Action
|
||||||
|
|
||||||
|
public class HumanPlayer : Player {
|
||||||
|
private let callback: MoveCallback
|
||||||
|
|
||||||
|
public init(name: String, piece_type: PieceType, callback: @escaping MoveCallback) {
|
||||||
|
self.callback = callback
|
||||||
|
|
||||||
|
super.init(name: name, piece_type: piece_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func chooseMove(allowed_moves moves: [Move.Action], board: Board) -> Move.Action {
|
||||||
|
callback(moves, board)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
public class Player : Equatable {
|
||||||
|
public let name: String
|
||||||
|
public let piece_type: PieceType
|
||||||
|
|
||||||
|
public init(name: String, piece_type: PieceType) {
|
||||||
|
self.name = name
|
||||||
|
self.piece_type = piece_type
|
||||||
|
}
|
||||||
|
|
||||||
|
public func chooseMove(allowed_moves moves: [Move.Action], board: Board) -> Move.Action {
|
||||||
|
fatalError("abstract method not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func == (lhs: Player, rhs: Player) -> Bool {
|
||||||
|
// TODO: name equality or reference equality?
|
||||||
|
lhs === rhs
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
public class RandomPlayer : Player {
|
||||||
|
public override func chooseMove(allowed_moves moves: [Move.Action], board: Board) -> Move.Action {
|
||||||
|
moves[Int.random(in: moves.indices)]
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,194 @@
|
|||||||
|
public struct FourInARowRules: Rules {
|
||||||
|
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
|
||||||
|
|
||||||
|
private let players: [Player]
|
||||||
|
|
||||||
|
// // internal for unit testing purposes
|
||||||
|
// internal(set) public var state: GameState
|
||||||
|
|
||||||
|
// private(set) public var history: [Move] = []
|
||||||
|
|
||||||
|
private let columns: Int, rows: Int, minAligned: Int
|
||||||
|
|
||||||
|
public init?(players: [Player]) {
|
||||||
|
self.init(columns: Self.COLUMNS_DEFAULT, rows: Self.ROWS_DEFAULT, players: players)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init?(columns: Int, rows: Int, minAligned: Int = 4, players: [Player]) {
|
||||||
|
guard
|
||||||
|
columns >= Self.COLUMNS_MIN,
|
||||||
|
rows >= Self.ROWS_MIN,
|
||||||
|
minAligned > 1,
|
||||||
|
players.count >= 2
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
self.players = players
|
||||||
|
self.columns = columns
|
||||||
|
self.rows = rows
|
||||||
|
self.minAligned = minAligned
|
||||||
|
}
|
||||||
|
|
||||||
|
public func createBoard() -> Board {
|
||||||
|
Board(columns: self.columns, rows: self.rows)!
|
||||||
|
}
|
||||||
|
|
||||||
|
public func isValid(board: Board, move: Move) -> Bool {
|
||||||
|
guard self.isValid(board: board) else { return false }
|
||||||
|
|
||||||
|
if case .InsertOnSide(.Top, let offset) = move.action, offset >= 0 && offset < self.columns {
|
||||||
|
return board.fallCoordinates(
|
||||||
|
initialCoords: Coords(offset, 0),
|
||||||
|
direction: .Bottom
|
||||||
|
) != .Occupied
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
public func isValid(board: Board) -> Bool {
|
||||||
|
for c in 0..<board.columns {
|
||||||
|
var had = false;
|
||||||
|
|
||||||
|
for r in 0..<board.rows {
|
||||||
|
switch board[c, r] != nil {
|
||||||
|
case false where had:
|
||||||
|
return false
|
||||||
|
|
||||||
|
case let has:
|
||||||
|
had = has
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public func validMoves(board: Board) -> [Move] {
|
||||||
|
return self.players.flatMap({
|
||||||
|
player in self.validMoves(board: board, for_player: player).map({
|
||||||
|
action in Move(player: player, action: action)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public func validMoves(board: Board, for_player player: Player) -> [Move.Action] {
|
||||||
|
var moves: [Move.Action] = [];
|
||||||
|
|
||||||
|
for c in 0..<board.columns {
|
||||||
|
switch board.fallCoordinates(initialCoords: Coords(c, 0), direction: .Bottom) {
|
||||||
|
case .Border, .Piece:
|
||||||
|
moves.append(.InsertOnSide(side: .Top, offset: c))
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return moves
|
||||||
|
}
|
||||||
|
|
||||||
|
public func gameState(board: Board, last_turn: Player?) -> GameState {
|
||||||
|
Self.rowOfLengthWin(board: board, last_turn: last_turn, minimum_aligned: self.minAligned, players: self.players)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static func rowOfLengthWin(board: Board, last_turn: Player?, minimum_aligned: Int, players: [Player]) -> GameState {
|
||||||
|
var occupied = 0
|
||||||
|
|
||||||
|
for column in 0..<board.columns {
|
||||||
|
for row in 0..<board.rows {
|
||||||
|
let current = Coords(column, row)
|
||||||
|
guard let piece = board[current] else { continue }
|
||||||
|
|
||||||
|
occupied += 1
|
||||||
|
|
||||||
|
// For each "axis" (described as one direction)
|
||||||
|
for dir: (Int, Int) in [(1, 0), (0, 1), (1, 1), (-1, 1)] {
|
||||||
|
let cells = Self.row(center: current, board: board, dir: dir)
|
||||||
|
|
||||||
|
if cells.count >= minimum_aligned {
|
||||||
|
let player = players.first(where: { $0.piece_type == piece.type })!
|
||||||
|
return GameState.Win(winner: player, cells: cells)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if occupied == board.columns * board.rows {
|
||||||
|
return .Draw
|
||||||
|
} else if let last_turn = last_turn {
|
||||||
|
return .Playing(turn: players[(players.firstIndex(of: last_turn)! + 1) % players.count])
|
||||||
|
} else {
|
||||||
|
return .Playing(turn: players.first!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* public mutating func onMoveDone(move: Move, board: Board) -> Void {
|
||||||
|
// self.history.append(move)
|
||||||
|
|
||||||
|
switch move.action {
|
||||||
|
case .InsertOnSide(side: .Top, let offset):
|
||||||
|
let initCoords = board.getInsertionCoordinates(from: .Top, offset: offset)
|
||||||
|
let pieceCoords = switch board.fallCoordinates(
|
||||||
|
initialCoords: initCoords,
|
||||||
|
direction: .Bottom
|
||||||
|
) {
|
||||||
|
case .Occupied:
|
||||||
|
initCoords
|
||||||
|
case .Piece(_, let touched):
|
||||||
|
touched
|
||||||
|
|
||||||
|
default:
|
||||||
|
fatalError("Illegal move \(move.action)")
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
|
let next: PieceType = switch move.player {
|
||||||
|
case .A: .B
|
||||||
|
case .B: .A
|
||||||
|
}
|
||||||
|
self.state = .Playing(turn: next)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fatalError("Illegal move \(move.action)")
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
|
||||||
|
@available(*, deprecated, message: "Old Rules design remnents")
|
||||||
|
internal static func countMaxRow(center: Coords, board: Board) -> Int {
|
||||||
|
var maxLength = 0
|
||||||
|
// For each "axis" (described as one direction)
|
||||||
|
for dir: (dc: Int, dr: Int) in [(1, 0), (0, 1), (1, 1), (-1, 1)] {
|
||||||
|
maxLength = max(maxLength, row(center: center, board: board, dir: dir).count)
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxLength
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func row(center: Coords, board: Board, dir: (dc: Int, dr: Int)) -> [Coords] {
|
||||||
|
guard let of = board[center]?.type else { return [] }
|
||||||
|
|
||||||
|
var cells = [center]
|
||||||
|
|
||||||
|
// Run in the two opposite directions of the axis
|
||||||
|
for (dc, dr) in [(dir.dc, dir.dr), (-dir.dc, -dir.dr)] {
|
||||||
|
var pos = center
|
||||||
|
|
||||||
|
while true {
|
||||||
|
pos = Coords(pos.col + dc, pos.row + dr)
|
||||||
|
if !board.isInBounds(pos) || board[pos]?.type != of { break }
|
||||||
|
cells.append(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cells
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
public struct Move: Equatable {
|
||||||
|
public let player: Player
|
||||||
|
public let action: 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)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
public protocol Rules {
|
||||||
|
// var state: GameState { get }
|
||||||
|
|
||||||
|
// var history: [Move] { get }
|
||||||
|
|
||||||
|
func createBoard() -> Board
|
||||||
|
|
||||||
|
func isValid(board: Board) -> Bool
|
||||||
|
|
||||||
|
func isValid(board: Board, move: Move) -> Bool
|
||||||
|
|
||||||
|
func validMoves(board: Board) -> [Move]
|
||||||
|
|
||||||
|
func validMoves(board: Board, for_player player: Player) -> [Move.Action]
|
||||||
|
|
||||||
|
func gameState(board: Board, last_turn: Player?) -> GameState
|
||||||
|
|
||||||
|
// mutating func onMoveDone(move: Move, board: Board) -> Void
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum GameState: Equatable {
|
||||||
|
case Playing(turn: Player)
|
||||||
|
|
||||||
|
case Win(winner: Player, cells: [Coords])
|
||||||
|
|
||||||
|
case Draw
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
public struct TicTacToeRules: Rules {
|
||||||
|
private let players: [Player]
|
||||||
|
private let columns: Int, rows: Int, minAligned: Int
|
||||||
|
|
||||||
|
public init(columns: Int, rows: Int, minAligned: Int = 3, players: [Player]) {
|
||||||
|
self.columns = columns
|
||||||
|
self.rows = rows
|
||||||
|
self.minAligned = minAligned
|
||||||
|
self.players = players
|
||||||
|
}
|
||||||
|
|
||||||
|
public func createBoard() -> Board {
|
||||||
|
Board(columns: self.columns, rows: self.rows)!
|
||||||
|
}
|
||||||
|
|
||||||
|
public func isValid(board: Board) -> Bool {
|
||||||
|
abs(board.countPieces(filter: { p in p.type == .A }) -
|
||||||
|
board.countPieces(filter: { p in p.type == .B })) <= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
public func isValid(board: Board, move: Move) -> Bool {
|
||||||
|
guard self.isValid(board: board) else { return false }
|
||||||
|
|
||||||
|
if case .InsertAt(let coord) = move.action, board.isInBounds(coord) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
public func validMoves(board: Board) -> [Move] {
|
||||||
|
return self.players.flatMap { player in
|
||||||
|
self.validMoves(board: board, for_player: player).map { Move(player: player, action: $0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func nextPlayer(board: Board) -> PieceType {
|
||||||
|
if board.countPieces(filter: { p in p.type == .A }) > board.countPieces(filter: { p in p.type == .B }) {
|
||||||
|
.B
|
||||||
|
} else {
|
||||||
|
.A
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func validMoves(board: Board, for_player player: Player) -> [Move.Action] {
|
||||||
|
var moves: [Move.Action] = []
|
||||||
|
|
||||||
|
for col in 0..<board.columns {
|
||||||
|
for row in 0..<board.rows {
|
||||||
|
if board[col, row] == nil {
|
||||||
|
moves.append(.InsertAt(where: Coords(col, row)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return moves
|
||||||
|
}
|
||||||
|
|
||||||
|
public func gameState(board: Board, last_turn: Player?) -> GameState {
|
||||||
|
FourInARowRules.rowOfLengthWin(board: board, last_turn: last_turn, minimum_aligned: self.minAligned, players: self.players)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* public mutating func onMoveDone(move: Move, board: Board) {
|
||||||
|
self.history.append(move)
|
||||||
|
|
||||||
|
guard case .InsertAt(let coords) = move.action else {
|
||||||
|
fatalError("Illegal move \(move.action)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if FourInARowRules.countMaxRow(center: coords, board: board) >= self.minAligned {
|
||||||
|
self.state = .Finished(winner: move.player)
|
||||||
|
} else if board.countPieces() == board.columns * board.rows {
|
||||||
|
self.state = .Finished(winner: nil)
|
||||||
|
} else {
|
||||||
|
let next: PieceType = switch move.player {
|
||||||
|
case .A: .B
|
||||||
|
case .B: .A
|
||||||
|
}
|
||||||
|
self.state = .Playing(turn: next)
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
import XCTest
|
||||||
|
@testable import Model
|
||||||
|
|
||||||
|
final class HumanPlayerTests: XCTestCase {
|
||||||
|
func testInit() {
|
||||||
|
let p = HumanPlayer(name: "the name", piece_type: .B, callback: { _, _ in fatalError("NOOP") })
|
||||||
|
|
||||||
|
XCTAssertEqual(p.name, "the name")
|
||||||
|
XCTAssertEqual(p.piece_type, .B)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testChoose() {
|
||||||
|
let board = Board(columns: 3, rows: 3)!
|
||||||
|
let moves: [Move.Action] = [.InsertAt(where: Coords(1, 2)), .InsertOnSide(side: .Left, offset: 1)]
|
||||||
|
|
||||||
|
let player = HumanPlayer(name: "name", piece_type: .A, callback: {
|
||||||
|
moves2, board2 in
|
||||||
|
|
||||||
|
XCTAssertEqual(moves2, moves)
|
||||||
|
XCTAssertEqual(board2, board)
|
||||||
|
|
||||||
|
return .InsertOnSide(side: .Bottom, offset: 99)
|
||||||
|
})
|
||||||
|
|
||||||
|
XCTAssertEqual(player.chooseMove(allowed_moves: moves, board: board), .InsertOnSide(side: .Bottom, offset: 99))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
import XCTest
|
||||||
|
@testable import Model
|
||||||
|
|
||||||
|
final class RandomPlayerTests: XCTestCase {
|
||||||
|
func testInit() {
|
||||||
|
for t in [PieceType.A, .B] {
|
||||||
|
let p = RandomPlayer(name: "the name", piece_type: t)
|
||||||
|
|
||||||
|
XCTAssertEqual(p.name, "the name")
|
||||||
|
XCTAssertEqual(p.piece_type, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testChoose() {
|
||||||
|
let board = Board(columns: 3, rows: 3)!
|
||||||
|
let moves: [Move.Action] = [.InsertAt(where: Coords(1, 2)), .InsertOnSide(side: .Left, offset: 1)]
|
||||||
|
|
||||||
|
let player = RandomPlayer(name: "name", piece_type: .A)
|
||||||
|
|
||||||
|
// +5 test quality credits
|
||||||
|
for _ in 1...10 {
|
||||||
|
let choosen = player.chooseMove(allowed_moves: moves, board: board)
|
||||||
|
|
||||||
|
XCTAssertNotNil(moves.firstIndex(of: choosen))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,137 @@
|
|||||||
|
import XCTest
|
||||||
|
@testable import Model
|
||||||
|
|
||||||
|
final class FourInARowRulesTests: XCTestCase {
|
||||||
|
private var rules: FourInARowRules!
|
||||||
|
private var board: Board!
|
||||||
|
private var players: [Player]!
|
||||||
|
|
||||||
|
override func setUpWithError() throws {
|
||||||
|
try super.setUpWithError()
|
||||||
|
|
||||||
|
self.players = [
|
||||||
|
Player(name: "A", piece_type: .A),
|
||||||
|
Player(name: "B", piece_type: .B)
|
||||||
|
]
|
||||||
|
|
||||||
|
guard let rules = FourInARowRules(columns: 3, rows: 3, minAligned: 3, players: players) else {
|
||||||
|
XCTFail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.rules = rules
|
||||||
|
self.board = self.rules.createBoard()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBoardIsValid() {
|
||||||
|
XCTAssertTrue(self.rules.isValid(board: self.board))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInitialState() {
|
||||||
|
XCTAssertEqual(
|
||||||
|
self.rules.gameState(board: board, last_turn: nil),
|
||||||
|
GameState.Playing(turn: self.players.first!)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMovesAreValid() {
|
||||||
|
for move in self.rules.validMoves(board: self.board) {
|
||||||
|
XCTAssertTrue(self.rules.isValid(board: board, move: move), "\(move)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMovesOfPlayerAreValid() throws {
|
||||||
|
for player in self.players {
|
||||||
|
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) {
|
||||||
|
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(type: .A)
|
||||||
|
|
||||||
|
XCTAssertEqual(
|
||||||
|
rules.gameState(board: board, last_turn: players.first!),
|
||||||
|
GameState.Playing(turn: players.last!)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testOnMoveDoneDraw() {
|
||||||
|
board[0, 0] = Piece(type: .A)
|
||||||
|
board[1, 0] = Piece(type: .A)
|
||||||
|
board[2, 0] = Piece(type: .B)
|
||||||
|
board[0, 1] = Piece(type: .B)
|
||||||
|
board[1, 1] = Piece(type: .B)
|
||||||
|
board[2, 1] = Piece(type: .A)
|
||||||
|
board[0, 2] = Piece(type: .A)
|
||||||
|
board[1, 2] = Piece(type: .A)
|
||||||
|
|
||||||
|
board[2, 2] = Piece(type: .B)
|
||||||
|
|
||||||
|
XCTAssertTrue(rules.isValid(board: board))
|
||||||
|
XCTAssertEqual(rules.gameState(board: board, last_turn: players.last!), GameState.Draw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testOnMoveDoneWin() {
|
||||||
|
board[0, 0] = Piece(type: .A)
|
||||||
|
board[1, 0] = Piece(type: .A)
|
||||||
|
board[2, 0] = Piece(type: .A) //
|
||||||
|
board[0, 1] = Piece(type: .B)
|
||||||
|
board[1, 1] = Piece(type: .B)
|
||||||
|
board[2, 1] = Piece(type: .A)
|
||||||
|
board[0, 2] = Piece(type: .A)
|
||||||
|
board[1, 2] = Piece(type: .A)
|
||||||
|
board[2, 2] = Piece(type: .B)
|
||||||
|
|
||||||
|
XCTAssertTrue(rules.isValid(board: board))
|
||||||
|
|
||||||
|
XCTAssertEqual(
|
||||||
|
rules.gameState(board: board, last_turn: players.first!),
|
||||||
|
GameState.Win(winner: players.first!, cells: [Coords(0, 0), Coords(1, 0), Coords(2, 0)])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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(type: .A)
|
||||||
|
board[1, 0] = Piece(type: .A)
|
||||||
|
board[2, 0] = Piece(type: .A)
|
||||||
|
board[0, 1] = Piece(type: .B)
|
||||||
|
board[1, 1] = Piece(type: .A)
|
||||||
|
board[2, 1] = Piece(type: .B)
|
||||||
|
board[0, 2] = Piece(type: .B)
|
||||||
|
board[1, 2] = Piece(type: .B)
|
||||||
|
board[2, 2] = Piece(type: .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…
Reference in new issue