Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for betting #5

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/main/java/pw/chew/chewbotcca/util/DatabaseHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import pw.chew.mlb.models.Bet;
import pw.chew.mlb.models.Channel;
import pw.chew.mlb.models.Profile;

import java.io.File;

Expand All @@ -36,6 +38,8 @@ public static void openConnection() {
sessionFactory = new MetadataSources(registry)
// MLB Bot - Change to channel
.addAnnotatedClass(Channel.class)
.addAnnotatedClass(Profile.class)
.addAnnotatedClass(Bet.class)
.buildMetadata()
.buildSessionFactory();
} catch (Exception e) {
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/pw/chew/mlb/MLBBot.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import pw.chew.chewbotcca.util.DatabaseHelper;
import pw.chew.chewbotcca.util.RestClient;
import pw.chew.mlb.commands.AdminCommand;
import pw.chew.mlb.commands.BettingCommand;
import pw.chew.mlb.commands.ConfigCommand;
import pw.chew.mlb.commands.PlanGameCommand;
import pw.chew.mlb.commands.ScoreCommand;
Expand Down Expand Up @@ -64,7 +65,7 @@ public static void main(String[] args) throws IOException {
new StartGameCommand(), new StopGameCommand(), new ScoreCommand(), new SetInfoCommand(), new ConfigCommand(),
new PlanGameCommand()
, // Util Commands
new StandingsCommand()
new StandingsCommand(), new BettingCommand()
);

//client.forceGuildOnly("148195924567392257");
Expand Down
245 changes: 245 additions & 0 deletions src/main/java/pw/chew/mlb/commands/BettingCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
package pw.chew.mlb.commands;

import com.jagrosh.jdautilities.command.SlashCommand;
import com.jagrosh.jdautilities.command.SlashCommandEvent;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import net.dv8tion.jda.api.utils.TimeFormat;
import org.hibernate.Transaction;
import pw.chew.chewbotcca.util.DatabaseHelper;
import pw.chew.mlb.models.Bet;
import pw.chew.mlb.models.BetKind;
import pw.chew.mlb.models.Profile;
import pw.chew.mlb.objects.BetHelper;
import pw.chew.mlb.objects.GameBlurb;
import pw.chew.mlb.objects.GameState;
import pw.chew.mlb.util.AutocompleteUtil;

import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;

public class BettingCommand extends SlashCommand {
public BettingCommand() {
this.name = "betting";
this.help = "Bet on a team";
this.children = new SlashCommand[]{
new BettingProfileSubCommand(),
new BettingBetSubCommand(),
new BettingClaimSubCommand()
};
}

@Override
protected void execute(SlashCommandEvent event) {
// unused for children
}

public static class BettingProfileSubCommand extends SlashCommand {
public BettingProfileSubCommand() {
this.name = "profile";
this.help = "View your betting profile";
}

@Override
protected void execute(SlashCommandEvent event) {
// try to get profile
Profile profile = BetHelper.retrieveProfile(event.getUser().getIdLong());
List<Bet> bets = BetHelper.retrieveBets(event.getUser().getIdLong());

// build embed
event.replyEmbeds(buildProfileEmbed(event, profile, bets)).setEphemeral(true).queue();
}

private MessageEmbed buildProfileEmbed(SlashCommandEvent event, Profile profile, List<Bet> bets) {
EmbedBuilder embed = new EmbedBuilder();
embed.setTitle("Betting Profile for " + event.getUser().getName());
embed.addField("Credits", profile.getCredits() + "", true);

if (event.getMember() != null) {
embed.setColor(event.getMember().getColor());
}

// get 5 most recent bets
List<String> betString = new ArrayList<>();
for (int i = 0; i < 5 && i < bets.size(); i++) {
Bet bet = bets.get(i);
betString.add("%s | %s%s - %s".formatted(
TimeFormat.DATE_SHORT.format(bet.getCreatedAt()),
bet.amount() > 0 ? "+" : "",
bet.amount(),
bet.getReason()
));
}

embed.addField("Recent Bets", String.join("\n", betString), false);

return embed.build();
}
}

public static class BettingClaimSubCommand extends SlashCommand {
public BettingClaimSubCommand() {
this.name = "claim";
this.help = "Claim your free daily credits";
}

@Override
protected void execute(SlashCommandEvent event) {
Bet recentDailyCredit = BetHelper.getRecentDailyCredit(event.getUser().getIdLong());
long lastRecentDaily = 0;

if (recentDailyCredit != null) {
lastRecentDaily = recentDailyCredit.getCreatedAt().getEpochSecond();
}

long now = OffsetDateTime.now().toEpochSecond();
long diff = now - lastRecentDaily;

// must be less than 24 hours
if (diff < 86400) {
long canClaimAt = lastRecentDaily + 86400;

event.reply("You already claimed your daily credits! You can claim again <t:" + canClaimAt + ":R>").setEphemeral(true).queue();
return;
}

// add daily credit
BetHelper.addDailyCredit(event.getUser().getIdLong());

event.reply("You have claimed your daily credits!").setEphemeral(true).queue();
}
}

public static class BettingBetSubCommand extends SlashCommand {
public BettingBetSubCommand() {
this.name = "bet";
this.help = "Bet on a team";
this.options = List.of(
new OptionData(OptionType.INTEGER, "team", "Which team to bet on", true, true),
new OptionData(OptionType.INTEGER, "date", "Which game to bet on", true, true),
new OptionData(OptionType.INTEGER, "amount", "How much to bet. 0 to remove bet.", true, false)
.setMinValue(0)
);
}

@Override
protected void execute(SlashCommandEvent event) {
// options
int teamId = (int) event.optLong("team", 0);
int gamePk = (int) event.optLong("date", 0);
int amount = (int) event.optLong("amount", 0);

// Check the game status
GameState state = GameState.fromPk(String.valueOf(gamePk));
if (state.isFinal()) {
event.reply("This game is already over!").setEphemeral(true).queue();
return;
}
if (state.inning() > 4) {
event.reply("Bets cannot be placed or changed past the 4th inning! To see your bet, run `/betting placed`").setEphemeral(true).queue();
return;
}

// start session
var session = DatabaseHelper.getSessionFactory().openSession();

// check for a bet, check by user_id, game_pk, and team_id
Bet bet = session.createQuery("from Bet where userId = :userId and gamePk = :gamePk and teamId = :teamId", Bet.class)
.setParameter("userId", event.getUser().getIdLong())
.setParameter("gamePk", gamePk)
.setParameter("teamId", teamId)
.uniqueResult();

// Ensure we have enough credit to bet
Profile user = BetHelper.retrieveProfile(event.getUser().getIdLong());

// Get game blurb
GameBlurb blurb = new GameBlurb(gamePk + "");

// if bet exists, update it
if (bet == null) {
if (amount == 0) {
event.reply("You must bet at least 1 credit!").setEphemeral(true).queue();
return;
}

if (user.getCredits() < amount) {
event.reply("You don't have enough credits to bet that much! You only have " + user.getCredits() + " credits!").setEphemeral(true).queue();
return;
}

String team = blurb.away().id() == teamId ? blurb.away().name() : blurb.home().name();

Bet newBet = new Bet();
newBet.setUserId(event.getUser().getIdLong());
newBet.setGamePk(gamePk);
newBet.setTeamId(teamId);
newBet.setBet(amount);
newBet.setKind(BetKind.PENDING);
newBet.setReason("Bet on " + team + " for " + blurb.name());

user.setCredits(user.getCredits() - amount);

Transaction trans = session.beginTransaction();
session.save(newBet);
session.update(user);
trans.commit();

event.reply("Bet placed!").setEphemeral(true).queue();
} else if (amount == 0) {
int currentBet = bet.getBet();

Transaction trans = session.beginTransaction();
session.delete(bet);

user.setCredits(user.getCredits() + currentBet);
session.update(user);

trans.commit();

event.reply("Bet removed!").setEphemeral(true).queue();
} else {
int currentBet = bet.getBet();
int creditsToDeduct = amount - currentBet;
if (amount > currentBet && creditsToDeduct > user.getCredits()) {
event.reply("You don't have enough credits to bet that much! You only have " + user.getCredits() + " credits!").queue();
return;
}

user.setCredits(user.getCredits() - creditsToDeduct);
bet.setBet(amount);

Transaction trans = session.beginTransaction();
session.update(bet);
session.update(user);
trans.commit();

event.reply("Bet updated!").setEphemeral(true).queue();
}

session.close();
}

@Override
public void onAutoComplete(CommandAutoCompleteInteractionEvent event) {
event.replyChoices(AutocompleteUtil.handleInput(event)).queue();
}
}

public static class BettingPlacedSubCommand extends SlashCommand {
public BettingPlacedSubCommand() {
this.name = "placed";
this.help = "View your placed bets";
}

@Override
protected void execute(SlashCommandEvent event) {
List<Bet> bets = BetHelper.retrieveBets(event.getUser().getIdLong());

}
}
}
2 changes: 2 additions & 0 deletions src/main/java/pw/chew/mlb/commands/PlanGameCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/pw/chew/mlb/listeners/GameFeedHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pw.chew.mlb.commands.AdminCommand;
import pw.chew.mlb.models.Bet;
import pw.chew.mlb.objects.ActiveGame;
import pw.chew.mlb.objects.BetHelper;
import pw.chew.mlb.objects.ChannelConfig;
import pw.chew.mlb.objects.GameState;

Expand Down Expand Up @@ -583,6 +585,10 @@ public static void endGame(String gamePk, GameState currentState, String scoreca

// Remove the game thread
removeThread(gamePk);

// Handle bets
LoggerFactory.getLogger(GameFeedHandler.class).debug("Awarding bets for game " + gamePk);
BetHelper.awardWinners(gamePk, currentState.winningTeam());
}

/**
Expand Down
49 changes: 49 additions & 0 deletions src/main/java/pw/chew/mlb/models/Bet.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package pw.chew.mlb.models

import java.time.Instant
import javax.persistence.*

@Entity
@Table(name = "bets")
open class Bet {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
open var id: Int? = null

@Column(name = "user_id", nullable = false)
open var userId: Long? = null

@Column(name = "bet", nullable = false)
open var bet: Int = 0

@Column(name = "payout", nullable = false)
open var payout: Int = 0

@Column(name = "game_pk")
open var gamePk: Int? = null

@Column(name = "team_id")
open var teamId: Int? = null

@Enumerated(EnumType.STRING)
@Column(name = "kind", nullable = false)
open var kind: BetKind = BetKind.PENDING

@Column(name = "reason", nullable = false, length = 128)
open var reason: String = ""

@Column(name = "created_at", nullable = false)
open var createdAt: Instant = Instant.now()

fun amount(): Int {
return payout - bet
}
}

enum class BetKind {
AUTOMATED,
PENDING,
WIN,
LOSS
}
17 changes: 17 additions & 0 deletions src/main/java/pw/chew/mlb/models/Profile.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package pw.chew.mlb.models

import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.Id
import javax.persistence.Table

@Entity
@Table(name = "profiles")
open class Profile {
@Id
@Column(name = "id", nullable = false)
open var id: Long? = null

@Column(name = "credits", nullable = false)
open var credits: Int = 100
}
Loading
Loading