mirror of https://github.com/hykilpikonna/AquaDX
[F] Fix detailed ranks
parent
0100140dc0
commit
e85533686e
|
@ -76,4 +76,5 @@ gradle-app.setting
|
||||||
### Gradle Patch ###
|
### Gradle Patch ###
|
||||||
# Java heap dump
|
# Java heap dump
|
||||||
*.hprof
|
*.hprof
|
||||||
.jpb
|
.jpb
|
||||||
|
src/main/resources/meta/*/*.json
|
||||||
|
|
|
@ -7,7 +7,9 @@ import io.ktor.client.plugins.contentnegotiation.*
|
||||||
import io.ktor.serialization.kotlinx.json.*
|
import io.ktor.serialization.kotlinx.json.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonNamingStrategy
|
||||||
import org.apache.tika.Tika
|
import org.apache.tika.Tika
|
||||||
import org.apache.tika.mime.MimeTypes
|
import org.apache.tika.mime.MimeTypes
|
||||||
import org.springframework.http.HttpStatus
|
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)
|
fun Str.isValidEmail(): Bool = emailRegex.matches(this)
|
||||||
|
|
||||||
// Global tools
|
// Global tools
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
val JSON = Json {
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
isLenient = true
|
||||||
|
namingStrategy = JsonNamingStrategy.SnakeCase
|
||||||
|
}
|
||||||
val HTTP = HttpClient(CIO) {
|
val HTTP = HttpClient(CIO) {
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
json(Json {
|
json(JSON)
|
||||||
ignoreUnknownKeys = true
|
|
||||||
isLenient = true
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val TIKA = Tika()
|
val TIKA = Tika()
|
||||||
|
|
|
@ -19,7 +19,7 @@ class Chusan(
|
||||||
val userPlaylogRepository: UserPlaylogRepository,
|
val userPlaylogRepository: UserPlaylogRepository,
|
||||||
val userDataRepository: UserDataRepository,
|
val userDataRepository: UserDataRepository,
|
||||||
val userGeneralDataRepository: UserGeneralDataRepository
|
val userGeneralDataRepository: UserGeneralDataRepository
|
||||||
): GameApiController
|
): GameApiController("chu3")
|
||||||
{
|
{
|
||||||
override suspend fun trend(@RP username: Str): List<TrendOut> = us.cardByName(username) { card ->
|
override suspend fun trend(@RP username: Str): List<TrendOut> = us.cardByName(username) { card ->
|
||||||
findTrend(userPlaylogRepository.findByUser_Card_ExtId(card.extId)
|
findTrend(userPlaylogRepository.findByUser_Card_ExtId(card.extId)
|
||||||
|
|
|
@ -19,7 +19,7 @@ class Maimai2(
|
||||||
val userPlaylogRepository: UserPlaylogRepository,
|
val userPlaylogRepository: UserPlaylogRepository,
|
||||||
val userDataRepository: UserDataRepository,
|
val userDataRepository: UserDataRepository,
|
||||||
val userGeneralDataRepository: UserGeneralDataRepository
|
val userGeneralDataRepository: UserGeneralDataRepository
|
||||||
): GameApiController
|
): GameApiController("mai2")
|
||||||
{
|
{
|
||||||
override suspend fun trend(@RP username: Str): List<TrendOut> = us.cardByName(username) { card ->
|
override suspend fun trend(@RP username: Str): List<TrendOut> = us.cardByName(username) { card ->
|
||||||
findTrend(userPlaylogRepository.findByUser_Card_ExtId(card.extId)
|
findTrend(userPlaylogRepository.findByUser_Card_ExtId(card.extId)
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package icu.samnyan.aqua.net.games
|
package icu.samnyan.aqua.net.games
|
||||||
|
|
||||||
import ext.API
|
import ext.API
|
||||||
|
import ext.JSON
|
||||||
import ext.RP
|
import ext.RP
|
||||||
import icu.samnyan.aqua.net.utils.IGenericGamePlaylog
|
import icu.samnyan.aqua.net.utils.IGenericGamePlaylog
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
data class TrendOut(val date: String, val rating: Int, val plays: Int)
|
data class TrendOut(val date: String, val rating: Int, val plays: Int)
|
||||||
|
|
||||||
|
@ -19,6 +21,7 @@ data class GenericGameSummary(
|
||||||
val rating: Int,
|
val rating: Int,
|
||||||
val ratingHighest: Int,
|
val ratingHighest: Int,
|
||||||
val ranks: List<RankCount>,
|
val ranks: List<RankCount>,
|
||||||
|
val detailedRanks: Map<Int, Map<String, Int>>,
|
||||||
val maxCombo: Int,
|
val maxCombo: Int,
|
||||||
val fullCombo: Int,
|
val fullCombo: Int,
|
||||||
val allPerfect: Int,
|
val allPerfect: Int,
|
||||||
|
@ -46,15 +49,35 @@ data class GenericRankingPlayer(
|
||||||
val lastSeen: String
|
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")
|
@API("trend")
|
||||||
suspend fun trend(@RP username: String): List<TrendOut>
|
abstract suspend fun trend(@RP username: String): List<TrendOut>
|
||||||
@API("user-summary")
|
@API("user-summary")
|
||||||
suspend fun userSummary(@RP username: String): GenericGameSummary
|
abstract suspend fun userSummary(@RP username: String): GenericGameSummary
|
||||||
@API("ranking")
|
@API("ranking")
|
||||||
suspend fun ranking(): List<GenericRankingPlayer>
|
abstract suspend fun ranking(): List<GenericRankingPlayer>
|
||||||
@API("playlog")
|
@API("playlog")
|
||||||
suspend fun playlog(@RP id: Long): IGenericGamePlaylog
|
abstract suspend fun playlog(@RP id: Long): IGenericGamePlaylog
|
||||||
@API("recent")
|
@API("recent")
|
||||||
suspend fun recent(@RP username: String): List<IGenericGamePlaylog>
|
abstract suspend fun recent(@RP username: String): List<IGenericGamePlaylog>
|
||||||
}
|
}
|
|
@ -17,7 +17,7 @@ class Ongeki(
|
||||||
val userPlaylogRepository: UserPlaylogRepository,
|
val userPlaylogRepository: UserPlaylogRepository,
|
||||||
val userDataRepository: UserDataRepository,
|
val userDataRepository: UserDataRepository,
|
||||||
val userGeneralDataRepository: UserGeneralDataRepository
|
val userGeneralDataRepository: UserGeneralDataRepository
|
||||||
): GameApiController {
|
): GameApiController("ongeki") {
|
||||||
override suspend fun trend(username: String) = us.cardByName(username) { card ->
|
override suspend fun trend(username: String) = us.cardByName(username) { card ->
|
||||||
findTrend(userPlaylogRepository.findByUser_Card_ExtId(card.extId)
|
findTrend(userPlaylogRepository.findByUser_Card_ExtId(card.extId)
|
||||||
.map { TrendLog(it.playDate, it.playerRating) })
|
.map { TrendLog(it.playDate, it.playerRating) })
|
||||||
|
|
|
@ -3,10 +3,7 @@ package icu.samnyan.aqua.net.utils
|
||||||
import ext.isoDate
|
import ext.isoDate
|
||||||
import ext.millis
|
import ext.millis
|
||||||
import ext.minus
|
import ext.minus
|
||||||
import icu.samnyan.aqua.net.games.GenericGameSummary
|
import icu.samnyan.aqua.net.games.*
|
||||||
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.sega.general.model.Card
|
import icu.samnyan.aqua.sega.general.model.Card
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import org.springframework.data.repository.NoRepositoryBean
|
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 List<IGenericGamePlaylog>.acc() = if (isEmpty()) 0.0 else sumOf { it.achievement }.toDouble() / size / 10000.0
|
||||||
|
|
||||||
fun genericUserSummary(
|
fun GameApiController.genericUserSummary(
|
||||||
card: Card,
|
card: Card,
|
||||||
userDataRepo: GenericUserDataRepo<*, *>,
|
userDataRepo: GenericUserDataRepo<*, *>,
|
||||||
userPlaylogRepo: GenericPlaylogRepo,
|
userPlaylogRepo: GenericPlaylogRepo,
|
||||||
|
@ -95,11 +92,23 @@ fun genericUserSummary(
|
||||||
val user = userDataRepo.findByCard(card) ?: (404 - "Game data not found")
|
val user = userDataRepo.findByCard(card) ?: (404 - "Game data not found")
|
||||||
val plays = userPlaylogRepo.findByUserCardExtId(card.extId)
|
val plays = userPlaylogRepo.findByUserCardExtId(card.extId)
|
||||||
|
|
||||||
// O(6n) ranks algorithm: Loop through the entire list of plays,
|
// Detailed ranks: Find the number of each rank in each level category
|
||||||
// count the number of each rank
|
// map<level, map<rank, count>>
|
||||||
val ranks = shownRanks.associate { (_, v) -> v to 0 }.toMutableMap()
|
val rankMap = shownRanks.associate { (_, v) -> v to 0 }
|
||||||
plays.forEach {
|
val detailedRanks = HashMap<Int, MutableMap<String, Int>>()
|
||||||
shownRanks.find { (s, _) -> it.achievement > s }?.let { (_, v) -> ranks[v] = ranks[v]!! + 1 }
|
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(
|
return GenericGameSummary(
|
||||||
|
@ -111,6 +120,7 @@ fun genericUserSummary(
|
||||||
rating = user.playerRating,
|
rating = user.playerRating,
|
||||||
ratingHighest = user.highestRating,
|
ratingHighest = user.highestRating,
|
||||||
ranks = ranks.map { (k, v) -> RankCount(k, v) },
|
ranks = ranks.map { (k, v) -> RankCount(k, v) },
|
||||||
|
detailedRanks = detailedRanks,
|
||||||
maxCombo = plays.maxOfOrNull { it.maxCombo } ?: 0,
|
maxCombo = plays.maxOfOrNull { it.maxCombo } ?: 0,
|
||||||
fullCombo = plays.count { it.isFullCombo },
|
fullCombo = plays.count { it.isFullCombo },
|
||||||
allPerfect = plays.count { it.isAllPerfect },
|
allPerfect = plays.count { it.isAllPerfect },
|
||||||
|
|
|
@ -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"
|
Loading…
Reference in New Issue