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
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 {
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
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
+}
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
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(
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 }
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 {
}
}
}
+ 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"
+ +"##"
+ }
+ }
+ }
+ }
+ }
}
}