initial logic for detecing game states

This commit is contained in:
CypherPoet 2019-03-01 00:49:51 -05:00
parent be3947ab0c
commit d9d7656a2b
2 changed files with 160 additions and 16 deletions

View File

@ -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!")
}
}
}

View File

@ -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