From 50af52f2e478c19a9d24d525deb5cacae7a7162f Mon Sep 17 00:00:00 2001 From: Tyler Murphy Date: Wed, 5 Oct 2022 13:44:55 -0400 Subject: [PATCH] akinator --- pom.xml | 13 +- .../tylermurphy/ken/command/Responder.java | 10 +- .../net/tylermurphy/ken/command/Response.java | 8 + .../ken/command/game/Akinator.java | 293 ++++++++++++++++++ .../tylermurphy/ken/command/main/Help.java | 3 +- 5 files changed, 320 insertions(+), 7 deletions(-) create mode 100644 src/main/java/net/tylermurphy/ken/command/game/Akinator.java diff --git a/pom.xml b/pom.xml index e2b9978..4fc6d83 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ false - true + false true @@ -80,12 +80,12 @@ org.slf4j slf4j-api - 2.0.1 + 2.0.3 org.slf4j slf4j-simple - 2.0.1 + 2.0.3 com.sedmelluq @@ -110,7 +110,12 @@ org.json json - 20220320 + 20220924 + + + com.github.markozajc + akiwrapper + 1.5.2 org.mariadb.jdbc diff --git a/src/main/java/net/tylermurphy/ken/command/Responder.java b/src/main/java/net/tylermurphy/ken/command/Responder.java index 63aaf55..c8d3967 100644 --- a/src/main/java/net/tylermurphy/ken/command/Responder.java +++ b/src/main/java/net/tylermurphy/ken/command/Responder.java @@ -95,7 +95,8 @@ public class Responder extends ListenerAdapter { new UnMute(), new SetJoining(), new History(), - new Sticker() + new Sticker(), + new Akinator() }; Arrays.stream(objects).forEach(register::register); } @@ -194,13 +195,14 @@ public class Responder extends ListenerAdapter { try { event.deferEdit().queue(); Object temp = method.invoke(register.getHandle(method.getDeclaringClass().getName()), parameters); + if(temp == null) return; Response response = (Response) temp; edit(response, event.getHook()); } catch (Exception e) { EmbedBuilder builder = Ken.getInstance().getDefaultEmbed() .setColor(Color.RED) .setTitle(":x: **Error**") - .setDescription(e.getCause().getMessage()); + .setDescription(e.getCause() != null ? e.getCause().getMessage() : "An unknown error has occurred"); event.getHook().editOriginalEmbeds(builder.build()).queue(); event.getHook().editOriginalComponents(new ArrayList<>()).queue(); } @@ -242,6 +244,10 @@ public class Responder extends ListenerAdapter { // Invoke Method and Respond to User try { Object temp = method.invoke(register.getHandle(method.getDeclaringClass().getName()), parameters); + if(temp == null) { + event.reply("You dont have permission to use this!").setEphemeral(true).queue(); + return; + } Response response = (Response) temp; event.deferReply(response.isError()).queue(); reply(response, event.getHook()); diff --git a/src/main/java/net/tylermurphy/ken/command/Response.java b/src/main/java/net/tylermurphy/ken/command/Response.java index 763ccae..54148e0 100644 --- a/src/main/java/net/tylermurphy/ken/command/Response.java +++ b/src/main/java/net/tylermurphy/ken/command/Response.java @@ -22,6 +22,7 @@ public class Response { private String fileName; private boolean hidden = false; + private boolean removeComponents = false; public static Response error(String message){ Response response = new Response(); @@ -94,6 +95,11 @@ public class Response { return this; } + public Response setRemoveComponents(boolean removeComponents) { + this.removeComponents = removeComponents; + return this; + } + public boolean isError(){ return type.equals("error"); } @@ -110,6 +116,8 @@ public class Response { public boolean hasSelectMenu() { return menu != null; } + public boolean hasNoComponents() { return removeComponents; } + public Button[] getButtons() { Button[] arr = new Button[buttons.size()]; arr = buttons.toArray(arr); diff --git a/src/main/java/net/tylermurphy/ken/command/game/Akinator.java b/src/main/java/net/tylermurphy/ken/command/game/Akinator.java new file mode 100644 index 0000000..b86d99b --- /dev/null +++ b/src/main/java/net/tylermurphy/ken/command/game/Akinator.java @@ -0,0 +1,293 @@ +package net.tylermurphy.ken.command.game; + +import com.github.markozajc.akiwrapper.Akiwrapper; +import com.github.markozajc.akiwrapper.AkiwrapperBuilder; +import com.github.markozajc.akiwrapper.core.entities.Guess; +import com.github.markozajc.akiwrapper.core.entities.Identifiable; +import com.github.markozajc.akiwrapper.core.entities.Question; +import com.github.markozajc.akiwrapper.core.exceptions.ServerNotFoundException; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel; +import net.dv8tion.jda.api.requests.restaction.MessageCreateAction; +import net.tylermurphy.ken.Ken; +import net.tylermurphy.ken.command.Response; +import net.tylermurphy.ken.command.annotation.ButtonCallback; +import net.tylermurphy.ken.command.annotation.Command; +import net.tylermurphy.ken.util.Checks; + +import java.awt.*; +import java.util.*; +import java.util.List; + +public class Akinator { + + protected Map gameMappings = new HashMap<>(); + protected Map games = new HashMap<>(); + + @Command(name="akinator", description="Plays akinator") + public Response execute(Member sender, GuildMessageChannel channel) { + if(gameMappings.containsKey(sender.getIdLong())) { + games.remove(gameMappings.get(sender.getIdLong())); + gameMappings.remove(sender.getIdLong()); + } + new Thread(() -> { + try { + AkinatorGame game = new AkinatorGame(sender, this); + gameMappings.put(sender.getIdLong(), game.getGameId()); + games.put(game.getGameId(), game); + Response response = game.respond(null); + channel.sendMessageEmbeds(response.getEmbeds()).setActionRow(response.getButtons()).queue(); + } catch (ServerNotFoundException e) { + channel.sendMessage(":x: Failed to connect to Akinator").queue(); + } + }).start(); + return Response.success(":arrows_clockwise: Connecting to Akinator"); + } + + @ButtonCallback(name="akinator") + public Response onButton(String id, Message message, Member sender) { + + try { Checks.hasPagedEmbed(message); } + catch (RuntimeException e) { return Response.error(e.getMessage()); } + + int gameId = Integer.parseInt(message.getEmbeds().get(0).getFooter().getText().substring(9)); + + AkinatorGame game = games.get(gameId); + if(game == null) { + return Response.error("Game has ended"); + } + + if(game.getUserId() != sender.getIdLong()) { + return null; + } + + return game.respond(id); + + } + +} + +enum AkiStatus { + QUESTION, + GUESS, + CONTINUE +} + +class AkinatorGame { + + private static int lastGameId = 0; + + private final Akinator container; + private final Akiwrapper wrapper; + private final List sequence; + private final List guesses; + + private final String displayName; + private final long userId; + + private final int gameId; + + private AkiStatus status; + private int questionNumber; + private int sequenceIndex; + private int attemptedGuesses; + + + public AkinatorGame(Member member, Akinator container) throws ServerNotFoundException { + this.container = container; + this.wrapper = new AkiwrapperBuilder().build(); + this.sequence = new LinkedList<>(); + this.guesses = new LinkedList<>(); + + this.displayName = member.getEffectiveName(); + this.userId = member.getUser().getIdLong(); + + this.gameId = lastGameId++; + + this.status = AkiStatus.QUESTION; + this.questionNumber = 0; + this.sequenceIndex = -1; + this.attemptedGuesses = 0; + } + + public long getUserId() { + return userId; + } + + public int getGameId() { + return gameId; + } + + public Response respond(String label) { + if(label == null) return next(); + if(status == AkiStatus.GUESS) { + return respondGuess(label); + } else if (status == AkiStatus.QUESTION) { + return respondQuestion(label); + } else if (status == AkiStatus.CONTINUE) { + return respondContinue(label); + } + throw new RuntimeException("Failed to respond to user input"); + } + + private Response respondGuess(String label) { + switch (label) { + case "yes": + container.games.remove(gameId); + container.gameMappings.remove(userId); + return sendLoose(); + case "no": + status = AkiStatus.CONTINUE; + return sendContinue(); + case "back": + return back(); + } + throw new RuntimeException("Failed to respond to user input"); + } + + private Response respondContinue(String label) { + switch (label) { + case "yes": + return next(); + case "no": + container.games.remove(gameId); + container.gameMappings.remove(userId); + return sendWin(); + case "back": + return back(); + } + throw new RuntimeException("Failed to respond to user input"); + } + + private Response respondQuestion(String label) { + switch (label) { + case "yes": + wrapper.answer(Akiwrapper.Answer.YES); + break; + case "no": + wrapper.answer(Akiwrapper.Answer.NO); + break; + case "idk": + wrapper.answer(Akiwrapper.Answer.DONT_KNOW); + break; + case "back": + return back(); + } + return next(); + } + + private Response next() { + sequenceIndex++; + Optional guess = wrapper.getGuesses().stream().filter(g -> g.getProbability() > .85 && !guesses.contains(g.getId())).reduce((first, second) -> second); + if(guess.isPresent()) { + status = AkiStatus.GUESS; + sequence.add(guess.get()); + attemptedGuesses++; + return sendGuess(guess.get()); + } else { + status = AkiStatus.QUESTION; + Question question = wrapper.getQuestion(); + sequence.add(question); + questionNumber++; + if(question == null) { + // remove game + return sendWin(); + } + return sendQuestion(question); + } + } + + private Response back() { + sequenceIndex--; + Identifiable current = sequence.get(sequenceIndex+1); + if(current instanceof Guess) { + attemptedGuesses--; + guesses.remove(guesses.size() - 1); + sequence.remove(current); + } else if(current instanceof Question) { + questionNumber--; + sequence.remove(current); + } + Identifiable previous = sequence.get(sequenceIndex); + if(previous instanceof Guess guess) { + status = AkiStatus.GUESS; + return sendGuess(guess); + } else if(previous instanceof Question question){ + status = AkiStatus.QUESTION; + return sendQuestion(question); + } + throw new RuntimeException("Failed to go back in sequence"); + } + + private Response sendGuess(Guess guess) { + EmbedBuilder builder = Ken.getInstance().getDefaultEmbed(); + builder.setAuthor("Akinator: " + displayName); + builder.setTitle("Is this your character?"); + builder.appendDescription("**" + guess.getName() + "**\n" + guess.getDescription()); + builder.setImage(guess.getImage().toExternalForm()); + builder.setFooter("Game id: " + gameId); + Response response = Response.success(builder.build()) + .addPrimaryButton("akinator", "yes", "Yes") + .addPrimaryButton("akinator", "no", "No"); + if(sequenceIndex > 0) + response.addSecondaryButton("akinator", "back", "Back"); + return response; + } + + private Response sendQuestion(Question question) { + EmbedBuilder builder = Ken.getInstance().getDefaultEmbed(); + builder.setAuthor("Akinator: " + displayName); + builder.setTitle("Question " + questionNumber); + builder.appendDescription(question.getQuestion()); + builder.setFooter("Game id: " + gameId); + Response response = Response.success(builder.build()) + .addPrimaryButton("akinator", "yes", "Yes") + .addPrimaryButton("akinator", "no", "No") + .addPrimaryButton("akinator", "idk", "IDK"); + if(sequenceIndex > 0) + response.addSecondaryButton("akinator", "back", "Back"); + return response; + } + + private Response sendContinue() { + EmbedBuilder builder = Ken.getInstance().getDefaultEmbed(); + builder.setAuthor("Akinator: " + displayName); + builder.setTitle("Continue?"); + builder.appendDescription("Akinator guessed incorrectly, would you like to continue?"); + builder.setFooter("Game id: " + gameId); + Response response = Response.success(builder.build()) + .addPrimaryButton("akinator", "yes", "Yes") + .addPrimaryButton("akinator", "no", "No"); + if(sequenceIndex > 0) + response.addSecondaryButton("akinator", "back", "Back"); + return response; + } + + private Response sendWin() { + EmbedBuilder builder = Ken.getInstance().getDefaultEmbed(); + builder.setAuthor("Akinator: " + displayName); + builder.setTitle("You win!"); + builder.appendDescription("Total guesses: " + attemptedGuesses + "\n"); + builder.appendDescription("Total questions: " + questionNumber); + builder.setFooter("Game id: " + gameId); + builder.setColor(new Color(130,255,130)); + return Response.success(builder.build()).setRemoveComponents(true); + } + + private Response sendLoose() { + Guess guess = (Guess) sequence.get(sequenceIndex); + EmbedBuilder builder = Ken.getInstance().getDefaultEmbed(); + builder.setAuthor("Akinator: " + displayName); + builder.setTitle("You loose :("); + builder.appendDescription("Total guesses: " + attemptedGuesses + "\n"); + builder.appendDescription("Total questions: " + questionNumber + "\n"); + builder.appendDescription("Character Guessed: " + guess.getName()); + builder.setImage(guess.getImage().toExternalForm()); + builder.setFooter("Game id: " + gameId); + builder.setColor(new Color(255,130,130)); + return Response.success(builder.build()).setRemoveComponents(true); + } + +} diff --git a/src/main/java/net/tylermurphy/ken/command/main/Help.java b/src/main/java/net/tylermurphy/ken/command/main/Help.java index 80daa3c..b881a29 100644 --- a/src/main/java/net/tylermurphy/ken/command/main/Help.java +++ b/src/main/java/net/tylermurphy/ken/command/main/Help.java @@ -88,7 +88,8 @@ public class Help { .appendDescription("**/claim** Claim moneys every 24h\n") .appendDescription("**/money** Check how much moneys you have\n") .appendDescription("**/roulette ** Gamble on roulette\n") - .appendDescription("**/slots ** Gamble on slots\n"), + .appendDescription("**/slots ** Gamble on slots\n") + .appendDescription("**/akinator** Play akinator\n"), Ken.getInstance().getDefaultEmbed() .setAuthor("Command List") .setTitle(":desktop: **Social Commands**")