FrenBot/src/models/race.rs

204 lines
4.7 KiB
Rust

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<UserError> for RaceError {
fn from(value: UserError) -> Self {
Self::BetFundError(value)
}
}
impl From<Error> 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<u64>,
pub emoji: EmojiId,
pub amount: u32,
pub author: UserId,
}
impl JdbModel for Bet {
fn id(&self) -> Option<u64> {
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<Self, Error> {
Ok(db.insert(bet)?)
}
pub fn get_bets(db: &Database) -> Result<Vec<Self>, 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::<Self>(bet.id().unwrap())?;
}
Ok(())
}
pub fn has_user_bet(db: &Database, user: UserId) -> Result<bool, Error> {
Ok(db
.filter(|_, bet: &Self| bet.author == user)?
.next()
.is_some())
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Racer {
id: Option<u64>,
pub emoji: Emoji,
pub speed: f32,
pub pos: f32,
}
impl PartialEq<Self> 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<Self, Error> {
let racer = Self::new(emoji);
let racer = db.insert(racer)?;
Ok(racer)
}
pub fn get_racers(db: &Database) -> Result<Vec<Self>, 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::<Self>(racer.id().unwrap())?;
}
Ok(())
}
}
impl JdbModel for Racer {
fn id(&self) -> Option<u64> {
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<Racer>) -> 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()
}