forked from shadowfacts/Tusker
153 lines
4.9 KiB
Swift
153 lines
4.9 KiB
Swift
//
|
|
// 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
|
|
}
|
|
}
|