HelloGameKit WatchKit Extension/GameModel.swift

/*
     Copyright (C) 2016 Apple Inc. All Rights Reserved.
     See LICENSE.txt for this sample’s licensing information
     
     Abstract:
     This is }a simple game model that records points generated by gestures for the player who has the current turn
 */
 
import Foundation
import WatchKit
import GameKit
 
class GameModel: NSObject, NSCoding {
    // MARK: Types
    
    private struct PlayerIdentifiers {
        static let automatch = "automatchID"
        static let local = "localID"
    }
 
    // MARK: Properties
    
    private(set) var moves = [Move]()
    
    private var players = [String]()
    
    private var currentPlayerID: String
    
    private var match: GKTurnBasedMatch?
    
    var currentMove: Move? {
        return moves.last
    }
 
    // MARK: Initialization
    
    override init() {
        if let playerID = GKLocalPlayer.localPlayer().playerID {
            currentPlayerID = playerID
        }
        else {
            currentPlayerID = PlayerIdentifiers.local
        }
 
        players = [currentPlayerID]
        
        super.init()
    }
    
    // MARK: NSCoding
    
    required init?(coder aDecoder: NSCoder) {
        moves = aDecoder.decodeObject(forKey: "moves") as! [Move]
        players = aDecoder.decodeObject(forKey: "players") as! [String]
        currentPlayerID = aDecoder.decodeObject(forKey: "currentPlayerID") as! String
    }
    
    func encode(with aCoder: NSCoder) {
        aCoder.encode(moves as NSArray, forKey: "moves")
        aCoder.encode(players as NSArray, forKey: "players")
        aCoder.encode(currentPlayerID as NSString, forKey: "currentPlayerID")
    }
    
    // MARK: Factory methods
    
    class func loadGameModel(match: GKTurnBasedMatch, completionHandler: @escaping (GameModel?, Error?) -> Void) {
        print("***** loading matchData")
        match.loadMatchData { (data, error) in
            if error == nil {
                print("***** matchData load succeeded")
                var model: GameModel? = nil
                
                // If we have data use it otherwise make a new model.
                if let data = data, data.count > 0 {
                    model = NSKeyedUnarchiver.unarchiveObject(with: data) as? GameModel
                    if let model = model {
                        model.gameModelDidLoad(match: match)
                    }
                    else {
                        print("***** matchData unarchive failed")
                    }
                }
                else {  // this is a new match so make a model
                    model = GameModel()
                }
                model?.match = match
                completionHandler(model, nil)
            }
            else {
                print("***** matchData load failed")
                completionHandler(nil, error)
            }
        }
    }
 
    // MARK: Match updating
    
    func gameModelDidLoad(match: GKTurnBasedMatch) {
        if let playerID = match.currentParticipant?.player?.playerID {
            currentPlayerID = playerID
        }
        else {
            currentPlayerID = PlayerIdentifiers.automatch
        }
        
        // Remove previously initialized players.
        players.removeAll()
        
        guard let participants = match.participants else { return }
        
        for participant in participants {
            if let playerID = participant.player?.playerID {
                players.append(playerID)
            }
            else {
                players.append(PlayerIdentifiers.automatch)
            }
        }
    }
 
    func save() {
        let data = NSKeyedArchiver.archivedData(withRootObject: self)
        guard let match = match else {
            print("***** Error: no match to save to")
            return
        }
    
        print("***** saving matchData")
 
         match.saveCurrentTurn(withMatch: data) { error in
            if let error = error {
                print("***** save failed for matchData with error: \(error)")
            }
            else {
                print("***** save complete for matchData")
            }
        }
    }
    
    // MARK: Making Moves and turns
    
    func addMove() -> Move {
        let move = Move(playerID: currentPlayerID)
        moves.append(move)
        save()
        
        return move
    }
    
    func endTurn(for match: GKTurnBasedMatch, completionHandler: @escaping () -> Void) {
        guard let participants = match.participants else { fatalError("No participants.") }
        
        // Compute the index of the participant to whom we wish to pass the turn to.
        var nextIndex = 0
        if let index = players.index(of: currentPlayerID) {
            nextIndex = index + 1 % players.count
        }
        
        /*
            Build next participants list including all participants starting with
            the new turn holder.  The current turn holder will be included at the
            end so if the non of the players take their turn the turn will eventually
            return to the current turn holder.
        */
        var nextParticipants = participants[nextIndex..<participants.count]
        nextParticipants += participants[0..<nextIndex]
        
        let nextParticipant = nextParticipants.first
        
        // Automatch participants do not have a player identifier so we will use a placeholder.
        var nextPlayerID = PlayerIdentifiers.automatch
        if let playerID = nextParticipant?.player?.playerID {
            nextPlayerID = playerID
        }
        
        // Now that we have figured our our next player we update our currentPlayerID to be that player and then end the turn.
        currentPlayerID = nextPlayerID
        let data = NSKeyedArchiver.archivedData(withRootObject: self)
        match.endTurn(withNextParticipants: Array(nextParticipants), turnTimeout: 600, match: data) { error in
            completionHandler()
        }
    }
}