Commit before deleting lots of code
Signed-off-by: prescientmoon <git@moonythm.dev>
This commit is contained in:
parent
0c90628c9d
commit
d260a11263
164
Cargo.lock
generated
164
Cargo.lock
generated
|
@ -118,15 +118,6 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "atomic-polyfill"
|
|
||||||
version = "1.0.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
|
|
||||||
dependencies = [
|
|
||||||
"critical-section",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
@ -382,12 +373,6 @@ dependencies = [
|
||||||
"libloading",
|
"libloading",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cobs"
|
|
||||||
version = "0.2.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "color_quant"
|
name = "color_quant"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -485,12 +470,6 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "critical-section"
|
|
||||||
version = "1.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.5.13"
|
version = "0.5.13"
|
||||||
|
@ -602,7 +581,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
|
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"hashbrown 0.14.5",
|
"hashbrown",
|
||||||
"lock_api",
|
"lock_api",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot_core",
|
"parking_lot_core",
|
||||||
|
@ -707,12 +686,6 @@ dependencies = [
|
||||||
"wio",
|
"wio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "edit-distance"
|
|
||||||
version = "2.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bbbaaaf38131deb9ca518a274a45bfdb8771f139517b073b16c2d3d32ae5037b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.12.0"
|
version = "1.12.0"
|
||||||
|
@ -722,12 +695,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "embedded-io"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.34"
|
version = "0.8.34"
|
||||||
|
@ -1099,7 +1066,7 @@ dependencies = [
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http 0.2.12",
|
"http 0.2.12",
|
||||||
"indexmap 2.2.6",
|
"indexmap",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
@ -1116,21 +1083,6 @@ dependencies = [
|
||||||
"crunchy",
|
"crunchy",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hash32"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.12.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.14.5"
|
version = "0.14.5"
|
||||||
|
@ -1147,21 +1099,7 @@ version = "0.8.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
|
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hashbrown 0.14.5",
|
"hashbrown",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "heapless"
|
|
||||||
version = "0.7.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
|
|
||||||
dependencies = [
|
|
||||||
"atomic-polyfill",
|
|
||||||
"hash32",
|
|
||||||
"rustc_version",
|
|
||||||
"serde",
|
|
||||||
"spin 0.9.8",
|
|
||||||
"stable_deref_trait",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1393,17 +1331,6 @@ version = "1.10.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126"
|
checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "indexmap"
|
|
||||||
version = "1.9.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"hashbrown 0.12.3",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.2.6"
|
version = "2.2.6"
|
||||||
|
@ -1411,8 +1338,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.14.5",
|
"hashbrown",
|
||||||
"serde",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1471,19 +1397,6 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "kd-tree"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5f89ee4e60e82cf7024e5e94618c646fbf61ce7501dc5898b3d12786442d3682"
|
|
||||||
dependencies = [
|
|
||||||
"num-traits",
|
|
||||||
"ordered-float",
|
|
||||||
"paste",
|
|
||||||
"serde",
|
|
||||||
"typenum",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -1851,15 +1764,6 @@ version = "1.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ordered-float"
|
|
||||||
version = "4.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e"
|
|
||||||
dependencies = [
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
|
@ -2052,18 +1956,6 @@ dependencies = [
|
||||||
"syn 2.0.66",
|
"syn 2.0.66",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "postcard"
|
|
||||||
version = "1.0.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a55c51ee6c0db07e68448e336cf8ea4131a620edefebf9893e759b2d793420f8"
|
|
||||||
dependencies = [
|
|
||||||
"cobs",
|
|
||||||
"embedded-io",
|
|
||||||
"heapless",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "powerfmt"
|
name = "powerfmt"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -2592,36 +2484,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_with"
|
|
||||||
version = "3.8.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e73139bc5ec2d45e6c5fd85be5a46949c1c39a4c18e56915f5eb4c12f975e377"
|
|
||||||
dependencies = [
|
|
||||||
"base64 0.22.1",
|
|
||||||
"chrono",
|
|
||||||
"hex",
|
|
||||||
"indexmap 1.9.3",
|
|
||||||
"indexmap 2.2.6",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
"serde_json",
|
|
||||||
"serde_with_macros",
|
|
||||||
"time",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_with_macros"
|
|
||||||
version = "3.8.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b80d3d6b56b64335c0180e5ffde23b3c5e08c14c585b51a15bd0e95393f46703"
|
|
||||||
dependencies = [
|
|
||||||
"darling",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.66",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serenity"
|
name = "serenity"
|
||||||
version = "0.12.2"
|
version = "0.12.2"
|
||||||
|
@ -2682,20 +2544,14 @@ name = "shimmeringmoon"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"edit-distance",
|
|
||||||
"freetype-rs",
|
"freetype-rs",
|
||||||
"image 0.25.1",
|
"image 0.25.1",
|
||||||
"kd-tree",
|
|
||||||
"num",
|
"num",
|
||||||
"plotters",
|
"plotters",
|
||||||
"poise",
|
"poise",
|
||||||
"postcard",
|
|
||||||
"serde",
|
|
||||||
"serde_with",
|
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"tesseract",
|
"tesseract",
|
||||||
"tokio",
|
"tokio",
|
||||||
"typenum",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2839,7 +2695,7 @@ dependencies = [
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"hashlink",
|
"hashlink",
|
||||||
"hex",
|
"hex",
|
||||||
"indexmap 2.2.6",
|
"indexmap",
|
||||||
"log",
|
"log",
|
||||||
"memchr",
|
"memchr",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
@ -3002,12 +2858,6 @@ dependencies = [
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "stable_deref_trait"
|
|
||||||
version = "1.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stringprep"
|
name = "stringprep"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
@ -3344,7 +3194,7 @@ version = "0.22.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
|
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.2.6",
|
"indexmap",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
|
@ -3448,7 +3298,7 @@ checksum = "eb704842c709bc76f63e99e704cb208beeccca2abbabd0d9aec02e48ca1cee0f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"hashbrown 0.14.5",
|
"hashbrown",
|
||||||
"mini-moka",
|
"mini-moka",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"secrecy",
|
"secrecy",
|
||||||
|
|
|
@ -5,20 +5,14 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4.38"
|
chrono = "0.4.38"
|
||||||
edit-distance = "2.1.0"
|
|
||||||
freetype-rs = "0.36.0"
|
freetype-rs = "0.36.0"
|
||||||
image = "0.25.1"
|
image = "0.25.1"
|
||||||
kd-tree = { version="0.6.0", features=["serde"] }
|
|
||||||
num = "0.4.3"
|
num = "0.4.3"
|
||||||
plotters = { git="https://github.com/starlitcanopy/plotters.git", rev="986cd959362a2dbec8d1b25670fd083b904d7b8c", features=["bitmap_backend"] }
|
plotters = { git="https://github.com/starlitcanopy/plotters.git", rev="986cd959362a2dbec8d1b25670fd083b904d7b8c", features=["bitmap_backend"] }
|
||||||
poise = "0.6.1"
|
poise = "0.6.1"
|
||||||
postcard = { version="1.0.8", features=["use-std"] }
|
|
||||||
serde = "1.0.204"
|
|
||||||
serde_with = "3.8.3"
|
|
||||||
sqlx = { version = "0.7.4", features = ["sqlite", "runtime-tokio", "chrono"] }
|
sqlx = { version = "0.7.4", features = ["sqlite", "runtime-tokio", "chrono"] }
|
||||||
tesseract = "0.15.1"
|
tesseract = "0.15.1"
|
||||||
tokio = {version="1.38.0", features=["rt-multi-thread"]}
|
tokio = {version="1.38.0", features=["rt-multi-thread"]}
|
||||||
typenum = "1.17.0"
|
|
||||||
|
|
||||||
[profile.dev.package."*"]
|
[profile.dev.package."*"]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|
16
data/ui.txt
Normal file
16
data/ui.txt
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
2160 1620
|
||||||
|
19 15 273 60 Play kind
|
||||||
|
841 683 500 92 Score screen — score
|
||||||
|
51 655 633 632 Score screen — jacket
|
||||||
|
155 546 167 38 Score screen — difficulty
|
||||||
|
1095 1087 87 34 Score screen — pures
|
||||||
|
1095 1150 87 34 Score screen — fars
|
||||||
|
1095 1212 87 34 Score screen — losts
|
||||||
|
364 593 87 34 Score screen — max recall
|
||||||
|
438 324 1244 104 Score screen — title
|
||||||
|
15 264 291 52 Song select — score
|
||||||
|
158 411 909 74 Song select — jacket
|
||||||
|
12 159 0 0 Song select — PST
|
||||||
|
199 159 0 0 Song select — PRS
|
||||||
|
389 159 0 0 Song select — FTR
|
||||||
|
579 159 0 0 Song select — ETR/BYD
|
|
@ -1,15 +1,12 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use image::{ImageBuffer, Rgb};
|
use image::{ImageBuffer, Rgb};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
use crate::context::Error;
|
use crate::context::Error;
|
||||||
|
|
||||||
// {{{ Difficuly
|
// {{{ Difficuly
|
||||||
#[derive(
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, sqlx::Type)]
|
||||||
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, sqlx::Type, Serialize, Deserialize,
|
|
||||||
)]
|
|
||||||
pub enum Difficulty {
|
pub enum Difficulty {
|
||||||
PST,
|
PST,
|
||||||
PRS,
|
PRS,
|
||||||
|
|
|
@ -280,7 +280,9 @@ Title error: {:?}
|
||||||
// }}}
|
// }}}
|
||||||
// }}}
|
// }}}
|
||||||
// {{{ Deliver embed
|
// {{{ Deliver embed
|
||||||
let (mut embed, attachment) = play.to_embed(&song, &chart, i, None).await?;
|
let (mut embed, attachment) = play
|
||||||
|
.to_embed(&ctx.data().db, &user, &song, &chart, i, None)
|
||||||
|
.await?;
|
||||||
if let Some(warning) = score_warning {
|
if let Some(warning) = score_warning {
|
||||||
embed = embed.description(warning);
|
embed = embed.description(warning);
|
||||||
}
|
}
|
||||||
|
@ -401,10 +403,13 @@ pub async fn show(
|
||||||
creation_zeta_ptt: None,
|
creation_zeta_ptt: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let user = discord_it_to_discord_user(&ctx, &res.discord_id).await?;
|
let author = discord_it_to_discord_user(&ctx, &res.discord_id).await?;
|
||||||
|
let user = User::by_id(&ctx.data().db, play.user_id).await?;
|
||||||
|
|
||||||
let (song, chart) = ctx.data().song_cache.lookup_chart(play.chart_id)?;
|
let (song, chart) = ctx.data().song_cache.lookup_chart(play.chart_id)?;
|
||||||
let (embed, attachment) = play.to_embed(song, chart, i, Some(&user)).await?;
|
let (embed, attachment) = play
|
||||||
|
.to_embed(&ctx.data().db, &user, song, chart, i, Some(&author))
|
||||||
|
.await?;
|
||||||
|
|
||||||
embeds.push(embed);
|
embeds.push(embed);
|
||||||
attachments.extend(attachment);
|
attachments.extend(attachment);
|
||||||
|
|
|
@ -73,7 +73,6 @@ pub async fn best(
|
||||||
};
|
};
|
||||||
|
|
||||||
let (song, chart) = guess_song_and_chart(&ctx.data(), &name)?;
|
let (song, chart) = guess_song_and_chart(&ctx.data(), &name)?;
|
||||||
|
|
||||||
let play = query_as!(
|
let play = query_as!(
|
||||||
DbPlay,
|
DbPlay,
|
||||||
"
|
"
|
||||||
|
@ -97,6 +96,8 @@ pub async fn best(
|
||||||
|
|
||||||
let (embed, attachment) = play
|
let (embed, attachment) = play
|
||||||
.to_embed(
|
.to_embed(
|
||||||
|
&ctx.data().db,
|
||||||
|
&user,
|
||||||
&song,
|
&song,
|
||||||
&chart,
|
&chart,
|
||||||
0,
|
0,
|
||||||
|
@ -602,10 +603,7 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
(top_left_center, 94),
|
(top_left_center, 94),
|
||||||
font,
|
font,
|
||||||
style,
|
style,
|
||||||
&format!(
|
&format!("{:.2}", play.score.play_rating_f32(chart.chart_constant)),
|
||||||
"{:.2}",
|
|
||||||
(play.score.play_rating(chart.chart_constant)) as f32 / 100.
|
|
||||||
),
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
use crate::{chart::SongCache, jacket::JacketCache};
|
use crate::{chart::SongCache, jacket::JacketCache, ocr::ui_interp::UIMeasurements};
|
||||||
|
|
||||||
// Types used by all command functions
|
// Types used by all command functions
|
||||||
pub type Error = Box<dyn std::error::Error + Send + Sync>;
|
pub type Error = Box<dyn std::error::Error + Send + Sync>;
|
||||||
|
@ -15,6 +15,7 @@ pub struct UserContext {
|
||||||
pub db: SqlitePool,
|
pub db: SqlitePool,
|
||||||
pub song_cache: SongCache,
|
pub song_cache: SongCache,
|
||||||
pub jacket_cache: JacketCache,
|
pub jacket_cache: JacketCache,
|
||||||
|
pub ui_measurements: UIMeasurements,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserContext {
|
impl UserContext {
|
||||||
|
@ -25,6 +26,7 @@ impl UserContext {
|
||||||
|
|
||||||
let mut song_cache = SongCache::new(&db).await?;
|
let mut song_cache = SongCache::new(&db).await?;
|
||||||
let jacket_cache = JacketCache::new(&data_dir, &mut song_cache)?;
|
let jacket_cache = JacketCache::new(&data_dir, &mut song_cache)?;
|
||||||
|
let ui_measurements = UIMeasurements::read(&data_dir)?;
|
||||||
|
|
||||||
println!("Created user context");
|
println!("Created user context");
|
||||||
|
|
||||||
|
@ -33,6 +35,7 @@ impl UserContext {
|
||||||
db,
|
db,
|
||||||
song_cache,
|
song_cache,
|
||||||
jacket_cache,
|
jacket_cache,
|
||||||
|
ui_measurements,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use std::{fs, path::PathBuf, str::FromStr};
|
use std::{fs, path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
use image::{imageops::FilterType, GenericImageView, Rgba};
|
use image::{imageops::FilterType, GenericImageView, Rgba};
|
||||||
use kd_tree::{KdMap, KdPoint};
|
|
||||||
use num::Integer;
|
use num::Integer;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -59,24 +58,23 @@ impl ImageVec {
|
||||||
|
|
||||||
Self { colors }
|
Self { colors }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn distance_squared_to(&self, other: &Self) -> f32 {
|
||||||
|
let mut total = 0.0;
|
||||||
|
|
||||||
|
for i in 0..IMAGE_VEC_DIM {
|
||||||
|
let d = self.colors[i] - other.colors[i];
|
||||||
|
total += d * d;
|
||||||
|
}
|
||||||
|
|
||||||
|
total
|
||||||
|
}
|
||||||
// }}}
|
// }}}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KdPoint for ImageVec {
|
|
||||||
type Dim = typenum::U75;
|
|
||||||
type Scalar = f32;
|
|
||||||
|
|
||||||
fn dim() -> usize {
|
|
||||||
IMAGE_VEC_DIM
|
|
||||||
}
|
|
||||||
|
|
||||||
fn at(&self, i: usize) -> Self::Scalar {
|
|
||||||
self.colors[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct JacketCache {
|
pub struct JacketCache {
|
||||||
tree: KdMap<ImageVec, u32>,
|
jackets: Vec<(u32, ImageVec)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JacketCache {
|
impl JacketCache {
|
||||||
|
@ -91,7 +89,7 @@ impl JacketCache {
|
||||||
|
|
||||||
fs::create_dir_all(&jacket_dir).expect("Could not create jacket dir");
|
fs::create_dir_all(&jacket_dir).expect("Could not create jacket dir");
|
||||||
|
|
||||||
let tree_entries = if should_skip_jacket_art() {
|
let jacket_vectors = if should_skip_jacket_art() {
|
||||||
let path = get_assets_dir().join("placeholder_jacket.jpg");
|
let path = get_assets_dir().join("placeholder_jacket.jpg");
|
||||||
let contents: &'static _ = fs::read(path)?.leak();
|
let contents: &'static _ = fs::read(path)?.leak();
|
||||||
let image = image::load_from_memory(contents)?;
|
let image = image::load_from_memory(contents)?;
|
||||||
|
@ -114,7 +112,7 @@ impl JacketCache {
|
||||||
} else {
|
} else {
|
||||||
let entries =
|
let entries =
|
||||||
fs::read_dir(data_dir.join("songs")).expect("Couldn't read songs directory");
|
fs::read_dir(data_dir.join("songs")).expect("Couldn't read songs directory");
|
||||||
let mut tree_entries = vec![];
|
let mut jacket_vectors = vec![];
|
||||||
|
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
let dir = entry?;
|
let dir = entry?;
|
||||||
|
@ -147,7 +145,7 @@ impl JacketCache {
|
||||||
let contents: &'static _ = fs::read(file.path())?.leak();
|
let contents: &'static _ = fs::read(file.path())?.leak();
|
||||||
|
|
||||||
let image = image::load_from_memory(contents)?;
|
let image = image::load_from_memory(contents)?;
|
||||||
tree_entries.push((ImageVec::from_image(&image), song.id));
|
jacket_vectors.push((song.id, ImageVec::from_image(&image)));
|
||||||
|
|
||||||
let bitmap: &'static _ = Box::leak(Box::new(
|
let bitmap: &'static _ = Box::leak(Box::new(
|
||||||
image
|
image
|
||||||
|
@ -201,11 +199,11 @@ impl JacketCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tree_entries
|
jacket_vectors
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = Self {
|
let result = Self {
|
||||||
tree: KdMap::build_by_ordered_float(tree_entries),
|
jackets: jacket_vectors,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
|
@ -217,9 +215,12 @@ impl JacketCache {
|
||||||
&self,
|
&self,
|
||||||
image: &impl GenericImageView<Pixel = Rgba<u8>>,
|
image: &impl GenericImageView<Pixel = Rgba<u8>>,
|
||||||
) -> Option<(f32, &u32)> {
|
) -> Option<(f32, &u32)> {
|
||||||
self.tree
|
let vec = ImageVec::from_image(image);
|
||||||
.nearest(&ImageVec::from_image(image))
|
self.jackets
|
||||||
.map(|p| (p.squared_distance.sqrt(), &p.item.1))
|
.iter()
|
||||||
|
.map(|(i, v)| (i, v, v.distance_squared_to(&vec)))
|
||||||
|
.min_by(|(_, _, d1), (_, _, d2)| d1.partial_cmp(d2).expect("NaN distance encountered"))
|
||||||
|
.map(|(i, _, d)| (d, i))
|
||||||
}
|
}
|
||||||
// }}}
|
// }}}
|
||||||
}
|
}
|
||||||
|
|
61
src/levenshtein.rs
Normal file
61
src/levenshtein.rs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
// Modified version of https://docs.rs/edit-distance/latest/src/edit_distance/lib.rs.html#1-76
|
||||||
|
|
||||||
|
/// Similar to `edit_distance`, but takes in a preallocated vec so consecutive calls are efficient.
|
||||||
|
pub fn edit_distance_with(a: &str, b: &str, cur: &mut Vec<usize>) -> usize {
|
||||||
|
let len_a = a.chars().count();
|
||||||
|
let len_b = b.chars().count();
|
||||||
|
if len_a < len_b {
|
||||||
|
return edit_distance_with(b, a, cur);
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle special case of 0 length
|
||||||
|
if len_a == 0 {
|
||||||
|
return len_b;
|
||||||
|
} else if len_b == 0 {
|
||||||
|
return len_a;
|
||||||
|
}
|
||||||
|
|
||||||
|
let len_b = len_b + 1;
|
||||||
|
|
||||||
|
let mut pre;
|
||||||
|
let mut tmp;
|
||||||
|
|
||||||
|
cur.clear();
|
||||||
|
cur.resize(len_b, 0);
|
||||||
|
|
||||||
|
// initialize string b
|
||||||
|
for i in 1..len_b {
|
||||||
|
cur[i] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate edit distance
|
||||||
|
for (i, ca) in a.chars().enumerate() {
|
||||||
|
// get first column for this row
|
||||||
|
pre = cur[0];
|
||||||
|
cur[0] = i + 1;
|
||||||
|
for (j, cb) in b.chars().enumerate() {
|
||||||
|
tmp = cur[j + 1];
|
||||||
|
cur[j + 1] = std::cmp::min(
|
||||||
|
// deletion
|
||||||
|
tmp + 1,
|
||||||
|
std::cmp::min(
|
||||||
|
// insertion
|
||||||
|
cur[j] + 1,
|
||||||
|
// match or substitution
|
||||||
|
pre + if ca == cb { 0 } else { 1 },
|
||||||
|
),
|
||||||
|
);
|
||||||
|
pre = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cur[len_b - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the edit distance between strings `a` and `b`.
|
||||||
|
///
|
||||||
|
/// The runtime complexity is `O(m*n)`, where `m` and `n` are the
|
||||||
|
/// strings' lengths.
|
||||||
|
#[inline]
|
||||||
|
pub fn edit_distance(a: &str, b: &str) -> usize {
|
||||||
|
edit_distance_with(a, b, &mut Vec::new())
|
||||||
|
}
|
|
@ -11,6 +11,8 @@ mod commands;
|
||||||
mod context;
|
mod context;
|
||||||
mod image;
|
mod image;
|
||||||
mod jacket;
|
mod jacket;
|
||||||
|
mod levenshtein;
|
||||||
|
mod ocr;
|
||||||
mod score;
|
mod score;
|
||||||
mod user;
|
mod user;
|
||||||
|
|
||||||
|
|
1
src/ocr/mod.rs
Normal file
1
src/ocr/mod.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod ui_interp;
|
176
src/ocr/ui_interp.rs
Normal file
176
src/ocr/ui_interp.rs
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
|
use crate::context::Error;
|
||||||
|
|
||||||
|
// {{{ Rects
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum ScoreScreenRect {
|
||||||
|
Score,
|
||||||
|
Jacket,
|
||||||
|
Difficulty,
|
||||||
|
Pure,
|
||||||
|
Far,
|
||||||
|
Lost,
|
||||||
|
MaxRecall,
|
||||||
|
Title,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum SongSelectRect {
|
||||||
|
Score,
|
||||||
|
Jacket,
|
||||||
|
Past,
|
||||||
|
Present,
|
||||||
|
Future,
|
||||||
|
Beyond,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum UIMeasurementRect {
|
||||||
|
PlayKind,
|
||||||
|
ScoreScreen(ScoreScreenRect),
|
||||||
|
SongSelect(SongSelectRect),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UIMeasurementRect {
|
||||||
|
#[inline]
|
||||||
|
pub fn to_index(self) -> usize {
|
||||||
|
match self {
|
||||||
|
Self::PlayKind => 0,
|
||||||
|
Self::ScoreScreen(ScoreScreenRect::Score) => 1,
|
||||||
|
Self::ScoreScreen(ScoreScreenRect::Jacket) => 2,
|
||||||
|
Self::ScoreScreen(ScoreScreenRect::Difficulty) => 3,
|
||||||
|
Self::ScoreScreen(ScoreScreenRect::Pure) => 4,
|
||||||
|
Self::ScoreScreen(ScoreScreenRect::Far) => 5,
|
||||||
|
Self::ScoreScreen(ScoreScreenRect::Lost) => 6,
|
||||||
|
Self::ScoreScreen(ScoreScreenRect::MaxRecall) => 7,
|
||||||
|
Self::ScoreScreen(ScoreScreenRect::Title) => 8,
|
||||||
|
Self::SongSelect(SongSelectRect::Score) => 9,
|
||||||
|
Self::SongSelect(SongSelectRect::Jacket) => 10,
|
||||||
|
Self::SongSelect(SongSelectRect::Past) => 11,
|
||||||
|
Self::SongSelect(SongSelectRect::Present) => 12,
|
||||||
|
Self::SongSelect(SongSelectRect::Future) => 13,
|
||||||
|
Self::SongSelect(SongSelectRect::Beyond) => 14,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const UI_RECT_COUNT: usize = 15;
|
||||||
|
// }}}
|
||||||
|
// {{{ Measurement
|
||||||
|
pub struct UIMeasurement {
|
||||||
|
dimensions: [u32; 2],
|
||||||
|
datapoints: [u32; UI_RECT_COUNT * 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for UIMeasurement {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new([0; 2], [0; UI_RECT_COUNT * 4])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UIMeasurement {
|
||||||
|
pub fn new(dimensions: [u32; 2], datapoints: [u32; UI_RECT_COUNT * 4]) -> Self {
|
||||||
|
Self {
|
||||||
|
dimensions,
|
||||||
|
datapoints,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn aspect_ratio(&self) -> f32 {
|
||||||
|
self.dimensions[0] as f32 / self.dimensions[1] as f32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
// {{{ Measurements
|
||||||
|
pub struct UIMeasurements {
|
||||||
|
pub measurements: Vec<UIMeasurement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UIMeasurements {
|
||||||
|
// {{{ Read
|
||||||
|
pub fn read(data_dir: &PathBuf) -> Result<Self, Error> {
|
||||||
|
let mut measurements = Vec::new();
|
||||||
|
let mut measurement = UIMeasurement::default();
|
||||||
|
|
||||||
|
let path = data_dir.join("ui.txt");
|
||||||
|
let contents = fs::read_to_string(path)?;
|
||||||
|
|
||||||
|
// {{{ Parse measurement file
|
||||||
|
for (i, line) in contents.split('\n').enumerate() {
|
||||||
|
let i = i % (UI_RECT_COUNT + 2);
|
||||||
|
if i == 0 {
|
||||||
|
for (j, str) in line.split_whitespace().enumerate().take(2) {
|
||||||
|
measurement.dimensions[j] = u32::from_str_radix(str, 10)?;
|
||||||
|
}
|
||||||
|
} else if i == UI_RECT_COUNT + 2 {
|
||||||
|
measurements.push(measurement);
|
||||||
|
measurement = UIMeasurement::default();
|
||||||
|
} else {
|
||||||
|
for (j, str) in line.split_whitespace().enumerate().take(4) {
|
||||||
|
measurement.datapoints[(i - 1) * 4 + j] = u32::from_str_radix(str, 10)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
measurements.push(measurement);
|
||||||
|
measurements.sort_by_key(|r| (r.aspect_ratio() * 1000.0) as u32);
|
||||||
|
|
||||||
|
// {{{ Filter datapoints that are close together
|
||||||
|
let mut i = 0;
|
||||||
|
while i < measurements.len() - 1 {
|
||||||
|
let low = &measurements[i];
|
||||||
|
let high = &measurements[i + 1];
|
||||||
|
|
||||||
|
if (low.aspect_ratio() - high.aspect_ratio()).abs() < 0.001 {
|
||||||
|
// TODO: we could interpolate here but oh well
|
||||||
|
measurements.remove(i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
Ok(Self { measurements })
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
// {{{ Interpolate
|
||||||
|
pub fn interpolate(
|
||||||
|
&self,
|
||||||
|
rect: UIMeasurementRect,
|
||||||
|
dimensions: [u32; 2],
|
||||||
|
) -> Result<[u32; 4], Error> {
|
||||||
|
let aspect_ratio = dimensions[0] as f32 / dimensions[1] as f32;
|
||||||
|
let r = rect.to_index();
|
||||||
|
|
||||||
|
for i in 0..(self.measurements.len() - 1) {
|
||||||
|
let low = &self.measurements[i];
|
||||||
|
let high = &self.measurements[i + 1];
|
||||||
|
|
||||||
|
let low_ratio = low.aspect_ratio();
|
||||||
|
let high_ratio = high.aspect_ratio();
|
||||||
|
|
||||||
|
if (i == 0 || low_ratio <= aspect_ratio)
|
||||||
|
&& (aspect_ratio <= high_ratio || i == self.measurements.len() - 2)
|
||||||
|
{
|
||||||
|
let p = (aspect_ratio - low_ratio) / (high_ratio - low_ratio);
|
||||||
|
let mut out = [0; 4];
|
||||||
|
for j in 0..4 {
|
||||||
|
let l = low.datapoints[4 * r + j] as f32 / low.dimensions[j % 2] as f32;
|
||||||
|
let h = high.datapoints[4 * r + j] as f32 / high.dimensions[j % 2] as f32;
|
||||||
|
out[j] = ((l + (h - l) * p) * dimensions[j % 2] as f32) as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(format!("Could no find rect for {rect:?} in image").into())
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
}
|
||||||
|
// }}}
|
135
src/score.rs
135
src/score.rs
|
@ -5,7 +5,6 @@ use std::io::Cursor;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use edit_distance::edit_distance;
|
|
||||||
use image::{imageops::FilterType, DynamicImage, GenericImageView};
|
use image::{imageops::FilterType, DynamicImage, GenericImageView};
|
||||||
use num::integer::Roots;
|
use num::integer::Roots;
|
||||||
use num::{traits::Euclid, Rational64};
|
use num::{traits::Euclid, Rational64};
|
||||||
|
@ -20,8 +19,15 @@ use crate::chart::{Chart, Difficulty, Song, SongCache};
|
||||||
use crate::context::{Error, UserContext};
|
use crate::context::{Error, UserContext};
|
||||||
use crate::image::rotate;
|
use crate::image::rotate;
|
||||||
use crate::jacket::IMAGE_VEC_DIM;
|
use crate::jacket::IMAGE_VEC_DIM;
|
||||||
|
use crate::levenshtein::{edit_distance, edit_distance_with};
|
||||||
use crate::user::User;
|
use crate::user::User;
|
||||||
|
|
||||||
|
// {{{ Utils
|
||||||
|
#[inline]
|
||||||
|
fn lerp(i: f32, a: f32, b: f32) -> f32 {
|
||||||
|
a + (b - a) * i
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
// {{{ Grade
|
// {{{ Grade
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum Grade {
|
pub enum Grade {
|
||||||
|
@ -138,6 +144,11 @@ impl Score {
|
||||||
(self.0 as i32 - 9_500_000) / 3_000
|
(self.0 as i32 - 9_500_000) / 3_000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn play_rating_f32(self, chart_constant: u32) -> f32 {
|
||||||
|
(self.play_rating(chart_constant)) as f32 / 100.0
|
||||||
|
}
|
||||||
// }}}
|
// }}}
|
||||||
// {{{ Score => grade
|
// {{{ Score => grade
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -390,13 +401,13 @@ impl CreatePlay {
|
||||||
let attachment_id = self.discord_attachment_id.map(|i| i.get() as i64);
|
let attachment_id = self.discord_attachment_id.map(|i| i.get() as i64);
|
||||||
let play = sqlx::query!(
|
let play = sqlx::query!(
|
||||||
"
|
"
|
||||||
INSERT INTO plays(
|
INSERT INTO plays(
|
||||||
user_id,chart_id,discord_attachment_id,
|
user_id,chart_id,discord_attachment_id,
|
||||||
score,zeta_score,max_recall,far_notes
|
score,zeta_score,max_recall,far_notes
|
||||||
)
|
)
|
||||||
VALUES(?,?,?,?,?,?,?)
|
VALUES(?,?,?,?,?,?,?)
|
||||||
RETURNING id, created_at
|
RETURNING id, created_at
|
||||||
",
|
",
|
||||||
self.user_id,
|
self.user_id,
|
||||||
self.chart_id,
|
self.chart_id,
|
||||||
attachment_id,
|
attachment_id,
|
||||||
|
@ -559,23 +570,44 @@ impl Play {
|
||||||
/// The `index` variable is only used to create distinct filenames.
|
/// The `index` variable is only used to create distinct filenames.
|
||||||
pub async fn to_embed(
|
pub async fn to_embed(
|
||||||
&self,
|
&self,
|
||||||
|
db: &SqlitePool,
|
||||||
|
user: &User,
|
||||||
song: &Song,
|
song: &Song,
|
||||||
chart: &Chart,
|
chart: &Chart,
|
||||||
index: usize,
|
index: usize,
|
||||||
author: Option<&poise::serenity_prelude::User>,
|
author: Option<&poise::serenity_prelude::User>,
|
||||||
) -> Result<(CreateEmbed, Option<CreateAttachment>), Error> {
|
) -> Result<(CreateEmbed, Option<CreateAttachment>), Error> {
|
||||||
|
// {{{ Get previously best score
|
||||||
|
let previously_best = query_as!(
|
||||||
|
DbPlay,
|
||||||
|
"
|
||||||
|
SELECT * FROM plays
|
||||||
|
WHERE user_id=?
|
||||||
|
AND chart_id=?
|
||||||
|
AND created_at<?
|
||||||
|
ORDER BY score DESC
|
||||||
|
",
|
||||||
|
user.id,
|
||||||
|
chart.id,
|
||||||
|
self.created_at
|
||||||
|
)
|
||||||
|
.fetch_optional(db)
|
||||||
|
.await
|
||||||
|
.map_err(|_| {
|
||||||
|
format!(
|
||||||
|
"Could not find any scores for {} [{:?}]",
|
||||||
|
song.title, chart.difficulty
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.map(|p| p.to_play());
|
||||||
|
// }}}
|
||||||
|
|
||||||
let attachement_name = format!("{:?}-{:?}-{:?}.png", song.id, self.score.0, index);
|
let attachement_name = format!("{:?}-{:?}-{:?}.png", song.id, self.score.0, index);
|
||||||
let icon_attachement = match chart.cached_jacket.as_ref() {
|
let icon_attachement = match chart.cached_jacket.as_ref() {
|
||||||
Some(jacket) => Some(CreateAttachment::bytes(jacket.raw, &attachement_name)),
|
Some(jacket) => Some(CreateAttachment::bytes(jacket.raw, &attachement_name)),
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("Rating {:?}", self.score.play_rating(chart.chart_constant));
|
|
||||||
println!(
|
|
||||||
"Rating {:?}",
|
|
||||||
self.score.play_rating(chart.chart_constant) as f32 / 100.0
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut embed = CreateEmbed::default()
|
let mut embed = CreateEmbed::default()
|
||||||
.title(format!(
|
.title(format!(
|
||||||
"{} [{:?} {}]",
|
"{} [{:?} {}]",
|
||||||
|
@ -586,20 +618,41 @@ impl Play {
|
||||||
"Rating",
|
"Rating",
|
||||||
format!(
|
format!(
|
||||||
"{:.2} (+?)",
|
"{:.2} (+?)",
|
||||||
(self.score.play_rating(chart.chart_constant)) as f32 / 100.0
|
self.score.play_rating_f32(chart.chart_constant)
|
||||||
),
|
),
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
.field("Grade", format!("{}", self.score.grade()), true)
|
.field("Grade", format!("{}", self.score.grade()), true)
|
||||||
.field("ξ-Score", format!("{} (+?)", self.zeta_score), true)
|
.field("ξ-Score", format!("{} (+?)", self.zeta_score), true)
|
||||||
|
// {{{ ξ-Rating
|
||||||
.field(
|
.field(
|
||||||
"ξ-Rating",
|
"ξ-Rating",
|
||||||
format!(
|
{
|
||||||
"{:.2} (+?)",
|
let play_rating = self.zeta_score.play_rating_f32(chart.chart_constant);
|
||||||
(self.zeta_score.play_rating(chart.chart_constant)) as f32 / 100.
|
if let Some(previous) = previously_best {
|
||||||
),
|
let previous_play_rating =
|
||||||
|
previous.zeta_score.play_rating_f32(chart.chart_constant);
|
||||||
|
|
||||||
|
if play_rating >= previous_play_rating {
|
||||||
|
format!(
|
||||||
|
"{:.2} (+{})",
|
||||||
|
play_rating,
|
||||||
|
play_rating - previous_play_rating
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"{:.2} (-{})",
|
||||||
|
play_rating,
|
||||||
|
play_rating - previous_play_rating
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
format!("{:.2}", play_rating)
|
||||||
|
}
|
||||||
|
},
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
|
// }}}
|
||||||
.field("ξ-Grade", format!("{}", self.zeta_score.grade()), true)
|
.field("ξ-Grade", format!("{}", self.zeta_score.grade()), true)
|
||||||
.field(
|
.field(
|
||||||
"Status",
|
"Status",
|
||||||
|
@ -629,37 +682,6 @@ impl Play {
|
||||||
Ok((embed, icon_attachement))
|
Ok((embed, icon_attachement))
|
||||||
}
|
}
|
||||||
// }}}
|
// }}}
|
||||||
// {{{ Get best play
|
|
||||||
pub async fn best_play(
|
|
||||||
db: &SqlitePool,
|
|
||||||
user: User,
|
|
||||||
song: Song,
|
|
||||||
chart: Chart,
|
|
||||||
) -> Result<Self, Error> {
|
|
||||||
let play = query_as!(
|
|
||||||
DbPlay,
|
|
||||||
"
|
|
||||||
SELECT * FROM plays
|
|
||||||
WHERE user_id=?
|
|
||||||
AND chart_id=?
|
|
||||||
ORDER BY score DESC
|
|
||||||
",
|
|
||||||
user.id,
|
|
||||||
chart.id
|
|
||||||
)
|
|
||||||
.fetch_one(db)
|
|
||||||
.await
|
|
||||||
.map_err(|_| {
|
|
||||||
format!(
|
|
||||||
"Could not find any scores for {} [{:?}]",
|
|
||||||
song.title, chart.difficulty
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.to_play();
|
|
||||||
|
|
||||||
Ok(play)
|
|
||||||
}
|
|
||||||
// }}}
|
|
||||||
}
|
}
|
||||||
// }}}
|
// }}}
|
||||||
// {{{ Tests
|
// {{{ Tests
|
||||||
|
@ -768,10 +790,6 @@ pub struct RelativeRect {
|
||||||
pub dimensions: ImageDimensions,
|
pub dimensions: ImageDimensions,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lerp(i: f32, a: f32, b: f32) -> f32 {
|
|
||||||
a + (b - a) * i
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RelativeRect {
|
impl RelativeRect {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(x: f32, y: f32, width: f32, height: f32, dimensions: ImageDimensions) -> Self {
|
pub fn new(x: f32, y: f32, width: f32, height: f32, dimensions: ImageDimensions) -> Self {
|
||||||
|
@ -1229,8 +1247,11 @@ pub fn guess_chart_name<'a>(
|
||||||
let raw_text = raw_text.trim(); // not quite raw 🤔
|
let raw_text = raw_text.trim(); // not quite raw 🤔
|
||||||
let mut text: &str = &raw_text.to_lowercase();
|
let mut text: &str = &raw_text.to_lowercase();
|
||||||
|
|
||||||
|
// Cached vec used by the levenshtein distance function
|
||||||
|
let mut levenshtein_vec = Vec::with_capacity(20);
|
||||||
// Cached vec used to store distance calculations
|
// Cached vec used to store distance calculations
|
||||||
let mut distance_vec = Vec::with_capacity(3);
|
let mut distance_vec = Vec::with_capacity(3);
|
||||||
|
|
||||||
let (song, chart) = loop {
|
let (song, chart) = loop {
|
||||||
let mut close_enough: Vec<_> = cache
|
let mut close_enough: Vec<_> = cache
|
||||||
.songs()
|
.songs()
|
||||||
|
@ -1245,7 +1266,7 @@ pub fn guess_chart_name<'a>(
|
||||||
let song_title = &song.lowercase_title;
|
let song_title = &song.lowercase_title;
|
||||||
distance_vec.clear();
|
distance_vec.clear();
|
||||||
|
|
||||||
let base_distance = edit_distance(&text, &song_title);
|
let base_distance = edit_distance_with(&text, &song_title, &mut levenshtein_vec);
|
||||||
if base_distance < 1.max(song.title.len() / 3) {
|
if base_distance < 1.max(song.title.len() / 3) {
|
||||||
distance_vec.push(base_distance * 10 + 2);
|
distance_vec.push(base_distance * 10 + 2);
|
||||||
}
|
}
|
||||||
|
@ -1254,7 +1275,7 @@ pub fn guess_chart_name<'a>(
|
||||||
if let Some(sliced) = &song_title.get(..shortest_len)
|
if let Some(sliced) = &song_title.get(..shortest_len)
|
||||||
&& (text.len() >= 6 || unsafe_heuristics)
|
&& (text.len() >= 6 || unsafe_heuristics)
|
||||||
{
|
{
|
||||||
let slice_distance = edit_distance(&text, sliced);
|
let slice_distance = edit_distance_with(&text, sliced, &mut levenshtein_vec);
|
||||||
if slice_distance < 1 {
|
if slice_distance < 1 {
|
||||||
distance_vec.push(slice_distance * 10 + 3);
|
distance_vec.push(slice_distance * 10 + 3);
|
||||||
}
|
}
|
||||||
|
@ -1263,7 +1284,7 @@ pub fn guess_chart_name<'a>(
|
||||||
if let Some(shorthand) = &chart.shorthand
|
if let Some(shorthand) = &chart.shorthand
|
||||||
&& unsafe_heuristics
|
&& unsafe_heuristics
|
||||||
{
|
{
|
||||||
let short_distance = edit_distance(&text, shorthand);
|
let short_distance = edit_distance_with(&text, shorthand, &mut levenshtein_vec);
|
||||||
if short_distance < 1.max(shorthand.len() / 3) {
|
if short_distance < 1.max(shorthand.len() / 3) {
|
||||||
distance_vec.push(short_distance * 10 + 1);
|
distance_vec.push(short_distance * 10 + 1);
|
||||||
}
|
}
|
||||||
|
|
12
src/user.rs
12
src/user.rs
|
@ -1,6 +1,7 @@
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use poise::serenity_prelude::UserId;
|
use poise::serenity_prelude::UserId;
|
||||||
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
use crate::context::{Context, Error};
|
use crate::context::{Context, Error};
|
||||||
|
|
||||||
|
@ -22,6 +23,17 @@ impl User {
|
||||||
discord_id: user.discord_id,
|
discord_id: user.discord_id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn by_id(db: &SqlitePool, id: u32) -> Result<Self, Error> {
|
||||||
|
let user = sqlx::query!("SELECT * FROM users WHERE id = ?", id)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(User {
|
||||||
|
id: user.id as u32,
|
||||||
|
discord_id: user.discord_id,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
Loading…
Reference in a new issue