// // GameModel.swift // TTTKit // // Created by Shadowfacts on 12/21/22. // import Foundation import GameKit public class GameModel: NSObject, NSCopying, GKGameModel { private var controller: GameController public init(controller: GameController) { self.controller = controller } // MARK: GKGameModel public var players: [GKGameModelPlayer]? { [Player.x, Player.o] } public var activePlayer: GKGameModelPlayer? { switch controller.state { case .playAnywhere(let mark), .playSpecific(let mark, column: _, row: _): switch mark { case .x: return Player.x case .o: return Player.o } case .end(_): return nil } } public func setGameModel(_ gameModel: GKGameModel) { let other = (gameModel as! GameModel).controller self.controller = GameController(state: other.state, board: other.board) } public func gameModelUpdates(for player: GKGameModelPlayer) -> [GKGameModelUpdate]? { guard let player = player as? Player else { return nil } let mark = player.mark switch controller.state { case .playAnywhere: return updatesForPlayAnywhere(mark: mark) case .playSpecific(_, column: let col, row: let row): return updatesForPlaySpecific(mark: mark, board: (col, row)) case .end: return nil } } public func apply(_ gameModelUpdate: GKGameModelUpdate) { let update = gameModelUpdate as! Update switch controller.state { case .playAnywhere(update.mark), .playSpecific(update.mark, column: update.subBoard.column, row: update.subBoard.row): break default: return } controller.play(on: update.subBoard, column: update.column, row: update.row) } public func score(for player: GKGameModelPlayer) -> Int { guard let player = player as? Player else { return .min } var score = 0 for column in 0..<3 { for row in 0..<3 { let subBoard = controller.board.getSubBoard(column: column, row: row) if let win = subBoard.win { if win.mark == player.mark { score += 10 } else { score -= 5 } } else { score += 4 * subBoard.potentialWinCount(for: player.mark) score -= 2 * subBoard.potentialWinCount(for: player.mark.next) } } } if case .playAnywhere(let mark) = controller.state { if mark == player.mark { score += 5 } } score += 8 * controller.board.potentialWinCount(for: player.mark) score -= 4 * controller.board.potentialWinCount(for: player.mark.next) return score } public func isWin(for player: GKGameModelPlayer) -> Bool { let mark = (player as! Player).mark return controller.board.win?.mark == mark } private func updatesForPlayAnywhere(mark: Mark) -> [Update] { var updates = [Update]() for boardColumn in 0..<3 { for boardRow in 0..<3 { let subBoard = controller.board.getSubBoard(column: boardColumn, row: boardRow) guard !subBoard.ended else { continue } for column in 0..<3 { for row in 0..<3 { guard subBoard[column, row] == nil else { continue } updates.append(Update(mark: mark, subBoard: (boardColumn, boardRow), column: column, row: row)) } } } } return updates } private func updatesForPlaySpecific(mark: Mark, board: (column: Int, row: Int)) -> [Update] { let subBoard = controller.board.getSubBoard(column: board.column, row: board.row) var updates = [Update]() for column in 0..<3 { for row in 0..<3 { guard subBoard[column, row] == nil else { continue } updates.append(Update(mark: mark, subBoard: board, column: column, row: row)) } } return updates } // MARK: NSCopying public func copy(with zone: NSZone? = nil) -> Any { return GameModel(controller: GameController(state: controller.state, board: controller.board)) } } extension Board { func potentialWinCount(for mark: Mark) -> Int { return Win.allPoints.filter { points in let empty = points.filter { self[$0] == nil }.count let matching = points.filter { self[$0] == mark }.count return matching == 2 && empty == 1 }.count } }