parent
397d0df2b1
commit
e49690cde6
@ -0,0 +1,145 @@
|
||||
public struct FourInARowRules: Rules {
|
||||
private(set) public var state: GameState = .Playing(turn: .A)
|
||||
|
||||
private(set) public var history: [Move] = []
|
||||
|
||||
private let columns: Int, rows: Int, minAligned: Int
|
||||
|
||||
init?(columns: Int, rows: Int, minAligned: Int = 4) {
|
||||
guard columns >= 3, rows >= 3 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
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 }
|
||||
|
||||
switch self.state {
|
||||
case .Playing(let turn) where turn != move.player:
|
||||
fallthrough
|
||||
case .Finished(_):
|
||||
return false
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
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 [ Player.A, Player.B ].flatMap({
|
||||
player in self.validMoves(board: board, for_player: player).map({
|
||||
action in Move(player: .A, 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 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 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: Player = switch move.player {
|
||||
case .A: .B
|
||||
case .B: .A
|
||||
}
|
||||
self.state = .Playing(turn: next)
|
||||
}
|
||||
default:
|
||||
fatalError("Illegal move \(move.action)")
|
||||
}
|
||||
}
|
||||
|
||||
private 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)] {
|
||||
var length = 1
|
||||
|
||||
// Run in the two opposite directions of the axis to sum the length
|
||||
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]?.owner != of { break }
|
||||
length += 1
|
||||
}
|
||||
}
|
||||
|
||||
maxLength = max(maxLength, length)
|
||||
}
|
||||
|
||||
return maxLength
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
public struct Move {
|
||||
let player: Player
|
||||
let action: Action
|
||||
|
||||
public enum Action {
|
||||
case InsertOnSide(side: Direction, offset: Int)
|
||||
case InsertAt(where: Coords)
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
public protocol Rules {
|
||||
var state: GameState { get }
|
||||
|
||||
var history: [Move] { get }
|
||||
|
||||
mutating 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]
|
||||
|
||||
mutating func onMoveDone(move: Move, board: Board) -> Void
|
||||
}
|
||||
|
||||
public enum GameState {
|
||||
case Playing(turn: Player)
|
||||
|
||||
case Finished(winner: Player?)
|
||||
}
|
Loading…
Reference in new issue