diff --git a/GoldWars/GoldWars/Components/TimerComponent.swift b/GoldWars/GoldWars/Components/TimerComponent.swift index fc6df85..1a7e678 100644 --- a/GoldWars/GoldWars/Components/TimerComponent.swift +++ b/GoldWars/GoldWars/Components/TimerComponent.swift @@ -57,7 +57,7 @@ class TimerComponent: GKComponent { self.labelNode.text = "Synching" RoundCalculatorService.sharedInstance.resetNumberOfAttacksAndFormats() if !MultiplayerNetwork.sharedInstance.isSending { - MultiplayerNetwork.sharedInstance.sendPlayerMoves(playerMoves: DataService.sharedInstance.localPlayerMoves) + MultiplayerNetwork.sharedInstance.sendPlayerMoves(localRoundData: DataService.sharedInstance.localRoundData) } if !RoundCalculatorService.sharedInstance.isCalculating && DataService.sharedInstance.didReceiveAllData() diff --git a/GoldWars/GoldWars/DataService.swift b/GoldWars/GoldWars/DataService.swift index cba4f4d..23fe5f2 100644 --- a/GoldWars/GoldWars/DataService.swift +++ b/GoldWars/GoldWars/DataService.swift @@ -6,12 +6,24 @@ // Copyright © 2020 SP2. All rights reserved. // -struct PlayerMove: Codable{ +struct PlayerMove: Codable { let fromBase: Int let toBase: Int var unitCount: Int } +struct LocalRoundData: Codable { + var localPlayerMoves: [PlayerMove] + var hasAttackBoost: Bool + var hasDefenceBoost: Bool + + init() { + localPlayerMoves = [] + hasAttackBoost = false + hasDefenceBoost = false + } +} + class SnapshotModel: Codable { var baseEntites: [BaseEntityModel] @@ -25,7 +37,7 @@ class BaseEntityModel: Codable { var unitCount: Int var ownership: String? - init(baseId: Int, unitCount: Int, ownership: String?) { + init(baseId: Int, unitCount: Int, ownership: String?, hasAttackBoost: Bool, hasDefenceBoost: Bool) { self.baseId = baseId self.unitCount = unitCount self.ownership = ownership @@ -34,8 +46,8 @@ class BaseEntityModel: Codable { class DataService { static let sharedInstance = DataService() - var localPlayerMoves: [PlayerMove] = [] - var remotePlayerMoves: [String: [PlayerMove]] = [:] + var localRoundData: LocalRoundData = LocalRoundData() + var remotePlayerMoves: [String: LocalRoundData] = [:] var snapshotModel: SnapshotModel? var hostingPlayer = GameCenterManager.sharedInstance.hostingPlayer @@ -43,18 +55,18 @@ class DataService { var entityManager = EntityManager.gameEMInstance func addMove(playerMove: PlayerMove) { - var equalMove = localPlayerMoves.filter { (ele) -> Bool in + var equalMove = localRoundData.localPlayerMoves.filter { (ele) -> Bool in ele.fromBase == playerMove.fromBase && ele.toBase == playerMove.toBase } if equalMove.count == 1 { equalMove[0].unitCount = Int(equalMove[0].unitCount) + Int(playerMove.unitCount) } else { - self.localPlayerMoves.append(playerMove) + self.localRoundData.localPlayerMoves.append(playerMove) } } - func addRemotePlayerMoves(playerName: String, playerMoves: [PlayerMove]) { - self.remotePlayerMoves[playerName] = playerMoves + func addRemotePlayerMoves(playerName: String, localRoundData: LocalRoundData) { + self.remotePlayerMoves[playerName] = localRoundData } func didReceiveAllData() -> Bool { diff --git a/GoldWars/GoldWars/Entities/Base.swift b/GoldWars/GoldWars/Entities/Base.swift index 947a6c2..1306885 100644 --- a/GoldWars/GoldWars/Entities/Base.swift +++ b/GoldWars/GoldWars/Entities/Base.swift @@ -53,7 +53,8 @@ class Base: GKEntity{ base.component(ofType: DefaultBaseComponent.self)?.labelNode.text = "\(base.unitCount)" DataService.sharedInstance.addMove(playerMove: PlayerMove(fromBase: self.baseID, toBase: base.baseID, - unitCount: units * playerMoveType.rawValue)) + unitCount: units * playerMoveType.rawValue) + ) return [self, base] } diff --git a/GoldWars/GoldWars/Entities/EntityManager.swift b/GoldWars/GoldWars/Entities/EntityManager.swift index 6bf1d5b..04e02fc 100644 --- a/GoldWars/GoldWars/Entities/EntityManager.swift +++ b/GoldWars/GoldWars/Entities/EntityManager.swift @@ -136,6 +136,7 @@ class EntityManager { getOwnerBySnapBase = GameCenterManager.sharedInstance.getGKPlayerByUsername(displayName: snapBase.ownership!) } else { entity.removeComponent(ofType: TeamComponent.self) + base.ownershipPlayer = nil } if getOwnerBySnapBase != nil { if getOwnerBySnapBase == GKLocalPlayer.local { @@ -249,7 +250,15 @@ class EntityManager { for entity in bases { let base = entity as! Base - snapBase.append(BaseEntityModel(baseId: base.baseID, unitCount: base.unitCount, ownership: base.ownershipPlayer?.displayName)) + snapBase.append( + BaseEntityModel( + baseId: base.baseID, + unitCount: base.unitCount, + ownership: base.ownershipPlayer?.displayName, + hasAttackBoost: base.hasAttackBoost, + hasDefenceBoost: base.hasDefenseBoost + ) + ) } return SnapshotModel(baseEntites: snapBase) diff --git a/GoldWars/GoldWars/Entities/HUD.swift b/GoldWars/GoldWars/Entities/HUD.swift index 3b71497..4229f3f 100644 --- a/GoldWars/GoldWars/Entities/HUD.swift +++ b/GoldWars/GoldWars/Entities/HUD.swift @@ -44,14 +44,14 @@ class HUD: GKEntity { text: "Def", isEnabled: true, position: CGPoint(x: EntityManager.gameEMInstance.scene.size.width * 0.85, y: EntityManager.gameEMInstance.scene.size.height * 0.1), - onButtonPress: {EntityManager.gameEMInstance.getBasesByTeam(for: .team2).forEach({base in base.hasDefenseBoost = true})} + onButtonPress: {DataService.sharedInstance.localRoundData.hasDefenceBoost = true} ) atkSkill = SingeClickButtonNode( textureName: "yellow_circle", text: "Atk", isEnabled: true, position: CGPoint(x: EntityManager.gameEMInstance.scene.size.width * 0.95, y: EntityManager.gameEMInstance.scene.size.height * 0.1), - onButtonPress: {EntityManager.gameEMInstance.getBasesByTeam(for: .team2).forEach({base in base.hasAttackBoost = true})} + onButtonPress: {DataService.sharedInstance.localRoundData.hasAttackBoost = true} ) super.init() hostLabel.position = CGPoint(x: size.width * 0.02, y: size.height * 0.95) diff --git a/GoldWars/GoldWars/GameCenterManager.swift b/GoldWars/GoldWars/GameCenterManager.swift index 7571212..8b7ab1e 100644 --- a/GoldWars/GoldWars/GameCenterManager.swift +++ b/GoldWars/GoldWars/GameCenterManager.swift @@ -122,8 +122,8 @@ final class GameCenterManager: NSObject, GKMatchmakerViewControllerDelegate ,GKM break } } - if let playerMoves = try? jsonDecoder.decode([PlayerMove].self, from: data) { - DataService.sharedInstance.addRemotePlayerMoves(playerName: player.displayName, playerMoves: playerMoves) + if let roundData = try? jsonDecoder.decode(LocalRoundData.self, from: data) { + DataService.sharedInstance.addRemotePlayerMoves(playerName: player.displayName, localRoundData: roundData) } if let snapshotModel = try? jsonDecoder.decode(SnapshotModel.self, from: data) { DataService.sharedInstance.snapshotModel = snapshotModel diff --git a/GoldWars/GoldWars/MultiplayerNetwork.swift b/GoldWars/GoldWars/MultiplayerNetwork.swift index 89b1479..985ec14 100644 --- a/GoldWars/GoldWars/MultiplayerNetwork.swift +++ b/GoldWars/GoldWars/MultiplayerNetwork.swift @@ -36,13 +36,15 @@ class MultiplayerNetwork{ } } - func sendPlayerMoves(playerMoves: [PlayerMove]) { + func sendPlayerMoves(localRoundData: LocalRoundData) { if GameCenterManager.sharedInstance.isServer == false { self.isSending = true let encoder = JSONEncoder() - let encoded = (try? encoder.encode(playerMoves))! + let encoded = (try? encoder.encode(localRoundData))! sendDataToHost(data: encoded) - DataService.sharedInstance.localPlayerMoves.removeAll() + DataService.sharedInstance.localRoundData.localPlayerMoves.removeAll() + DataService.sharedInstance.localRoundData.hasAttackBoost = false + DataService.sharedInstance.localRoundData.hasDefenceBoost = false } } diff --git a/GoldWars/GoldWars/RoundCalculatorService.swift b/GoldWars/GoldWars/RoundCalculatorService.swift index 8e61759..af824c4 100644 --- a/GoldWars/GoldWars/RoundCalculatorService.swift +++ b/GoldWars/GoldWars/RoundCalculatorService.swift @@ -13,8 +13,12 @@ class RoundCalculatorService { static let sharedInstance = RoundCalculatorService() static let LOG = OSLog.init(subsystem: "Round Calculator", category: "RoundCalculatorService") - var allPlayerMoves: [String: [PlayerMove]] = [:] - var baseSpecificMoves: [Int: [String: [PlayerMove]]] = [:] + let ATK_BOOST_MULTIPLICATOR = 1.1 + let DEF_BOOST_MULTIPLICATOR = 1.1 + + // TODO: Better data structure + var boosts: [String: (Bool, Bool)] = [:] // First bool is atk boost, second is def boost + var entityManager = EntityManager.gameEMInstance var isCalculating = false @@ -27,21 +31,10 @@ class RoundCalculatorService { isCalculating = true let currentSnapshotModel = DataService.sharedInstance.snapshotModel - for playerMove in DataService.sharedInstance.remotePlayerMoves { - addPlayerMove(playerName: playerMove.key, playerMoves: playerMove.value) - } - addPlayerMove(playerName: GKLocalPlayer.local.displayName, playerMoves: DataService.sharedInstance.localPlayerMoves) - - for playerMove in allPlayerMoves { - for move in playerMove.value { - mapPlayerMoveToAttackedBase(playerName: playerMove.key, playerMove: move) - } - } + var baseSpecificMoves = collectBaseSpecificMoves() // TODO: Refactor to a less complex way - for (key, value) in baseSpecificMoves { - let baseId = key - var playerMovesByBase = value + for (baseId, var playerMovesByBase) in baseSpecificMoves { let targetBase = currentSnapshotModel?.baseEntites.filter { $0.baseId == baseId }[0] let possiblyOwnershipMoves = playerMovesByBase.filter { $0.key == targetBase?.ownership} @@ -58,7 +51,7 @@ class RoundCalculatorService { } playerMovesByBase.removeValue(forKey: playerName) } - + for (_, playerMoves) in playerMovesByBase { for playerMove in playerMoves { for base in currentSnapshotModel!.baseEntites { @@ -72,17 +65,36 @@ class RoundCalculatorService { var combinePotentionalForces: [String: PlayerMove] = [:] for playerMoves in playerMovesByBase { - combinePotentionalForces[playerMoves.key] = PlayerMove(fromBase: playerMoves.value[0].fromBase, toBase: playerMoves.value[0].toBase, unitCount: 0) + combinePotentionalForces[playerMoves.key] = PlayerMove( + fromBase: playerMoves.value[0].fromBase, + toBase: playerMoves.value[0].toBase, + unitCount: 0 + ) for move in playerMoves.value { combinePotentionalForces[playerMoves.key]!.unitCount += move.unitCount } } - if(combinePotentionalForces.count > 0) { + + if combinePotentionalForces.count > 0 { let sortedPotentionalCombinedForces = combinePotentionalForces.sorted { $0.1.unitCount > $1.1.unitCount } var playerMoveWithMaxUnits = sortedPotentionalCombinedForces[0] + if playerMovesByBase.count >= 2 { - let playerMoveWithSecMaxUnits = sortedPotentionalCombinedForces[1] + var playerMoveWithSecMaxUnits = sortedPotentionalCombinedForces[1] + if boosts[playerMoveWithMaxUnits.key]!.0 { + playerMoveWithMaxUnits.value.unitCount = Int(Double(playerMoveWithMaxUnits.value.unitCount) * ATK_BOOST_MULTIPLICATOR) + } + if boosts[playerMoveWithSecMaxUnits.key]!.0 { + playerMoveWithSecMaxUnits.value.unitCount = Int(Double(playerMoveWithSecMaxUnits.value.unitCount) * ATK_BOOST_MULTIPLICATOR) + } + + if playerMoveWithMaxUnits.value.unitCount < playerMoveWithSecMaxUnits.value.unitCount { + let temp = playerMoveWithMaxUnits + playerMoveWithMaxUnits = playerMoveWithSecMaxUnits + playerMoveWithSecMaxUnits = temp + } + playerMoveWithMaxUnits.value.unitCount -= playerMoveWithSecMaxUnits.value.unitCount } @@ -90,17 +102,17 @@ class RoundCalculatorService { if base.baseId == playerMoveWithMaxUnits.value.toBase { if base.ownership == nil { base.unitCount += playerMoveWithMaxUnits.value.unitCount - if playerMoveWithMaxUnits.value.unitCount == 0 { - base.ownership = nil - } else { - base.ownership = playerMoveWithMaxUnits.key - } + base.ownership = playerMoveWithMaxUnits.value.unitCount == 0 ? nil : playerMoveWithMaxUnits.key } else { + if boosts[base.ownership!]!.1 { + base.unitCount = Int(Double(base.unitCount) * DEF_BOOST_MULTIPLICATOR) + } if base.unitCount < playerMoveWithMaxUnits.value.unitCount { base.unitCount = playerMoveWithMaxUnits.value.unitCount - base.unitCount base.ownership = playerMoveWithMaxUnits.key } else if (base.unitCount == playerMoveWithMaxUnits.value.unitCount) { base.ownership = nil + base.unitCount = 0 } else { base.unitCount -= playerMoveWithMaxUnits.value.unitCount } @@ -110,8 +122,9 @@ class RoundCalculatorService { } baseSpecificMoves.removeValue(forKey: baseId) } - allPlayerMoves.removeAll() - DataService.sharedInstance.localPlayerMoves.removeAll() + DataService.sharedInstance.localRoundData.localPlayerMoves.removeAll() + DataService.sharedInstance.localRoundData.hasAttackBoost = false + DataService.sharedInstance.localRoundData.hasDefenceBoost = false MultiplayerNetwork.sharedInstance.sendSnapshotModelToPlayers() DataService.sharedInstance.snapshotModel = currentSnapshotModel entityManager.updateSnapshotModel(snapshotModel: currentSnapshotModel!) @@ -120,20 +133,47 @@ class RoundCalculatorService { os_log("Finished calculating Round", log: RoundCalculatorService.LOG, type: .info) } - func addPlayerMove(playerName: String, playerMoves: [PlayerMove]) { - self.allPlayerMoves[playerName] = playerMoves - } - - func mapPlayerMoveToAttackedBase(playerName: String, playerMove: PlayerMove) { - if self.baseSpecificMoves.keys.contains(playerMove.toBase) { - if (self.baseSpecificMoves[playerMove.toBase]?.keys.contains(playerName))!{ - self.baseSpecificMoves[playerMove.toBase]?[playerName]?.append(playerMove) - } else { - self.baseSpecificMoves[playerMove.toBase]?.merge([playerName: [playerMove]]){(current, _) in current} - } - } else { - self.baseSpecificMoves[playerMove.toBase] = [playerName: [playerMove]] + func collectBaseSpecificMoves() -> [Int: [String: [PlayerMove]]] { + + var allPlayerMoves: [String: [PlayerMove]] = [:] + + // collect moves from remote player + for playerMove in DataService.sharedInstance.remotePlayerMoves { + allPlayerMoves[playerMove.key] = playerMove.value.localPlayerMoves } + + boosts[GameCenterManager.sharedInstance.hostingPlayer!.displayName] = ( + DataService.sharedInstance.localRoundData.hasAttackBoost, + DataService.sharedInstance.localRoundData.hasDefenceBoost + ) + + boosts[GameCenterManager.sharedInstance.peerPlayer!.displayName] = ( + DataService.sharedInstance.remotePlayerMoves[GameCenterManager.sharedInstance.peerPlayer!.displayName]!.hasAttackBoost, + DataService.sharedInstance.remotePlayerMoves[GameCenterManager.sharedInstance.peerPlayer!.displayName]!.hasDefenceBoost + ) + + // collect moves from local player + allPlayerMoves[GKLocalPlayer.local.displayName] = DataService.sharedInstance.localRoundData.localPlayerMoves + + var baseSpecificMoves: [Int: [String: [PlayerMove]]] = [:] + + for playerMove in allPlayerMoves { + for move in playerMove.value { + if baseSpecificMoves.keys.contains(move.toBase) { + if (baseSpecificMoves[move.toBase]?.keys.contains(playerMove.key))! { + baseSpecificMoves[move.toBase]?[playerMove.key]?.append(move) + } else { + baseSpecificMoves[move.toBase]?.merge([playerMove.key: [move]]){(current, _) in current} + } + } else { + baseSpecificMoves[move.toBase] = [playerMove.key: [move]] + } + } + } + + DataService.sharedInstance.remotePlayerMoves.removeAll() + + return baseSpecificMoves } func resolvePlayerMove(playerMove: PlayerMove, unitCount: Int, ownership: String?, resolveType: String) {