initial logic for detecing game states
This commit is contained in:
parent
be3947ab0c
commit
d9d7656a2b
|
@ -11,16 +11,31 @@ import UIKit
|
|||
class HomeViewController: UIViewController {
|
||||
@IBOutlet var columnButtons: [UIButton]!
|
||||
|
||||
enum GameplayState {
|
||||
case playing
|
||||
case fullBoardDraw
|
||||
case playerHasWon
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Instance Properties
|
||||
|
||||
var board: Board!
|
||||
lazy var placedChipColumns: [[Chip]] = Array(repeating: [Chip](), count: Board.columns)
|
||||
|
||||
|
||||
var currentGameplayState = GameplayState.playing {
|
||||
didSet {
|
||||
gameplayStateChanged()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
// Do any additional setup after loading the view, typically from a nib.
|
||||
resetBoard()
|
||||
resetGame()
|
||||
}
|
||||
|
||||
|
||||
|
@ -32,25 +47,30 @@ class HomeViewController: UIViewController {
|
|||
assert(column < columnButtons.count, "button tag should be able to index a button in memory")
|
||||
|
||||
if let row = board.nextEmptyRow(inColumn: column) {
|
||||
board.add(chip: .red, toColumn: column)
|
||||
addChip(inColumn: column, row: row, colored: Player.ChipUIColor.red)
|
||||
board.add(chip: board.currentPlayer.chipColor, toColumn: column)
|
||||
addChip(inColumn: column, row: row, forPlayer: board.currentPlayer)
|
||||
|
||||
advanceGame()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Helper functions
|
||||
|
||||
func resetBoard() {
|
||||
func resetGame() {
|
||||
board = Board()
|
||||
|
||||
for var column in placedChipColumns {
|
||||
column.forEach { chip in chip.removeFromSuperview() }
|
||||
column.removeAll(keepingCapacity: true)
|
||||
|
||||
for i in 0..<placedChipColumns.count {
|
||||
placedChipColumns[i].forEach({ $0.removeFromSuperview() })
|
||||
placedChipColumns[i].removeAll(keepingCapacity: true)
|
||||
}
|
||||
|
||||
currentGameplayState = .playing
|
||||
}
|
||||
|
||||
|
||||
func addChip(inColumn column: Int, row: Int, colored color: UIColor) {
|
||||
func addChip(inColumn column: Int, row: Int, forPlayer player: Player) {
|
||||
let button = columnButtons[column]
|
||||
let size = min(button.frame.width, button.frame.height / CGFloat(Board.rows))
|
||||
let rect = CGRect(x: 0, y: 0, width: size, height: size)
|
||||
|
@ -59,7 +79,7 @@ class HomeViewController: UIViewController {
|
|||
let newChip = Chip(frame: rect)
|
||||
|
||||
newChip.isUserInteractionEnabled = false
|
||||
newChip.backgroundColor = color
|
||||
newChip.backgroundColor = player.uiColor
|
||||
newChip.layer.cornerRadius = size / 2
|
||||
newChip.center = positionForChip(inColumn: column, row: row)
|
||||
newChip.transform = CGAffineTransform(translationX: 0, y: -800)
|
||||
|
@ -90,6 +110,49 @@ class HomeViewController: UIViewController {
|
|||
return min(columnButton.frame.width, columnButton.frame.height / CGFloat(Board.rows))
|
||||
}
|
||||
|
||||
|
||||
|
||||
func advanceGame() {
|
||||
if board.isFull {
|
||||
if board.hasWin(forPlayer: board.currentPlayer) {
|
||||
currentGameplayState = .playerHasWon
|
||||
} else {
|
||||
currentGameplayState = .fullBoardDraw
|
||||
}
|
||||
} else {
|
||||
if board.hasWin(forPlayer: board.currentPlayer) {
|
||||
currentGameplayState = .playerHasWon
|
||||
} else {
|
||||
currentGameplayState = .playing
|
||||
board.switchCurrentPlayer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func endGame(message: String) {
|
||||
let alertController = UIAlertController(title: "Game Over", message: message, preferredStyle: .alert)
|
||||
|
||||
alertController.addAction(UIAlertAction(title: "Restart", style: .default) { [unowned self] _ in
|
||||
self.resetGame()
|
||||
})
|
||||
|
||||
present(alertController, animated: true)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Private functions
|
||||
|
||||
private func gameplayStateChanged() {
|
||||
switch currentGameplayState {
|
||||
case .playing:
|
||||
title = "\(board.currentPlayer.name)'s Turn"
|
||||
case .fullBoardDraw:
|
||||
endGame(message: "The board is full and game has ended in a draw.")
|
||||
case .playerHasWon:
|
||||
endGame(message: "\(board.currentPlayer.name) has won!")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -9,13 +9,13 @@
|
|||
import UIKit
|
||||
|
||||
class Board: NSObject {
|
||||
// MARK: - Properties
|
||||
// MARK: - Static Properties
|
||||
|
||||
static let rows = 6
|
||||
static let columns = 7
|
||||
|
||||
static let slotCount = {
|
||||
return rows * columns
|
||||
static var slotCapacity: Int {
|
||||
return Board.rows * Board.columns
|
||||
}
|
||||
|
||||
enum ChipColor: Int {
|
||||
|
@ -24,13 +24,21 @@ class Board: NSObject {
|
|||
case black
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Instance Properties
|
||||
|
||||
var slots: [ChipColor]
|
||||
var currentPlayer: Player = Player.allPlayers[0]
|
||||
|
||||
var isFull: Bool {
|
||||
return !slots.contains(.none)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Lifecylcle
|
||||
|
||||
override init() {
|
||||
self.slots = Array(repeating: .none, count: Board.rows * Board.columns)
|
||||
self.slots = Array(repeating: .none, count: Board.slotCapacity)
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
@ -68,6 +76,79 @@ class Board: NSObject {
|
|||
set(color: color, inColumn: column, row: row)
|
||||
}
|
||||
|
||||
func switchCurrentPlayer() {
|
||||
currentPlayer = currentPlayer.opponent
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Victory occurs when a player has lined up four consecutive chips
|
||||
in any direction (horizontally, vertically or diagonally)
|
||||
*/
|
||||
func hasWin(forPlayer player: Player) -> Bool {
|
||||
for row in 0 ..< Board.rows {
|
||||
for column in 0 ..< Board.columns {
|
||||
if hasHorizontalWin(row: row, column: column, color: player.chipColor) { return true }
|
||||
if hasVerticalWin(row: row, column: column, color: player.chipColor) { return true }
|
||||
if hasDiagonalWin(row: row, column: column, color: player.chipColor, rowDirection: 1) { return true }
|
||||
if hasDiagonalWin(row: row, column: column, color: player.chipColor, rowDirection: -1) { return true }
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
func hasHorizontalWin(row: Int, column: Int, color: ChipColor) -> Bool {
|
||||
for rowOffset in 0 ... 3 {
|
||||
if row + rowOffset >= Board.rows {
|
||||
return false
|
||||
}
|
||||
|
||||
if slots[slotIndex(forColumn: column, row: row + rowOffset)] != color {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
func hasVerticalWin(row: Int, column: Int, color: ChipColor) -> Bool {
|
||||
for columnOffset in 0 ... 3 {
|
||||
if column + columnOffset >= Board.columns {
|
||||
return false
|
||||
}
|
||||
|
||||
if slots[slotIndex(forColumn: column + columnOffset, row: row)] != color {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
func hasDiagonalWin(row: Int, column: Int, color: ChipColor, rowDirection: Int = 1) -> Bool {
|
||||
for offset in 0...3 {
|
||||
if column + offset >= Board.columns {
|
||||
return false
|
||||
}
|
||||
|
||||
if row + (offset * rowDirection) >= Board.rows {
|
||||
return false
|
||||
}
|
||||
|
||||
if row + (offset * rowDirection) < 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// search diagonal rightwards and either upwards or downwards (depending on the `rowDirection` multiplier)
|
||||
if slots[slotIndex(forColumn: column + offset, row: row + (offset * rowDirection))] != color {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Private functions
|
||||
|
||||
|
@ -76,7 +157,7 @@ class Board: NSObject {
|
|||
///
|
||||
/// - Parameters:
|
||||
/// - column: 0-based column number, moving left-to-right
|
||||
/// - row: 0-based row number, moving top-to-bottom
|
||||
/// - row: 0-based row number, moving bottom-to-top
|
||||
/// - Returns: the corresponding index to the `slots` array
|
||||
private func slotIndex(forColumn column: Int, row: Int) -> Int {
|
||||
return (Board.rows * column) + row
|
||||
|
|
Loading…
Reference in New Issue