diff --git a/src/main/java/com/mazawrath/beanbot/Main.java b/src/main/java/com/mazawrath/beanbot/Main.java index 466f9fc..d6946b0 100644 --- a/src/main/java/com/mazawrath/beanbot/Main.java +++ b/src/main/java/com/mazawrath/beanbot/Main.java @@ -26,6 +26,7 @@ public class Main { private static DiscordApi api; public static void main(String[] args) { + Sentry.init(); System.setProperty("log4j2.loggerContextFactory", "org.apache.logging.log4j.core.impl.Log4jContextFactory"); @@ -62,6 +63,7 @@ public static void main(String[] args) { cmdHandler.registerCommand(new ServerInfoCommand()); cmdHandler.registerCommand(new ReactCommand(points)); cmdHandler.registerCommand(new SourceCommand()); + cmdHandler.registerCommand(new MinesweeperCommand(points)); // beanCoin cmdHandler.registerCommand(new BeanBalanceCommand(points)); cmdHandler.registerCommand(new BeanFreeCommand(points)); diff --git a/src/main/java/com/mazawrath/beanbot/commands/MinesweeperCommand.java b/src/main/java/com/mazawrath/beanbot/commands/MinesweeperCommand.java new file mode 100644 index 0000000..19deec3 --- /dev/null +++ b/src/main/java/com/mazawrath/beanbot/commands/MinesweeperCommand.java @@ -0,0 +1,130 @@ +package com.mazawrath.beanbot.commands; + +import com.mazawrath.beanbot.utilities.Points; +import com.mazawrath.beanbot.utilities.SentryLog; +import de.btobastian.sdcf4j.Command; +import de.btobastian.sdcf4j.CommandExecutor; +import io.sentry.Sentry; +import org.apache.commons.lang3.StringUtils; +import org.javacord.api.DiscordApi; +import org.javacord.api.entity.channel.ServerTextChannel; +import org.javacord.api.entity.message.Message; +import org.javacord.api.entity.server.Server; +import org.javacord.api.entity.user.User; + +import java.util.Arrays; +import java.util.Random; + +public class MinesweeperCommand implements CommandExecutor { + private Points points; + + public MinesweeperCommand(Points points) { + this.points = points; + } + + @Command( + aliases = {"minesweeper"}, + usage = "minesweeper [size] [mines]", + description = "Creates a minesweeper field with custom size options.", + privateMessages = false + ) + + public void onCommand(String[] args, DiscordApi api, ServerTextChannel serverTextChannel, User author, Server server, Message message) { + SentryLog.addContext(args, author, server); + + int size; + int mines; + + if (args.length >= 2) { + if (StringUtils.isNumeric(args[0]) && StringUtils.isNumeric(args[1])) { + size = Integer.parseInt(args[0]); + mines = Integer.parseInt(args[1]); + } else { + serverTextChannel.sendMessage("Invalid numbers."); + return; + } + } else { + size = 10; + mines = size * size / 6; + } + + if (points.removePoints(author.getIdAsString(), api.getYourself().getIdAsString(), server.getIdAsString(), Points.COMMAND_COST)) { + serverTextChannel.sendMessage(new MinesweeperField(ensureInRange(size, 1,14), ensureInRange(mines, 1, size*size)).toDiscordString()); + } + + Sentry.clearContext(); + } + + private int ensureInRange(int value, int min, int max) { + return (value < min) ? min : ((value > max) ? max : value); + } + + private static class MinesweeperField { + private final char[][] field; + + public MinesweeperField(int size, int mines) { + if (size * size < mines) { + throw new IllegalArgumentException("too many mines"); + } + this.field = new char[size][size]; + for (char[] row : field) { + Arrays.fill(row, ' '); + } + placeMines(mines); + calculateFields(); + } + + private void placeMines(int mines) { + Random random = new Random(); + for (int i = 0; i < mines; ) { + int x = random.nextInt(field.length); + int y = random.nextInt(field.length); + if (field[y][x] == ' ') { + field[y][x] = 'B'; + i++; + } + } + } + + private void calculateFields() { + for (int y = 0; y < field.length; y++) { + for (int x = 0; x < field[y].length; x++) { + if (field[y][x] == ' ') field[y][x] = (char) ('0' + countMinesAround(x, y)); + } + } + } + + private int countMinesAround(int xCenter, int yCenter) { + int numMines = 0; + for (int y = yCenter - 1; y <= yCenter + 1; y++) { + if (y < 0 || y == field.length) continue; + for (int x = xCenter - 1; x <= xCenter + 1; x++) { + if (x < 0 || x == field[y].length) continue; + if (field[y][x] == 'B') numMines++; + } + } + return numMines; + } + + public String toString() { + StringBuilder builder = new StringBuilder(); + for (char[] row : field) { + builder.append(new String(row)).append("\n"); + } + return builder.toString(); + } + + public String toDiscordString() { + StringBuilder builder = new StringBuilder(); + for (char[] row : field) { + for (char c : row) { + builder.append("||"); + builder.append((c == 'B') ? "\uD83D\uDCA3" : c + "\u20E3"); + builder.append("||"); + } + builder.append("\n"); + } + return builder.toString(); + } + } +} diff --git a/src/main/java/com/mazawrath/beanbot/commands/admin/AdminPostChangeLogCommand.java b/src/main/java/com/mazawrath/beanbot/commands/admin/AdminPostChangeLogCommand.java index 3422043..5061a91 100644 --- a/src/main/java/com/mazawrath/beanbot/commands/admin/AdminPostChangeLogCommand.java +++ b/src/main/java/com/mazawrath/beanbot/commands/admin/AdminPostChangeLogCommand.java @@ -40,17 +40,19 @@ public void onCommand(String[] args, DiscordApi api, ServerTextChannel serverTex private String getRecentChangeLog() { return "**New beanBOT update released.**\n" + - "Release can be found on https://github.com/Mazawrath/beanBOT/releases/tag/v3.3.0\n" + - "Detailed changelog can be found on https://github.com/Mazawrath/beanBOT/compare/v3.2.0...v3.3.0\n" + + "Release can be found on https://github.com/Mazawrath/beanBOT/releases/tag/v3.4.0\n" + + "Detailed changelog can be found on https://github.com/Mazawrath/beanBOT/compare/v3.3.0...v3.4.0\n" + "\n" + - "**v3.3.0**\n" + + "**v3.4.0**\n" + "**New**\n" + - "\t- Added `.adminlookupuser`.\n" + - "\t- Added Sentry to track and manage run time errors in production.\n" + -// "**Changes**\n" + -// "\t- Disabled `.beanlottery draw`." + + "\t- Added `.minesweeper`.\n" + + "**Changes**\n" + + "\t- Set limit to how many lottery tickets can be bought in a single drawing to 200.\n" + + "\t- Set max lottery number to be drawn from 40 to 20.\n" + + "\t- Removed minimum amount required for automatic bean lottery drawings.\n" + + "\t- Bean coin from `.beanbet` will only go to the bot when the bet loses.\n" + "**Bug Fixes**\n" + - "\t- Fixed an issue where users could buy less than one bean lottery ticket.\n" + - "\t- Fixed an issue with mentioning users with nicknames."; + "\t- Fixed outdated info with `.beanlottery draw`.\n" + + "\t- Fixed an issue with integer checks with `.beanlottery`."; } } \ No newline at end of file diff --git a/src/main/java/com/mazawrath/beanbot/commands/beancoin/BeanBetCommand.java b/src/main/java/com/mazawrath/beanbot/commands/beancoin/BeanBetCommand.java index 018e1a8..93b3214 100644 --- a/src/main/java/com/mazawrath/beanbot/commands/beancoin/BeanBetCommand.java +++ b/src/main/java/com/mazawrath/beanbot/commands/beancoin/BeanBetCommand.java @@ -39,7 +39,7 @@ public void onCommand(String[] args, DiscordApi api, ServerTextChannel serverTex if (winningPoints.compareTo(BigDecimal.ZERO) == 0) { serverTextChannel.sendMessage("You can't bet 0 beanCoin!"); } else { - if (points.removePoints(author.getIdAsString(), api.getYourself().getIdAsString(), server.getIdAsString(), winningPoints)) { + if (points.removePoints(author.getIdAsString(), null, server.getIdAsString(), winningPoints)) { Random rand = new Random(); int winningChance = rand.nextInt(100) + 1; @@ -67,6 +67,7 @@ public void onCommand(String[] args, DiscordApi api, ServerTextChannel serverTex } } else { serverTextChannel.sendMessage("Sorry, you lost " + Points.pointsToString(winningPoints) + "."); + points.addPoints(api.getYourself().getIdAsString(), server.getIdAsString(), winningPoints); Sentry.getContext().recordBreadcrumb( new BreadcrumbBuilder() .setMessage("User lost") diff --git a/src/main/java/com/mazawrath/beanbot/commands/beanlottery/BeanLotteryCommand.java b/src/main/java/com/mazawrath/beanbot/commands/beanlottery/BeanLotteryCommand.java index ce6bbe5..c112f7a 100644 --- a/src/main/java/com/mazawrath/beanbot/commands/beanlottery/BeanLotteryCommand.java +++ b/src/main/java/com/mazawrath/beanbot/commands/beanlottery/BeanLotteryCommand.java @@ -6,6 +6,7 @@ import de.btobastian.sdcf4j.Command; import de.btobastian.sdcf4j.CommandExecutor; import io.sentry.Sentry; +import org.apache.commons.lang3.StringUtils; import org.javacord.api.DiscordApi; import org.javacord.api.entity.channel.ServerTextChannel; import org.javacord.api.entity.message.MessageBuilder; @@ -36,77 +37,93 @@ public BeanLotteryCommand(Points points, Lottery lottery) { public void onCommand(String[] args, DiscordApi api, ServerTextChannel serverTextChannel, User author, Server server) { SentryLog.addContext(args, author, server); - if (args.length == 1) { - if (args[0].equalsIgnoreCase("start")) { - if (!author.isBotOwner() && !server.isOwner(author)) { - try { - serverTextChannel.sendMessage("Only " + api.getOwner().get().getDiscriminatedName() + " or " + server.getOwner().getDisplayName(server) + " can use this command."); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } - return; - } else { - serverTextChannel.sendMessage("Weekly drawings now active. When the bot has more than " + Points.pointsToString(Lottery.MIN_WEEKLY_VALUE) + " it will do an automatic drawing every 7 days."); - lottery.scheduleWeeklyDrawing(points, server, api, serverTextChannel); - return; + if (args.length == 1) { + if (args[0].equalsIgnoreCase("start")) { + if (!author.isBotOwner() && !server.isOwner(author)) { + try { + serverTextChannel.sendMessage("Only " + api.getOwner().get().getDiscriminatedName() + " or " + server.getOwner().getDisplayName(server) + " can use this command."); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); } - } //else if (args[0].equalsIgnoreCase("draw")) { + return; + } else { + serverTextChannel.sendMessage("Automatic drawings now active. Drawing will happen at this time every 3 days."); + lottery.scheduleWeeklyDrawing(points, server, api, serverTextChannel); + return; + } + } //else if (args[0].equalsIgnoreCase("draw")) { // if (points.removePoints(author.getIdAsString(), api.getYourself().getIdAsString(), server.getIdAsString(), Points.LOTTERY_DRAWING_COST)) { // lottery.drawNumbers(points, server, api, serverTextChannel); // } else // serverTextChannel.sendMessage("You do not have enough beanCoin for this command"); // return; // } - if (Integer.parseInt(args[0]) > 200) { - serverTextChannel.sendMessage("You can only buy 200 tickets at a time."); - return; - } + if (Integer.parseInt(args[0]) > 200) { + serverTextChannel.sendMessage("You can only buy 200 tickets at a time."); + return; + } - if (Integer.parseInt(args[0]) < 1) { - serverTextChannel.sendMessage("You cannot buy less than 1 ticket."); - return; + if (Integer.parseInt(args[0]) < 1) { + serverTextChannel.sendMessage("You cannot buy less than 1 ticket."); + return; + } + + if (!lottery.canBuyTickets(author.getIdAsString(), server.getIdAsString(), Integer.parseInt(args[0]))) { + serverTextChannel.sendMessage("You can only buy " + Lottery.MAX_TICKETS + " tickets at a time for a bean lottery drawing. You have bought " + lottery.getTicketCount(author.getIdAsString(), server.getIdAsString()) + " tickets."); + return; + } + + if (points.removePoints(author.getIdAsString(), api.getYourself().getIdAsString(), server.getIdAsString(), Points.LOTTERY_TICKET_COST.multiply(new BigDecimal(Integer.parseInt(args[0]))))) { + + ArrayList> numbers = lottery.addEntry(author.getIdAsString(), server.getIdAsString(), Integer.parseInt(args[0])); + + serverTextChannel.sendMessage(args[0] + " tickets bought.\n" + + "The numbers generated have been sent to you in a private message."); + author.sendMessage(args[0] + " tickets bought.\n" + + "Your numbers are:"); + MessageBuilder message = new MessageBuilder(); + + for (int i = 0; i < numbers.size(); i++) { + for (int j = 0; j < Lottery.AMOUNT_DRAWN; j++) + message.append(numbers.get(i).get(j) + " "); + message.append("\n"); } + message.send(author); + } else + serverTextChannel.sendMessage("You don't have enough beanCoin to buy that many tickets."); + } else if (args.length >= Lottery.AMOUNT_DRAWN) { + if (!StringUtils.isNumeric(args[0])) { + serverTextChannel.sendMessage("Invalid amount."); + return; + } - if (points.removePoints(author.getIdAsString(), api.getYourself().getIdAsString(), server.getIdAsString(), Points.LOTTERY_TICKET_COST.multiply(new BigDecimal(Integer.parseInt(args[0]))))) { - ArrayList> numbers = lottery.addEntry(author.getIdAsString(), server.getIdAsString(), Integer.parseInt(args[0])); + if (!lottery.canBuyTickets(author.getIdAsString(), server.getIdAsString(), 1)) { + serverTextChannel.sendMessage("You can only buy " + Lottery.MAX_TICKETS + " tickets at a time for a bean lottery drawing. You have bought " + lottery.getTicketCount(author.getIdAsString(), server.getIdAsString()) + " tickets."); + return; + } - serverTextChannel.sendMessage(args[0] + " tickets bought.\n" + - "The numbers generated have been sent to you in a private message."); - author.sendMessage(args[0] + " tickets bought.\n" + - "Your numbers are:"); - MessageBuilder message = new MessageBuilder(); + int[] numbers = new int[Lottery.AMOUNT_DRAWN]; - for (int i = 0; i < numbers.size(); i++) { - for (int j = 0; j < Lottery.AMOUNT_DRAWN; j++) - message.append(numbers.get(i).get(j) + " "); - message.append("\n"); - } - message.send(author); - } else - serverTextChannel.sendMessage("You don't have enough beanCoin to buy that many tickets."); - } else if (args.length >= Lottery.AMOUNT_DRAWN) { - int[] numbers = new int[Lottery.AMOUNT_DRAWN]; - - for (int i = 0; i < Lottery.AMOUNT_DRAWN; i++) { - if (Integer.parseInt(args[i]) >= Lottery.MIN_NUMBER && Integer.parseInt(args[i]) <= Lottery.MAX_NUMBER) - numbers[i] = Integer.parseInt(args[i]); - else { - serverTextChannel.sendMessage(args[i] + " is an invalid number. Numbers must be greater than or equal to " + Lottery.MIN_NUMBER + " and less than or equal to " + Lottery.MAX_NUMBER); - return; - } + for (int i = 0; i < Lottery.AMOUNT_DRAWN; i++) { + if (Integer.parseInt(args[i]) >= Lottery.MIN_NUMBER && Integer.parseInt(args[i]) <= Lottery.MAX_NUMBER) + numbers[i] = Integer.parseInt(args[i]); + else { + serverTextChannel.sendMessage(args[i] + " is an invalid number. Numbers must be greater than or equal to " + Lottery.MIN_NUMBER + " and less than or equal to " + Lottery.MAX_NUMBER); + return; } - if (points.removePoints(author.getIdAsString(), api.getYourself().getIdAsString(), server.getIdAsString(), Points.LOTTERY_TICKET_COST)) { - lottery.addEntry(author.getIdAsString(), server.getIdAsString(), numbers); - - serverTextChannel.sendMessage("1 ticket bought.\n" + - "Your numbers have been sent to you in a private message."); - author.sendMessage("1 ticket bought.\n" + - "Your numbers are:\n"); - author.sendMessage(args[0] + " " + args[1] + " " + args[2]); - } else - serverTextChannel.sendMessage("You do not have enough beanCoin to buy a ticket."); + } + if (points.removePoints(author.getIdAsString(), api.getYourself().getIdAsString(), server.getIdAsString(), Points.LOTTERY_TICKET_COST)) { + lottery.addEntry(author.getIdAsString(), server.getIdAsString(), numbers); + + serverTextChannel.sendMessage("1 ticket bought.\n" + + "Your numbers have been sent to you in a private message."); + author.sendMessage("1 ticket bought.\n" + + "Your numbers are:\n"); + author.sendMessage(args[0] + " " + args[1] + " " + args[2]); } else - serverTextChannel.sendMessage("You must have 1 number with how many tickets you want to buy, " + Lottery.AMOUNT_DRAWN + " numbers >= " + Lottery.MIN_NUMBER + " and <= " + Lottery.MAX_NUMBER + ", or the word `draw` to have your own drawing."); + serverTextChannel.sendMessage("You do not have enough beanCoin to buy a ticket."); + } else + serverTextChannel.sendMessage("You must have 1 number with how many tickets you want to buy, " + Lottery.AMOUNT_DRAWN + " numbers >= " + Lottery.MIN_NUMBER + " and <= " + Lottery.MAX_NUMBER + ", or the word `draw` to have your own drawing."); Sentry.clearContext(); } diff --git a/src/main/java/com/mazawrath/beanbot/utilities/Lottery.java b/src/main/java/com/mazawrath/beanbot/utilities/Lottery.java index 9b70b8a..d3f16f7 100644 --- a/src/main/java/com/mazawrath/beanbot/utilities/Lottery.java +++ b/src/main/java/com/mazawrath/beanbot/utilities/Lottery.java @@ -19,8 +19,8 @@ public class Lottery { public static final int AMOUNT_DRAWN = 3; public static final int MIN_NUMBER = 1; - public static final int MAX_NUMBER = 40; - public static final BigDecimal MIN_WEEKLY_VALUE = new BigDecimal(50000); + public static final int MAX_NUMBER = 20; + public static final int MAX_TICKETS = 200; private static final String DB_NAME = "beanBotLottery"; private Connection conn; @@ -50,7 +50,20 @@ private void checkUser(String userId, String serverId) { if (r.db(DB_NAME).table(serverId).getField("id").contains(userId).run(conn)) { } else r.db(DB_NAME).table(serverId).insert(r.array( - r.hashMap("id", userId))).run(conn); + r.hashMap("id", userId) + .with("TicketCount", 0))).run(conn); + } + + public boolean canBuyTickets(String userId, String serverId, int amount) { + checkUser(userId, serverId); + + return getTicketCount(userId, serverId) + amount <= 200; + } + + public long getTicketCount(String userId, String serverId) { + checkUser(userId, serverId); + + return r.db(DB_NAME).table(serverId).get(userId).getField("TicketCount").run(conn); } public void clearTickets(String serverId) { @@ -75,6 +88,8 @@ public ArrayList> addEntry(String userId, String serverId, in r.db(DB_NAME).table(serverId).filter(r.hashMap("id", userId)) .update(row -> r.hashMap("Lottery ticket", row.g("Lottery ticket").default_(r.array()).append(ticketArray.get(finalI)))).run(conn); } + r.db(DB_NAME).table(serverId).get(userId).update(r.hashMap("TicketCount", getTicketCount(userId, serverId) + amount)).run(conn); + return ticketArray; } @@ -82,6 +97,8 @@ public void addEntry(String userId, String serverId, int[] numbers) { checkUser(userId, serverId); r.db(DB_NAME).table(serverId).filter(r.hashMap("id", userId)) .update(row -> r.hashMap("Lottery ticket", row.g("Lottery ticket").default_(r.array()).append(r.array(numbers[0], numbers[1], numbers[2])))).run(conn); + + r.db(DB_NAME).table(serverId).get(userId).update(r.hashMap("TicketCount", getTicketCount(userId, serverId) + 1)).run(conn); } public ArrayList getWinner(String serverId, int[] winningNumbers) { @@ -118,19 +135,17 @@ public void scheduleWeeklyDrawing(Points points, Server server, DiscordApi api, stpe.scheduleAtFixedRate(new LotteryDrawing() { @Override public void run() { - if (points.getBalance(api.getYourself().getIdAsString(), server.getIdAsString()).compareTo(MIN_WEEKLY_VALUE) >= 1) { - try { - serverTextChannel.sendMessage("30 minutes until the weekly bean lottery drawing! Buy tickets using `.beanlottery` while you can!"); - Thread.sleep(1200000); - serverTextChannel.sendMessage("Only 10 minutes until the weekly bean lottery drawing! Last chance to buy tickets using `.beanlottery`!"); - Thread.sleep(595000); - serverTextChannel.sendMessage("Starting lottery drawing..."); - Thread.sleep(5000); - - drawNumbers(points, server, api, serverTextChannel); - } catch (InterruptedException e) { - e.printStackTrace(); - } + try { + serverTextChannel.sendMessage("30 minutes until the weekly bean lottery drawing! Buy tickets using `.beanlottery` while you can!"); + Thread.sleep(1200000); + serverTextChannel.sendMessage("Only 10 minutes until the weekly bean lottery drawing! Last chance to buy tickets using `.beanlottery`!"); + Thread.sleep(595000); + serverTextChannel.sendMessage("Starting lottery drawing..."); + Thread.sleep(5000); + + drawNumbers(points, server, api, serverTextChannel); + } catch (InterruptedException e) { + e.printStackTrace(); } } }, 0, 4280, TimeUnit.MINUTES); diff --git a/src/main/java/com/mazawrath/beanbot/utilities/Points.java b/src/main/java/com/mazawrath/beanbot/utilities/Points.java index 90a7937..89a99d0 100644 --- a/src/main/java/com/mazawrath/beanbot/utilities/Points.java +++ b/src/main/java/com/mazawrath/beanbot/utilities/Points.java @@ -63,23 +63,23 @@ private void checkUser(String userID, String serverID) { public ArrayList getLeaderboard(String serverID) { return r.db(DB_NAME).table(serverID).map(doc -> - r.object( - "id", - doc.getField("id"), - "Points", - doc.getField("Points").split("P_").nth(1).coerceTo("NUMBER"), - "Last Received Free Points", - doc.getField("Last Received Free Points") - ) + r.object( + "id", + doc.getField("id"), + "Points", + doc.getField("Points").split("P_").nth(1).coerceTo("NUMBER"), + "Last Received Free Points", + doc.getField("Last Received Free Points") + ) ).orderBy(r.desc("Points")).limit(10).map(doc -> - r.object( - "id", - doc.getField("id"), - "Points", - r.expr("P_").add(doc.getField("Points").coerceTo("STRING")), - "Last Received Free Points", - doc.getField("Last Received Free Points") - ) + r.object( + "id", + doc.getField("id"), + "Points", + r.expr("P_").add(doc.getField("Points").coerceTo("STRING")), + "Last Received Free Points", + doc.getField("Last Received Free Points") + ) ).run(conn); }