use crate::error::Error; use crate::user::UserError; use j_db::database::Database; use j_db::error::JDbError; use j_db::model::JdbModel; use poise::serenity_prelude::{Emoji, EmojiId, MessageBuilder, UserId}; use rand::{rng, Rng}; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; pub const RACE_SIZE: usize = 25; const BASE_SPEED: f32 = 0.5; pub const NUMBER_OF_RACERS: usize = 5; #[derive(Debug)] pub enum RaceError { RacerNotFound, BetFundError(UserError), UserHasBet, GenericError(Error), } impl Display for RaceError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { RaceError::RacerNotFound => write!(f, "Racer not found"), RaceError::BetFundError(e) => write!(f, "Fund error: {}", e), RaceError::UserHasBet => write!(f, "User already bet"), RaceError::GenericError(e) => write!(f, "Sorry the horse died: {}", e), } } } impl From for RaceError { fn from(value: UserError) -> Self { Self::BetFundError(value) } } impl From for RaceError { fn from(value: Error) -> Self { Self::GenericError(value) } } impl std::error::Error for RaceError {} #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Bet { id: Option, pub emoji: EmojiId, pub amount: u32, pub author: UserId, } impl JdbModel for Bet { fn id(&self) -> Option { self.id } fn set_id(&mut self, id: u64) { self.id = Some(id) } fn tree() -> String { "Bet".to_string() } } impl Bet { pub fn new(emoji: EmojiId, amount: u32, author: UserId) -> Self { Self { id: None, emoji, amount, author, } } pub fn add_bet(db: &Database, bet: Self) -> Result { Ok(db.insert(bet)?) } pub fn get_bets(db: &Database) -> Result, Error> { Ok(db.filter(|_, _bet: &Self| true)?.collect()) } pub fn clear_bet(db: &Database) -> Result<(), Error> { let bets = Self::get_bets(db)?; for bet in &bets { db.remove::(bet.id().unwrap())?; } Ok(()) } pub fn has_user_bet(db: &Database, user: UserId) -> Result { Ok(db .filter(|_, bet: &Self| bet.author == user)? .next() .is_some()) } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Racer { id: Option, pub emoji: Emoji, pub speed: f32, pub pos: f32, } impl PartialEq for Racer { fn eq(&self, other: &Self) -> bool { self.emoji.id == other.emoji.id } } impl Eq for Racer {} impl Racer { pub fn new(emoji: Emoji) -> Self { let genetic_stat = (emoji.id.get() & 0xff) as f32 / 255.0; let random_stat = rng().random_range(0.0..0.5); let speed = BASE_SPEED + (0.50 * genetic_stat) + (0.50 * random_stat); Racer { id: None, emoji, speed, pos: 0.0, } } pub fn update_pos(&mut self) { let speed_boost = rng().gen_range(-0.75..0.75); self.pos += self.speed + (self.speed * speed_boost); self.pos = self.pos.clamp(0.0, RACE_SIZE as f32); } pub fn get_race_pos(&self) -> usize { self.pos.floor() as usize } pub fn add_racer(db: &Database, emoji: Emoji) -> Result { let racer = Self::new(emoji); let racer = db.insert(racer)?; Ok(racer) } pub fn get_racers(db: &Database) -> Result, Error> { Ok(db.filter(|_, _racer: &Self| true)?.collect()) } pub fn clear_racers(db: &Database) -> Result<(), Error> { let racers = Self::get_racers(db)?; for racer in &racers { db.remove::(racer.id().unwrap())?; } Ok(()) } } impl JdbModel for Racer { fn id(&self) -> Option { self.id } fn set_id(&mut self, id: u64) { self.id = Some(id) } fn tree() -> String { "Racer".to_string() } } pub fn draw_race(racers: &Vec) -> String { let mut msg = MessageBuilder::default(); for racer in racers { let mut race_line = MessageBuilder::new(); race_line.push("|"); race_line.push("=".repeat(racer.get_race_pos())); race_line.push("E"); if race_line.0.len() < RACE_SIZE { let padding_needed = RACE_SIZE - race_line.0.len() - 1; race_line.push("=".repeat(padding_needed)); race_line.push("🔳"); } let line = race_line.build(); let line = line.replace('E', &racer.emoji.to_string()); msg.push_line(line); } msg.build() }