use crate::error::Error; use crate::user::User; use chrono::Utc; use j_db::database::Database; use log::info; use poise::serenity_prelude::UserId; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::f32::consts::PI; #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct GogurtReserves { id: Option, pub reserve_contributors: HashMap, pub gogurt_rate_per_pound: f64, } impl j_db::model::JdbModel for GogurtReserves { fn id(&self) -> Option { self.id } fn set_id(&mut self, id: u64) { self.id = Some(id) } fn tree() -> String { "GogurtReserves".to_string() } } impl GogurtReserves { pub fn get_gogurt_reserve(db: &Database) -> Result { Ok(db .filter(|_, _: &Self| true)? .next() .unwrap_or(Self::default())) } pub fn add_contribution(db: &Database, user: UserId, fc_amount: u64) -> Result { let mut gogurt_reserve = Self::get_gogurt_reserve(db)?; let gogurt_amount = gogurt_reserve.convert_fc_to_pounds(fc_amount); let contribution = gogurt_reserve .reserve_contributors .entry(user) .or_insert_with(|| 0.0); User::try_take_funds(db, user, fc_amount as u32)?; *contribution += gogurt_amount; db.insert(gogurt_reserve)?; Ok(gogurt_amount) } pub fn take_contribution( db: &Database, user: UserId, pounds_of_gogurt: f64, ) -> Result { let mut gogurt_reserve = Self::get_gogurt_reserve(db)?; let contribution = gogurt_reserve .reserve_contributors .entry(user) .or_insert_with(|| 0.0); if pounds_of_gogurt.is_sign_negative() { return Err(Error::CommandError( "You can't take negative gogurt, stop trying".to_string(), )); } if pounds_of_gogurt > *contribution { Err(Error::NotEnoughGogurt) } else { *contribution -= pounds_of_gogurt; let fc_profit = gogurt_reserve.convert_pounds_to_fc(pounds_of_gogurt); db.insert(gogurt_reserve)?; User::give_funds(db, user, fc_profit as u32)?; Ok(fc_profit) } } pub fn get_contributors(&self) -> Vec<(UserId, f64)> { let mut contributors: Vec<(UserId, f64)> = self.reserve_contributors.clone().into_iter().collect(); contributors.sort_by(|(_, a), (_, b)| b.total_cmp(a)); contributors } pub fn get_total_amount(&self) -> f64 { self.reserve_contributors.values().sum() } pub fn get_total_worth(&self) -> u64 { (self.get_total_amount() * self.gogurt_rate_per_pound) as u64 } pub fn convert_pounds_to_fc(&self, pounds: f64) -> u64 { (pounds * self.gogurt_rate_per_pound) as u64 } pub fn convert_fc_to_pounds(&self, fc: u64) -> f64 { fc as f64 / self.gogurt_rate_per_pound } pub fn market_function(time: i64) -> f64 { let x = time as f64; (x.sin() + (PI as f64 * x).cos() + (PI as f64 * x).sin() + x.cos() - (x * 4.0).sin() - (x * 3.0).cos()) .clamp(0.0, 6.0) } pub fn calculate_new_rate(time: i64) -> f64 { Self::market_function(time) } pub fn update_rate(db: &Database) -> Result<(), Error> { let mut reserves = Self::get_gogurt_reserve(db)?; let time = Utc::now().timestamp(); let new_rate = Self::calculate_new_rate(time); info!("Updating rate to '{new_rate}'"); reserves.gogurt_rate_per_pound = new_rate; db.insert(reserves)?; Ok(()) } } #[cfg(test)] mod test { use crate::models::gogurt_reserves::GogurtReserves; #[test] fn test_avg_market_function() { let sum: f64 = (0..100000).map(GogurtReserves::market_function).sum(); let avg = sum / 100000.0; println!("Average = {avg}"); assert!(avg < 1.0 && avg > 0.0) } }