Skip to content

Commit

Permalink
Improve sorting of initiative members (#3)
Browse files Browse the repository at this point in the history
### What's done:
* Extract sorting logic into an interface
* Sort by base modifier if initiatives are equal
  • Loading branch information
petertrr committed Aug 29, 2021
1 parent 3177b5c commit 2c5b72e
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package io.github.petertrr.initbot

import io.github.petertrr.initbot.entities.Combatant
import io.github.petertrr.initbot.sorting.CombatantsSorter
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import kotlin.random.Random

class Initiative(
internal val members: MutableList<Combatant> = Collections.synchronizedList(mutableListOf()),
private val sorter: CombatantsSorter,
private val random: Random = Random.Default,
private val turnDurationSeconds: Long = 45L
) {
Expand Down Expand Up @@ -85,10 +87,8 @@ class Initiative(
} else {
currentCombatantIdx.set(0)
currentRound.incrementAndGet()
// sort members according to current initiative *in place*
members.sortByDescending {
it.getCurrentInitiativeSafe()
}
// sort members *in place*
sorter.sort(members)
RoundResult(currentRound.get(), members.asSequence())
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.github.petertrr.initbot.sorting

import io.github.petertrr.initbot.entities.Combatant

/**
* Provided list of combatants, sort them *in place* according to a particular initiative rules
*/
fun interface CombatantsSorter {
fun sort(combatants: MutableList<Combatant>)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.github.petertrr.initbot.sorting

import io.github.petertrr.initbot.entities.Combatant
import kotlin.random.Random
import kotlin.random.nextInt

class DescendantSorter(private val random: Random = Random.Default) : CombatantsSorter {
override fun sort(combatants: MutableList<Combatant>) {
combatants.sortWith { a, b ->
(b.getCurrentInitiativeSafe() - a.getCurrentInitiativeSafe()).let { initDiff ->
if (initDiff == 0){
(b.baseModifier - a.baseModifier).let { modDiff ->
if (modDiff == 0) {
// in this case, choose randomly
random.nextInt(-10..10)
} else {
modDiff
}
}
} else {
initDiff
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package io.github.petertrr.initbot

import io.github.petertrr.initbot.entities.Combatant
import io.github.petertrr.initbot.sorting.CombatantsSorter
import io.github.petertrr.initbot.sorting.DescendantSorter
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.Mock
Expand All @@ -12,40 +15,46 @@ import kotlin.random.Random
@ExtendWith(MockitoExtension::class)
class InitiativeTest {
@Mock lateinit var random: Random
private lateinit var sorter: CombatantsSorter

@BeforeEach
fun setUp() {
sorter = DescendantSorter(random)
}

@Test
fun `default initiative flow`() {
Mockito.`when`(random.nextInt(1, 20))
.thenReturn(1, 3, 5)
val initiative = Initiative(random = random)
.thenReturn(5, 3, 5)
val initiative = Initiative(sorter = sorter, random = random)
val defaultName = "Fallback"

initiative.executeCommands(
defaultName = defaultName,
"start",
"add 0 Tom",
"add 0 Jerry",
"add -2 Tom",
"add 0 Tuffy",
)

Assertions.assertIterableEquals(
listOf(Combatant("Tom", 0), Combatant("Jerry", 0), Combatant("Tuffy", 0)),
listOf(Combatant("Jerry", 0), Combatant("Tom", -2), Combatant("Tuffy", 0)),
initiative.members
)

// first round
initiative.executeCommands(
defaultName = defaultName,
"roll 0 Tom",
"roll +2 Jerry",
"roll +0 Jerry",
"roll 2 Tuffy",
)

Assertions.assertIterableEquals(
listOf(
Combatant("Tuffy", 0, 7),
Combatant("Jerry", 0, 5),
Combatant("Tom", 0, 1),
Combatant("Jerry", 0, 3),
Combatant("Tom", -2, 3),
),
(initiative.round() as RoundResult).combatants.asIterable()
)
Expand Down Expand Up @@ -83,7 +92,7 @@ class InitiativeTest {
fun `test with 6 members and removing before round`() {
Mockito.`when`(random.nextInt(1, 20))
.thenReturn(17, 14, 10, 10, 15, 7)
val initiative = Initiative(random = random)
val initiative = Initiative(sorter = sorter, random = random)
initiative.executeCommands(
defaultName = "Fallback",
"start",
Expand Down Expand Up @@ -132,7 +141,7 @@ class InitiativeTest {
fun `test`() {
Mockito.`when`(random.nextInt(1, 20))
.thenReturn(17, 14, 10, 10, 15, 7)
val initiative = Initiative(random = random)
val initiative = Initiative(sorter = sorter, random = random)
initiative.executeCommands(
defaultName = "Fallback",
"start",
Expand Down Expand Up @@ -170,7 +179,7 @@ class InitiativeTest {
@Test
fun `should fail if end is requested before start`() {
Assertions.assertThrows(IllegalStateException::class.java) {
val initiative = Initiative(random = random)
val initiative = Initiative(sorter = sorter, random = random)
val defaultName = "Fallback"
initiative.executeCommands(defaultName, "end")
}
Expand All @@ -179,7 +188,7 @@ class InitiativeTest {
@Test
fun `should fail if start is called multiple times`() {
Assertions.assertThrows(IllegalStateException::class.java) {
val initiative = Initiative(random = random)
val initiative = Initiative(sorter = sorter, random = random)
val defaultName = "Fallback"
initiative.executeCommands(defaultName, "start", "start")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import discord4j.rest.util.AllowedMentions
import io.github.petertrr.initbot.*
import io.github.petertrr.initbot.discord.entities.BotConfiguration
import io.github.petertrr.initbot.discord.entities.UserState
import io.github.petertrr.initbot.sorting.DescendantSorter
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.reactive.asFlow
import kotlinx.coroutines.reactor.mono
Expand Down Expand Up @@ -44,7 +45,10 @@ class InitiativeBot(private val botConfiguration: BotConfiguration) {
private fun initializeInitiativeIfAbsent(channelId: String) {
initiatives.computeIfAbsent(channelId) {
logger.info("Creating new Initiative for channel $it")
Initiative(turnDurationSeconds = botConfiguration.turnDurationSeconds)
Initiative(
sorter = DescendantSorter(),
turnDurationSeconds = botConfiguration.turnDurationSeconds
)
}
userNameByCharacterNameByChannel.computeIfAbsent(channelId) {
mutableMapOf()
Expand Down

0 comments on commit 2c5b72e

Please sign in to comment.