Add histograms to AI training
authorTheSaminator <TheSaminator@users.noreply.github.com>
Sun, 5 Jun 2022 19:52:13 +0000 (15:52 -0400)
committerTheSaminator <TheSaminator@users.noreply.github.com>
Sun, 5 Jun 2022 19:52:13 +0000 (15:52 -0400)
src/commonMain/kotlin/starshipfights/game/ai/ai_optimization.kt
src/commonMain/kotlin/starshipfights/game/ai/util.kt
src/jvmTest/kotlin/starshipfights/game/ai/AITesting.kt

index 035724cc8d1ee7580e96ede9c1a7d1172e788eff..09695b85eff59ca913b2faed193775e4400e198f 100644 (file)
@@ -5,6 +5,7 @@ import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.channels.ReceiveChannel
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.selects.select
 import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.sync.withLock
 import starshipfights.data.Id
@@ -249,7 +250,7 @@ data class InstinctGamePairing(
        val guest: Instincts
 )
 
-suspend fun performTrials(numTrialsPerPairing: Int, instincts: Set<Instincts>, validBattleSizes: Set<BattleSize> = BattleSize.values().toSet(), validFactions: Set<Faction> = Faction.values().toSet()): Map<InstinctGamePairing, Int> {
+suspend fun performTrials(numTrialsPerPairing: Int, instincts: Set<Instincts>, validBattleSizes: Set<BattleSize> = BattleSize.values().toSet(), validFactions: Set<Faction> = Faction.values().toSet(), cancellationJob: Job = Job(), onProgress: suspend () -> Unit = {}): Map<InstinctGamePairing, Int> {
        return coroutineScope {
                instincts.associateWith { host ->
                        async {
@@ -263,11 +264,19 @@ suspend fun performTrials(numTrialsPerPairing: Int, instincts: Set<Instincts>, v
                                                                val guestFaction = validFactions.random()
                                                                
                                                                val gameState = generateOptimizationInitialState(hostFaction, guestFaction, BattleInfo(battleSize, BattleBackground.BLUE_BROWN))
-                                                               val winner = withTimeoutOrNull(30_000L * numTrialsPerPairing) {
-                                                                       performTestSession(gameState, host, guest)
+                                                               val winner = withTimeoutOrNull((20_000L * numTrialsPerPairing) + (400L * numTrialsPerPairing * numTrialsPerPairing)) {
+                                                                       val deferred = async(cancellationJob) {
+                                                                               performTestSession(gameState, host, guest)
+                                                                       }
+                                                                       
+                                                                       select<GlobalSide?> {
+                                                                               cancellationJob.onJoin { null }
+                                                                               deferred.onAwait { it }
+                                                                       }
                                                                }
                                                                
                                                                logInfo("A trial has ended! Winner: ${winner ?: "NEITHER"}")
+                                                               onProgress()
                                                                
                                                                when (winner) {
                                                                        GlobalSide.HOST -> 1
@@ -297,3 +306,17 @@ fun Map<InstinctGamePairing, Int>.toVictoryMap() = keys.associate { (host, _) ->
 fun Map<InstinctGamePairing, Int>.toVictoryPairingMap() = keys.associate { (host, guest) ->
        InstinctVictoryPairing(host, guest) to ((get(InstinctGamePairing(host, guest)) ?: 0) - (get(InstinctGamePairing(guest, host)) ?: 0))
 }
+
+fun Map<Instincts, Int>.successHistograms(numSegments: Int) = allInstincts.associateWith { instinct ->
+       val ranges = (0..numSegments).map { it.toDouble() / numSegments }.windowed(2) { (begin, end) ->
+               val rBegin = instinct.randRange.start + (begin * instinct.randRange.size)
+               val rEnd = instinct.randRange.start + (end * instinct.randRange.size)
+               rBegin..rEnd
+       }
+       
+       val perRange = ranges.associateWith { range ->
+               filterKeys { it[instinct] in range }.values.sum()
+       }
+       
+       perRange
+}
index 07cd069d938c9e8111f9b8894204be3e0c488393..92bb8a873064603667ff0fdfdd2e9de697f746c8 100644 (file)
@@ -16,6 +16,9 @@ expect fun logError(message: Any?)
 
 fun ClosedFloatingPointRange<Double>.random(random: Random = Random) = random.nextDouble(start, endInclusive.nextUp())
 
+val ClosedFloatingPointRange<Double>.size: Double
+       get() = endInclusive.nextUp() - start
+
 fun <T : Any> Map<T, Double>.weightedRandom(random: Random = Random): T {
        return weightedRandomOrNull(random) ?: error("Cannot take weighted random of effectively-empty collection!")
 }
index 625279dda445d85d185c9ee975a1603076cf1135..8c5a1415502b26a08d9ec194323bffe8f8ffb921 100644 (file)
@@ -1,13 +1,16 @@
 package starshipfights.game.ai
 
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.runBlocking
 import kotlinx.html.*
 import kotlinx.html.stream.createHTML
 import starshipfights.game.BattleSize
 import starshipfights.game.Faction
 import java.io.File
+import java.util.concurrent.atomic.AtomicInteger
 import javax.swing.JOptionPane
 import javax.swing.UIManager
+import kotlin.concurrent.thread
 
 object AITesting {
        @JvmStatic
@@ -26,9 +29,10 @@ object AITesting {
                
                if (instinctVectorIndex == JOptionPane.CLOSED_OPTION) return
                
-               val instinctVectors = genInstinctCandidates(instinctVectorCounts[instinctVectorIndex])
+               val instinctVectorCount = instinctVectorCounts[instinctVectorIndex]
+               val instinctVectors = genInstinctCandidates(instinctVectorCount)
                
-               val numTrialCounts = listOf(3, 5, 7, 10)
+               val numTrialCounts = listOf(3, 5, 7, 10, 25)
                val numTrialOptions = numTrialCounts.map { it.toString() }.toTypedArray()
                
                val numTrialIndex = JOptionPane.showOptionDialog(
@@ -70,14 +74,40 @@ object AITesting {
                
                val allowedFactions = allowedFactionChoices[allowedFactionIndex]
                
+               val allTrials = numTrials * instinctVectorCount * instinctVectorCount
+               val doneTrials = AtomicInteger(0)
+               val cancelJob = Job()
+               
+               thread {
+                       while (true) {
+                               val options = arrayOf("Update", "Cancel")
+                               
+                               val option = JOptionPane.showOptionDialog(
+                                       null, "Please select an action. ${doneTrials.get()}/$allTrials trials are done.",
+                                       "Trials in Progress", JOptionPane.DEFAULT_OPTION,
+                                       JOptionPane.INFORMATION_MESSAGE, null,
+                                       options, options[0]
+                               )
+                               
+                               if (option == 1) {
+                                       cancelJob.cancel()
+                                       break
+                               }
+                       }
+               }
+               
                val instinctPairingSuccessRate = runBlocking {
-                       performTrials(numTrials, instinctVectors, allowedBattleSizes, allowedFactions)
+                       performTrials(numTrials, instinctVectors, allowedBattleSizes, allowedFactions, cancelJob) {
+                               doneTrials.getAndIncrement()
+                       }
                }
                
                val instinctVictories = instinctPairingSuccessRate.toVictoryPairingMap()
                
                val instinctSuccessRate = instinctPairingSuccessRate.toVictoryMap()
                
+               val instinctHistograms = instinctSuccessRate.successHistograms(instinctVectorCount)
+               
                val indexedInstincts = instinctSuccessRate
                        .toList()
                        .sortedBy { (_, v) -> v }
@@ -98,7 +128,7 @@ 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)"
+                               val cellStyle = "border:1px solid rgba(0, 0, 0, 0.6)"
                                table {
                                        thead {
                                                tr {
@@ -167,6 +197,54 @@ object AITesting {
                                                                }
                                                }
                                }
+                               h2 { +"Instinct Victory Histograms" }
+                               for ((instinct, histogram) in instinctHistograms) {
+                                       val sortedHistogram = histogram.toList().sortedBy { (range, _) -> range.start }
+                                       val lowestNumber = sortedHistogram.minOf { (_, score) -> score }
+                                       val highestNumber = sortedHistogram.maxOf { (_, score) -> score }
+                                       
+                                       h3 { +"Instinct ${instinct.key}" }
+                                       table {
+                                               thead {
+                                                       tr {
+                                                               th(scope = ThScope.col) {
+                                                                       style = cellStyle
+                                                                       +"Value Range"
+                                                               }
+                                                               for (num in lowestNumber..highestNumber)
+                                                                       th(scope = ThScope.col) {
+                                                                               style = cellStyle
+                                                                               +"$num"
+                                                                       }
+                                                       }
+                                               }
+                                               tbody {
+                                                       for ((range, successRate) in sortedHistogram)
+                                                               tr {
+                                                                       th {
+                                                                               style = cellStyle
+                                                                               +"${range.start}"
+                                                                               br
+                                                                               +"${range.endInclusive}"
+                                                                       }
+                                                                       for (i in lowestNumber until successRate)
+                                                                               td {
+                                                                                       style = "$cellStyle;background-color:#AAA;color:#AAA"
+                                                                                       +"##"
+                                                                               }
+                                                                       td {
+                                                                               style = "$cellStyle;background-color:#555;color:#555"
+                                                                               +"##"
+                                                                       }
+                                                                       for (i in successRate until highestNumber)
+                                                                               td {
+                                                                                       style = "$cellStyle;background-color:#FFF;color:#FFF"
+                                                                                       +"##"
+                                                                               }
+                                                               }
+                                               }
+                                       }
+                               }
                        }
                }