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 crate::{Result};
|
||||
|
||||
mod default {
|
||||
use crate::score::SECONDS_IN_A_DAY;
|
||||
|
||||
pub const INITIAL_SCORE: i64 = 70;
|
||||
pub const GRAVITY: i64 = -10;
|
||||
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 {
|
||||
($name:ident, $default:expr) => {
|
||||
|
|
@ -21,69 +26,144 @@ macro_rules! rich_i64 {
|
|||
|
||||
rich_i64!(Gravity, default::GRAVITY);
|
||||
rich_i64!(Boost, default::BOOST);
|
||||
impl Boost {
|
||||
fn should_apply(
|
||||
old_boost_time: Option<DateTime<Utc>>, new_boost_time: DateTime<Utc>
|
||||
) -> bool {
|
||||
old_boost_time.map_or(true, |obs| {
|
||||
let time_delta = obs - &new_boost_time;
|
||||
time_delta.num_days() >= 1
|
||||
})
|
||||
pub struct UnparsedScore {
|
||||
pub value: i64,
|
||||
pub last_updated: DateTime<Utc>,
|
||||
pub last_boosted: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
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,
|
||||
last_updated: DateTime<Utc>,
|
||||
last_boosted: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
pub enum Score {
|
||||
Decaying(DecayingScore),
|
||||
Boosted(BoostedScore),
|
||||
}
|
||||
|
||||
impl Score {
|
||||
pub fn new() -> Self {
|
||||
Self::new_at_time(Utc::now())
|
||||
}
|
||||
|
||||
pub fn new_at_time(creation_time: DateTime<Utc>) -> Self {
|
||||
Score {
|
||||
pub fn new() -> DecayingScore {
|
||||
DecayingScore {
|
||||
value: default::INITIAL_SCORE,
|
||||
last_updated: creation_time,
|
||||
last_boosted: Some(creation_time),
|
||||
last_updated: Utc::now(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(self) -> Self {
|
||||
self.update_at_time(Utc::now())
|
||||
pub fn get_score(&self) -> i64 {
|
||||
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 {
|
||||
let to_elapse_from = self.last_boosted.map_or(self.last_updated, |lb| {
|
||||
std::cmp::max(self.last_updated, lb + TimeDelta::days(1))
|
||||
});
|
||||
let elapsed_time = update_time.signed_duration_since(to_elapse_from);
|
||||
|
||||
self
|
||||
pub fn update_score(self, gravity: Gravity) -> Self {
|
||||
match self {
|
||||
Self::Decaying(d) => Score::Decaying(d.update_score(gravity)),
|
||||
Self::Boosted(b) => {
|
||||
let try_unfrozen = b.try_unfreeze();
|
||||
match try_unfrozen {
|
||||
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> {
|
||||
match self {
|
||||
Self::Decaying(s) => Ok(s),
|
||||
Self::Boosted(_) => Err("Attempted to get_decaying() of a boosted score".into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_boosted(self) -> Result<BoostedScore> {
|
||||
match self {
|
||||
Self::Decaying(_) => Err("Attempted to get_boosted() of a decaying score".into()),
|
||||
Self::Boosted(b) => Ok(b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BoostedScore {
|
||||
value: i64,
|
||||
boosted_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
pub struct DecayingScore {
|
||||
value: i64,
|
||||
last_updated: DateTime<Utc>,
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
// This function does not "fast-forward" the score's value to the time of
|
||||
// boost, but still updates `last_updated`. Meaning that hours of gravity may
|
||||
// not be applied.
|
||||
// The expectation is that a boost is a user interaction, and that the user
|
||||
// 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 {
|
||||
if Boost::should_apply(self.last_boosted, boost_time) {
|
||||
Self {
|
||||
fn boost_at_time(self, boost: Boost, boost_time: DateTime<Utc>) -> BoostedScore {
|
||||
BoostedScore {
|
||||
value: self.value + i64::from(boost),
|
||||
last_updated: boost_time,
|
||||
last_boosted: Some(boost_time),
|
||||
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 {
|
||||
self
|
||||
Score::Boosted(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue