A virtual currency betting bot for Twitch chat. https://ddark.net/better
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

221 lines
7.3 KiB

#pragma warning (disable: 4996) // This function or variable may be unsafe (strcpy, sprintf, ...)
#pragma warning (disable: 4200) // nonstandard extension used: zero-sized array in struct/union
#include <cstdio>
#include <stb_ds.h>
#include "better_alloc.h"
#include "better_App.h"
#include "better_bets.h"
#include "better_func.h"
#include "better_irc.h"
u8 bets_status(App* app)
{
if (app->timer_left > 0.0f)
return BETS_STATUS_OPEN;
if (app->timer_left > - (f32)app->settings.coyote_time)
return BETS_STATUS_COYOTE;
return BETS_STATUS_CLOSED;
}
// betting_on_option is 0-indexed
u64 available_points(App* app, const char* name, i32 betting_on_option)
{
assert(betting_on_option >= 0 && betting_on_option < arrlen(app->bet_options));
UserInfo* user = shget(app->users, name);
if (!user) return 0;
u64 res = user->points;
if (app->settings.add_mode)
{
// Subtract wager on this option.
auto bet_map = app->bet_options[betting_on_option].bets;
auto bet = shgeti(bet_map, name);
if (bet != -1)
{
if (bet_map[bet].value > res)
res = 0;
else
res -= bet_map[bet].value;
}
}
if (app->settings.allow_multibets)
{
// Subtract wagers on other options.
for (int i = 0; i < arrlen(app->bet_options); ++i)
{
if (i == betting_on_option)
{
continue;
}
auto bet_map = app->bet_options[i].bets;
auto bet = shgeti(bet_map, name);
if (bet != -1)
{
if (bet_map[bet].value > res)
res = 0;
else
res -= bet_map[bet].value;
}
}
}
return res;
}
void register_max_bet(App* app, const char* user, i32 option)
{
if (option < 0 || option >= arrlen(app->bet_options)) return;
u64 amount = available_points(app, user, option);
if (amount > 0) register_bet(app, user, amount, option);
}
void register_bet(App* app, const char* user, i64 amount, i32 option)
{
if (option < 0 || option >= arrlen(app->bet_options)) return;
u64 avail = available_points(app, user, option);
if (amount > 0 && avail < (u64)amount)
{
amount = avail;
}
// If multibets are not allowed, remove wagers on other options
if (!app->settings.allow_multibets)
{
for (int i = 0; i < arrlen(app->bet_options); ++i)
if (i != option) shdel(app->bet_options[i].bets, user);
}
if (app->settings.add_mode)
{
u64 current_amount = shget(app->bet_options[option].bets, user);
i64 new_amount = current_amount + amount;
if (new_amount <= 0)
shdel(app->bet_options[option].bets, user);
else
shput(app->bet_options[option].bets, user, (u64)new_amount);
}
else
{
if (amount <= 0)
shdel(app->bet_options[option].bets, user);
else
shput(app->bet_options[option].bets, user, (u64)amount);
}
}
void open_bets(App* app)
{
if (arrlen(app->bet_options) == 0)
{
add_log(app, LOGLEVEL_USERERROR, "Cannot open bets with no options.");
return;
}
add_log(app, LOGLEVEL_INFO, "Opening bets for the next %i+%u seconds.", app->settings.timer_setting, app->settings.coyote_time);
app->timer_left = (f32) app->settings.timer_setting;
app->fish = app->shark = NULL;
app->fish_loss = app->shark_win = 0;
if (app->settings.announce_bets_open)
{
#define MSG_LEN (200 + CHANNEL_NAME_MAX + 2*POINTS_NAME_MAX + OPTION_NAME_MAX)
static_assert(MSG_LEN < 512);
char* buf = (char*) BETTER_ALLOC(MSG_LEN, "writebuf_announcement_open_bets");
sprintf(buf, "PRIVMSG #%s :Bets are now OPEN! Type %s%s to see how many %s you have, and %sbet (amount) (option) to place a bet. E.g. \"%sbet 100 %s\" or \"%sbet all %llu\"\r\n", app->settings.channel, app->settings.command_prefix, app->settings.points_name, app->settings.points_name, app->settings.command_prefix, app->settings.command_prefix, app->bet_options[0].option_name, app->settings.command_prefix, arrlen(app->bet_options));
irc_queue_write(app, buf, true);
}
}
void close_bets(App* app)
{
add_log(app, LOGLEVEL_INFO, "Closing bets.");
app->timer_left = -INFINITY;
if (app->settings.announce_bets_close)
{
char* buf = (char*) BETTER_ALLOC(CHANNEL_NAME_MAX+50, "writebuf_announcement_close_bets");
sprintf(buf, "PRIVMSG #%s :Bets are now CLOSED!\r\n", app->settings.channel);
irc_queue_write(app, buf, true);
}
}
void reset_bets(App* app)
{
add_log(app, LOGLEVEL_INFO, "Resetting bets.");
for (int i = 0; i < arrlen(app->bet_options); ++i)
BetTable_reset_bets(&app->bet_options[i]);
}
// NOTE: winning_option is 0-indexed
void do_payout(App* app, i32 winning_option, f64 table_total, f64 grand_total)
{
add_log(app, LOGLEVEL_INFO, "Collecting bets. Payout for %i betters on option %i (%s).", shlen(app->bet_options[winning_option].bets), winning_option+1, app->bet_options[winning_option].option_name);
i32 better_count = 0;
BETTER_ASSERT(winning_option >= 0 && winning_option < arrlen(app->bet_options) && "winning option is outside the range of existing options");
for (int option_idx = 0; option_idx < arrlen(app->bet_options); ++option_idx)
{
auto bet_map = app->bet_options[option_idx].bets;
for (int bet_idx = 0; bet_idx < shlen(bet_map); ++bet_idx)
{
++better_count;
UserInfo* user = shget(app->users, bet_map[bet_idx].key);
if (!user)
{
add_log(app, LOGLEVEL_DEVERROR, "User from bet doesn't exist in table: %s", bet_map[bet_idx].key);
continue;
}
if (bet_map[bet_idx].value > user->points)
{
add_log(app, LOGLEVEL_DEVERROR, "User's (%s) bet (%llu) was higher than their points (%llu)", bet_map[bet_idx].key, bet_map[bet_idx].value, user->points);
user->points = 0;
}
else
{
user->points -= bet_map[bet_idx].value;
}
if (option_idx == winning_option)
{
u64 win = BETTER_CLAMP((u64)((grand_total*bet_map[bet_idx].value)/table_total), 0, POINTS_MAX);
user->points = BETTER_CLAMP(user->points + win, 0, POINTS_MAX);
if (!app->shark || win - bet_map[bet_idx].value > app->shark_win)
{
app->shark = user;
app->shark_win = win - bet_map[bet_idx].value;
}
}
else if (!app->fish || bet_map[bet_idx].value > app->fish_loss)
{
app->fish = user;
app->fish_loss = bet_map[bet_idx].value;
}
}
}
rebuild_leaderboard(app);
if (app->settings.announce_payout && better_count > 0)
{
char* buf = (char*) BETTER_ALLOC(100 + CHANNEL_NAME_MAX + OPTION_NAME_MAX, "writebuf_announcement_payout");
sprintf(buf, "PRIVMSG #%s :Option %i (%s) wins! Bets have been collected, and rewards paid out.\r\n", app->settings.channel, winning_option+1, app->bet_options[winning_option].option_name);
irc_queue_write(app, buf, true);
}
save_leaderboard_to_disk(app);
reset_bets(app);
}