Merge pull request #14 from AffluentAvo/master

added database, cleaned up code, major changes
This commit is contained in:
PolyMars
2020-08-20 08:46:28 -05:00
committed by GitHub
21 changed files with 1223 additions and 641 deletions
-75
View File
@@ -1,75 +0,0 @@
import net.dv8tion.jda.api.OnlineStatus;
import net.dv8tion.jda.api.entities.Activity;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.sharding.DefaultShardManagerBuilder;
import net.dv8tion.jda.api.sharding.ShardManager;
import javax.security.auth.login.LoginException;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Scanner;
public class Bot {
static HashMap<Long, String> prefixes = new HashMap<>();
private static ShardManager shardManager;
public static void main(String[] args) throws LoginException {
String token = null;
try {
File tokenFile = Paths.get("token.txt").toFile();
if (!tokenFile.exists()) {
System.out.println("[ERROR] Could not find token.txt file");
System.out.println("[ERROR] Please create a file called \"token.txt\" in the same folder as the jar " + "file and paste in your bot token.");
return;
}
token = new String(Files.readAllBytes(tokenFile.toPath()));
} catch (Exception ex) {
ex.printStackTrace();
}
if (token == null) return;
DefaultShardManagerBuilder builder = new DefaultShardManagerBuilder(token);
builder.setStatus(OnlineStatus.ONLINE);
builder.setActivity(Activity.playing("@Sokobot for info!"));
builder.addEventListeners(new Commands());
shardManager = builder.build();
Thread consoleThread = new Thread(() -> {
Scanner s = new Scanner(System.in);
while (s.hasNextLine()) {
processCommand(s.nextLine());
}
});
consoleThread.setDaemon(true);
consoleThread.setName("Console Thread");
consoleThread.start();
}
private static void processCommand(String cmd) {
if (cmd.equalsIgnoreCase("help")) {
System.out.println("Commands:\nstop - Shuts down the bot and exits the program");
return;
}
if (cmd.equalsIgnoreCase("stop")) {
System.out.println("Shutting down...");
shardManager.shutdown();
System.out.println("Bye!");
System.exit(0);
return;
}
System.out.println("Unknown command. Please use \"help\" for a list of commands.");
}
public static ShardManager getShardManager() {
return shardManager;
}
static void setPrefix(Guild guild, String prefix) {
prefixes.put(guild.getIdLong(), prefix);
}
static String getPrefix(Guild guild) {
return prefixes.getOrDefault(guild.getIdLong(), "!");
}
}
-221
View File
@@ -1,221 +0,0 @@
import com.vdurmont.emoji.EmojiManager;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.events.guild.GuildLeaveEvent;
import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
import net.dv8tion.jda.api.events.message.guild.react.GuildMessageReactionAddEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import java.util.*;
public class Commands extends ListenerAdapter {
HashMap<Long, Game> games = new HashMap<>();
ArrayList<String> commandsPrefix = new ArrayList<>(Arrays.asList("play", "continue", "stop"));
ArrayList<String> commandsNoPrefix = new ArrayList<>(Arrays.asList("w", "a", "s", "d", "up", "left", "down",
"right", "r"));
@Override
public void onGuildLeave(GuildLeaveEvent event) {
Guild guild = event.getGuild();
Bot.prefixes.remove(guild.getIdLong());
}
@Override
public void onGuildMessageReceived(GuildMessageReceivedEvent event) {
User user = event.getAuthor();
Member member = event.getMember();
Message message = event.getMessage();
TextChannel channel = event.getChannel();
Guild guild = event.getGuild();
String prefix = Bot.getPrefix(guild);
if (user.getId().equals(event.getJDA().getSelfUser().getId())) {
List<MessageEmbed> embeds = message.getEmbeds();
if (embeds.size() > 0) {
MessageEmbed embed = embeds.get(0);
if (embed.getTitle() != null && embed.getTitle().length() > 0) {
if (embed.getTitle().startsWith("Sokobot | Level ")) {
message.addReaction("U+2B05").queue();
message.addReaction("U+27A1").queue();
message.addReaction("U+2B06").queue();
message.addReaction("U+2B07").queue();
message.addReaction("U+1F504").queue();
MessageEmbed.Footer footerObject = embed.getFooter();
if (footerObject != null) {
String footer = footerObject.getText();
if (footer != null) {
long playerId = Long.parseLong(footer.substring(10, footer.length() - 1));
if (games.containsKey(playerId)) {
Game game = games.get(playerId);
game.setGameMessage(message);
}
}
}
}
}
}
return;
}
String[] args = message.getContentRaw().split("\\s+");
if (args.length > 0) {
String arg = args[0].toLowerCase();
if (arg.equals(prefix + "prefix")) {
if (!hasPermissions(guild, channel)) {
sendInvalidPermissionsMessage(user, channel);
return;
}
if (member.hasPermission(Permission.ADMINISTRATOR)) {
if (args.length == 2 && args[1].length() == 1) {
String newPrefix = args[1].toLowerCase();
Bot.setPrefix(event.getGuild(), newPrefix);
channel.sendMessage("Prefix successfully changed to ``" + newPrefix + "``.").queue();
} else channel.sendMessage("The prefix must be one character long!").queue();
} else
channel.sendMessage(user.getAsMention() + ", you do not have permission to use this " + "command" + ".").queue();
// No need to delete prefix-set command
// message.delete().queue();
} else if (((commandsNoPrefix.contains(arg)) || (arg.length() > 0 && Character.toString(arg.charAt(0)).equals(prefix) && commandsPrefix.contains(arg.substring(1))))) {
if (!hasPermissions(guild, channel)) {
sendInvalidPermissionsMessage(user, channel);
return;
}
Game game;
if (!games.containsKey(user.getIdLong())) {
game = new Game(user);
games.put(user.getIdLong(), game);
} else game = games.get(user.getIdLong());
String userInput = arg;
if (userInput.substring(0, 1).equals(prefix)) userInput = userInput.substring(1);
if (!game.gameActive && userInput.equals("play") && args.length == 2 && EmojiManager.isEmoji(args[1])) {
game.setPlayerEmote(args[1]);
}
game.run(event.getGuild(), channel, userInput);
if (userInput.equals("stop")) games.remove(user.getIdLong());
if (guild.getSelfMember().hasPermission(channel, Permission.MESSAGE_MANAGE)) message.delete().queue();
} else if ((arg.equals(prefix + "info")) || (message.getMentionedUsers().size() > 0 && message.getMentionedUsers().get(0).equals(event.getJDA().getSelfUser()))) {
if (!hasPermissions(guild, channel)) {
sendInvalidPermissionsMessage(user, channel);
return;
}
channel.sendMessage(info(event.getGuild()).build()).queue();
if (guild.getSelfMember().hasPermission(channel, Permission.MESSAGE_MANAGE)) message.delete().queue();
}
}
}
private static final Collection<Permission> requiredPermissions = Arrays.asList(Permission.MESSAGE_ADD_REACTION,
Permission.MESSAGE_EMBED_LINKS, Permission.MESSAGE_MANAGE, Permission.MESSAGE_WRITE);
private boolean hasPermissions(Guild guild, TextChannel channel) {
Member self = guild.getSelfMember();
if (self.hasPermission(Permission.ADMINISTRATOR)) return true;
return self.hasPermission(channel, requiredPermissions);
}
private void sendInvalidPermissionsMessage(User user, TextChannel channel) {
if (channel.canTalk()) {
StringBuilder requiredPermissionsDisplay = new StringBuilder();
for (Permission requiredPermission : requiredPermissions) {
requiredPermissionsDisplay.append("`").append(requiredPermission.getName()).append("`, ");
}
if (requiredPermissionsDisplay.toString().endsWith(", "))
requiredPermissionsDisplay = new StringBuilder(requiredPermissionsDisplay.substring(0,
requiredPermissionsDisplay.length() - 2));
channel.sendMessage(user.getAsMention() + ", I don't have enough permissions to work properly.\nMake " +
"sure I have the following permissions: " + requiredPermissionsDisplay + "\nIf you think this is "
+ "an error, please contact a server administrator.").queue();
}
}
@Override
public void onGuildMessageReactionAdd(GuildMessageReactionAddEvent event) {
User user = event.getUser();
if (user.isBot()) {
return;
}
Guild guild = event.getGuild();
MessageReaction reaction = event.getReaction();
TextChannel channel = event.getChannel();
channel.retrieveMessageById(event.getMessageId()).queue(message -> {
if (message.getAuthor().getId().equals(event.getJDA().getSelfUser().getId())) {
Game game;
if (!games.containsKey(user.getIdLong())) {
game = new Game(user);
games.put(user.getIdLong(), game);
} else game = games.get(user.getIdLong());
boolean reactionCommand = true;
String userInput = "";
switch (event.getReactionEmote().toString()) {
case "RE:U+2b05":
userInput = "left";
break;
case "RE:U+27a1":
userInput = "right";
break;
case "RE:U+2b06":
userInput = "up";
break;
case "RE:U+2b07":
userInput = "down";
break;
case "RE:U+1f504":
userInput = "r";
break;
default:
reactionCommand = false;
break;
}
if (reactionCommand) game.run(guild, channel, userInput);
reaction.removeReaction(user).queue();
}
});
}
EmbedBuilder info(Guild guild) {
EmbedBuilder info = new EmbedBuilder();
info.setTitle("Sokobot");
info.setThumbnail(guild.getSelfMember().getUser().getAvatarUrl());
info.setDescription("Sokobot is a bot that lets you play Sokoban, the classic box-pushing puzzle game.");
info.setColor(0xdd2e53);
info.addField("How to Play", "You are a **Sokoban** :flushed:.\nYour job is to push **boxes** :brown_square: "
+ "on top of their **destinations** :negative_squared_cross_mark:.", false);
info.addField("Features", ":white_small_square:**Infinite levels**\nThe maps in Sokobot are randomly " +
"generated, increasing in difficulty as you progress.\n:white_small_square:**Varied " + "controls" +
"**\nSokobot has multiple control options to improve the player's experience, including " +
"reactions and wasd commands!\n:white_small_square:**Simultaneous games**\nThanks to the power of " + "Java HashMaps:tm:, multiple users can use the bot at the same time without interfering with one " + "another.\n:white_small_square:**Custom prefixes**\nTo prevent Sokobot from conflicting with other " + "bots, admins can choose any single-character prefix to preface Sokobot's commands.", false);
info.addField("Commands",
("``" + Bot.getPrefix(guild) + "play`` can be used to start a game if you are not " + "currently in " + "one.\n``" + Bot.getPrefix(guild) + "stop`` can be used to stop your active game at any " + "time.\n``" + Bot.getPrefix(guild) + "info`` provides some useful details about the bot and " + "rules of " + "the game.\n``" + Bot.getPrefix(guild) + "prefix [character]`` can be used to " + "change the prefix the " + "bot responds to."), false);
info.addField("Add to your server",
"https://top.gg/bot/713635251703906336\nSokobot is currently in " + Bot.getShardManager().getGuilds().size() + " servers.", false);
info.addField("Source code", "https://github.com/PolyMarsDev/Sokobot", false);
info.setFooter("created by PolyMars", "https://avatars0.githubusercontent" + ".com/u/51007356?s=460&u" +
"=4eb8fd498421a2eee9781edfbadf654386cf06c7&v=4");
return info;
}
public static void sendGameEmbed(MessageChannel channel, String level, String game, User user) {
EmbedBuilder embed = new EmbedBuilder();
embed.setTitle("Sokobot | Level " + level);
embed.setDescription(game);
embed.addField("Enter direction (``up``, ``down``, ``left``, ``right``/``wasd``) or ``r`` to reset", "", false);
embed.setFooter("Game of " + user.getAsMention(), user.getAvatarUrl());
channel.sendMessage(embed.build()).queue();
}
public static void updateGameEmbed(Message message, String level, String game, User user) {
EmbedBuilder embed = new EmbedBuilder();
embed.setTitle("Sokobot | Level " + level);
embed.setDescription(game);
embed.addField("Enter direction (``up``, ``down``, ``left``, ``right``/``wasd``) or ``r`` to reset", "", false);
embed.setFooter("Game of " + user.getAsMention(), user.getAvatarUrl());
message.editMessage(embed.build()).queue();
}
public static void sendWinEmbed(Guild guild, Message message, String level) {
EmbedBuilder embed = new EmbedBuilder();
embed.setTitle("Sokobot | You win!");
embed.setDescription("Type ``" + Bot.getPrefix(guild) + "continue`` to continue to Level " + level + " or ``" + Bot.getPrefix(guild) + "stop`` to quit ");
embed.setFooter("You can also press any reaction to continue.");
message.editMessage(embed.build()).queue();
}
}
-216
View File
@@ -1,216 +0,0 @@
public class Grid
{
final int GROUND = 0;
final int WALL = 1;
final int BOX = 2;
final int DESTINATION = 3;
final int PLAYER = 4;
final int MAX_BOXES = 8;
Tile[][] grid;
Box[] boxes;
Destination[] destinations;
int boxCount;
int height = 0;
int width = 0;
int color = 0;
Player player;
String playerEmote;
Randomizer rand = new Randomizer();
public Grid(int width, int height, int boxCount, String playerEmote) //create a random grid with specific width, height, and number of boxes
{
this.playerEmote = playerEmote;
player = new Player(2, 2, this);
if (boxCount > MAX_BOXES)
{
boxCount = MAX_BOXES;
}
this.boxCount = boxCount;
boxes = new Box[boxCount];
destinations = new Destination[boxCount];
this.height = height;
this.width = width;
grid = new Tile[width][height];
createBoxes();
createDestinations();
updateGrid();
}
public Player getPlayer()
{
return player;
}
public void reset()
{
player.setPosition(2, 2);
for (int i = 0; i < boxCount; i++)
{
boxes[i].reset();
}
updateGrid();
}
public void setStatus(int x, int y, int status)
{
grid[x][y].setStatus(status);
}
public int getStatus(int x, int y)
{
return grid[x][y].getStatus();
}
public boolean isWall(int x, int y)
{
return grid[x][y].getStatus() == WALL;
}
public boolean isBox(int x, int y)
{
return grid[x][y].getStatus() == BOX;
}
public boolean isBoxRaw(int x, int y) //allows you to check if a box is at a position before grid is set up
{
for (int i = 0; i < boxCount; i++)
{
if (x == boxes[i].getX() && y == boxes[i].getY())
{
return true;
}
}
return false;
}
public boolean isDestination(int x, int y)
{
return grid[x][y].getStatus() == DESTINATION;
}
public Box getBox(int x, int y)
{
for (int i = 0; i < boxCount; i++)
{
if (x == boxes[i].getX() && y == boxes[i].getY())
{
return boxes[i];
}
}
return null;
}
public void createBoxes()
{
color = rand.nextInt(6); //runs after each level
for (int i = 0; i < boxCount; i++)
{
int x = rand.nextInt(width - 4) + 2;
int y = rand.nextInt(height - 4) + 2;
for (int j = 0; j < i; j++)
{
while ((x == boxes[j].getX() && y == boxes[j].getY()) || (x == 2 && y == 2) || (x - 1 == boxes[j].getX() && y == boxes[j].getY()) || (x + 1 == boxes[j].getX() && y == boxes[j].getY()))
{
x = rand.nextInt(width - 4) + 2;
y = rand.nextInt(height - 4) + 2;
}
}
boxes[i] = new Box(x, y, this);
}
}
public void createDestinations()
{
for (int i = 0; i < boxCount; i++)
{
int x = rand.nextInt(width - 2) + 1;
int y = rand.nextInt(height - 2) + 1;
for (int j = 0; j < i; j++)
{
while (((x == destinations[j].getX() && y == destinations[j].getY())) || isBoxRaw(x, y))
{
x = rand.nextInt(width - 2) + 1;
y = rand.nextInt(height - 2) + 1;
}
}
destinations[i] = new Destination(x, y, this);
}
}
public void updateGrid()
{
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
grid[j][i] = new Tile(GROUND, playerEmote);
if (j == 0 || j == width - 1 || i == 0 || i == height - 1)
{
grid[j][i] = new Tile(WALL, color, playerEmote);
}
for (int k = 0; k < boxCount; k++)
{
if (destinations[k].getX() == j && destinations[k].getY() == i)
{
grid[j][i] = new Tile(DESTINATION, playerEmote);
}
}
if (player.getX() == j && player.getY() == i)
{
grid[j][i] = new Tile(PLAYER, playerEmote);
}
for (int k = 0; k < boxCount; k++)
{
if (boxes[k].getX() == j && boxes[k].getY() == i)
{
if (boxes[k].onDestination())
{
grid[j][i] = new Tile(WALL, color, playerEmote);
} else {
grid[j][i] = new Tile(BOX, playerEmote);
}
}
}
}
}
}
public boolean hasWon()
{
for (int i = 0; i < boxCount; i++)
{
if (!destinations[i].hasBox(this))
{
return false;
}
}
return true;
}
public Tile[][] getGrid()
{
return grid;
}
public Box[] getBoxes()
{
return boxes;
}
public Destination[] getDestinations()
{
return destinations;
}
public int getBoxCount()
{
return boxCount;
}
public int getHeight()
{
return height;
}
public int getWidth()
{
return width;
}
public String toString()
{
updateGrid();
String result = "";
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
result += grid[j][i];
}
result += "\n";
}
return result;
}
}
-98
View File
@@ -1,98 +0,0 @@
public class Player
{
int x = 0;
int y = 0;
Grid currentGrid;
public Player(int x, int y, Grid currentGrid)
{
this.x = x;
this.y = y;
this.currentGrid = currentGrid;
}
public int getX()
{
return x;
}
public int getY()
{
return y;
}
public void setPosition(int x, int y)
{
this.x = x;
this.y = y;
}
public boolean moveUp()
{
if (!currentGrid.isWall(x, y - 1))
{
if (currentGrid.isBox(x, y - 1))
{
if (currentGrid.getBox(x, y - 1).moveUp())
{
y -= 1;
return true;
}
return false;
}
y -= 1;
return true;
}
return false;
}
public boolean moveDown()
{
if (!currentGrid.isWall(x, y + 1))
{
if (currentGrid.isBox(x, y + 1))
{
if (currentGrid.getBox(x, y + 1).moveDown())
{
y += 1;
return true;
}
return false;
}
y += 1;
return true;
}
return false;
}
public boolean moveLeft()
{
if (!currentGrid.isWall(x - 1, y))
{
if (currentGrid.isBox(x - 1, y))
{
if (currentGrid.getBox(x - 1, y).moveLeft())
{
x -= 1;
return true;
}
return false;
}
x -= 1;
return true;
}
return false;
}
public boolean moveRight()
{
if (!currentGrid.isWall(x + 1, y))
{
if (currentGrid.isBox(x + 1, y))
{
if (currentGrid.getBox(x + 1, y).moveRight())
{
x += 1;
return true;
}
return false;
}
x += 1;
return true;
}
return false;
}
}
@@ -0,0 +1,147 @@
package me.polymarsdev.sokobot;
import me.polymarsdev.sokobot.database.Database;
import me.polymarsdev.sokobot.listener.CommandListener;
import me.polymarsdev.sokobot.listener.GameListener;
import me.polymarsdev.sokobot.util.GameUtil;
import net.dv8tion.jda.api.OnlineStatus;
import net.dv8tion.jda.api.entities.Activity;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.sharding.DefaultShardManagerBuilder;
import net.dv8tion.jda.api.sharding.ShardManager;
import javax.security.auth.login.LoginException;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Scanner;
public class Bot {
static HashMap<Long, String> prefixes = new HashMap<>();
/**
* You can enable the database here.
* Set the DB Type to MySQL or SQLite, which you want to use.
* -
* You can configure login data in the Database class.
*/
private static final boolean enableDatabase = false;
private static final Database.DBType dbType = Database.DBType.SQLite;
private static ShardManager shardManager;
private static Database database = null;
public static void main(String[] args) throws LoginException {
String token = null;
try {
File tokenFile = Paths.get("token.txt").toFile();
if (!tokenFile.exists()) {
System.out.println("[ERROR] Could not find token.txt file");
System.out.print("Please paste in your bot token: ");
Scanner s = new Scanner(System.in);
token = s.nextLine();
System.out.println();
System.out.println("[INFO] Creating token.txt - please wait");
if (!tokenFile.createNewFile()) {
System.out.println(
"[ERROR] Could not create token.txt - please create this file and paste in your token"
+ ".");
s.close();
return;
}
Files.write(tokenFile.toPath(), token.getBytes());
s.close();
}
token = new String(Files.readAllBytes(tokenFile.toPath()));
} catch (Exception ex) {
ex.printStackTrace();
}
if (token == null) return;
if (enableDatabase) database = new Database(dbType);
if (database != null) {
if (!database.isConnected()) {
database = null;
System.out.println("[ERROR] Database connection failed. Continuing without database.");
} else {
database.update(
"CREATE TABLE IF NOT EXISTS guildprefix (guildId VARCHAR(18) NOT NULL, prefix VARCHAR(8) NOT "
+ "NULL);");
}
}
DefaultShardManagerBuilder builder = new DefaultShardManagerBuilder(token);
builder.setStatus(OnlineStatus.ONLINE);
builder.setActivity(Activity.playing("@Sokobot for info!"));
builder.addEventListeners(new GameListener(), new CommandListener());
shardManager = builder.build();
GameUtil.runGameTimer();
Thread consoleThread = new Thread(() -> {
Scanner s = new Scanner(System.in);
while (s.hasNextLine()) {
processCommand(s.nextLine());
}
});
consoleThread.setDaemon(true);
consoleThread.setName("Console Thread");
consoleThread.start();
}
private static void processCommand(String cmd) {
if (cmd.equalsIgnoreCase("help")) {
System.out.println("Commands:\nstop - Shuts down the bot and exits the program");
return;
}
if (cmd.equalsIgnoreCase("stop")) {
System.out.println("Shutting down...");
shardManager.shutdown();
if (database != null) {
System.out.println("Disconnecting database...");
database.disconnect();
}
System.out.println("Bye!");
System.exit(0);
return;
}
System.out.println("Unknown command. Please use \"help\" for a list of commands.");
}
public static ShardManager getShardManager() {
return shardManager;
}
public static void removePrefix(long guildId) {
prefixes.remove(guildId);
if (database != null) {
database.update("DELETE FROM guildprefix WHERE guildId=?;", String.valueOf(guildId));
}
}
public static void setPrefix(Guild guild, String prefix) {
prefixes.put(guild.getIdLong(), prefix);
if (database != null) {
database.update("DELETE FROM guildprefix WHERE guildId=?;", guild.getId());
database.update("INSERT INTO guildprefix VALUES (?, ?);", guild.getId(), prefix);
}
}
public static String getPrefix(Guild guild) {
if (prefixes.containsKey(guild.getIdLong())) return prefixes.get(guild.getIdLong());
if (database != null) {
try (ResultSet rs = database.query("SELECT prefix FROM guildprefix WHERE guildId=?;", guild.getId())) {
if (rs.next()) {
String prefix = rs.getString("prefix");
prefixes.put(guild.getIdLong(), prefix);
return prefix;
}
prefixes.put(guild.getIdLong(), "!");
return "!";
} catch (SQLException ex) {
System.out.println("[ERROR] Error at retrieving guild prefix of guild id " + guild.getId() + ": " + ex
.getMessage());
}
}
return "!";
}
}
@@ -1,5 +1,11 @@
package me.polymarsdev.sokobot;
import me.polymarsdev.sokobot.objects.Grid;
import me.polymarsdev.sokobot.util.GameUtil;
import net.dv8tion.jda.api.entities.*; import net.dv8tion.jda.api.entities.*;
import java.util.concurrent.TimeUnit;
public class Game { public class Game {
long gameMessageID; long gameMessageID;
long channelID; long channelID;
@@ -9,6 +15,7 @@ public class Game {
public int level = 1; public int level = 1;
int width = 9; int width = 9;
int height = 6; int height = 6;
public long lastAction;
Grid grid = new Grid(width, height, level, playerEmote); Grid grid = new Grid(width, height, level, playerEmote);
public Game(User user) { public Game(User user) {
@@ -32,36 +39,60 @@ public class Game {
height = 6; height = 6;
grid = new Grid(width, height, level, playerEmote); grid = new Grid(width, height, level, playerEmote);
gameActive = true; gameActive = true;
Commands.sendGameEmbed(channel, String.valueOf(level), grid.toString(), user); lastAction = System.currentTimeMillis();
GameUtil.sendGameEmbed(channel, String.valueOf(level), grid.toString(), user);
}
}
public void stop() {
gameActive = false;
TextChannel textChannel = Bot.getShardManager().getTextChannelById(channelID);
if (textChannel != null) {
textChannel.retrieveMessageById(gameMessageID).queue(gameMessage -> gameMessage.delete().queue());
} }
} }
public void run(Guild guild, TextChannel channel, String userInput) { public void run(Guild guild, TextChannel channel, String userInput) {
lastAction = System.currentTimeMillis();
if (userInput.equals("stop") && gameActive) { if (userInput.equals("stop") && gameActive) {
channel.sendMessage("Thanks for playing, " + user.getAsMention() + "!").queue(); stop();
gameActive = false; channel.sendMessage("Thanks for playing, " + user.getAsMention() + "!")
.queue(msg -> msg.delete().queueAfter(10, TimeUnit.SECONDS));
} }
if (userInput.equals("play") && !gameActive) { if (userInput.equals("play") && !gameActive) {
newGame(channel); newGame(channel);
} else if (gameActive) { } else if (gameActive) {
if (!grid.hasWon()) { if (!grid.hasWon()) {
String direction = userInput; String direction = userInput;
if (direction.equals("up") || direction.equals("w")) { switch (direction) {
grid.getPlayer().moveUp(); case "up":
} else if (direction.equals("down") || direction.equals("s")) { case "w":
grid.getPlayer().moveDown(); grid.getPlayer().moveUp();
} else if (direction.equals("left") || direction.equals("a")) { break;
grid.getPlayer().moveLeft(); case "down":
} else if (direction.equals("right") || direction.equals("d")) { case "s":
grid.getPlayer().moveRight(); grid.getPlayer().moveDown();
} else if (direction.equals("r")) { break;
grid.reset(); case "left":
case "a":
grid.getPlayer().moveLeft();
break;
case "right":
case "d":
grid.getPlayer().moveRight();
break;
case "mr":
grid.resetMap();
break;
case "r":
grid.reset();
break;
} }
if (!grid.hasWon()) { if (!grid.hasWon()) {
TextChannel textChannel = Bot.getShardManager().getTextChannelById(channelID); TextChannel textChannel = Bot.getShardManager().getTextChannelById(channelID);
if (textChannel != null) { if (textChannel != null) {
textChannel.retrieveMessageById(gameMessageID).queue(gameMessage -> Commands.updateGameEmbed(gameMessage, String.valueOf(level), grid.toString(), user)); textChannel.retrieveMessageById(gameMessageID).queue(gameMessage -> GameUtil
.updateGameEmbed(gameMessage, String.valueOf(level), grid.toString(), user));
} }
} }
} }
@@ -75,8 +106,8 @@ public class Game {
} }
TextChannel textChannel = Bot.getShardManager().getTextChannelById(channelID); TextChannel textChannel = Bot.getShardManager().getTextChannelById(channelID);
if (textChannel != null) { if (textChannel != null) {
textChannel.retrieveMessageById(gameMessageID).queue(gameMessage -> Commands.sendWinEmbed(guild, textChannel.retrieveMessageById(gameMessageID)
gameMessage, String.valueOf(level))); .queue(gameMessage -> GameUtil.sendWinEmbed(guild, gameMessage, String.valueOf(level)));
} }
grid = new Grid(width, height, level, playerEmote); grid = new Grid(width, height, level, playerEmote);
} }
@@ -0,0 +1,47 @@
package me.polymarsdev.sokobot.commands;
import com.vdurmont.emoji.EmojiManager;
import me.polymarsdev.sokobot.Bot;
import me.polymarsdev.sokobot.Game;
import me.polymarsdev.sokobot.entity.Command;
import me.polymarsdev.sokobot.event.CommandEvent;
import me.polymarsdev.sokobot.util.GameUtil;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.User;
public class GameInputCommand extends Command {
public GameInputCommand(String name) {
super(name);
}
@Override
public void execute(CommandEvent event) {
User user = event.getAuthor();
String[] args = event.getArgs();
String prefix = Bot.getPrefix(event.getGuild());
Game game;
if (!GameUtil.hasGame(user.getIdLong())) {
game = new Game(user);
GameUtil.setGame(user.getIdLong(), game);
} else game = GameUtil.getGame(user.getIdLong());
//
String userInput = this.getName().toLowerCase();
if (userInput.equals("play")) {
if (!game.gameActive) {
if (args.length > 0 && EmojiManager.isEmoji(args[0])) game.setPlayerEmote(args[0]);
} else {
event.reply(user.getAsMention() + ", you already have an active game.\nUse `" + prefix
+ "stop` to stop your current game first.");
}
}
Guild guild = event.getGuild();
TextChannel channel = event.getTextChannel();
game.run(event.getGuild(), channel, userInput);
if (userInput.equals("stop")) GameUtil.removeGame(user.getIdLong());
if (game.gameActive && guild.getSelfMember().hasPermission(channel, Permission.MESSAGE_MANAGE))
event.getMessage().delete().queue();
}
}
@@ -0,0 +1,53 @@
package me.polymarsdev.sokobot.commands;
import me.polymarsdev.sokobot.Bot;
import me.polymarsdev.sokobot.entity.Command;
import me.polymarsdev.sokobot.event.CommandEvent;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Guild;
public class InfoCommand extends Command {
public InfoCommand() {
super("info");
}
@Override
public void execute(CommandEvent event) {
Guild guild = event.getGuild();
EmbedBuilder info = new EmbedBuilder();
final String prefix = Bot.getPrefix(guild);
info.setTitle("Sokobot");
info.setThumbnail(guild.getSelfMember().getUser().getAvatarUrl());
info.setDescription("Sokobot is a bot that lets you play Sokoban, the classic box-pushing puzzle game.");
info.setColor(0xdd2e53);
info.addField("How to Play", "You are a **Sokoban** :flushed:.\nYour job is to push **boxes** :brown_square: "
+ "on top of their **destinations** :negative_squared_cross_mark:.", false);
info.addField("Features", ":white_small_square:**Infinite levels**\nThe maps in Sokobot are randomly "
+ "generated, increasing in difficulty as you progress.\n:white_small_square:**Varied " + "controls"
+ "**\nSokobot has multiple control options to improve the player's experience, including "
+ "reactions and wasd commands!\n:white_small_square:**Simultaneous games**\nThanks to the power of "
+ "Java HashMaps:tm:, multiple users can use the bot at the same time without interfering with one "
+ "another.\n:white_small_square:**Custom prefixes**\nTo prevent Sokobot from conflicting with other "
+ "bots, admins can choose any single-character prefix to preface Sokobot's commands.", false);
info.addField("Commands",
("``" + prefix + "play`` can be used to start a game if you are not " + "currently in "
+ "one.\n``" + prefix + "stop`` can be used to stop your active game at any "
+ "time.\n``" + prefix + "info`` provides some useful details about the bot and "
+ "rules of " + "the game.\n``" + Bot.getPrefix(guild)
+ "prefix [character]`` can be used to " + "change the prefix the " + "bot responds to."),
false);
info.addField("Add to your server",
"https://top.gg/bot/713635251703906336\nSokobot is currently in " + Bot.getShardManager()
.getGuilds().size() + " servers.", false);
/*
// Official Support Server
info.addField("Support / Feedback",
"Official Support Server: https://invite.affluentproductions.org/apserver", false);
*/
info.addField("Source code", "https://github.com/PolyMarsDev/Sokobot", false);
info.setFooter("created by PolyMars", "https://avatars0.githubusercontent" + ".com/u/51007356?s=460&u"
+ "=4eb8fd498421a2eee9781edfbadf654386cf06c7&v=4");
event.reply(info.build());
}
}
@@ -0,0 +1,37 @@
package me.polymarsdev.sokobot.commands;
import me.polymarsdev.sokobot.Bot;
import me.polymarsdev.sokobot.entity.Command;
import me.polymarsdev.sokobot.event.CommandEvent;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
public class PrefixCommand extends Command {
public PrefixCommand() {
super("prefix");
}
@Override
public void execute(CommandEvent event) {
User user = event.getAuthor();
Member member = event.getMember();
String[] args = event.getArgs();
if (args.length > 0) {
if (!member.hasPermission(Permission.ADMINISTRATOR)) {
event.reply(user.getAsMention() + ", you do not have permission to use this command.");
return;
}
String newPrefix = args[0].toLowerCase();
if (newPrefix.length() > 1) {
event.reply(user.getAsMention() + ", the prefix must be one character long!");
return;
}
Bot.setPrefix(event.getGuild(), newPrefix);
event.reply("Prefix successfully changed to ``" + newPrefix + "``.");
}
}
}
@@ -0,0 +1,118 @@
package me.polymarsdev.sokobot.database;
import java.io.File;
import java.sql.*;
public class Database {
public enum DBType {MySQL, SQLite}
/**
* SQLite Data
* Set this data if you use DBType#SQLite
*
* field filePath - This can either be a relative or absolute path.
* ex: sokobot.db
* or: C:/sqlite/db/sokobot.db
*/
private final String filePath = "sokobot.db";
/**
* MySQL Data
* Set this data if you use DBType#MySQL
*/
private final String mysql_hostname = "127.0.0.1";
private final int mysql_port = 3306;
private final String mysql_database = "sokobot";
private final String mysql_username = "sokobot";
private final String mysql_password = "$€cUR€_P4sSw0R!)";
private Connection con = null;
public Database(DBType dbType) {
try {
if (dbType == DBType.MySQL) {
con = DriverManager.getConnection(
"jdbc:mysql://" + mysql_hostname + ":" + mysql_port + "/" + mysql_database
+ "?autoReconnect=true", mysql_username, mysql_password);
System.out.println("[INFO] Successfully initialized database connection.");
} else if (dbType == DBType.SQLite) {
File sqliteFile = new File(filePath);
if (!sqliteFile.exists()) {
System.out.println("[INFO] SQLite file \"" + filePath + "\" not found, creating file...");
boolean create = sqliteFile.createNewFile();
if (!create) System.out.println("[ERROR] Could not create SQLite file at " + filePath);
}
con = DriverManager.getConnection("jdbc:sqlite:" + filePath);
System.out.println("[INFO] Successfully initialized database connection.");
}
} catch (Exception ex) {
System.out.println("[ERROR] Error at creating database connection: " + ex.getMessage());
}
}
public void disconnect() {
try {
con.clearWarnings();
con.close();
con = null;
} catch (SQLException ex) {
ex.printStackTrace();
}
}
public Connection getCon() {
return con;
}
public ResultSet query(String sql, Object... preparedParameters) {
try {
PreparedStatement ps = con.prepareStatement(sql);
int id = 1;
for (Object preparedParameter : preparedParameters) {
ps.setObject(id, preparedParameter);
id++;
}
return ps.executeQuery();
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
public ResultSet query(String sql) {
try {
ResultSet rs = con.prepareStatement(sql).executeQuery();
return rs;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
public void update(String sql, Object... preparedParameters) {
try (PreparedStatement ps = con.prepareStatement(sql)) {
int id = 1;
for (Object preparedParameter : preparedParameters) {
ps.setObject(id, preparedParameter);
id++;
}
ps.executeUpdate();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
public void update(String sql) {
try (PreparedStatement ps = con.prepareStatement(sql)) {
ps.executeUpdate();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
public boolean isConnected() {
return con != null;
}
}
@@ -0,0 +1,18 @@
package me.polymarsdev.sokobot.entity;
import me.polymarsdev.sokobot.event.CommandEvent;
public abstract class Command {
private final String name;
public Command(String name){
this.name = name;
}
public String getName() {
return name;
}
public abstract void execute(CommandEvent commandEvent);
}
@@ -0,0 +1,97 @@
package me.polymarsdev.sokobot.entity;
import me.polymarsdev.sokobot.objects.Grid;
public class Player {
int x = 0;
int y = 0;
Grid currentGrid;
public Player(int x, int y, Grid currentGrid) {
this.x = x;
this.y = y;
this.currentGrid = currentGrid;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public void resetPosition() {
int setX = 2;
int setY = 2;
while (currentGrid.isBoxRaw(setX, setY)) {
if (setX >= currentGrid.getWidth() - 1) {
setY++;
setX = 1;
} else setX++;
}
this.x = setX;
this.y = setY;
}
public boolean moveUp() {
if (!currentGrid.isWall(x, y - 1)) {
if (currentGrid.isBox(x, y - 1)) {
if (currentGrid.getBox(x, y - 1).moveUp()) {
y -= 1;
return true;
}
return false;
}
y -= 1;
return true;
}
return false;
}
public boolean moveDown() {
if (!currentGrid.isWall(x, y + 1)) {
if (currentGrid.isBox(x, y + 1)) {
if (currentGrid.getBox(x, y + 1).moveDown()) {
y += 1;
return true;
}
return false;
}
y += 1;
return true;
}
return false;
}
public boolean moveLeft() {
if (!currentGrid.isWall(x - 1, y)) {
if (currentGrid.isBox(x - 1, y)) {
if (currentGrid.getBox(x - 1, y).moveLeft()) {
x -= 1;
return true;
}
return false;
}
x -= 1;
return true;
}
return false;
}
public boolean moveRight() {
if (!currentGrid.isWall(x + 1, y)) {
if (currentGrid.isBox(x + 1, y)) {
if (currentGrid.getBox(x + 1, y).moveRight()) {
x += 1;
return true;
}
return false;
}
x += 1;
return true;
}
return false;
}
}
@@ -0,0 +1,159 @@
package me.polymarsdev.sokobot.event;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class CommandEvent {
private static final int MAX_MESSAGES = 2;
private final GuildMessageReceivedEvent event;
private String[] args;
public CommandEvent(GuildMessageReceivedEvent event, String[] args) {
this.event = event;
this.args = args;
}
public String[] getArgs() {
return args;
}
public GuildMessageReceivedEvent getEvent() {
return event;
}
public List<Member> getMentionedMembers() {
List<Member> mentionedMembers = new ArrayList<>(event.getMessage().getMentionedMembers());
if (event.getMessage().getContentRaw().startsWith("<@!" + event.getJDA().getSelfUser().getId() + ">"))
mentionedMembers.remove(0);
return mentionedMembers;
}
public void reply(String message) {
sendMessage(event.getChannel(), message);
}
public void reply(String message, Consumer<Message> success) {
sendMessage(event.getChannel(), message, success);
}
public void reply(String message, Consumer<Message> success, Consumer<Throwable> failure) {
sendMessage(event.getChannel(), message, success, failure);
}
public void reply(MessageEmbed embed) {
event.getChannel().sendMessage(embed).queue();
}
public void reply(MessageEmbed embed, Consumer<Message> success) {
event.getChannel().sendMessage(embed).queue(success);
}
public void reply(MessageEmbed embed, Consumer<Message> success, Consumer<Throwable> failure) {
event.getChannel().sendMessage(embed).queue(success, failure);
}
public void reply(Message message) {
event.getChannel().sendMessage(message).queue();
}
public void reply(Message message, Consumer<Message> success) {
event.getChannel().sendMessage(message).queue(success);
}
public void reply(Message message, Consumer<Message> success, Consumer<Throwable> failure) {
event.getChannel().sendMessage(message).queue(success, failure);
}
public void reply(File file, String filename) {
event.getChannel().sendFile(file, filename).queue();
}
public void reply(String message, File file, String filename) {
String msg = message == null ? null : splitMessage(message).get(0);
if (msg == null) event.getChannel().sendFile(file, filename).queue();
else event.getChannel().sendMessage(msg).addFile(file, filename).queue();
}
private void sendMessage(MessageChannel chan, String message) {
ArrayList<String> messages = splitMessage(message);
for (int i = 0; i < MAX_MESSAGES && i < messages.size(); i++) {
chan.sendMessage(messages.get(i)).queue();
}
}
private void sendMessage(MessageChannel chan, String message, Consumer<Message> success) {
ArrayList<String> messages = splitMessage(message);
for (int i = 0; i < MAX_MESSAGES && i < messages.size(); i++) {
if (i + 1 == MAX_MESSAGES || i + 1 == messages.size()) {
chan.sendMessage(messages.get(i)).queue(success);
} else {
chan.sendMessage(messages.get(i)).queue();
}
}
}
private void sendMessage(MessageChannel chan, String message, Consumer<Message> success,
Consumer<Throwable> failure) {
ArrayList<String> messages = splitMessage(message);
for (int i = 0; i < MAX_MESSAGES && i < messages.size(); i++) {
if (i + 1 == MAX_MESSAGES || i + 1 == messages.size()) {
chan.sendMessage(messages.get(i)).queue(success, failure);
} else {
chan.sendMessage(messages.get(i)).queue();
}
}
}
private static ArrayList<String> splitMessage(String stringtoSend) {
ArrayList<String> msgs = new ArrayList<>();
if (stringtoSend != null) {
stringtoSend = stringtoSend.replace("@everyone", "@\u0435veryone").replace("@here", "@h\u0435re").trim();
while (stringtoSend.length() > 2000) {
int leeway = 2000 - (stringtoSend.length() % 2000);
int index = stringtoSend.lastIndexOf("\n", 2000);
if (index < leeway) index = stringtoSend.lastIndexOf(" ", 2000);
if (index < leeway) index = 2000;
String temp = stringtoSend.substring(0, index).trim();
if (!temp.equals("")) msgs.add(temp);
stringtoSend = stringtoSend.substring(index).trim();
}
if (!stringtoSend.equals("")) msgs.add(stringtoSend);
}
return msgs;
}
SelfUser getSelfUser() {
return event.getJDA().getSelfUser();
}
public User getAuthor() {
return event.getAuthor();
}
public Guild getGuild() {
return event.getGuild();
}
public JDA getJDA() {
return event.getJDA();
}
public Member getMember() {
return event.getMember();
}
public Message getMessage() {
return event.getMessage();
}
public TextChannel getTextChannel() {
return event.getChannel();
}
}
@@ -0,0 +1,127 @@
package me.polymarsdev.sokobot.listener;
import me.polymarsdev.sokobot.Bot;
import me.polymarsdev.sokobot.Game;
import me.polymarsdev.sokobot.commands.GameInputCommand;
import me.polymarsdev.sokobot.commands.InfoCommand;
import me.polymarsdev.sokobot.commands.PrefixCommand;
import me.polymarsdev.sokobot.entity.Command;
import me.polymarsdev.sokobot.event.CommandEvent;
import me.polymarsdev.sokobot.util.GameUtil;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import java.util.*;
public class CommandListener extends ListenerAdapter {
private static final ArrayList<String> commandsNoPrefix = new ArrayList<>(
Arrays.asList("w", "a", "s", "d", "up", "left", "down", "right", "r", "mr"));
private static final HashMap<String, Command> commands = new HashMap<>();
public CommandListener() {
List<Command> botCommands = new ArrayList<>(Arrays.asList(new InfoCommand(), new PrefixCommand()));
botCommands.addAll(Arrays.asList(new GameInputCommand("play"), new GameInputCommand("continue"),
new GameInputCommand("stop")));
for (String cnp : commandsNoPrefix) botCommands.add(new GameInputCommand(cnp));
for (Command command : botCommands) commands.put(command.getName().toLowerCase(), command);
System.out.println("[INFO] Loaded " + commands.size() + " commands");
}
@Override
public void onGuildMessageReceived(GuildMessageReceivedEvent event) {
User user = event.getAuthor();
Message message = event.getMessage();
TextChannel channel = event.getChannel();
Guild guild = event.getGuild();
if (user.getId().equals(event.getJDA().getSelfUser().getId())) {
List<MessageEmbed> embeds = message.getEmbeds();
if (embeds.size() > 0) {
MessageEmbed embed = embeds.get(0);
if (embed.getTitle() != null && embed.getTitle().length() > 0) {
if (embed.getTitle().startsWith("Sokobot | Level ")) {
message.addReaction("U+2B05").queue();
message.addReaction("U+27A1").queue();
message.addReaction("U+2B06").queue();
message.addReaction("U+2B07").queue();
message.addReaction("U+1F504").queue();
List<MessageEmbed.Field> fields = embed.getFields();
for (MessageEmbed.Field field : fields) {
if (field.getName() != null && field.getName().equals("Player")) {
if (field.getValue() != null) {
long playerId = Long
.parseLong(field.getValue().substring(2, field.getValue().length() - 1));
if (GameUtil.hasGame(playerId)) {
Game game = GameUtil.getGame(playerId);
game.setGameMessage(message);
}
}
}
}
}
}
}
return;
}
String msgRaw = message.getContentRaw();
String[] args = msgRaw.split("\\s+");
if (args.length > 0) {
boolean isMention = msgRaw.equals("<@" + event.getJDA().getSelfUser().getId() + ">") || msgRaw
.equals("<@!" + event.getJDA().getSelfUser().getId() + ">");
String prefix = Bot.getPrefix(guild);
String arg = args[0].toLowerCase();
boolean isCommand;
if (isMention) isCommand = true;
else {
if (arg.startsWith(prefix)) {
if (commandsNoPrefix.contains(arg)) {
isCommand = true;
} else {
String commandName = arg.substring(prefix.length()).toLowerCase();
isCommand = commands.containsKey(commandName);
if (isCommand) arg = commandName;
}
} else {
isCommand = commandsNoPrefix.contains(arg);
}
}
if (isCommand) {
if (!hasPermissions(guild, channel)) {
sendInvalidPermissionsMessage(user, channel);
return;
}
Command command = commands.get(arg);
if (isMention) command = commands.get("info");
if (command == null) return;
command.execute(new CommandEvent(event, Arrays.copyOfRange(msgRaw.split("\\s+"), 1, args.length)));
}
}
}
private static final Collection<Permission> requiredPermissions = Arrays
.asList(Permission.MESSAGE_ADD_REACTION, Permission.MESSAGE_EMBED_LINKS, Permission.MESSAGE_MANAGE,
Permission.MESSAGE_WRITE);
private boolean hasPermissions(Guild guild, TextChannel channel) {
Member self = guild.getSelfMember();
if (self.hasPermission(Permission.ADMINISTRATOR)) return true;
return self.hasPermission(channel, requiredPermissions);
}
private void sendInvalidPermissionsMessage(User user, TextChannel channel) {
if (channel.canTalk()) {
StringBuilder requiredPermissionsDisplay = new StringBuilder();
for (Permission requiredPermission : requiredPermissions) {
requiredPermissionsDisplay.append("`").append(requiredPermission.getName()).append("`, ");
}
if (requiredPermissionsDisplay.toString().endsWith(", ")) requiredPermissionsDisplay = new StringBuilder(
requiredPermissionsDisplay.substring(0, requiredPermissionsDisplay.length() - 2));
channel.sendMessage(user.getAsMention() + ", I don't have enough permissions to work properly.\nMake "
+ "sure I have the following permissions: " + requiredPermissionsDisplay
+ "\nIf you think this is "
+ "an error, please contact a server administrator.").queue();
}
}
}
@@ -0,0 +1,68 @@
package me.polymarsdev.sokobot.listener;
import me.polymarsdev.sokobot.Bot;
import me.polymarsdev.sokobot.Game;
import me.polymarsdev.sokobot.util.GameUtil;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.MessageReaction;
import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.events.guild.GuildLeaveEvent;
import net.dv8tion.jda.api.events.message.guild.react.GuildMessageReactionAddEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
public class GameListener extends ListenerAdapter {
@Override
public void onGuildLeave(GuildLeaveEvent event) {
Guild guild = event.getGuild();
Bot.removePrefix(guild.getIdLong());
}
@Override
public void onGuildMessageReactionAdd(GuildMessageReactionAddEvent event) {
User user = event.getUser();
if (user.isBot()) {
return;
}
Guild guild = event.getGuild();
MessageReaction reaction = event.getReaction();
TextChannel channel = event.getChannel();
channel.retrieveMessageById(event.getMessageId()).queue(message -> {
if (message.getAuthor().getId().equals(event.getJDA().getSelfUser().getId())) {
Game game;
if (!GameUtil.hasGame(user.getIdLong())) {
game = new Game(user);
GameUtil.setGame(user.getIdLong(), game);
} else game = GameUtil.getGame(user.getIdLong());
boolean reactionCommand = true;
String userInput = "";
switch (event.getReactionEmote().toString()) {
case "RE:U+2b05":
userInput = "left";
break;
case "RE:U+27a1":
userInput = "right";
break;
case "RE:U+2b06":
userInput = "up";
break;
case "RE:U+2b07":
userInput = "down";
break;
case "RE:U+1f504":
userInput = "r";
break;
default:
reactionCommand = false;
break;
}
if (reactionCommand) game.run(guild, channel, userInput);
if (guild.getSelfMember().hasPermission(channel, Permission.MESSAGE_MANAGE))
reaction.removeReaction(user).queue();
}
});
}
}
@@ -1,3 +1,5 @@
package me.polymarsdev.sokobot.objects;
public class Box public class Box
{ {
int x = 0; int x = 0;
@@ -1,24 +1,25 @@
public class Destination package me.polymarsdev.sokobot.objects;
{
public class Destination {
int x = 0; int x = 0;
int y = 0; int y = 0;
Grid currentGrid; Grid currentGrid;
public Destination(int x, int y, Grid currentGrid)
{ public Destination(int x, int y, Grid currentGrid) {
this.x = x; this.x = x;
this.y = y; this.y = y;
this.currentGrid = currentGrid; this.currentGrid = currentGrid;
} }
public boolean hasBox(Grid currentGrid)
{ public boolean hasBox(Grid currentGrid) {
return currentGrid.isWall(x, y); return currentGrid.isWall(x, y);
} }
public int getX()
{ public int getX() {
return x; return x;
} }
public int getY()
{ public int getY() {
return y; return y;
} }
} }
@@ -0,0 +1,206 @@
package me.polymarsdev.sokobot.objects;
import me.polymarsdev.sokobot.entity.Player;
import me.polymarsdev.sokobot.util.Randomizer;
public class Grid {
final int GROUND = 0;
final int WALL = 1;
final int BOX = 2;
final int DESTINATION = 3;
final int PLAYER = 4;
final int MAX_BOXES = 8;
Tile[][] grid;
Box[] boxes;
Destination[] destinations;
int boxCount;
int height = 0;
int width = 0;
int color = 0;
Player player;
String playerEmote;
public Grid(int width, int height, int boxCount, String playerEmote) //create a random grid with specific width,
// height, and number of boxes
{
this.playerEmote = playerEmote;
player = new Player(2, 2, this);
if (boxCount > MAX_BOXES) boxCount = MAX_BOXES;
this.boxCount = boxCount;
boxes = new Box[boxCount];
destinations = new Destination[boxCount];
this.height = height;
this.width = width;
grid = new Tile[width][height];
createBoxes();
createDestinations();
player.resetPosition();
updateGrid();
}
public Player getPlayer() {
return player;
}
public void reset() {
for (int i = 0; i < boxCount; i++) {
boxes[i].reset();
}
player.resetPosition();
updateGrid();
}
public void resetMap() {
boxes = new Box[boxCount];
destinations = new Destination[boxCount];
createBoxes();
createDestinations();
player.resetPosition();
updateGrid();
}
public void setStatus(int x, int y, int status) {
grid[x][y].setStatus(status);
}
public int getStatus(int x, int y) {
return grid[x][y].getStatus();
}
public boolean isWall(int x, int y) {
return grid[x][y].getStatus() == WALL;
}
public boolean isBox(int x, int y) {
return grid[x][y].getStatus() == BOX;
}
public boolean isBoxRaw(int x, int y) //allows you to check if a box is at a position before grid is set up
{
for (int i = 0; i < boxCount; i++) {
if (x == boxes[i].getX() && y == boxes[i].getY()) {
return true;
}
}
return false;
}
public boolean isDestination(int x, int y) {
return grid[x][y].getStatus() == DESTINATION;
}
public Box getBox(int x, int y) {
for (int i = 0; i < boxCount; i++) {
if (x == boxes[i].getX() && y == boxes[i].getY()) {
return boxes[i];
}
}
return null;
}
public void createBoxes() {
color = Randomizer.nextInt(6); //runs after each level
for (int i = 0; i < boxCount; i++) {
int x = Randomizer.nextInt(width - 4) + 2;
int y = Randomizer.nextInt(height - 4) + 2;
for (int j = 0; j < i; j++) {
while ((x == boxes[j].getX() && y == boxes[j].getY()) || (x == 2 && y == 2) || (x - 1 == boxes[j].getX()
&& y == boxes[j].getY()) || (x + 1 == boxes[j].getX() && y == boxes[j].getY())) {
x = Randomizer.nextInt(width - 4) + 2;
y = Randomizer.nextInt(height - 4) + 2;
}
}
boxes[i] = new Box(x, y, this);
}
}
public void createDestinations() {
for (int i = 0; i < boxCount; i++) {
int x = Randomizer.nextInt(width - 2) + 1;
int y = Randomizer.nextInt(height - 2) + 1;
for (int j = 0; j < i; j++) {
while (((x == destinations[j].getX() && y == destinations[j].getY())) || isBoxRaw(x, y)) {
x = Randomizer.nextInt(width - 2) + 1;
y = Randomizer.nextInt(height - 2) + 1;
}
}
destinations[i] = new Destination(x, y, this);
}
}
public void updateGrid() {
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
grid[j][i] = new Tile(GROUND, playerEmote);
if (j == 0 || j == width - 1 || i == 0 || i == height - 1) {
grid[j][i] = new Tile(WALL, color, playerEmote);
}
for (int k = 0; k < boxCount; k++) {
if (destinations[k].getX() == j && destinations[k].getY() == i) {
grid[j][i] = new Tile(DESTINATION, playerEmote);
}
}
if (player.getX() == j && player.getY() == i) {
grid[j][i] = new Tile(PLAYER, playerEmote);
}
for (int k = 0; k < boxCount; k++) {
if (boxes[k].getX() == j && boxes[k].getY() == i) {
if (boxes[k].onDestination()) {
grid[j][i] = new Tile(WALL, color, playerEmote);
} else {
grid[j][i] = new Tile(BOX, playerEmote);
}
}
}
}
}
}
public boolean hasWon() {
for (int i = 0; i < boxCount; i++) {
if (!destinations[i].hasBox(this)) {
return false;
}
}
return true;
}
public Tile[][] getGrid() {
return grid;
}
public Box[] getBoxes() {
return boxes;
}
public Destination[] getDestinations() {
return destinations;
}
public int getBoxCount() {
return boxCount;
}
public int getHeight() {
return height;
}
public int getWidth() {
return width;
}
public String toString() {
updateGrid();
String result = "";
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
result += grid[j][i];
}
result += "\n";
}
return result;
}
}
@@ -1,3 +1,5 @@
package me.polymarsdev.sokobot.objects;
public class Tile public class Tile
{ {
final int GROUND = 0; final int GROUND = 0;
@@ -0,0 +1,81 @@
package me.polymarsdev.sokobot.util;
import me.polymarsdev.sokobot.Bot;
import me.polymarsdev.sokobot.Game;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageChannel;
import net.dv8tion.jda.api.entities.User;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;
public class GameUtil {
private static final HashMap<Long, Game> games = new HashMap<>();
public static void setGame(long userId, Game game) {
games.put(userId, game);
}
public static boolean hasGame(long userId) {
return games.containsKey(userId);
}
public static Game getGame(long userId) {
return games.get(userId);
}
public static void removeGame(long userId) {
games.remove(userId);
}
public static void sendGameEmbed(MessageChannel channel, String level, String game, User user) {
EmbedBuilder embed = new EmbedBuilder();
embed.setTitle("Sokobot | Level " + level);
embed.setDescription(game);
embed.addField("Enter direction (``up``, ``down``, ``left``, ``right``/``wasd``), ``r`` to reset or ``mr`` to "
+ "recreate the map", "", false);
embed.addField("Player", user.getAsMention(), false);
channel.sendMessage(embed.build()).queue();
}
public static void updateGameEmbed(Message message, String level, String game, User user) {
EmbedBuilder embed = new EmbedBuilder();
embed.setTitle("Sokobot | Level " + level);
embed.setDescription(game);
embed.addField("Enter direction (``up``, ``down``, ``left``, ``right``/``wasd``), ``r`` to reset or ``mr`` to "
+ "recreate the map", "", false);
embed.addField("Player", user.getAsMention(), false);
message.editMessage(embed.build()).queue();
}
public static void sendWinEmbed(Guild guild, Message message, String level) {
EmbedBuilder embed = new EmbedBuilder();
embed.setTitle("Sokobot | You win!");
embed.setDescription(
"Type ``" + Bot.getPrefix(guild) + "continue`` to continue to Level " + level + " or ``" + Bot
.getPrefix(guild) + "stop`` to quit ");
embed.setFooter("You can also press any reaction to continue.");
message.editMessage(embed.build()).queue();
}
public static void runGameTimer() {
new Timer().scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
long now = System.currentTimeMillis();
for (long playerId : games.keySet()) {
Game game = games.get(playerId);
long timeDifference = now - game.lastAction;
if (timeDifference > 10 * 60 * 1000) {
game.stop();
GameUtil.removeGame(playerId);
}
}
}
}, 10 * 60 * 1000, 60 * 1000);
}
}
@@ -1,13 +1,11 @@
package me.polymarsdev.sokobot.util;
import java.util.*; import java.util.*;
public class Randomizer{ public class Randomizer{
public static Random theInstance = null; public static Random theInstance = null;
public Randomizer(){
}
public static Random getInstance(){ public static Random getInstance(){
if(theInstance == null){ if(theInstance == null){
theInstance = new Random(); theInstance = new Random();