Skip to content

Commit

Permalink
Merge pull request #82 from approvers/add-formula-v3
Browse files Browse the repository at this point in the history
feat: add genkai-point-formula v3
  • Loading branch information
kawaemon authored Jan 5, 2024
2 parents 1efccef + c8bce70 commit 7cc2abc
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 111 deletions.
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

0 comments on commit 7cc2abc

Please sign in to comment.