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