Merge branch '68-attack-defense-boost-in-roundcalculator' into 'development'

Resolve "Attack / Defense Boost in RoundCalculator"

Closes #68

See merge request marcel.schwarz/software-projekt-2!88
This commit is contained in:
Aldin Duraki 2020-06-01 17:16:37 +00:00
commit 823dda29ef
8 changed files with 121 additions and 57 deletions

View File

@ -57,7 +57,7 @@ class TimerComponent: GKComponent {
self.labelNode.text = "Synching" self.labelNode.text = "Synching"
RoundCalculatorService.sharedInstance.resetNumberOfAttacksAndFormats() RoundCalculatorService.sharedInstance.resetNumberOfAttacksAndFormats()
if !MultiplayerNetwork.sharedInstance.isSending { if !MultiplayerNetwork.sharedInstance.isSending {
MultiplayerNetwork.sharedInstance.sendPlayerMoves(playerMoves: DataService.sharedInstance.localPlayerMoves) MultiplayerNetwork.sharedInstance.sendPlayerMoves(localRoundData: DataService.sharedInstance.localRoundData)
} }
if !RoundCalculatorService.sharedInstance.isCalculating if !RoundCalculatorService.sharedInstance.isCalculating
&& DataService.sharedInstance.didReceiveAllData() && DataService.sharedInstance.didReceiveAllData()

View File

@ -6,12 +6,24 @@
// Copyright © 2020 SP2. All rights reserved. // Copyright © 2020 SP2. All rights reserved.
// //
struct PlayerMove: Codable{ struct PlayerMove: Codable {
let fromBase: Int let fromBase: Int
let toBase: Int let toBase: Int
var unitCount: 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 { class SnapshotModel: Codable {
var baseEntites: [BaseEntityModel] var baseEntites: [BaseEntityModel]
@ -25,7 +37,7 @@ class BaseEntityModel: Codable {
var unitCount: Int var unitCount: Int
var ownership: String? 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.baseId = baseId
self.unitCount = unitCount self.unitCount = unitCount
self.ownership = ownership self.ownership = ownership
@ -34,8 +46,8 @@ class BaseEntityModel: Codable {
class DataService { class DataService {
static let sharedInstance = DataService() static let sharedInstance = DataService()
var localPlayerMoves: [PlayerMove] = [] var localRoundData: LocalRoundData = LocalRoundData()
var remotePlayerMoves: [String: [PlayerMove]] = [:] var remotePlayerMoves: [String: LocalRoundData] = [:]
var snapshotModel: SnapshotModel? var snapshotModel: SnapshotModel?
var hostingPlayer = GameCenterManager.sharedInstance.hostingPlayer var hostingPlayer = GameCenterManager.sharedInstance.hostingPlayer
@ -43,18 +55,18 @@ class DataService {
var entityManager = EntityManager.gameEMInstance var entityManager = EntityManager.gameEMInstance
func addMove(playerMove: PlayerMove) { 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 ele.fromBase == playerMove.fromBase && ele.toBase == playerMove.toBase
} }
if equalMove.count == 1 { if equalMove.count == 1 {
equalMove[0].unitCount = Int(equalMove[0].unitCount) + Int(playerMove.unitCount) equalMove[0].unitCount = Int(equalMove[0].unitCount) + Int(playerMove.unitCount)
} else { } else {
self.localPlayerMoves.append(playerMove) self.localRoundData.localPlayerMoves.append(playerMove)
} }
} }
func addRemotePlayerMoves(playerName: String, playerMoves: [PlayerMove]) { func addRemotePlayerMoves(playerName: String, localRoundData: LocalRoundData) {
self.remotePlayerMoves[playerName] = playerMoves self.remotePlayerMoves[playerName] = localRoundData
} }
func didReceiveAllData() -> Bool { func didReceiveAllData() -> Bool {

View File

@ -53,7 +53,8 @@ class Base: GKEntity{
base.component(ofType: DefaultBaseComponent.self)?.labelNode.text = "\(base.unitCount)" base.component(ofType: DefaultBaseComponent.self)?.labelNode.text = "\(base.unitCount)"
DataService.sharedInstance.addMove(playerMove: PlayerMove(fromBase: self.baseID, DataService.sharedInstance.addMove(playerMove: PlayerMove(fromBase: self.baseID,
toBase: base.baseID, toBase: base.baseID,
unitCount: units * playerMoveType.rawValue)) unitCount: units * playerMoveType.rawValue)
)
return [self, base] return [self, base]
} }

View File

@ -136,6 +136,7 @@ class EntityManager {
getOwnerBySnapBase = GameCenterManager.sharedInstance.getGKPlayerByUsername(displayName: snapBase.ownership!) getOwnerBySnapBase = GameCenterManager.sharedInstance.getGKPlayerByUsername(displayName: snapBase.ownership!)
} else { } else {
entity.removeComponent(ofType: TeamComponent.self) entity.removeComponent(ofType: TeamComponent.self)
base.ownershipPlayer = nil
} }
if getOwnerBySnapBase != nil { if getOwnerBySnapBase != nil {
if getOwnerBySnapBase == GKLocalPlayer.local { if getOwnerBySnapBase == GKLocalPlayer.local {
@ -249,7 +250,15 @@ class EntityManager {
for entity in bases { for entity in bases {
let base = entity as! Base 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) return SnapshotModel(baseEntites: snapBase)

View File

@ -44,14 +44,14 @@ class HUD: GKEntity {
text: "Def", text: "Def",
isEnabled: true, isEnabled: true,
position: CGPoint(x: EntityManager.gameEMInstance.scene.size.width * 0.85, y: EntityManager.gameEMInstance.scene.size.height * 0.1), 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( atkSkill = SingeClickButtonNode(
textureName: "yellow_circle", textureName: "yellow_circle",
text: "Atk", text: "Atk",
isEnabled: true, isEnabled: true,
position: CGPoint(x: EntityManager.gameEMInstance.scene.size.width * 0.95, y: EntityManager.gameEMInstance.scene.size.height * 0.1), 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() super.init()
hostLabel.position = CGPoint(x: size.width * 0.02, y: size.height * 0.95) hostLabel.position = CGPoint(x: size.width * 0.02, y: size.height * 0.95)

View File

@ -122,8 +122,8 @@ final class GameCenterManager: NSObject, GKMatchmakerViewControllerDelegate ,GKM
break break
} }
} }
if let playerMoves = try? jsonDecoder.decode([PlayerMove].self, from: data) { if let roundData = try? jsonDecoder.decode(LocalRoundData.self, from: data) {
DataService.sharedInstance.addRemotePlayerMoves(playerName: player.displayName, playerMoves: playerMoves) DataService.sharedInstance.addRemotePlayerMoves(playerName: player.displayName, localRoundData: roundData)
} }
if let snapshotModel = try? jsonDecoder.decode(SnapshotModel.self, from: data) { if let snapshotModel = try? jsonDecoder.decode(SnapshotModel.self, from: data) {
DataService.sharedInstance.snapshotModel = snapshotModel DataService.sharedInstance.snapshotModel = snapshotModel

View File

@ -36,13 +36,15 @@ class MultiplayerNetwork{
} }
} }
func sendPlayerMoves(playerMoves: [PlayerMove]) { func sendPlayerMoves(localRoundData: LocalRoundData) {
if GameCenterManager.sharedInstance.isServer == false { if GameCenterManager.sharedInstance.isServer == false {
self.isSending = true self.isSending = true
let encoder = JSONEncoder() let encoder = JSONEncoder()
let encoded = (try? encoder.encode(playerMoves))! let encoded = (try? encoder.encode(localRoundData))!
sendDataToHost(data: encoded) sendDataToHost(data: encoded)
DataService.sharedInstance.localPlayerMoves.removeAll() DataService.sharedInstance.localRoundData.localPlayerMoves.removeAll()
DataService.sharedInstance.localRoundData.hasAttackBoost = false
DataService.sharedInstance.localRoundData.hasDefenceBoost = false
} }
} }

View File

@ -13,8 +13,12 @@ class RoundCalculatorService {
static let sharedInstance = RoundCalculatorService() static let sharedInstance = RoundCalculatorService()
static let LOG = OSLog.init(subsystem: "Round Calculator", category: "RoundCalculatorService") static let LOG = OSLog.init(subsystem: "Round Calculator", category: "RoundCalculatorService")
var allPlayerMoves: [String: [PlayerMove]] = [:] let ATK_BOOST_MULTIPLICATOR = 1.1
var baseSpecificMoves: [Int: [String: [PlayerMove]]] = [:] 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 entityManager = EntityManager.gameEMInstance
var isCalculating = false var isCalculating = false
@ -27,21 +31,10 @@ class RoundCalculatorService {
isCalculating = true isCalculating = true
let currentSnapshotModel = DataService.sharedInstance.snapshotModel let currentSnapshotModel = DataService.sharedInstance.snapshotModel
for playerMove in DataService.sharedInstance.remotePlayerMoves { var baseSpecificMoves = collectBaseSpecificMoves()
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)
}
}
// TODO: Refactor to a less complex way // TODO: Refactor to a less complex way
for (key, value) in baseSpecificMoves { for (baseId, var playerMovesByBase) in baseSpecificMoves {
let baseId = key
var playerMovesByBase = value
let targetBase = currentSnapshotModel?.baseEntites.filter { $0.baseId == baseId }[0] let targetBase = currentSnapshotModel?.baseEntites.filter { $0.baseId == baseId }[0]
let possiblyOwnershipMoves = playerMovesByBase.filter { $0.key == targetBase?.ownership} let possiblyOwnershipMoves = playerMovesByBase.filter { $0.key == targetBase?.ownership}
@ -72,17 +65,36 @@ class RoundCalculatorService {
var combinePotentionalForces: [String: PlayerMove] = [:] var combinePotentionalForces: [String: PlayerMove] = [:]
for playerMoves in playerMovesByBase { 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 { for move in playerMoves.value {
combinePotentionalForces[playerMoves.key]!.unitCount += move.unitCount combinePotentionalForces[playerMoves.key]!.unitCount += move.unitCount
} }
} }
if(combinePotentionalForces.count > 0) {
if combinePotentionalForces.count > 0 {
let sortedPotentionalCombinedForces = combinePotentionalForces.sorted { $0.1.unitCount > $1.1.unitCount } let sortedPotentionalCombinedForces = combinePotentionalForces.sorted { $0.1.unitCount > $1.1.unitCount }
var playerMoveWithMaxUnits = sortedPotentionalCombinedForces[0] var playerMoveWithMaxUnits = sortedPotentionalCombinedForces[0]
if playerMovesByBase.count >= 2 { 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 playerMoveWithMaxUnits.value.unitCount -= playerMoveWithSecMaxUnits.value.unitCount
} }
@ -90,17 +102,17 @@ class RoundCalculatorService {
if base.baseId == playerMoveWithMaxUnits.value.toBase { if base.baseId == playerMoveWithMaxUnits.value.toBase {
if base.ownership == nil { if base.ownership == nil {
base.unitCount += playerMoveWithMaxUnits.value.unitCount base.unitCount += playerMoveWithMaxUnits.value.unitCount
if playerMoveWithMaxUnits.value.unitCount == 0 { base.ownership = playerMoveWithMaxUnits.value.unitCount == 0 ? nil : playerMoveWithMaxUnits.key
base.ownership = nil
} else {
base.ownership = playerMoveWithMaxUnits.key
}
} else { } else {
if boosts[base.ownership!]!.1 {
base.unitCount = Int(Double(base.unitCount) * DEF_BOOST_MULTIPLICATOR)
}
if base.unitCount < playerMoveWithMaxUnits.value.unitCount { if base.unitCount < playerMoveWithMaxUnits.value.unitCount {
base.unitCount = playerMoveWithMaxUnits.value.unitCount - base.unitCount base.unitCount = playerMoveWithMaxUnits.value.unitCount - base.unitCount
base.ownership = playerMoveWithMaxUnits.key base.ownership = playerMoveWithMaxUnits.key
} else if (base.unitCount == playerMoveWithMaxUnits.value.unitCount) { } else if (base.unitCount == playerMoveWithMaxUnits.value.unitCount) {
base.ownership = nil base.ownership = nil
base.unitCount = 0
} else { } else {
base.unitCount -= playerMoveWithMaxUnits.value.unitCount base.unitCount -= playerMoveWithMaxUnits.value.unitCount
} }
@ -110,8 +122,9 @@ class RoundCalculatorService {
} }
baseSpecificMoves.removeValue(forKey: baseId) baseSpecificMoves.removeValue(forKey: baseId)
} }
allPlayerMoves.removeAll() DataService.sharedInstance.localRoundData.localPlayerMoves.removeAll()
DataService.sharedInstance.localPlayerMoves.removeAll() DataService.sharedInstance.localRoundData.hasAttackBoost = false
DataService.sharedInstance.localRoundData.hasDefenceBoost = false
MultiplayerNetwork.sharedInstance.sendSnapshotModelToPlayers() MultiplayerNetwork.sharedInstance.sendSnapshotModelToPlayers()
DataService.sharedInstance.snapshotModel = currentSnapshotModel DataService.sharedInstance.snapshotModel = currentSnapshotModel
entityManager.updateSnapshotModel(snapshotModel: currentSnapshotModel!) entityManager.updateSnapshotModel(snapshotModel: currentSnapshotModel!)
@ -120,20 +133,47 @@ class RoundCalculatorService {
os_log("Finished calculating Round", log: RoundCalculatorService.LOG, type: .info) os_log("Finished calculating Round", log: RoundCalculatorService.LOG, type: .info)
} }
func addPlayerMove(playerName: String, playerMoves: [PlayerMove]) { func collectBaseSpecificMoves() -> [Int: [String: [PlayerMove]]] {
self.allPlayerMoves[playerName] = playerMoves
}
func mapPlayerMoveToAttackedBase(playerName: String, playerMove: PlayerMove) { var allPlayerMoves: [String: [PlayerMove]] = [:]
if self.baseSpecificMoves.keys.contains(playerMove.toBase) {
if (self.baseSpecificMoves[playerMove.toBase]?.keys.contains(playerName))!{ // collect moves from remote player
self.baseSpecificMoves[playerMove.toBase]?[playerName]?.append(playerMove) for playerMove in DataService.sharedInstance.remotePlayerMoves {
} else { allPlayerMoves[playerMove.key] = playerMove.value.localPlayerMoves
self.baseSpecificMoves[playerMove.toBase]?.merge([playerName: [playerMove]]){(current, _) in current}
}
} else {
self.baseSpecificMoves[playerMove.toBase] = [playerName: [playerMove]]
} }
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) { func resolvePlayerMove(playerMove: PlayerMove, unitCount: Int, ownership: String?, resolveType: String) {