use crate::discord::Context; use crate::error::Error; use crate::models::race; use crate::models::race::{Bet, RaceError, Racer, NUMBER_OF_RACERS, RACE_SIZE}; use crate::user::User; use j_db::database::Database; use poise::serenity_prelude::builder::EditMessage; use poise::serenity_prelude::model::id::UserId; use poise::serenity_prelude::utils::MessageBuilder; use poise::serenity_prelude::{EmojiIdentifier, Mentionable}; use rand::seq::IteratorRandom; use rand::{rng, Rng}; use std::time::Duration; fn cleanup_race(db: &Database) -> Result<(), Error> { log::info!("Cleaning up race state"); Racer::clear_racers(db)?; Bet::clear_bet(db)?; Ok(()) } async fn add_bet(ctx: Context<'_>, bet: Bet, racers: &[Racer]) -> Result<(), RaceError> { User::try_take_funds(&ctx.data().db, bet.author, bet.amount).map_err(|err| match err { Error::UserError(e) => RaceError::BetFundError(e), _ => panic!("Recv'ed error when trying to bet: {}", err), })?; if !racers.iter().any(|r| r.emoji.id == bet.emoji) { return Err(RaceError::RacerNotFound); } if Bet::has_user_bet(&ctx.data().db, bet.author)? { return Err(RaceError::UserHasBet); } Bet::add_bet(&ctx.data().db, bet)?; Ok(()) } #[poise::command(prefix_command, guild_only, category = "Race")] pub async fn race(ctx: Context<'_>) -> Result<(), Error> { cleanup_race(&ctx.data().db)?; let emojis = ctx.guild_id().unwrap().emojis(ctx.http()).await?; let racers: Vec<_> = emojis.iter().choose_multiple(&mut rng(), NUMBER_OF_RACERS); let racers: Result, Error> = racers .iter() .map(|e| Racer::add_racer(&ctx.data().db, (*e).clone())) .collect(); let racers = racers?; let mut racers_msg = MessageBuilder::new(); racers_msg.push_bold_line("Welcome to the Emoji Races, the racers today are:"); for racer in &racers { racers_msg.push_line(format!("* {}", racer.emoji)); } racers_msg.push_line("Place your bets using !bet. And start the race with !start_race"); ctx.say(racers_msg.build()).await?; Ok(()) } #[poise::command(prefix_command, guild_only, category = "Race")] pub async fn start_race(ctx: Context<'_>) -> Result<(), Error> { let mut racers = Racer::get_racers(&ctx.data().db)?; if racers.is_empty() { ctx.reply("Woops, no race is in progress. Use !race to kick this bad boy off") .await?; return Ok(()); } let race_channel = ctx.channel_id(); let mut race_msg = race_channel .say(&ctx.http(), race::draw_race(&racers)) .await?; let mut final_order: Vec = Vec::new(); while final_order.len() < NUMBER_OF_RACERS { tokio::time::sleep(Duration::from_millis(500)).await; for racer in &mut racers { racer.update_pos(); } race_msg .edit( ctx.http(), EditMessage::new().content(race::draw_race(&racers)), ) .await?; for racer in &racers { if racer.pos >= (RACE_SIZE as f32) && !final_order.contains(racer) { final_order.push(racer.clone()) } } } let bets = Bet::get_bets(&ctx.data().db)?; let bet_winners: Vec = bets .iter() .filter_map(|b| { if final_order.first().unwrap().emoji.id == b.emoji { Some(b.author) } else { None } }) .collect(); let mut winner_msg = MessageBuilder::new(); winner_msg.push_bold_line("The final outcome: "); for (pos, racer) in final_order.iter().enumerate() { winner_msg.push(format!("{}. ", pos + 1)); winner_msg.emoji(&racer.emoji); winner_msg.push_line(""); } let mut payout: i64 = 0; for bet in bets { payout += bet.amount as i64; } if bet_winners.is_empty() { winner_msg.push_line("Sadly there were no bet winners, better luck next time!"); } else { payout /= bet_winners.len() as i64; winner_msg.push_bold_line(format!( "The following winner(s) got a payout of {}:", payout )); for winner in bet_winners { winner_msg.push("* "); winner_msg.mention(&winner); winner_msg.push_line(""); User::give_funds(&ctx.data().db, winner, payout as u32)?; } } race_channel.say(ctx.http(), winner_msg.build()).await?; cleanup_race(&ctx.data().db)?; Ok(()) } #[poise::command(prefix_command, guild_only, category = "Race")] pub async fn bet( ctx: Context<'_>, #[description = "Racer to bet on"] racer: EmojiIdentifier, #[description = "Bet amount"] bet_amount: u32, ) -> Result<(), Error> { let racers = Racer::get_racers(&ctx.data().db)?; if racers.is_empty() { ctx.reply("No races are in progress use, !race to kick it off!") .await?; return Ok(()); } let bet = Bet::new(racer.id, bet_amount, ctx.author().id); let resp = match add_bet(ctx, bet, &racers).await { Ok(_) => format!( "{} has placed {} Fren Coins on {}", ctx.author().mention(), bet_amount, racer ), Err(err) => format!("Yeah something's fucked. {}", err), }; ctx.reply(resp).await?; Ok(()) }