Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add genkai-point-formula v3 #82

Merged
merged 16 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions src/bot/genkai_point/formula/mod.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
use crate::bot::genkai_point::{formula::v2::FormulaV2, model::Session};
use {self::v3::FormulaV3, crate::bot::genkai_point::model::Session};

pub mod v1;
pub mod v2;
pub mod v3;

pub(crate) trait GenkaiPointFormula: Send + Sync + 'static {
fn name(&self) -> &'static str;
fn calc(&self, session: &Session) -> u64;
fn calc(&self, sessions: &[Session]) -> GenkaiPointFormulaOutput;
}

pub(crate) struct GenkaiPointFormulaOutput {
pub(crate) point: u64,
pub(crate) efficiency: f64,
}

pub(crate) fn default_formula() -> impl GenkaiPointFormula {
FormulaV2
FormulaV3
}

pub(crate) struct DynGenkaiPointFormula(pub Box<dyn GenkaiPointFormula>);
Expand All @@ -19,7 +25,7 @@ impl GenkaiPointFormula for DynGenkaiPointFormula {
self.0.name()
}

fn calc(&self, session: &Session) -> u64 {
self.0.calc(session)
fn calc(&self, sessions: &[Session]) -> GenkaiPointFormulaOutput {
self.0.calc(sessions)
}
}
41 changes: 30 additions & 11 deletions src/bot/genkai_point/formula/v1.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use {
crate::bot::genkai_point::{formula::GenkaiPointFormula, model::Session},
crate::bot::genkai_point::{
formula::{GenkaiPointFormula, GenkaiPointFormulaOutput},
model::Session,
},
chrono::{Duration, Timelike, Utc},
chrono_tz::Asia::Tokyo,
};
Expand All @@ -11,16 +14,32 @@ impl GenkaiPointFormula for FormulaV1 {
"v1"
}

fn calc(&self, session: &Session) -> u64 {
let joined_at = session.joined_at.with_timezone(&Tokyo);
let left_at = session.left_at.unwrap_or_else(Utc::now);
fn calc(&self, sessions: &[Session]) -> GenkaiPointFormulaOutput {
let point = sessions
.iter()
.map(|session| {
let joined_at = session.joined_at.with_timezone(&Tokyo);
let left_at = session.left_at.unwrap_or_else(Utc::now);

(1..)
.map(|x| joined_at + Duration::hours(x))
.take_while(|x| *x <= left_at)
.map(|x| x.hour())
.map(hour_to_point)
.sum()
(1..)
.map(|x| joined_at + Duration::hours(x))
.take_while(|x| *x <= left_at)
.map(|x| x.hour())
.map(hour_to_point)
.sum::<u64>()
})
.sum();

let total_hours = sessions
.iter()
.map(|s| s.left_at() - s.joined_at)
.sum::<Duration>()
.num_milliseconds() as f64
/ (60.0 * 60.0 * 1000.0);

let efficiency = (point as f64 / 10.0) / total_hours;

GenkaiPointFormulaOutput { point, efficiency }
}
}

Expand Down Expand Up @@ -60,7 +79,7 @@ fn session_test() {
joined_at: $d1,
left_at: Some($d2),
};
assert_eq!(FormulaV1.calc(&session), $point);
assert_eq!(FormulaV1.calc(&[session]).point, $point);
}};
}

Expand Down
151 changes: 84 additions & 67 deletions src/bot/genkai_point/formula/v2.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use {
crate::bot::genkai_point::{formula::GenkaiPointFormula, model::Session},
crate::bot::genkai_point::{
formula::{GenkaiPointFormula, GenkaiPointFormulaOutput},
model::Session,
},
chrono::{DateTime, Duration, TimeZone, Timelike},
chrono_tz::Asia::Tokyo,
};
Expand All @@ -11,76 +14,90 @@ impl GenkaiPointFormula for FormulaV2 {
"v2"
}

fn calc(&self, session: &Session) -> u64 {
let start = session.joined_at.with_timezone(&Tokyo);
let end = session.left_at().with_timezone(&Tokyo);

let mut start_cursor = start;

let sub = |a: f64, b: f64, f: fn(f64) -> f64| f(b) - f(a);

let mut res = 0.0;

while end > start_cursor {
let end_cursor;

// v1 に用いた関数をそれぞれ積分したもの

let d = match start_cursor.hour() {
0..=2 => {
end_cursor = end.min(start_cursor.with_hms(3, 0, 0).unwrap());
sub(start_cursor.hour_f64(), end_cursor.hour_f64(), |x| {
(x.powi(2) / 2.0) + (7.0 * x)
})
}
3..=5 => {
end_cursor = end.min(start_cursor.with_hms(6, 0, 0).unwrap());
sub(start_cursor.hour_f64(), end_cursor.hour_f64(), |x| {
-(x.powi(2) / 2.0) + (13.0 * x)
})
}
6..=8 => {
end_cursor = end.min(start_cursor.with_hms(9, 0, 0).unwrap());
sub(start_cursor.hour_f64(), end_cursor.hour_f64(), |x| {
-x.powi(2) + (19.0 * x)
})
}
9 => {
end_cursor = end.min(start_cursor.with_hms(10, 0, 0).unwrap());
sub(start_cursor.hour_f64(), end_cursor.hour_f64(), |x| {
-(x.powi(2) / 2.0) + (10.0 * x)
})
}
10..=19 => {
end_cursor = end.min(start_cursor.with_hms(20, 0, 0).unwrap());
0.0
}
20 => {
end_cursor = end.min(start_cursor.with_hms(21, 0, 0).unwrap());
sub(start_cursor.hour_f64(), end_cursor.hour_f64(), |x| {
(x.powi(2) / 2.0) - (20.0 * x)
})
}
21..=23 => {
end_cursor =
end.min(start_cursor.with_hms(0, 0, 0).unwrap() + Duration::days(1));

let e = end_cursor.hour_f64();
let e = if e == 0.0 { 23.9999 } else { e };

sub(start_cursor.hour_f64(), e, |x| x.powi(2) - (41.0 * x))
}
x => unreachable!("hour {x} is not possible"),
};
fn calc(&self, sessions: &[Session]) -> GenkaiPointFormulaOutput {
let pts = sessions.iter().map(formula).sum::<f64>();
let total_hours = sessions
.iter()
.map(|s| s.left_at() - s.joined_at)
.sum::<Duration>()
.num_milliseconds() as f64
/ (60.0 * 60.0 * 1000.0);

res += d;
start_cursor = end_cursor;
}
let point = pts.round() as u64;
let efficiency = (pts / 10.0) / total_hours;

res.round() as _
GenkaiPointFormulaOutput { point, efficiency }
}
}

fn formula(session: &Session) -> f64 {
let start = session.joined_at.with_timezone(&Tokyo);
let end = session.left_at().with_timezone(&Tokyo);

let mut start_cursor = start;

let sub = |a: f64, b: f64, f: fn(f64) -> f64| f(b) - f(a);

let mut res = 0.0;

while end > start_cursor {
let end_cursor;

// v1 に用いた関数をそれぞれ積分したもの

let d = match start_cursor.hour() {
0..=2 => {
end_cursor = end.min(start_cursor.with_hms(3, 0, 0).unwrap());
sub(start_cursor.hour_f64(), end_cursor.hour_f64(), |x| {
(x.powi(2) / 2.0) + (7.0 * x)
})
}
3..=5 => {
end_cursor = end.min(start_cursor.with_hms(6, 0, 0).unwrap());
sub(start_cursor.hour_f64(), end_cursor.hour_f64(), |x| {
-(x.powi(2) / 2.0) + (13.0 * x)
})
}
6..=8 => {
end_cursor = end.min(start_cursor.with_hms(9, 0, 0).unwrap());
sub(start_cursor.hour_f64(), end_cursor.hour_f64(), |x| {
-x.powi(2) + (19.0 * x)
})
}
9 => {
end_cursor = end.min(start_cursor.with_hms(10, 0, 0).unwrap());
sub(start_cursor.hour_f64(), end_cursor.hour_f64(), |x| {
-(x.powi(2) / 2.0) + (10.0 * x)
})
}
10..=19 => {
end_cursor = end.min(start_cursor.with_hms(20, 0, 0).unwrap());
0.0
}
20 => {
end_cursor = end.min(start_cursor.with_hms(21, 0, 0).unwrap());
sub(start_cursor.hour_f64(), end_cursor.hour_f64(), |x| {
(x.powi(2) / 2.0) - (20.0 * x)
})
}
21..=23 => {
end_cursor = end.min(start_cursor.with_hms(0, 0, 0).unwrap() + Duration::days(1));

let e = end_cursor.hour_f64();
let e = if e == 0.0 { 23.9999 } else { e };

sub(start_cursor.hour_f64(), e, |x| x.powi(2) - (41.0 * x))
}
x => unreachable!("hour {x} is not possible"),
};

res += d;
start_cursor = end_cursor;
}

res.round()
}

trait DateTimeExt<Tz: TimeZone> {
fn with_hms(&self, hour: u32, minute: u32, second: u32) -> Option<DateTime<Tz>>;
fn hour_f64(&self) -> f64;
Expand Down Expand Up @@ -113,7 +130,7 @@ fn session_test() {
joined_at: $d1,
left_at: Some($d2),
};
assert_eq!(FormulaV2.calc(&session), $point);
assert_eq!(FormulaV2.calc(&[session]).point, $point);
}};
}

Expand Down
86 changes: 86 additions & 0 deletions src/bot/genkai_point/formula/v3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use {
super::{GenkaiPointFormula, GenkaiPointFormulaOutput},
crate::bot::genkai_point::model::Session,
chrono_tz::Asia::Tokyo,
};

pub(crate) struct FormulaV3;

impl GenkaiPointFormula for FormulaV3 {
fn name(&self) -> &'static str {
"v3"
}

fn calc(&self, sessions: &[Session]) -> GenkaiPointFormulaOutput {
let (now_points, max_points) = sessions
.iter()
.map(|s| {
let jdt = s.joined_at.with_timezone(&Tokyo);
let ldt = s.left_at().with_timezone(&Tokyo);

(jdt, ldt)
})
.map(|(jdt, ldt)| {
const ONE_HOUR_MILLIS: i64 = 60 * 60 * 1000;

let c = (jdt.timestamp_millis() % (24 * ONE_HOUR_MILLIS)) as f64
/ ONE_HOUR_MILLIS as f64;

let t = (ldt - jdt).num_milliseconds() as f64 / ONE_HOUR_MILLIS as f64;

#[rustfmt::skip]
let now_point = formula( c, t) - formula( c, 0.0);
let max_point = formula(23.0, t) - formula(23.0, 0.0);

(now_point, max_point)
})
.unzip::<_, _, Vec<_>, Vec<_>>();

let now_point = now_points.iter().sum::<f64>() * 10.0;
let max_point = max_points.iter().sum::<f64>() * 10.0;

let point = now_point.round() as u64;
let efficiency = now_point / max_point;

GenkaiPointFormulaOutput { point, efficiency }
}
}

#[rustfmt::skip]
// rendered: https://www.geogebra.org/graphing/esdsm7rz
// used for integrate: https://www.integral-calculator.com
fn formula(c: f64, t: f64) -> f64 {
let pi = core::f64::consts::PI;

let sin = f64::sin;
let cos = f64::cos;
let exp = f64::exp;
let pow = f64::powi;

let pi2 = pow(pi, 2);

let pi2_36 = pi2 + 36.0;
let pi2_09 = pi2 + 9.0;

let pi2_36_2 = pow(pi2_36, 2);
let pi2_09_2 = pow(pi2_09, 2);

let tc5_pi_06 = ((t + c + 5.0) * pi / 6.0).rem_euclid(pi * 2.0);
let tc5_pi_12 = ((t + c + 5.0) * pi / 12.0).rem_euclid(pi * 2.0);

let ex0 = pi * pi2_36_2 * (pi2_09 * t + 36.0 ) * sin(tc5_pi_06);
let ex1 = -3.0 * pi2_36_2 * (pi2_09 * t + 18.0 - 2.0 * pi2) * cos(tc5_pi_06);
let ex2 = 48.0 * pi2_09_2 * (pi2_36 * t + 72.0 - 2.0 * pi2) * sin(tc5_pi_12);
let ex3 = 8.0 * pi * pi2_09_2 * (pi2_36 * t + 144.0 ) * cos(tc5_pi_12);

let ex4 = pi2_36_2 * pi2_09_2 * t;

let ex5 = 2.0 * pow(pi, 8);
let ex6 = 180.0 * pow(pi, 6);
let ex7 = 5346.0 * pow(pi, 4);
let ex8 = 58320.0 * pow(pi, 2);
let ex9 = 209952.0 ;

let ex = ex0 + ex1 + ex2 + ex3 + ex4 + ex5 + ex6 + ex7 + ex8 + ex9;
-1.0 * (3.0 * exp((-t + 2.0) / 2.0) * ex) / (8.0 * pi2_36_2 * pi2_09_2)
}
10 changes: 8 additions & 2 deletions src/bot/genkai_point/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ ui! {
prefix: PREFIX,
command: Command,

#[clap(short, long, value_enum, default_value_t=Formula::V2)]
#[clap(short, long, value_enum, default_value_t=Formula::V3)]
formula: Formula,
}
}
Expand Down Expand Up @@ -87,12 +87,14 @@ enum RankingBy {
enum Formula {
V1,
V2,
V3,
}
impl Formula {
fn instance(self) -> DynGenkaiPointFormula {
match self {
Formula::V1 => DynGenkaiPointFormula(Box::new(FormulaV1)),
Formula::V2 => DynGenkaiPointFormula(Box::new(FormulaV2)),
Formula::V3 => DynGenkaiPointFormula(Box::new(FormulaV3)),
}
}
}
Expand Down Expand Up @@ -464,7 +466,9 @@ impl<D: GenkaiPointDatabase, P: Plotter> BotService for GenkaiPointBot<D, P> {
sessions.sort_unstable_by_key(|x| x.left_at());

let last_session = sessions.last().unwrap();
let this_time_point = default_formula().calc(last_session);
let this_time_point = default_formula()
.calc(core::slice::from_ref(last_session))
.point;

if this_time_point > 0 {
let stat = UserStat::from_sessions(&sessions, &default_formula())
Expand Down Expand Up @@ -551,3 +555,5 @@ macro_rules! datetime {

#[cfg(test)]
use datetime;

use self::formula::v3::FormulaV3;
Loading
Loading