187 lines
5.3 KiB
Rust
187 lines
5.3 KiB
Rust
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<Vec<Racer>, 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<Racer> = 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<UserId> = 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(())
|
|
}
|