finalized score (for now lol)
This commit is contained in:
parent
5487a1801f
commit
e353977da9
1 changed files with 131 additions and 51 deletions
|
|
@ -1,10 +1,15 @@
|
||||||
use chrono::{DateTime, Utc, TimeDelta};
|
use chrono::{DateTime, Utc, TimeDelta};
|
||||||
|
use crate::{Result};
|
||||||
|
|
||||||
mod default {
|
mod default {
|
||||||
|
use crate::score::SECONDS_IN_A_DAY;
|
||||||
|
|
||||||
pub const INITIAL_SCORE: i64 = 70;
|
pub const INITIAL_SCORE: i64 = 70;
|
||||||
pub const GRAVITY: i64 = -10;
|
pub const GRAVITY: i64 = -10;
|
||||||
pub const BOOST: i64 = 12;
|
pub const BOOST: i64 = 12;
|
||||||
|
pub const BOOST_FREEZE_IN_SECONDS: i64 = SECONDS_IN_A_DAY;
|
||||||
}
|
}
|
||||||
|
const SECONDS_IN_A_DAY: i64 = 60 * 60 * 24;
|
||||||
|
|
||||||
macro_rules! rich_i64 {
|
macro_rules! rich_i64 {
|
||||||
($name:ident, $default:expr) => {
|
($name:ident, $default:expr) => {
|
||||||
|
|
@ -21,69 +26,144 @@ macro_rules! rich_i64 {
|
||||||
|
|
||||||
rich_i64!(Gravity, default::GRAVITY);
|
rich_i64!(Gravity, default::GRAVITY);
|
||||||
rich_i64!(Boost, default::BOOST);
|
rich_i64!(Boost, default::BOOST);
|
||||||
impl Boost {
|
pub struct UnparsedScore {
|
||||||
fn should_apply(
|
pub value: i64,
|
||||||
old_boost_time: Option<DateTime<Utc>>, new_boost_time: DateTime<Utc>
|
pub last_updated: DateTime<Utc>,
|
||||||
) -> bool {
|
pub last_boosted: Option<DateTime<Utc>>,
|
||||||
old_boost_time.map_or(true, |obs| {
|
}
|
||||||
let time_delta = obs - &new_boost_time;
|
|
||||||
time_delta.num_days() >= 1
|
impl UnparsedScore {
|
||||||
})
|
pub fn parse(self) -> Score {
|
||||||
|
match self.last_boosted {
|
||||||
|
None => Score::Decaying(DecayingScore {
|
||||||
|
value: self.value,
|
||||||
|
last_updated: self.last_updated,
|
||||||
|
}),
|
||||||
|
Some(last_boosted) => Score::Boosted(BoostedScore {
|
||||||
|
value: self.value,
|
||||||
|
boosted_at: last_boosted,
|
||||||
|
}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub struct Score {
|
|
||||||
value: i64,
|
pub enum Score {
|
||||||
last_updated: DateTime<Utc>,
|
Decaying(DecayingScore),
|
||||||
last_boosted: Option<DateTime<Utc>>,
|
Boosted(BoostedScore),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Score {
|
impl Score {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> DecayingScore {
|
||||||
Self::new_at_time(Utc::now())
|
DecayingScore {
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_at_time(creation_time: DateTime<Utc>) -> Self {
|
|
||||||
Score {
|
|
||||||
value: default::INITIAL_SCORE,
|
value: default::INITIAL_SCORE,
|
||||||
last_updated: creation_time,
|
last_updated: Utc::now(),
|
||||||
last_boosted: Some(creation_time),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(self) -> Self {
|
pub fn get_score(&self) -> i64 {
|
||||||
self.update_at_time(Utc::now())
|
match self {
|
||||||
|
Self::Decaying(s) => s.get_score(),
|
||||||
|
Self::Boosted(b) => b.get_score(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_at_time(self, update_time: DateTime<Utc>) -> Self {
|
pub fn update_score(self, gravity: Gravity) -> Self {
|
||||||
let to_elapse_from = self.last_boosted.map_or(self.last_updated, |lb| {
|
match self {
|
||||||
std::cmp::max(self.last_updated, lb + TimeDelta::days(1))
|
Self::Decaying(d) => Score::Decaying(d.update_score(gravity)),
|
||||||
});
|
Self::Boosted(b) => {
|
||||||
let elapsed_time = update_time.signed_duration_since(to_elapse_from);
|
let try_unfrozen = b.try_unfreeze();
|
||||||
|
match try_unfrozen {
|
||||||
self
|
Self::Decaying(s) => Score::Decaying(s.update_score(gravity)),
|
||||||
|
Self::Boosted(b) => Score::Boosted(b),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn boost(self, boost: Boost) -> Self {
|
pub fn get_decaying(self) -> Result<DecayingScore> {
|
||||||
self.boost_at_time(boost, Utc::now())
|
match self {
|
||||||
|
Self::Decaying(s) => Ok(s),
|
||||||
|
Self::Boosted(_) => Err("Attempted to get_decaying() of a boosted score".into()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function does not "fast-forward" the score's value to the time of
|
pub fn get_boosted(self) -> Result<BoostedScore> {
|
||||||
// boost, but still updates `last_updated`. Meaning that hours of gravity may
|
match self {
|
||||||
// not be applied.
|
Self::Decaying(_) => Err("Attempted to get_boosted() of a decaying score".into()),
|
||||||
// The expectation is that a boost is a user interaction, and that the user
|
Self::Boosted(b) => Ok(b),
|
||||||
// should be seeing a "fresh" score. We wouldn't want a boost to end up
|
}
|
||||||
// applying lots of gravity. But care should be taken to ensure that only
|
}
|
||||||
// "fresh" scores are boosted. Or that boost_time reflects the state of the
|
}
|
||||||
// user experience.
|
|
||||||
pub fn boost_at_time(self, boost: Boost, boost_time: DateTime<Utc>) -> Self {
|
pub struct BoostedScore {
|
||||||
if Boost::should_apply(self.last_boosted, boost_time) {
|
value: i64,
|
||||||
Self {
|
boosted_at: DateTime<Utc>,
|
||||||
value: self.value + i64::from(boost),
|
}
|
||||||
last_updated: boost_time,
|
|
||||||
last_boosted: Some(boost_time),
|
pub struct DecayingScore {
|
||||||
}
|
value: i64,
|
||||||
} else {
|
last_updated: DateTime<Utc>,
|
||||||
self
|
}
|
||||||
|
|
||||||
|
impl DecayingScore {
|
||||||
|
fn get_score(&self) -> i64 {
|
||||||
|
self.value
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_score(self, gravity: Gravity) -> Self {
|
||||||
|
self.update_score_at_time(gravity, Utc::now())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_score_at_time(
|
||||||
|
self, gravity: Gravity, update_time: DateTime<Utc>
|
||||||
|
) -> Self {
|
||||||
|
let elapsed_time: TimeDelta = update_time.signed_duration_since(self.last_updated);
|
||||||
|
let new_value = if elapsed_time <= TimeDelta::zero() { self.value } else {
|
||||||
|
i64::from(gravity) * (elapsed_time.num_seconds() / SECONDS_IN_A_DAY)
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
last_updated: update_time,
|
||||||
|
value: new_value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn boost(self, boost: Boost) -> BoostedScore {
|
||||||
|
self.boost_at_time(boost, Utc::now())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn boost_at_time(self, boost: Boost, boost_time: DateTime<Utc>) -> BoostedScore {
|
||||||
|
BoostedScore {
|
||||||
|
value: self.value + i64::from(boost),
|
||||||
|
boosted_at: boost_time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BoostedScore {
|
||||||
|
fn get_score(&self) -> i64 {
|
||||||
|
self.value
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unboost(self, boost: Boost) -> DecayingScore {
|
||||||
|
DecayingScore {
|
||||||
|
value: self.value - i64::from(boost),
|
||||||
|
last_updated: self.boosted_at,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_unfreeze(self) -> Score {
|
||||||
|
self.try_unfreeze_at_time(Utc::now())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_unfreeze_at_time(self, update_time: DateTime<Utc>) -> Score {
|
||||||
|
let boost_end = self.boosted_at + TimeDelta::seconds(default::BOOST_FREEZE_IN_SECONDS);
|
||||||
|
if boost_end > update_time {
|
||||||
|
Score::Decaying(DecayingScore {
|
||||||
|
value: self.value,
|
||||||
|
last_updated: boost_end,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Score::Boosted(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue