Add back in race commands
This commit is contained in:
parent
8c9d98f031
commit
095d6e82a0
@ -11,9 +11,7 @@ use reqwest::Url;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use tokio::sync::mpsc::{channel, Receiver, Sender};
|
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, StructOpt)]
|
||||||
@ -109,24 +107,3 @@ impl GlobalData {
|
|||||||
impl TypeMapKey for GlobalData {
|
impl TypeMapKey for GlobalData {
|
||||||
type Value = GlobalData;
|
type Value = GlobalData;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Channel<T> {
|
|
||||||
pub recv: Arc<Mutex<Receiver<T>>>,
|
|
||||||
pub send: Arc<Mutex<Sender<T>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Channel<T> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let (send, recv) = channel::<T>(10);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
recv: Arc::new(Mutex::new(recv)),
|
|
||||||
send: Arc::new(Mutex::new(send)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static + Send + Sync> TypeMapKey for Channel<T> {
|
|
||||||
type Value = Channel<T>;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use crate::error::Error;
|
|||||||
use poise::serenity_prelude::builder::EditRole;
|
use poise::serenity_prelude::builder::EditRole;
|
||||||
use poise::serenity_prelude::model::Colour;
|
use poise::serenity_prelude::model::Colour;
|
||||||
|
|
||||||
#[poise::command(prefix_command, guild_only)]
|
#[poise::command(prefix_command, guild_only, category = "Color")]
|
||||||
pub async fn set_color(
|
pub async fn set_color(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[description = "Color you want your role to be"]
|
#[description = "Color you want your role to be"]
|
||||||
@ -77,7 +77,7 @@ pub async fn set_color(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[poise::command(prefix_command, guild_only)]
|
#[poise::command(prefix_command, guild_only, category = "Color")]
|
||||||
pub async fn remove_color(ctx: Context<'_>) -> Result<(), Error> {
|
pub async fn remove_color(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
let guild_id = ctx.guild_id().unwrap();
|
let guild_id = ctx.guild_id().unwrap();
|
||||||
let member = ctx.author_member().await.unwrap();
|
let member = ctx.author_member().await.unwrap();
|
||||||
|
|||||||
@ -1,140 +1,27 @@
|
|||||||
use crate::config::{Channel, GlobalData};
|
use crate::discord::Context;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::user::{User, UserError};
|
use crate::models::race;
|
||||||
use crate::{command, group};
|
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::seq::IteratorRandom;
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{rng, Rng};
|
||||||
use serenity::builder::EditMessage;
|
|
||||||
use serenity::client::Context;
|
|
||||||
use serenity::framework::standard::{Args, CommandResult};
|
|
||||||
use serenity::model::id::{EmojiId, UserId};
|
|
||||||
use serenity::model::misc::EmojiIdentifier;
|
|
||||||
use serenity::model::prelude::{Emoji, Message};
|
|
||||||
use serenity::prelude::Mentionable;
|
|
||||||
use serenity::utils::MessageBuilder;
|
|
||||||
use std::fmt::{Display, Formatter};
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
const RACE_SIZE: usize = 25;
|
fn cleanup_race(db: &Database) -> Result<(), Error> {
|
||||||
const BASE_SPEED: f32 = 0.5;
|
log::info!("Cleaning up race state");
|
||||||
const NUMBER_OF_RACERS: usize = 5;
|
Racer::clear_racers(db)?;
|
||||||
|
Bet::clear_bet(db)?;
|
||||||
|
|
||||||
#[group]
|
Ok(())
|
||||||
#[commands(race, start_race, bet)]
|
|
||||||
pub struct EmojiRace;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum RaceError {
|
|
||||||
RacerNotFound,
|
|
||||||
BetFundError(UserError),
|
|
||||||
UserHasBet,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for RaceError {
|
async fn add_bet(ctx: Context<'_>, bet: Bet, racers: &[Racer]) -> Result<(), RaceError> {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
User::try_take_funds(&ctx.data().db, bet.author, bet.amount).map_err(|err| match err {
|
||||||
match self {
|
|
||||||
RaceError::RacerNotFound => write!(f, "Racer not found"),
|
|
||||||
RaceError::BetFundError(e) => write!(f, "Fund error: {}", e),
|
|
||||||
RaceError::UserHasBet => write!(f, "User already bet"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<UserError> for RaceError {
|
|
||||||
fn from(value: UserError) -> Self {
|
|
||||||
Self::BetFundError(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for RaceError {}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum RaceMessage {
|
|
||||||
StartRace,
|
|
||||||
Bet(Bet),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Bet {
|
|
||||||
pub emoji: EmojiId,
|
|
||||||
pub amount: u32,
|
|
||||||
pub author: UserId,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
struct Racer {
|
|
||||||
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 = thread_rng().gen_range(0.0..0.5);
|
|
||||||
let speed = BASE_SPEED + (0.50 * genetic_stat) + (0.50 * random_stat);
|
|
||||||
|
|
||||||
Racer {
|
|
||||||
emoji,
|
|
||||||
speed,
|
|
||||||
pos: 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_pos(&mut self) {
|
|
||||||
let speed_boost = thread_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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn add_bet(
|
|
||||||
ctx: &Context,
|
|
||||||
bet: Bet,
|
|
||||||
bets: &mut Vec<Bet>,
|
|
||||||
racers: &[Racer],
|
|
||||||
) -> Result<(), RaceError> {
|
|
||||||
let mut data = ctx.data.write().await;
|
|
||||||
|
|
||||||
let global = data.get_mut::<GlobalData>().unwrap();
|
|
||||||
|
|
||||||
User::try_take_funds(&global.db, bet.author, bet.amount).map_err(|err| match err {
|
|
||||||
Error::UserError(e) => RaceError::BetFundError(e),
|
Error::UserError(e) => RaceError::BetFundError(e),
|
||||||
_ => panic!("Recv'ed error when trying to bet: {}", err),
|
_ => panic!("Recv'ed error when trying to bet: {}", err),
|
||||||
})?;
|
})?;
|
||||||
@ -143,44 +30,28 @@ async fn add_bet(
|
|||||||
return Err(RaceError::RacerNotFound);
|
return Err(RaceError::RacerNotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
if bets.iter().any(|b| b.author == bet.author) {
|
if Bet::has_user_bet(&ctx.data().db, bet.author)? {
|
||||||
return Err(RaceError::UserHasBet);
|
return Err(RaceError::UserHasBet);
|
||||||
}
|
}
|
||||||
|
|
||||||
bets.push(bet);
|
Bet::add_bet(&ctx.data().db, bet)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[command]
|
#[poise::command(prefix_command, guild_only, category = "Race")]
|
||||||
#[description("Create a race")]
|
pub async fn race(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
#[only_in(guilds)]
|
cleanup_race(&ctx.data().db)?;
|
||||||
async fn race(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
|
|
||||||
let race_msg_channel = {
|
|
||||||
let data = ctx.data.read().await;
|
|
||||||
|
|
||||||
data.get::<Channel<RaceMessage>>().unwrap().clone()
|
let emojis = ctx.guild_id().unwrap().emojis(ctx.http()).await?;
|
||||||
};
|
|
||||||
|
|
||||||
let mut recv = match race_msg_channel.recv.try_lock() {
|
let racers: Vec<_> = emojis.iter().choose_multiple(&mut rng(), NUMBER_OF_RACERS);
|
||||||
Ok(recv) => recv,
|
|
||||||
Err(_) => {
|
|
||||||
msg.reply(&ctx.http, "Race in progress").await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let guild = msg.guild_id.unwrap();
|
let racers: Result<Vec<Racer>, Error> = racers
|
||||||
let channels = guild.channels(&ctx.http).await?;
|
|
||||||
let race_channel = channels.get(&msg.channel_id).unwrap();
|
|
||||||
|
|
||||||
let emojis = guild.emojis(&ctx.http).await.unwrap();
|
|
||||||
|
|
||||||
let racers: Vec<_> = emojis
|
|
||||||
.iter()
|
.iter()
|
||||||
.choose_multiple(&mut thread_rng(), NUMBER_OF_RACERS);
|
.map(|e| Racer::add_racer(&ctx.data().db, (*e).clone()))
|
||||||
|
.collect();
|
||||||
let mut racers: Vec<Racer> = racers.iter().map(|e| Racer::new((*e).clone())).collect();
|
let racers = racers?;
|
||||||
|
|
||||||
let mut racers_msg = MessageBuilder::new();
|
let mut racers_msg = MessageBuilder::new();
|
||||||
racers_msg.push_bold_line("Welcome to the Emoji Races, the racers today are:");
|
racers_msg.push_bold_line("Welcome to the Emoji Races, the racers today are:");
|
||||||
@ -188,62 +59,26 @@ async fn race(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
|
|||||||
racers_msg.push_line(format!("* {}", racer.emoji));
|
racers_msg.push_line(format!("* {}", racer.emoji));
|
||||||
}
|
}
|
||||||
racers_msg.push_line("Place your bets using !bet. And start the race with !start_race");
|
racers_msg.push_line("Place your bets using !bet. And start the race with !start_race");
|
||||||
race_channel.say(&ctx.http, racers_msg.build()).await?;
|
ctx.say(racers_msg.build()).await?;
|
||||||
|
|
||||||
let mut bets: Vec<Bet> = Vec::new();
|
Ok(())
|
||||||
loop {
|
}
|
||||||
let race_msg = recv.recv().await.unwrap();
|
|
||||||
|
|
||||||
match race_msg {
|
#[poise::command(prefix_command, guild_only, category = "Race")]
|
||||||
RaceMessage::StartRace => {
|
pub async fn start_race(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
race_channel.say(&ctx, "And they're off!").await?;
|
let mut racers = Racer::get_racers(&ctx.data().db)?;
|
||||||
break;
|
|
||||||
}
|
|
||||||
RaceMessage::Bet(bet) => {
|
|
||||||
let emoji = match msg.guild_id.unwrap().emoji(&ctx.http, bet.emoji).await {
|
|
||||||
Ok(emoji) => emoji,
|
|
||||||
Err(_) => {
|
|
||||||
race_channel
|
|
||||||
.say(&ctx, "That's a weird looking racer, pick a normal one!")
|
|
||||||
.await?;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(e) = add_bet(ctx, bet.clone(), &mut bets, &racers).await {
|
if racers.is_empty() {
|
||||||
let msg = match e {
|
ctx.reply("Woops, no race is in progress. Use !race to kick this bad boy off")
|
||||||
RaceError::RacerNotFound => {
|
.await?;
|
||||||
format!("{} is not in this race!", emoji)
|
return Ok(());
|
||||||
}
|
|
||||||
RaceError::BetFundError(_) => {
|
|
||||||
"Only rich people can bet in this race, sorry".to_string()
|
|
||||||
}
|
|
||||||
RaceError::UserHasBet => {
|
|
||||||
"You can't bet more than once in this race, that's against the law and you are going to jail!".to_string()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
race_channel
|
|
||||||
.say(&ctx.http, format!("{} {}", bet.author.mention(), msg))
|
|
||||||
.await?;
|
|
||||||
} else {
|
|
||||||
race_channel
|
|
||||||
.say(
|
|
||||||
&ctx.http,
|
|
||||||
format!(
|
|
||||||
"{} has put {} on {}",
|
|
||||||
bet.author.mention(),
|
|
||||||
bet.amount,
|
|
||||||
emoji,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut race_msg = race_channel.say(&ctx.http, draw_race(&racers)).await?;
|
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();
|
let mut final_order: Vec<Racer> = Vec::new();
|
||||||
while final_order.len() < NUMBER_OF_RACERS {
|
while final_order.len() < NUMBER_OF_RACERS {
|
||||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||||
@ -252,7 +87,10 @@ async fn race(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
race_msg
|
race_msg
|
||||||
.edit(&ctx.http, EditMessage::new().content(draw_race(&racers)))
|
.edit(
|
||||||
|
ctx.http(),
|
||||||
|
EditMessage::new().content(race::draw_race(&racers)),
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
for racer in &racers {
|
for racer in &racers {
|
||||||
@ -262,6 +100,8 @@ async fn race(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let bets = Bet::get_bets(&ctx.data().db)?;
|
||||||
|
|
||||||
let bet_winners: Vec<UserId> = bets
|
let bet_winners: Vec<UserId> = bets
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|b| {
|
.filter_map(|b| {
|
||||||
@ -297,85 +137,50 @@ async fn race(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
|
|||||||
"The following winner(s) got a payout of {}:",
|
"The following winner(s) got a payout of {}:",
|
||||||
payout
|
payout
|
||||||
));
|
));
|
||||||
{
|
|
||||||
let mut data = ctx.data.write().await;
|
|
||||||
let global_data = data.get_mut::<GlobalData>().unwrap();
|
|
||||||
for winner in bet_winners {
|
|
||||||
winner_msg.push("* ");
|
|
||||||
winner_msg.mention(&winner);
|
|
||||||
winner_msg.push_line("");
|
|
||||||
|
|
||||||
User::give_funds(&global_data.db, winner, payout as u32)?;
|
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?;
|
race_channel.say(ctx.http(), winner_msg.build()).await?;
|
||||||
|
|
||||||
|
cleanup_race(&ctx.data().db)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_if_race_in_progress(race_channel: &Channel<RaceMessage>) -> bool {
|
#[poise::command(prefix_command, guild_only, category = "Race")]
|
||||||
race_channel.recv.try_lock().is_err()
|
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)?;
|
||||||
|
|
||||||
#[command]
|
if racers.is_empty() {
|
||||||
#[description("Start a race")]
|
ctx.reply("No races are in progress use, !race to kick it off!")
|
||||||
#[only_in(guilds)]
|
.await?;
|
||||||
async fn start_race(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
|
return Ok(());
|
||||||
let data = ctx.data.read().await;
|
|
||||||
let race_msg_channel = data.get::<Channel<RaceMessage>>().unwrap();
|
|
||||||
|
|
||||||
if !check_if_race_in_progress(race_msg_channel) {
|
|
||||||
msg.reply(&ctx.http, "Race not in progress").await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let send = race_msg_channel.send.lock().await;
|
let bet = Bet::new(racer.id, bet_amount, ctx.author().id);
|
||||||
|
|
||||||
send.send(RaceMessage::StartRace).await?;
|
let resp = match add_bet(ctx, bet, &racers).await {
|
||||||
|
Ok(_) => format!(
|
||||||
Ok(())
|
"{} has placed {} Fren Coins on {}",
|
||||||
}
|
ctx.author().mention(),
|
||||||
|
bet_amount,
|
||||||
#[command]
|
racer
|
||||||
#[description("Bet on a race")]
|
),
|
||||||
#[usage("<emoji> <amount>")]
|
Err(err) => format!("Yeah something's fucked. {}", err),
|
||||||
#[usage(":) 69")]
|
|
||||||
#[only_in(guilds)]
|
|
||||||
async fn bet(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
|
||||||
let data = ctx.data.read().await;
|
|
||||||
|
|
||||||
let race_msg_channel = data.get::<Channel<RaceMessage>>().unwrap();
|
|
||||||
if !check_if_race_in_progress(race_msg_channel) {
|
|
||||||
msg.reply(&ctx.http, "Race not in progress").await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let racer = match args.parse::<EmojiIdentifier>() {
|
|
||||||
Ok(emoji) => emoji,
|
|
||||||
Err(e) => {
|
|
||||||
msg.reply(&ctx.http, format!("Invalid emoji: {}", e))
|
|
||||||
.await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
args.advance();
|
|
||||||
let amount = match args.parse::<u32>() {
|
|
||||||
Ok(amount) => amount,
|
|
||||||
Err(err) => {
|
|
||||||
msg.reply(&ctx.http, format!("Invalid bet amount: {}", err))
|
|
||||||
.await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let send = race_msg_channel.send.lock().await;
|
ctx.reply(resp).await?;
|
||||||
|
|
||||||
send.send(RaceMessage::Bet(Bet {
|
|
||||||
emoji: racer.id,
|
|
||||||
amount,
|
|
||||||
author: msg.author.id,
|
|
||||||
}))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ mod album;
|
|||||||
mod birthday;
|
mod birthday;
|
||||||
mod celeryman;
|
mod celeryman;
|
||||||
mod color;
|
mod color;
|
||||||
|
mod emoji_race;
|
||||||
|
|
||||||
use crate::config::GlobalData;
|
use crate::config::GlobalData;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
@ -121,6 +122,9 @@ pub async fn run_bot(global_data: GlobalData) {
|
|||||||
celeryman::tayne(),
|
celeryman::tayne(),
|
||||||
color::set_color(),
|
color::set_color(),
|
||||||
color::remove_color(),
|
color::remove_color(),
|
||||||
|
emoji_race::bet(),
|
||||||
|
emoji_race::race(),
|
||||||
|
emoji_race::start_race(),
|
||||||
],
|
],
|
||||||
event_handler: |ctx, event, framework, data| {
|
event_handler: |ctx, event, framework, data| {
|
||||||
Box::pin(event_handler(ctx, event, framework, data))
|
Box::pin(event_handler(ctx, event, framework, data))
|
||||||
|
|||||||
@ -7,9 +7,7 @@ mod migrations;
|
|||||||
mod models;
|
mod models;
|
||||||
mod user;
|
mod user;
|
||||||
|
|
||||||
use crate::config::{Args, BotConfig, Channel, GlobalData};
|
use crate::config::{Args, BotConfig, GlobalData};
|
||||||
//use crate::discord::emoji_race::RaceMessage;
|
|
||||||
//use crate::discord::{unrecognised_command_hook, Handler};
|
|
||||||
use crate::discord::run_bot;
|
use crate::discord::run_bot;
|
||||||
use log::{error, info, LevelFilter};
|
use log::{error, info, LevelFilter};
|
||||||
use magick_rust::magick_wand_genesis;
|
use magick_rust::magick_wand_genesis;
|
||||||
|
|||||||
@ -3,5 +3,6 @@ pub mod birthday;
|
|||||||
pub mod insult_compliment;
|
pub mod insult_compliment;
|
||||||
pub mod lil_fren;
|
pub mod lil_fren;
|
||||||
pub mod motivation;
|
pub mod motivation;
|
||||||
|
pub mod race;
|
||||||
pub mod random;
|
pub mod random;
|
||||||
pub mod task;
|
pub mod task;
|
||||||
|
|||||||
203
src/models/race.rs
Normal file
203
src/models/race.rs
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user