[F] Fix detailed ranks

pull/23/head
Azalea 2024-03-15 00:37:30 -04:00
parent 0100140dc0
commit e85533686e
8 changed files with 67 additions and 24 deletions

3
.gitignore vendored
View File

@ -76,4 +76,5 @@ gradle-app.setting
### Gradle Patch ###
# Java heap dump
*.hprof
.jpb
.jpb
src/main/resources/meta/*/*.json

View File

@ -7,7 +7,9 @@ import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNamingStrategy
import org.apache.tika.Tika
import org.apache.tika.mime.MimeTypes
import org.springframework.http.HttpStatus
@ -47,12 +49,15 @@ val emailRegex = "^(?=.{1,64}@)[\\p{L}0-9_-]+(\\.[\\p{L}0-9_-]+)*@[^-][\\p{L}0-9
fun Str.isValidEmail(): Bool = emailRegex.matches(this)
// Global tools
@OptIn(ExperimentalSerializationApi::class)
val JSON = Json {
ignoreUnknownKeys = true
isLenient = true
namingStrategy = JsonNamingStrategy.SnakeCase
}
val HTTP = HttpClient(CIO) {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
isLenient = true
})
json(JSON)
}
}
val TIKA = Tika()

View File

@ -19,7 +19,7 @@ class Chusan(
val userPlaylogRepository: UserPlaylogRepository,
val userDataRepository: UserDataRepository,
val userGeneralDataRepository: UserGeneralDataRepository
): GameApiController
): GameApiController("chu3")
{
override suspend fun trend(@RP username: Str): List<TrendOut> = us.cardByName(username) { card ->
findTrend(userPlaylogRepository.findByUser_Card_ExtId(card.extId)

View File

@ -19,7 +19,7 @@ class Maimai2(
val userPlaylogRepository: UserPlaylogRepository,
val userDataRepository: UserDataRepository,
val userGeneralDataRepository: UserGeneralDataRepository
): GameApiController
): GameApiController("mai2")
{
override suspend fun trend(@RP username: Str): List<TrendOut> = us.cardByName(username) { card ->
findTrend(userPlaylogRepository.findByUser_Card_ExtId(card.extId)

View File

@ -1,8 +1,10 @@
package icu.samnyan.aqua.net.games
import ext.API
import ext.JSON
import ext.RP
import icu.samnyan.aqua.net.utils.IGenericGamePlaylog
import kotlinx.serialization.Serializable
data class TrendOut(val date: String, val rating: Int, val plays: Int)
@ -19,6 +21,7 @@ data class GenericGameSummary(
val rating: Int,
val ratingHighest: Int,
val ranks: List<RankCount>,
val detailedRanks: Map<Int, Map<String, Int>>,
val maxCombo: Int,
val fullCombo: Int,
val allPerfect: Int,
@ -46,15 +49,35 @@ data class GenericRankingPlayer(
val lastSeen: String
)
interface GameApiController {
@Serializable
data class GenericMusicMeta(
val name: String?,
val ver: String,
val notes: List<GenericNoteMeta>
)
@Serializable
data class GenericNoteMeta(
val lv: Double,
val lvId: Int
)
abstract class GameApiController(name: String) {
val musicMapping: Map<Int, GenericMusicMeta> = GameApiController::class.java
.getResourceAsStream("/meta/$name/music.json")
.use { it?.reader()?.readText() }
?.let { JSON.decodeFromString<Map<String, GenericMusicMeta>>(it) }
?.mapKeys { it.key.toInt() }
?: emptyMap()
@API("trend")
suspend fun trend(@RP username: String): List<TrendOut>
abstract suspend fun trend(@RP username: String): List<TrendOut>
@API("user-summary")
suspend fun userSummary(@RP username: String): GenericGameSummary
abstract suspend fun userSummary(@RP username: String): GenericGameSummary
@API("ranking")
suspend fun ranking(): List<GenericRankingPlayer>
abstract suspend fun ranking(): List<GenericRankingPlayer>
@API("playlog")
suspend fun playlog(@RP id: Long): IGenericGamePlaylog
abstract suspend fun playlog(@RP id: Long): IGenericGamePlaylog
@API("recent")
suspend fun recent(@RP username: String): List<IGenericGamePlaylog>
abstract suspend fun recent(@RP username: String): List<IGenericGamePlaylog>
}

View File

@ -17,7 +17,7 @@ class Ongeki(
val userPlaylogRepository: UserPlaylogRepository,
val userDataRepository: UserDataRepository,
val userGeneralDataRepository: UserGeneralDataRepository
): GameApiController {
): GameApiController("ongeki") {
override suspend fun trend(username: String) = us.cardByName(username) { card ->
findTrend(userPlaylogRepository.findByUser_Card_ExtId(card.extId)
.map { TrendLog(it.playDate, it.playerRating) })

View File

@ -3,10 +3,7 @@ package icu.samnyan.aqua.net.utils
import ext.isoDate
import ext.millis
import ext.minus
import icu.samnyan.aqua.net.games.GenericGameSummary
import icu.samnyan.aqua.net.games.GenericRankingPlayer
import icu.samnyan.aqua.net.games.RankCount
import icu.samnyan.aqua.net.games.TrendOut
import icu.samnyan.aqua.net.games.*
import icu.samnyan.aqua.sega.general.model.Card
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.repository.NoRepositoryBean
@ -83,7 +80,7 @@ interface GenericPlaylogRepo {
fun List<IGenericGamePlaylog>.acc() = if (isEmpty()) 0.0 else sumOf { it.achievement }.toDouble() / size / 10000.0
fun genericUserSummary(
fun GameApiController.genericUserSummary(
card: Card,
userDataRepo: GenericUserDataRepo<*, *>,
userPlaylogRepo: GenericPlaylogRepo,
@ -95,11 +92,23 @@ fun genericUserSummary(
val user = userDataRepo.findByCard(card) ?: (404 - "Game data not found")
val plays = userPlaylogRepo.findByUserCardExtId(card.extId)
// O(6n) ranks algorithm: Loop through the entire list of plays,
// count the number of each rank
val ranks = shownRanks.associate { (_, v) -> v to 0 }.toMutableMap()
plays.forEach {
shownRanks.find { (s, _) -> it.achievement > s }?.let { (_, v) -> ranks[v] = ranks[v]!! + 1 }
// Detailed ranks: Find the number of each rank in each level category
// map<level, map<rank, count>>
val rankMap = shownRanks.associate { (_, v) -> v to 0 }
val detailedRanks = HashMap<Int, MutableMap<String, Int>>()
plays.forEach { play ->
val lvl = musicMapping[play.musicId]?.notes?.getOrNull(if (play.level == 10) 0 else play.level)?.lv ?: return@forEach
shownRanks.find { (s, _) -> play.achievement > s }?.let { (_, v) ->
val ranks = detailedRanks.getOrPut(lvl.toInt()) { rankMap.toMutableMap() }
ranks[v] = ranks[v]!! + 1
}
}
// Collapse detailed ranks to get non-detailed ranks map<rank, count>
val ranks = shownRanks.associate { (_, v) -> v to 0 }.toMutableMap().also { ranks ->
plays.forEach { play ->
shownRanks.find { (s, _) -> play.achievement > s }?.let { (_, v) -> ranks[v] = ranks[v]!! + 1 }
}
}
return GenericGameSummary(
@ -111,6 +120,7 @@ fun genericUserSummary(
rating = user.playerRating,
ratingHighest = user.highestRating,
ranks = ranks.map { (k, v) -> RankCount(k, v) },
detailedRanks = detailedRanks,
maxCombo = plays.maxOfOrNull { it.maxCombo } ?: 0,
fullCombo = plays.count { it.isFullCombo },
allPerfect = plays.count { it.isAllPerfect },

View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
curl "https://aquadx.net/d/ongeki/00/all-music.json" -o "ongeki/music.json"
curl "https://aquadx.net/d/mai2/00/all-music.json" -o "mai2/music.json"
curl "https://aquadx.net/d/chu3/00/all-music.json" -o "chu3/music.json"