From d613daa90be6a2e6c5c92ce3b6a6d01ac4b453db Mon Sep 17 00:00:00 2001 From: TheSaminator Date: Fri, 3 Jun 2022 11:27:29 -0400 Subject: [PATCH] Make changes to AI testing --- .../starshipfights/game/ai/ai_optimization.kt | 30 ++++++++++-- .../kotlin/starshipfights/game/ai/util.kt | 7 +++ .../starshipfights/game/ai/AITesting.kt | 47 +++++++++++++++---- 3 files changed, 71 insertions(+), 13 deletions(-) diff --git a/src/commonMain/kotlin/starshipfights/game/ai/ai_optimization.kt b/src/commonMain/kotlin/starshipfights/game/ai/ai_optimization.kt index fd998e5..cc41052 100644 --- a/src/commonMain/kotlin/starshipfights/game/ai/ai_optimization.kt +++ b/src/commonMain/kotlin/starshipfights/game/ai/ai_optimization.kt @@ -244,7 +244,12 @@ fun generateOptimizationInitialState(hostFaction: Faction, guestFaction: Faction ) } -suspend fun performTrials(numTrialsPerPairing: Int, instincts: Set, validBattleSizes: Set = BattleSize.values().toSet(), validFactions: Set = Faction.values().toSet()): Map { +data class InstinctGamePairing( + val host: Instincts, + val guest: Instincts +) + +suspend fun performTrials(numTrialsPerPairing: Int, instincts: Set, validBattleSizes: Set = BattleSize.values().toSet(), validFactions: Set = Faction.values().toSet()): Map { return coroutineScope { instincts.associateWith { host -> async { @@ -258,7 +263,7 @@ suspend fun performTrials(numTrialsPerPairing: Int, instincts: Set, v val guestFaction = validFactions.random() val gameState = generateOptimizationInitialState(hostFaction, guestFaction, BattleInfo(battleSize, BattleBackground.BLUE_BROWN)) - val winner = withTimeoutOrNull(150_000L) { + val winner = withTimeoutOrNull(30_000L * numTrialsPerPairing) { performTestSession(gameState, host, guest) } @@ -270,10 +275,25 @@ suspend fun performTrials(numTrialsPerPairing: Int, instincts: Set, v else -> 0 } } - }.sumOf { it.await() } + }.map { guest to it.await() } } - }.sumOf { it.await() } + }.flatMap { it.await() }.toMap() } - }.mapValues { (_, it) -> it.await() } + }.mapValues { (_, it) -> it.await() }.flatten().mapKeys { (k, _) -> + InstinctGamePairing(k.first, k.second) + } } } + +data class InstinctVictoryPairing( + val winner: Instincts, + val loser: Instincts +) + +fun Map.victoriesFor(instincts: Instincts) = filterKeys { (host, _) -> host == instincts }.values.sum() - filterKeys { (_, guest) -> guest == instincts }.values.sum() + +fun Map.toVictoryMap() = keys.associate { (host, _) -> host to victoriesFor(host) } + +fun Map.toVictoryPairingMap() = keys.associate { (host, guest) -> + InstinctVictoryPairing(host, guest) to ((get(InstinctGamePairing(host, guest)) ?: 0) - (get(InstinctGamePairing(guest, host)) ?: 0)) +} diff --git a/src/commonMain/kotlin/starshipfights/game/ai/util.kt b/src/commonMain/kotlin/starshipfights/game/ai/util.kt index 9e121bb..3f3d42c 100644 --- a/src/commonMain/kotlin/starshipfights/game/ai/util.kt +++ b/src/commonMain/kotlin/starshipfights/game/ai/util.kt @@ -40,6 +40,13 @@ fun Map.weightedRandomOrNull(random: Random = Random): T? { else null } +fun Map>.flatten(): Map, V> = + toList().flatMap { (k, v) -> + v.map { (l, w) -> + (k to l) to w + } + }.toMap() + fun Map>.transpose(): Map> = flatMap { (k, v) -> v.map { it to k } } .groupBy(Pair::first, Pair::second) diff --git a/src/jvmTest/kotlin/starshipfights/game/ai/AITesting.kt b/src/jvmTest/kotlin/starshipfights/game/ai/AITesting.kt index 67433e4..bf6287e 100644 --- a/src/jvmTest/kotlin/starshipfights/game/ai/AITesting.kt +++ b/src/jvmTest/kotlin/starshipfights/game/ai/AITesting.kt @@ -70,10 +70,14 @@ object AITesting { val allowedFactions = allowedFactionChoices[allowedFactionIndex] - val instinctSuccessRate = runBlocking { + val instinctPairingSuccessRate = runBlocking { performTrials(numTrials, instinctVectors, allowedBattleSizes, allowedFactions) } + val instinctVictories = instinctPairingSuccessRate.toVictoryPairingMap() + + val instinctSuccessRate = instinctPairingSuccessRate.toVictoryMap() + val indexedInstincts = instinctSuccessRate .toList() .sortedBy { (_, v) -> v } @@ -94,8 +98,8 @@ object AITesting { p { +"Battle Sizes Allowed: ${allowedBattleSizes.singleOrNull()?.displayName ?: "All"}" } p { +"Factions Allowed: ${allowedFactions.singleOrNull()?.polityName ?: "All"}" } h2 { +"Instincts Vectors and Battle Results" } + val cellStyle = "border: 1px solid rgba(0, 0, 0, 0.6)" table { - val cellStyle = "border: 1px solid rgba(0, 0, 0, 0.6)" thead { tr { th(scope = ThScope.row) { @@ -104,18 +108,17 @@ object AITesting { } th(scope = ThScope.col) { style = cellStyle - +"Battles Won as Host" + +"Battles Won" } - allInstincts.forEach { + for (it in allInstincts) th(scope = ThScope.col) { style = cellStyle +it.key } - } } } tbody { - indexedInstincts.forEach { (i, pair) -> + for ((i, pair) in indexedInstincts) { val (instincts, successRate) = pair tr { th(scope = ThScope.row) { @@ -126,16 +129,44 @@ object AITesting { style = cellStyle +"$successRate" } - allInstincts.forEach { key -> + for (key in allInstincts) td { style = cellStyle +"${instincts[key]}" } - } } } } } + h2 { +"Instincts Pairing Battle Results" } + table { + tr { + th { + style = cellStyle + +"Winner \\ Loser" + } + for ((i, _) in indexedInstincts) + th(scope = ThScope.col) { + style = cellStyle + +"Instincts $i" + } + } + for ((i, v) in indexedInstincts) + tr { + th(scope = ThScope.row) { + style = cellStyle + +"Instincts $i" + } + for ((_, w) in indexedInstincts) + td { + val pairing = InstinctVictoryPairing(v.first, w.first) + val victories = instinctVictories[pairing] ?: 0 + + style = cellStyle + +"$victories" + } + } + } } } -- 2.25.1