From c706aad1ee8f86bc3f353ce7d1dc4e04c137d643 Mon Sep 17 00:00:00 2001 From: wow890209 Date: Sun, 1 Oct 2023 17:51:42 +0800 Subject: [PATCH] refactor player class --- utopia-gamification/pom.xml | 5 + .../utopia/utopiagamification/HotFixTool.kt | 129 ------------------ .../utopiagamification/quest/domain/Player.kt | 74 +++------- .../quest/extensions/LevelSheet.kt | 42 +++--- .../listeners/ButtonInteractionListener.kt | 9 +- .../repositories/PlayerRepository.kt | 1 + .../repositoryimpl/MongodbPlayerRepository.kt | 2 +- .../quest/UtopiaGamificationQuestTest.kt | 11 ++ 8 files changed, 65 insertions(+), 208 deletions(-) delete mode 100644 utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/HotFixTool.kt diff --git a/utopia-gamification/pom.xml b/utopia-gamification/pom.xml index f8fc789b..cfa1162e 100644 --- a/utopia-gamification/pom.xml +++ b/utopia-gamification/pom.xml @@ -28,5 +28,10 @@ mongo-gateway ${revision} + + tw.waterballsa.utopia + utopia-test-kit + ${revision} + diff --git a/utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/HotFixTool.kt b/utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/HotFixTool.kt deleted file mode 100644 index cce1b1ca..00000000 --- a/utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/HotFixTool.kt +++ /dev/null @@ -1,129 +0,0 @@ -package tw.waterballsa.utopia.utopiagamification - -import net.dv8tion.jda.api.entities.Guild -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent -import net.dv8tion.jda.api.interactions.commands.OptionType -import net.dv8tion.jda.api.interactions.commands.build.CommandData -import net.dv8tion.jda.api.interactions.commands.build.Commands -import net.dv8tion.jda.api.interactions.commands.build.SubcommandData -import tw.waterballsa.utopia.utopiagamification.quest.domain.State -import tw.waterballsa.utopia.utopiagamification.quest.listeners.UtopiaGamificationListener -import tw.waterballsa.utopia.utopiagamification.repositories.MissionRepository -import tw.waterballsa.utopia.utopiagamification.repositories.PlayerRepository - -const val COMMAND_NAME = "hotfix" -const val FIND_COMMAND_NAME = "mission-log" -const val FIND_OPTION_NAME = "player" -const val CHECK_COMMAND_NAME = "check" - -//只能使用在 local 端。 -//@Component -class HotFixTool( - guild: Guild, - playerRepository: PlayerRepository, - private val missionRepository: MissionRepository -) : UtopiaGamificationListener(guild, playerRepository) { - - override fun commands(): List = listOf( - Commands.slash(COMMAND_NAME, "it is tools of fix quest system error") - .addSubcommands( - SubcommandData(FIND_COMMAND_NAME, "find repository state") - .addOption(OptionType.USER, FIND_OPTION_NAME, "quest player", true), - SubcommandData(CHECK_COMMAND_NAME, "check mission state fail"), - ) - ) - - override fun onSlashCommandInteraction(event: SlashCommandInteractionEvent) { - with(event) { - val commandInfo = fullCommandName.split(" ") - - if (commandInfo.first() != COMMAND_NAME) { - return - } - - when (commandInfo[1]) { - FIND_COMMAND_NAME -> handleFindCommand() - CHECK_COMMAND_NAME -> handleCheckCommand() - } - } - } - - private fun SlashCommandInteractionEvent.handleFindCommand() { - val user = getOption(FIND_OPTION_NAME)?.asUser ?: return - - deferReply().setEphemeral(true).queue() - - val missions = missionRepository.findAllByPlayerId(user.id) - var result = """ - |${user.effectiveName} (${user.id}) - |-------------------------------------------- - | - """.trimMargin() - - missions.ifEmpty { - result += "not found\n" - } - - missions.forEach { - result += "${it.quest.title}(${it.quest.id}) : state -> ${it.state}, date -> ${it.completedTime}\n" - } - - result += "--------------------------------------------\n" - - hook.editOriginal(result).queue() - } - - private fun SlashCommandInteractionEvent.handleCheckCommand() { - deferReply().setEphemeral(true).queue() - - val isOk = mutableListOf() - val notOK = mutableListOf() - val workerRound = mutableListOf() - val questProgressRate = mutableMapOf>() - - (10 downTo 1).forEach { - questProgressRate[it] = mutableListOf() - } - - missionRepository.findAllByQuestId(10).forEach { - isOk.add(it.player.id) - workerRound.add(it.player.id) - if (it.state == State.COMPLETED) { - questProgressRate.getOrDefault(10, mutableListOf()).add(it.player.id) - } - } - - (9 downTo 1).forEach { - val missions = missionRepository.findAllByQuestId(it) - - missions.forEach { mission -> - if (mission.state == State.COMPLETED) { - questProgressRate.getOrDefault(it, mutableListOf()).add(mission.player.id) - } - if (isOk.contains(mission.player.id).not() && notOK.contains(mission.player.id).not()) { - if (mission.state == State.IN_PROGRESS || mission.state == State.COMPLETED) { - isOk.add(mission.player.id) - } else { - notOK.add(mission.player.id) - } - } - } - } - - val rank = questProgressRate.map { - it.key to it.value.map { id -> - playerRepository.findPlayerById(id)?.name ?: id - } - } - - hook.editOriginal( - """ - not ok count: ${notOK.size} - is ok count: ${isOk.size} - -------------------------------------------------- - $rank - -------------------------------------------------- - """.trimIndent() - ).queue() - } -} diff --git a/utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/quest/domain/Player.kt b/utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/quest/domain/Player.kt index c0863d20..b2c86c38 100644 --- a/utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/quest/domain/Player.kt +++ b/utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/quest/domain/Player.kt @@ -1,79 +1,43 @@ package tw.waterballsa.utopia.utopiagamification.quest.domain -import tw.waterballsa.utopia.utopiagamification.quest.extensions.LevelSheet.Companion.calculateLevel +import tw.waterballsa.utopia.utopiagamification.quest.extensions.LevelSheet.Companion.toLevel import java.time.OffsetDateTime import java.time.OffsetDateTime.now -import kotlin.ULong.Companion.MIN_VALUE class Player( val id: String, var name: String, - var exp: ULong = MIN_VALUE, - var level: UInt = 1u, + exp: ULong = 0uL, + level: UInt = 1u, val joinDate: OffsetDateTime = now(), - var latestActivateDate: OffsetDateTime = now(), - var levelUpgradeDate: OffsetDateTime = now(), - val jdaRoles: MutableList = mutableListOf(), + latestActivateDate: OffsetDateTime = now(), + levelUpgradeDate: OffsetDateTime? = null, + val jdaRoles: MutableList = mutableListOf() ) { - init { - calculateLevel() - } + var exp = exp + private set - val currentLevelExpLimit - get() = getLevelExpLimit(level) + var level = level + private set - fun gainExp(rewardExp: ULong) { - exp += rewardExp - calculateLevel() - activate() - } + var levelUpgradeDate = levelUpgradeDate + private set - private fun calculateLevel() { - var explimit = getLevelExpLimit(level) - while (exp >= explimit) { - exp -= explimit - level++ - explimit = getLevelExpLimit(level) - levelUpgradeDate = now() - } - } + var latestActivateDate = latestActivateDate + private set - private fun calculateLevel1() { - val newLevel = calculateLevel(exp) - if (newLevel > level) { + fun gainExp(rewardExp: ULong) { + exp += rewardExp + val newLevel = exp.toLevel() + if (newLevel != level) { level = newLevel levelUpgradeDate = now() } + activate() } private fun activate() { latestActivateDate = now() } } - -private const val EXP_PER_MIN = 10u -private val COEFFICIENT = listOf(1u, 2u, 4u, 8u, 12u, 16u, 32u, 52u, 64u, 84u) -private val UPGRADE_TIME_TABLE = mutableListOf() -private fun getCoefficient(level: UInt): UInt { - if (level > 100u) { - return COEFFICIENT.last() - } - return COEFFICIENT[(level.toInt() - 1) / 10] -} - -private fun calculateUpgradeTime(level: UInt, table: MutableList): UInt { - table.getOrElse(level.toInt()) { - val result = if (level == 0u) { - 0u - } else { - calculateUpgradeTime(level - 1u, table) + EXP_PER_MIN * getCoefficient(level) - } - table.add(level.toInt(), result) - } - return table[level.toInt()] -} - -private fun getLevelExpLimit(level: UInt): UInt { - return calculateUpgradeTime(level, UPGRADE_TIME_TABLE) * EXP_PER_MIN -} diff --git a/utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/quest/extensions/LevelSheet.kt b/utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/quest/extensions/LevelSheet.kt index e5e95181..626dd013 100644 --- a/utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/quest/extensions/LevelSheet.kt +++ b/utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/quest/extensions/LevelSheet.kt @@ -1,54 +1,52 @@ package tw.waterballsa.utopia.utopiagamification.quest.extensions import tw.waterballsa.utopia.utopiagamification.quest.extensions.LevelSheet.LevelRange.Companion.LEVEL_ONE -import kotlin.ULong.Companion.MIN_VALUE +import java.lang.String.format + class LevelSheet private constructor() { companion object { - const val EXP_PER_MINUTES = 10u + private const val EXP_PER_MINUTES = 10u private const val MAX_LEVEL = 100 private val COEFFICIENTS = arrayOf(1u, 2u, 4u, 8u, 12u, 16u, 32u, 52u, 64u, 84u) - private val LEVEL_RANGES = generateSequence(LEVEL_ONE) { it.next() }.take(MAX_LEVEL) + private val LEVEL_TO_LEVEL_RANGE = generateSequence(LEVEL_ONE) { it.next() }.take(MAX_LEVEL).associateBy { it.level } + + // exp to level + fun ULong.toLevel() = (LEVEL_TO_LEVEL_RANGE.values.find { it.isLevelUp(this) } ?: LEVEL_ONE).level.toUInt() - fun calculateLevel(exp: ULong) = (LEVEL_RANGES.find { it.isExpGreaterThan(exp) } ?: LEVEL_ONE).level.toUInt() + // level to level range + fun UInt.toLevelRange(): LevelRange = LEVEL_TO_LEVEL_RANGE[toInt()] ?: throw IllegalArgumentException("The given level ($this) not found.") } - private class LevelRange private constructor(val level: Int = 1, previousLevelRange: LevelRange? = null) { + class LevelRange private constructor(val level: Int = 1, val previous: LevelRange? = null) { // 升級時間 - private val upgradeTime: ULong + val upgradeTime: ULong // 累積經驗值 - private val accExp: ULong + val accExp: ULong // 當前經驗值上限 - private val expLimit: ULong + val expLimit: ULong companion object { - val LEVEL_ONE = LevelRange() + val LEVEL_ONE = LevelRange(level = 1) } init { // 經驗值係數 val coefficient = COEFFICIENTS[level.coerceAtLeast(1).div(10).coerceAtMost(COEFFICIENTS.size.minus(1))] - upgradeTime = (previousLevelRange?.upgradeTime ?: MIN_VALUE).plus(EXP_PER_MINUTES.times(coefficient)) - accExp = (previousLevelRange?.accExp ?: MIN_VALUE).plus(EXP_PER_MINUTES.times(upgradeTime)) - expLimit = accExp.minus(previousLevelRange?.accExp ?: MIN_VALUE) + upgradeTime = (previous?.upgradeTime ?: 0u).plus(EXP_PER_MINUTES.times(coefficient)) + accExp = (previous?.accExp ?: 0u).plus(EXP_PER_MINUTES.times(upgradeTime)) + expLimit = accExp.minus(previous?.accExp ?: 0u) } fun next() = LevelRange(level.plus(1), this) - fun isExpGreaterThan(exp: ULong) = accExp > exp + fun isLevelUp(exp: ULong) = accExp > exp - override fun toString(): String { - return String.format( - "level: %3d, upgrade time: %5d, exp limit: %6d, acc exp: %7d", - level, - upgradeTime.toLong(), - expLimit.toLong(), - accExp.toLong() - ) - } + override fun toString(): String = format("| level: %3d | upgrade time: %5d | exp limit: %6d | acc exp: %7d | %n${"-".repeat(75)}", + level, upgradeTime.toLong(), expLimit.toLong(), accExp.toLong()) } } diff --git a/utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/quest/listeners/ButtonInteractionListener.kt b/utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/quest/listeners/ButtonInteractionListener.kt index 14bab171..fd75553e 100644 --- a/utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/quest/listeners/ButtonInteractionListener.kt +++ b/utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/quest/listeners/ButtonInteractionListener.kt @@ -6,6 +6,9 @@ import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle import org.springframework.stereotype.Component import tw.waterballsa.utopia.utopiagamification.quest.domain.Mission +import tw.waterballsa.utopia.utopiagamification.quest.domain.Player +import tw.waterballsa.utopia.utopiagamification.quest.extensions.LevelSheet.Companion.toLevelRange +import tw.waterballsa.utopia.utopiagamification.quest.extensions.LevelSheet.LevelRange.Companion.LEVEL_ONE import tw.waterballsa.utopia.utopiagamification.quest.extensions.publishToUser import tw.waterballsa.utopia.utopiagamification.quest.usecase.ClaimMissionRewardUsecase import tw.waterballsa.utopia.utopiagamification.repositories.PlayerRepository @@ -45,11 +48,12 @@ class UtopiaGamificationQuestListener( val request = ClaimMissionRewardUsecase.Request(player, questId) val presenter = object : ClaimMissionRewardUsecase.Presenter { override fun presentPlayerExpNotification(mission: Mission) { + publishMessage( """ ${player.name} 已獲得 ${mission.quest.reward.exp} exp!! 目前等級:${player.level} - 目前經驗值:${player.exp} / ${player.currentLevelExpLimit} + 目前經驗值:${player.currentExp()}/${player.level.toLevelRange().expLimit} """.trimIndent() ) } @@ -67,6 +71,9 @@ class UtopiaGamificationQuestListener( } } + private fun Player.currentExp(): ULong = + if(level == LEVEL_ONE.level.toUInt()) exp else exp - level.toLevelRange().previous!!.accExp + private fun ButtonInteractionEvent.splitButtonId(delimiters: String): List { val result = button.id?.split(delimiters) ?: return emptyList() if (result.size != 3) { diff --git a/utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/repositories/PlayerRepository.kt b/utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/repositories/PlayerRepository.kt index ed757864..03fef515 100644 --- a/utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/repositories/PlayerRepository.kt +++ b/utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/repositories/PlayerRepository.kt @@ -6,4 +6,5 @@ interface PlayerRepository { fun findPlayerById(id: String): Player? fun savePlayer(player: Player): Player + } diff --git a/utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/repositories/mongodb/repositoryimpl/MongodbPlayerRepository.kt b/utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/repositories/mongodb/repositoryimpl/MongodbPlayerRepository.kt index 094d82a2..0aa4749d 100644 --- a/utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/repositories/mongodb/repositoryimpl/MongodbPlayerRepository.kt +++ b/utopia-gamification/src/main/kotlin/tw/waterballsa/utopia/utopiagamification/repositories/mongodb/repositoryimpl/MongodbPlayerRepository.kt @@ -46,5 +46,5 @@ data class PlayerDocument( val level: Int, val joinDate: OffsetDateTime, val latestActivateDate: OffsetDateTime, - val levelUpgradeDate: OffsetDateTime, + val levelUpgradeDate: OffsetDateTime? ) diff --git a/utopia-gamification/src/test/kotlin/tw/waterballsa/utopia/utopiagmification/quest/UtopiaGamificationQuestTest.kt b/utopia-gamification/src/test/kotlin/tw/waterballsa/utopia/utopiagmification/quest/UtopiaGamificationQuestTest.kt index 83828834..f46e1d3a 100644 --- a/utopia-gamification/src/test/kotlin/tw/waterballsa/utopia/utopiagmification/quest/UtopiaGamificationQuestTest.kt +++ b/utopia-gamification/src/test/kotlin/tw/waterballsa/utopia/utopiagmification/quest/UtopiaGamificationQuestTest.kt @@ -1,5 +1,6 @@ package tw.waterballsa.utopia.utopiagmification.quest +import org.assertj.core.api.Assertions.* import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -74,5 +75,15 @@ class UtopiaGamificationQuestTest { assertFalse(activityMission.isCompleted()) } + @Test + fun `given playerA level 3 and exp 650, when player gain 6000 exp, then level is 11 and exp is 6650`() { + val player = Player("A", "A", 650u, 3u) + val rewardExp = 6000uL + player.gainExp(rewardExp) + + assertThat(player.exp).isEqualTo(6650uL) + assertThat(player.level).isEqualTo(11u) + } + private fun Player.acceptQuest(quest: Quest) = Mission(this, quest) }