diff --git a/Model/Sources/Model/Board.swift b/Model/Sources/Model/Board.swift index 3ade19a..6217766 100644 --- a/Model/Sources/Model/Board.swift +++ b/Model/Sources/Model/Board.swift @@ -42,15 +42,21 @@ public struct Board { } } + public func forEach(_ consumer: (Piece?) -> Void) { + for column in grid { + for piece in column { + consumer(piece) + } + } + } + public func countPieces(filter: (Piece) -> Bool = { piece in true }) -> Int { var count = 0 - for column in grid { - for piece in column { - if let piece = piece { - if filter(piece) { - count += 1 - } + self.forEach { + if let piece = $0 { + if filter(piece) { + count += 1 } } } diff --git a/Model/Sources/Model/Rules/FourInARowRules.swift b/Model/Sources/Model/Rules/FourInARowRules.swift index e9851ff..15d7305 100644 --- a/Model/Sources/Model/Rules/FourInARowRules.swift +++ b/Model/Sources/Model/Rules/FourInARowRules.swift @@ -32,14 +32,7 @@ public struct FourInARowRules: Rules { public func isValid(board: Board, move: Move) -> Bool { guard self.isValid(board: board) else { return false } - switch self.state { - case .Playing(let turn) where turn != move.player: - fallthrough - case .Finished(_): - return false - default: - break - } + guard case .Playing(let turn) = state, turn == move.player else { return false } if case .InsertOnSide(.Top, let offset) = move.action, offset >= 0 && offset < self.columns { return board.fallCoordinates( diff --git a/Model/Sources/Model/Rules/TicTacToeRules.swift b/Model/Sources/Model/Rules/TicTacToeRules.swift new file mode 100644 index 0000000..3d5a61e --- /dev/null +++ b/Model/Sources/Model/Rules/TicTacToeRules.swift @@ -0,0 +1,82 @@ +public struct TicTacToeRules: Rules { + public var state: GameState = .Playing(turn: .A) + + public var history: [Move] = [] + + private let columns: Int, rows: Int, minAligned: Int + + public init(columns: Int, rows: Int, minAligned: Int = 3) { + 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) -> Bool { + abs(board.countPieces(filter: { p in p.owner == .A }) - + board.countPieces(filter: { p in p.owner == .B })) <= 1 + } + + public func isValid(board: Board, move: Move) -> Bool { + guard self.isValid(board: board) else { return false } + + guard case .Playing(let turn) = state, turn == move.player else { return false } + + if case .InsertAt(let coord) = move.action, board.isInBounds(coord) { + return true + } + + return false + } + + public func validMoves(board: Board) -> [Move] { + let player = Self.nextPlayer(board: board) + + return self.validMoves(board: board, for_player: player).map { Move(player: player, action: $0) } + } + + private static func nextPlayer(board: Board) -> Player { + if board.countPieces(filter: { p in p.owner == .A }) > board.countPieces(filter: { p in p.owner == .B }) { + .B + } else { + .A + } + } + + public func validMoves(board: Board, for_player player: Player) -> [Move.Action] { + var moves: [Move.Action] = [] + + for col in 0..= self.minAligned { + self.state = .Finished(winner: move.player) + } else if board.countPieces() == board.columns * board.rows { + self.state = .Finished(winner: nil) + } else { + let next: Player = switch move.player { + case .A: .B + case .B: .A + } + self.state = .Playing(turn: next) + } + } +}