From 492d648015af19e607f96c4739d95bb5ef982bd1 Mon Sep 17 00:00:00 2001 From: EJHacks Date: Mon, 9 Mar 2026 13:21:26 -0400 Subject: [PATCH 1/7] feat: add QuizQuestion and TriviaManager classes for Java trivia game --- .../tjplays/games/game2048/QuizQuestion.java | 27 ++++++++ .../tjplays/games/game2048/TriviaManager.java | 65 +++++++++++++++++++ .../services/chatgpt/ChatGptService.java | 1 - 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/togetherjava/tjplays/games/game2048/QuizQuestion.java create mode 100644 app/src/main/java/com/togetherjava/tjplays/games/game2048/TriviaManager.java diff --git a/app/src/main/java/com/togetherjava/tjplays/games/game2048/QuizQuestion.java b/app/src/main/java/com/togetherjava/tjplays/games/game2048/QuizQuestion.java new file mode 100644 index 0000000..20bb41e --- /dev/null +++ b/app/src/main/java/com/togetherjava/tjplays/games/game2048/QuizQuestion.java @@ -0,0 +1,27 @@ +package com.togetherjava.tjplays.games.game2048; + +import java.util.List; + +public class QuizQuestion { + private final String question; + private final List choices; + private final int correctIndex; + + public QuizQuestion(String question, List choices, int correctIndex) { + this.question = question; + this.choices = choices; + this.correctIndex = correctIndex; + } + + public String getQuestion() { + return question; + } + + public List getChoices() { + return choices; + } + + public int getCorrectIndex() { + return correctIndex; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/togetherjava/tjplays/games/game2048/TriviaManager.java b/app/src/main/java/com/togetherjava/tjplays/games/game2048/TriviaManager.java new file mode 100644 index 0000000..46694bd --- /dev/null +++ b/app/src/main/java/com/togetherjava/tjplays/games/game2048/TriviaManager.java @@ -0,0 +1,65 @@ +package com.togetherjava.tjplays.games.game2048; + +import com.togetherjava.tjplays.services.chatgpt.ChatGptService; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Random; + + +public class TriviaManager { + private final ChatGptService gpt; + private final Random random = new Random(); + private final ObjectMapper mapper = new ObjectMapper(); + + public TriviaManager(String openAiKey) { + this.gpt = new ChatGptService(openAiKey); + } + + + public Optional fetchRandomQuestion() { + String prompt = "Provide five different Java trivia questions in JSON array;" + + " each element should look like {\"question\":...,\"choices\":[...],\"correct\":}"; + Optional raw = gpt.ask(prompt, "java quiz"); + if (raw.isEmpty()) { + return Optional.empty(); + } + List list = parseArray(raw.get()); + if (list.isEmpty()) { + return Optional.empty(); + } + return Optional.of(list.get(random.nextInt(list.size()))); + } + + private List parseArray(String raw) { + try { + JsonNode arr = mapper.readTree(raw); + List questions = new ArrayList<>(); + if (arr.isArray()) { + for (JsonNode node : arr) { + parseNode(node).ifPresent(questions::add); + } + } + return questions; + } catch (Exception e) { + return List.of(); + } + } + + private Optional parseNode(JsonNode node) { + try { + String q = node.get("question").asText(); + List choices = new ArrayList<>(); + for (JsonNode c : node.get("choices")) { + choices.add(c.asText()); + } + int correct = node.get("correct").asInt(); + return Optional.of(new QuizQuestion(q, choices, correct)); + } catch (Exception e) { + return Optional.empty(); + } + } +} diff --git a/app/src/main/java/com/togetherjava/tjplays/services/chatgpt/ChatGptService.java b/app/src/main/java/com/togetherjava/tjplays/services/chatgpt/ChatGptService.java index e697dd7..e3025e4 100644 --- a/app/src/main/java/com/togetherjava/tjplays/services/chatgpt/ChatGptService.java +++ b/app/src/main/java/com/togetherjava/tjplays/services/chatgpt/ChatGptService.java @@ -11,7 +11,6 @@ import java.time.Duration; import java.util.List; -import java.util.Objects; import java.util.Optional; /** From 06d3d1c948664d4085f24fab278a90779eeaee01 Mon Sep 17 00:00:00 2001 From: EJHacks Date: Mon, 9 Mar 2026 17:06:16 -0400 Subject: [PATCH 2/7] feat: add javaquiz class to implement trivia game functionality --- .gitignore | 3 +++ app/build.gradle | 7 +++++ .../tjplays/games/game2048/javaquiz.java | 27 +++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 app/src/main/java/com/togetherjava/tjplays/games/game2048/javaquiz.java diff --git a/.gitignore b/.gitignore index 839c825..ca7d1c1 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,6 @@ build # Credentials bot-config.properties + +# Local Gradle settings (machine-specific JDK path) +gradle.properties diff --git a/app/build.gradle b/app/build.gradle index 793ce27..82ec6e6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,6 +42,13 @@ shadowJar { archiveVersion.set('') } +tasks.register('runQuiz', JavaExec) { + group = 'application' + description = 'Launch the javaquiz sample program' + classpath = sourceSets.main.runtimeClasspath + mainClass.set('com.togetherjava.tjplays.games.game2048.javaquiz') +} + dependencies { implementation 'net.dv8tion:JDA:5.0.0-beta.21' diff --git a/app/src/main/java/com/togetherjava/tjplays/games/game2048/javaquiz.java b/app/src/main/java/com/togetherjava/tjplays/games/game2048/javaquiz.java new file mode 100644 index 0000000..96b2031 --- /dev/null +++ b/app/src/main/java/com/togetherjava/tjplays/games/game2048/javaquiz.java @@ -0,0 +1,27 @@ +package com.togetherjava.tjplays.games.game2048; + +import java.util.Optional; + +public class javaquiz { + public static void main(String[] args) { + String key = System.getenv("OPENAI_API_KEY"); + if (key == null || key.isBlank()) { + System.err.println("OPENAI_API_KEY is not set. Set it as an environment variable."); + return; + } + + TriviaManager trivia = new TriviaManager(key); + Optional question = trivia.fetchRandomQuestion(); + + question.ifPresentOrElse( + q -> { + System.out.println("Question: " + q.getQuestion()); + var choices = q.getChoices(); + for (int i = 0; i < choices.size(); i++) { + System.out.println(" " + (i + 1) + ") " + choices.get(i)); + } + System.out.println("Correct answer: " + (q.getCorrectIndex() + 1)); + }, + () -> System.err.println("No response from ChatGPT.")); + } +} From 7d966ccedfcd7b28428448ec9e2c185fb609b370 Mon Sep 17 00:00:00 2001 From: EJHacks Date: Mon, 9 Mar 2026 17:55:02 -0400 Subject: [PATCH 3/7] feat: Add /javaquiz Discord slash command with ChatGPT-powered trivia - Added JavaQuizCommand: Discord slash command that fetches a random Java trivia question via ChatGPT and presents it with answer buttons - Added TriviaManager: service that prompts ChatGPT for quiz questions and parses the JSON response - Added QuizQuestion: data model for trivia questions - Added javaquiz.java: standalone entry point for local testing - Added runQuiz Gradle task for convenience testing - Registered /javaquiz command in Bot.java How to play: A server member types /javaquiz in Discord. The bot displays a random Java trivia question with 4 answer buttons. The user clicks their answer and the bot reveals whether it was correct. Everything should work please let me know if I am missing anything Best -EJ --- .../java/com/togetherjava/tjplays/Bot.java | 3 +- .../listeners/commands/JavaQuizCommand.java | 86 +++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/togetherjava/tjplays/listeners/commands/JavaQuizCommand.java diff --git a/app/src/main/java/com/togetherjava/tjplays/Bot.java b/app/src/main/java/com/togetherjava/tjplays/Bot.java index 292172d..f8968d3 100644 --- a/app/src/main/java/com/togetherjava/tjplays/Bot.java +++ b/app/src/main/java/com/togetherjava/tjplays/Bot.java @@ -34,7 +34,8 @@ private static JDA createBot(Config config) { private static List getCommands(Config config) { return List.of( new PingCommand(), - new Game2048Command() + new Game2048Command(), + new JavaQuizCommand(config.openAIApiKey()) ); } } diff --git a/app/src/main/java/com/togetherjava/tjplays/listeners/commands/JavaQuizCommand.java b/app/src/main/java/com/togetherjava/tjplays/listeners/commands/JavaQuizCommand.java new file mode 100644 index 0000000..ecf3644 --- /dev/null +++ b/app/src/main/java/com/togetherjava/tjplays/listeners/commands/JavaQuizCommand.java @@ -0,0 +1,86 @@ +package com.togetherjava.tjplays.listeners.commands; + +import com.togetherjava.tjplays.games.game2048.QuizQuestion; +import com.togetherjava.tjplays.games.game2048.TriviaManager; + +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.build.Commands; +import net.dv8tion.jda.api.interactions.components.buttons.Button; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +public final class JavaQuizCommand extends SlashCommand { + private static final String COMMAND_NAME = "javaquiz"; + + private final TriviaManager triviaManager; + private final ConcurrentHashMap activeQuestions = new ConcurrentHashMap<>(); + + public JavaQuizCommand(String openAiKey) { + super(Commands.slash(COMMAND_NAME, "Get a random Java trivia question")); + this.triviaManager = new TriviaManager(openAiKey); + } + + @Override + public void onSlashCommand(SlashCommandInteractionEvent event) { + event.deferReply().queue(); + + Optional question = triviaManager.fetchRandomQuestion(); + if (question.isEmpty()) { + event.getHook().editOriginal("Could not fetch a quiz question. Try again later.").queue(); + return; + } + + QuizQuestion q = question.get(); + List choices = q.getChoices(); + + StringBuilder sb = new StringBuilder(); + sb.append("**Java Quiz**\n\n"); + sb.append(q.getQuestion()).append("\n\n"); + for (int i = 0; i < choices.size(); i++) { + sb.append("`").append(i + 1).append(")` ").append(choices.get(i)).append("\n"); + } + + String messageId = event.getHook().editOriginal(sb.toString()) + .setActionRow( + Button.primary(COMMAND_NAME + "-1-" + event.getUser().getId(), "1"), + Button.primary(COMMAND_NAME + "-2-" + event.getUser().getId(), "2"), + Button.primary(COMMAND_NAME + "-3-" + event.getUser().getId(), "3"), + Button.primary(COMMAND_NAME + "-4-" + event.getUser().getId(), "4") + ) + .complete() + .getId(); + + activeQuestions.put(messageId, q); + } + + @Override + public void onButtonInteraction(ButtonInteractionEvent event) { + String buttonId = event.getButton().getId(); + if (buttonId == null || !buttonId.startsWith(COMMAND_NAME)) return; + + if (!buttonId.contains(event.getUser().getId())) { + event.reply("This isn't your quiz!").setEphemeral(true).queue(); + return; + } + + QuizQuestion q = activeQuestions.remove(event.getMessageId()); + if (q == null) { + event.reply("This quiz has already been answered.").setEphemeral(true).queue(); + return; + } + + int chosen = Character.getNumericValue(buttonId.charAt(COMMAND_NAME.length() + 1)) - 1; + boolean correct = chosen == q.getCorrectIndex(); + + String result = correct + ? "Correct! The answer is: **" + q.getChoices().get(q.getCorrectIndex()) + "**" + : "Wrong! The correct answer was: **" + q.getChoices().get(q.getCorrectIndex()) + "**"; + + event.editMessage(event.getMessage().getContentRaw() + "\n\n" + result) + .setActionRow() + .queue(); + } +} From 73890eda7c500bf698a492e6c1acf6ec4793f899 Mon Sep 17 00:00:00 2001 From: EJHacks Date: Mon, 9 Mar 2026 19:39:06 -0400 Subject: [PATCH 4/7] refactor: Simplify QuizQuestion class to use record and update TriviaManager for single question retrieval chore: Remove unused javaquiz main class and add gradle.properties for JDK configuration fix: Improve button interaction handling in JavaQuizCommand MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I addressed all the review feedback and pushed the changes. I wasn't able to provide screenshots because the bot hangs on startup — it appears the shared OpenAI API key from the pinned message may be expired or invalid. The bot connects to Discord fine (I confirmed it joins the server and receives slash commands), but the ChatGPT API call in ChatGptService times out during initialization. Could someone verify the key is still active? --- .gitignore | 3 -- app/build.gradle | 7 --- .../tjplays/games/game2048/QuizQuestion.java | 20 ++++----- .../tjplays/games/game2048/TriviaManager.java | 43 ++----------------- .../tjplays/games/game2048/javaquiz.java | 27 ------------ .../listeners/commands/JavaQuizCommand.java | 13 +++++- gradle.properties | 1 + 7 files changed, 26 insertions(+), 88 deletions(-) delete mode 100644 app/src/main/java/com/togetherjava/tjplays/games/game2048/javaquiz.java create mode 100644 gradle.properties diff --git a/.gitignore b/.gitignore index ca7d1c1..839c825 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,3 @@ build # Credentials bot-config.properties - -# Local Gradle settings (machine-specific JDK path) -gradle.properties diff --git a/app/build.gradle b/app/build.gradle index 82ec6e6..793ce27 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,13 +42,6 @@ shadowJar { archiveVersion.set('') } -tasks.register('runQuiz', JavaExec) { - group = 'application' - description = 'Launch the javaquiz sample program' - classpath = sourceSets.main.runtimeClasspath - mainClass.set('com.togetherjava.tjplays.games.game2048.javaquiz') -} - dependencies { implementation 'net.dv8tion:JDA:5.0.0-beta.21' diff --git a/app/src/main/java/com/togetherjava/tjplays/games/game2048/QuizQuestion.java b/app/src/main/java/com/togetherjava/tjplays/games/game2048/QuizQuestion.java index 20bb41e..d2c2624 100644 --- a/app/src/main/java/com/togetherjava/tjplays/games/game2048/QuizQuestion.java +++ b/app/src/main/java/com/togetherjava/tjplays/games/game2048/QuizQuestion.java @@ -1,17 +1,17 @@ package com.togetherjava.tjplays.games.game2048; -import java.util.List; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; -public class QuizQuestion { - private final String question; - private final List choices; - private final int correctIndex; +import java.util.List; - public QuizQuestion(String question, List choices, int correctIndex) { - this.question = question; - this.choices = choices; - this.correctIndex = correctIndex; - } +public record QuizQuestion( + @JsonProperty("question") String question, + @JsonProperty("choices") List choices, + @JsonProperty("correct") int correctIndex +) { + @JsonCreator + public QuizQuestion {} public String getQuestion() { return question; diff --git a/app/src/main/java/com/togetherjava/tjplays/games/game2048/TriviaManager.java b/app/src/main/java/com/togetherjava/tjplays/games/game2048/TriviaManager.java index 46694bd..099f837 100644 --- a/app/src/main/java/com/togetherjava/tjplays/games/game2048/TriviaManager.java +++ b/app/src/main/java/com/togetherjava/tjplays/games/game2048/TriviaManager.java @@ -1,63 +1,28 @@ package com.togetherjava.tjplays.games.game2048; import com.togetherjava.tjplays.services.chatgpt.ChatGptService; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.ArrayList; -import java.util.List; import java.util.Optional; -import java.util.Random; - public class TriviaManager { private final ChatGptService gpt; - private final Random random = new Random(); private final ObjectMapper mapper = new ObjectMapper(); public TriviaManager(String openAiKey) { this.gpt = new ChatGptService(openAiKey); } - public Optional fetchRandomQuestion() { - String prompt = "Provide five different Java trivia questions in JSON array;" - + " each element should look like {\"question\":...,\"choices\":[...],\"correct\":}"; + String prompt = "Provide one Java trivia question as a JSON object like" + + " {\"question\":\"...\",\"choices\":[\"A\",\"B\",\"C\",\"D\"],\"correct\":}"; Optional raw = gpt.ask(prompt, "java quiz"); if (raw.isEmpty()) { return Optional.empty(); } - List list = parseArray(raw.get()); - if (list.isEmpty()) { - return Optional.empty(); - } - return Optional.of(list.get(random.nextInt(list.size()))); - } - - private List parseArray(String raw) { - try { - JsonNode arr = mapper.readTree(raw); - List questions = new ArrayList<>(); - if (arr.isArray()) { - for (JsonNode node : arr) { - parseNode(node).ifPresent(questions::add); - } - } - return questions; - } catch (Exception e) { - return List.of(); - } - } - - private Optional parseNode(JsonNode node) { try { - String q = node.get("question").asText(); - List choices = new ArrayList<>(); - for (JsonNode c : node.get("choices")) { - choices.add(c.asText()); - } - int correct = node.get("correct").asInt(); - return Optional.of(new QuizQuestion(q, choices, correct)); + QuizQuestion question = mapper.readValue(raw.get(), QuizQuestion.class); + return Optional.of(question); } catch (Exception e) { return Optional.empty(); } diff --git a/app/src/main/java/com/togetherjava/tjplays/games/game2048/javaquiz.java b/app/src/main/java/com/togetherjava/tjplays/games/game2048/javaquiz.java deleted file mode 100644 index 96b2031..0000000 --- a/app/src/main/java/com/togetherjava/tjplays/games/game2048/javaquiz.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.togetherjava.tjplays.games.game2048; - -import java.util.Optional; - -public class javaquiz { - public static void main(String[] args) { - String key = System.getenv("OPENAI_API_KEY"); - if (key == null || key.isBlank()) { - System.err.println("OPENAI_API_KEY is not set. Set it as an environment variable."); - return; - } - - TriviaManager trivia = new TriviaManager(key); - Optional question = trivia.fetchRandomQuestion(); - - question.ifPresentOrElse( - q -> { - System.out.println("Question: " + q.getQuestion()); - var choices = q.getChoices(); - for (int i = 0; i < choices.size(); i++) { - System.out.println(" " + (i + 1) + ") " + choices.get(i)); - } - System.out.println("Correct answer: " + (q.getCorrectIndex() + 1)); - }, - () -> System.err.println("No response from ChatGPT.")); - } -} diff --git a/app/src/main/java/com/togetherjava/tjplays/listeners/commands/JavaQuizCommand.java b/app/src/main/java/com/togetherjava/tjplays/listeners/commands/JavaQuizCommand.java index ecf3644..566da1b 100644 --- a/app/src/main/java/com/togetherjava/tjplays/listeners/commands/JavaQuizCommand.java +++ b/app/src/main/java/com/togetherjava/tjplays/listeners/commands/JavaQuizCommand.java @@ -59,7 +59,9 @@ public void onSlashCommand(SlashCommandInteractionEvent event) { @Override public void onButtonInteraction(ButtonInteractionEvent event) { String buttonId = event.getButton().getId(); - if (buttonId == null || !buttonId.startsWith(COMMAND_NAME)) return; + if (buttonId == null || !buttonId.startsWith(COMMAND_NAME)) { + return; + } if (!buttonId.contains(event.getUser().getId())) { event.reply("This isn't your quiz!").setEphemeral(true).queue(); @@ -79,8 +81,15 @@ public void onButtonInteraction(ButtonInteractionEvent event) { ? "Correct! The answer is: **" + q.getChoices().get(q.getCorrectIndex()) + "**" : "Wrong! The correct answer was: **" + q.getChoices().get(q.getCorrectIndex()) + "**"; + String userId = event.getUser().getId(); + event.editMessage(event.getMessage().getContentRaw() + "\n\n" + result) - .setActionRow() + .setActionRow( + Button.primary(COMMAND_NAME + "-1-" + userId, "1").asDisabled(), + Button.primary(COMMAND_NAME + "-2-" + userId, "2").asDisabled(), + Button.primary(COMMAND_NAME + "-3-" + userId, "3").asDisabled(), + Button.primary(COMMAND_NAME + "-4-" + userId, "4").asDisabled() + ) .queue(); } } diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..de75298 --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +org.gradle.java.home=C:/Users/dudem/.gradle/jdks/eclipse_adoptium-21-amd64-windows/jdk-21.0.9+10 From 6e09485593c3a71225b0b58d3b28809bbc6c4a45 Mon Sep 17 00:00:00 2001 From: EJHacks Date: Tue, 10 Mar 2026 08:12:50 -0400 Subject: [PATCH 5/7] accidental commit --- gradle.properties | 1 - 1 file changed, 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index de75298..e69de29 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +0,0 @@ -org.gradle.java.home=C:/Users/dudem/.gradle/jdks/eclipse_adoptium-21-amd64-windows/jdk-21.0.9+10 From ab3d603ccd1dda4fa95bb417e1420705d6a6d915 Mon Sep 17 00:00:00 2001 From: EJHacks Date: Tue, 10 Mar 2026 12:20:07 -0400 Subject: [PATCH 6/7] refactor: Rename correctIndex to correctAnswerIndex in QuizQuestion and update JavaQuizCommand accordingly --- .../tjplays/games/game2048/QuizQuestion.java | 18 +++++--- .../listeners/commands/JavaQuizCommand.java | 45 ++++++++++++------- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/togetherjava/tjplays/games/game2048/QuizQuestion.java b/app/src/main/java/com/togetherjava/tjplays/games/game2048/QuizQuestion.java index d2c2624..90436fe 100644 --- a/app/src/main/java/com/togetherjava/tjplays/games/game2048/QuizQuestion.java +++ b/app/src/main/java/com/togetherjava/tjplays/games/game2048/QuizQuestion.java @@ -5,10 +5,15 @@ import java.util.List; +/** + * Represents a single quiz question for Java trivia. + * Used by TriviaManager and JavaQuizCommand to display questions and validate answers. + * Future contributors can use this for any quiz/trivia feature needing a question, choices, and answer index. + */ public record QuizQuestion( - @JsonProperty("question") String question, - @JsonProperty("choices") List choices, - @JsonProperty("correct") int correctIndex + @JsonProperty("question") String question, + @JsonProperty("choices") List choices, + @JsonProperty("correct") int correctAnswerIndex ) { @JsonCreator public QuizQuestion {} @@ -21,7 +26,10 @@ public List getChoices() { return choices; } - public int getCorrectIndex() { - return correctIndex; + /** + * Returns the index of the correct answer in the choices list. + */ + public int getCorrectAnswerIndex() { + return correctAnswerIndex; } } \ No newline at end of file diff --git a/app/src/main/java/com/togetherjava/tjplays/listeners/commands/JavaQuizCommand.java b/app/src/main/java/com/togetherjava/tjplays/listeners/commands/JavaQuizCommand.java index 566da1b..4ec6014 100644 --- a/app/src/main/java/com/togetherjava/tjplays/listeners/commands/JavaQuizCommand.java +++ b/app/src/main/java/com/togetherjava/tjplays/listeners/commands/JavaQuizCommand.java @@ -16,7 +16,10 @@ public final class JavaQuizCommand extends SlashCommand { private static final String COMMAND_NAME = "javaquiz"; private final TriviaManager triviaManager; + // messageId -> QuizQuestion private final ConcurrentHashMap activeQuestions = new ConcurrentHashMap<>(); + // userId -> messageId + private final ConcurrentHashMap userActiveQuiz = new ConcurrentHashMap<>(); public JavaQuizCommand(String openAiKey) { super(Commands.slash(COMMAND_NAME, "Get a random Java trivia question")); @@ -25,6 +28,13 @@ public JavaQuizCommand(String openAiKey) { @Override public void onSlashCommand(SlashCommandInteractionEvent event) { + String userId = event.getUser().getId(); + // Prevent new quiz if user has an active one + if (userActiveQuiz.containsKey(userId)) { + event.reply("You already have an active quiz. Please answer it before starting a new one.").setEphemeral(true).queue(); + return; + } + event.deferReply().queue(); Optional question = triviaManager.fetchRandomQuestion(); @@ -33,27 +43,28 @@ public void onSlashCommand(SlashCommandInteractionEvent event) { return; } - QuizQuestion q = question.get(); - List choices = q.getChoices(); + QuizQuestion quizQuestion = question.get(); + List choices = quizQuestion.getChoices(); StringBuilder sb = new StringBuilder(); sb.append("**Java Quiz**\n\n"); - sb.append(q.getQuestion()).append("\n\n"); + sb.append(quizQuestion.getQuestion()).append("\n\n"); for (int i = 0; i < choices.size(); i++) { - sb.append("`").append(i + 1).append(")` ").append(choices.get(i)).append("\n"); + sb.append("`").append(i + 1).append("`) ").append(choices.get(i)).append("\n"); } String messageId = event.getHook().editOriginal(sb.toString()) .setActionRow( - Button.primary(COMMAND_NAME + "-1-" + event.getUser().getId(), "1"), - Button.primary(COMMAND_NAME + "-2-" + event.getUser().getId(), "2"), - Button.primary(COMMAND_NAME + "-3-" + event.getUser().getId(), "3"), - Button.primary(COMMAND_NAME + "-4-" + event.getUser().getId(), "4") + Button.primary(COMMAND_NAME + "-1-" + userId, "1"), + Button.primary(COMMAND_NAME + "-2-" + userId, "2"), + Button.primary(COMMAND_NAME + "-3-" + userId, "3"), + Button.primary(COMMAND_NAME + "-4-" + userId, "4") ) .complete() .getId(); - activeQuestions.put(messageId, q); + activeQuestions.put(messageId, quizQuestion); + userActiveQuiz.put(userId, messageId); } @Override @@ -68,20 +79,22 @@ public void onButtonInteraction(ButtonInteractionEvent event) { return; } - QuizQuestion q = activeQuestions.remove(event.getMessageId()); - if (q == null) { + String userId = event.getUser().getId(); + QuizQuestion quizQuestion = activeQuestions.remove(event.getMessageId()); + // Remove user's active quiz + userActiveQuiz.remove(userId); + if (quizQuestion == null) { event.reply("This quiz has already been answered.").setEphemeral(true).queue(); return; } int chosen = Character.getNumericValue(buttonId.charAt(COMMAND_NAME.length() + 1)) - 1; - boolean correct = chosen == q.getCorrectIndex(); + boolean correct = chosen == quizQuestion.getCorrectAnswerIndex(); + String correctAnswer = quizQuestion.getChoices().get(quizQuestion.getCorrectAnswerIndex()); String result = correct - ? "Correct! The answer is: **" + q.getChoices().get(q.getCorrectIndex()) + "**" - : "Wrong! The correct answer was: **" + q.getChoices().get(q.getCorrectIndex()) + "**"; - - String userId = event.getUser().getId(); + ? "Correct! The answer is: **" + correctAnswer + "**" + : "Wrong! The correct answer was: **" + correctAnswer + "**"; event.editMessage(event.getMessage().getContentRaw() + "\n\n" + result) .setActionRow( From 607ec272a1e68b7b99db7d507d73042f1e1d997d Mon Sep 17 00:00:00 2001 From: EJHacks Date: Tue, 10 Mar 2026 14:39:55 -0400 Subject: [PATCH 7/7] feat: Implement TriviaManager and QuizQuestion for Java trivia functionality. Created a new folder for the package let me know if this works. --- .../java/com/togetherjava/tjplays/Bot.java | 7 +++-- .../listeners/commands/JavaQuizCommand.java | 31 ++++++++++++------- .../game2048 => trivia}/QuizQuestion.java | 5 ++- .../game2048 => trivia}/TriviaManager.java | 16 ++++++---- 4 files changed, 37 insertions(+), 22 deletions(-) rename app/src/main/java/com/togetherjava/tjplays/{games/game2048 => trivia}/QuizQuestion.java (94%) rename app/src/main/java/com/togetherjava/tjplays/{games/game2048 => trivia}/TriviaManager.java (63%) diff --git a/app/src/main/java/com/togetherjava/tjplays/Bot.java b/app/src/main/java/com/togetherjava/tjplays/Bot.java index f8968d3..826486e 100644 --- a/app/src/main/java/com/togetherjava/tjplays/Bot.java +++ b/app/src/main/java/com/togetherjava/tjplays/Bot.java @@ -4,6 +4,7 @@ import java.nio.file.Path; import java.util.List; import com.togetherjava.tjplays.listeners.commands.*; +import com.togetherjava.tjplays.trivia.TriviaManager; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDABuilder; @@ -32,10 +33,12 @@ private static JDA createBot(Config config) { } private static List getCommands(Config config) { - return List.of( + // Construct JavaQuizCommand with TriviaManager and ChatGptService + TriviaManager triviaManager = new TriviaManager(new com.togetherjava.tjplays.services.chatgpt.ChatGptService(config.openAIApiKey())); + return java.util.Arrays.asList( new PingCommand(), new Game2048Command(), - new JavaQuizCommand(config.openAIApiKey()) + new JavaQuizCommand(triviaManager) ); } } diff --git a/app/src/main/java/com/togetherjava/tjplays/listeners/commands/JavaQuizCommand.java b/app/src/main/java/com/togetherjava/tjplays/listeners/commands/JavaQuizCommand.java index 4ec6014..ba8dd5a 100644 --- a/app/src/main/java/com/togetherjava/tjplays/listeners/commands/JavaQuizCommand.java +++ b/app/src/main/java/com/togetherjava/tjplays/listeners/commands/JavaQuizCommand.java @@ -1,7 +1,7 @@ package com.togetherjava.tjplays.listeners.commands; -import com.togetherjava.tjplays.games.game2048.QuizQuestion; -import com.togetherjava.tjplays.games.game2048.TriviaManager; +import com.togetherjava.tjplays.trivia.QuizQuestion; +import com.togetherjava.tjplays.trivia.TriviaManager; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; @@ -12,6 +12,11 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +/** + * Handles the /javaquiz command, presenting Java trivia questions to users. + * Uses TriviaManager to fetch questions and manages user quiz state. + * Future contributors can extend this for more quiz features. + */ public final class JavaQuizCommand extends SlashCommand { private static final String COMMAND_NAME = "javaquiz"; @@ -21,9 +26,13 @@ public final class JavaQuizCommand extends SlashCommand { // userId -> messageId private final ConcurrentHashMap userActiveQuiz = new ConcurrentHashMap<>(); - public JavaQuizCommand(String openAiKey) { + /** + * Constructs a JavaQuizCommand with a TriviaManager instance. + * @param triviaManager the TriviaManager to use for fetching questions + */ + public JavaQuizCommand(TriviaManager triviaManager) { super(Commands.slash(COMMAND_NAME, "Get a random Java trivia question")); - this.triviaManager = new TriviaManager(openAiKey); + this.triviaManager = triviaManager; } @Override @@ -46,14 +55,14 @@ public void onSlashCommand(SlashCommandInteractionEvent event) { QuizQuestion quizQuestion = question.get(); List choices = quizQuestion.getChoices(); - StringBuilder sb = new StringBuilder(); - sb.append("**Java Quiz**\n\n"); - sb.append(quizQuestion.getQuestion()).append("\n\n"); - for (int i = 0; i < choices.size(); i++) { - sb.append("`").append(i + 1).append("`) ").append(choices.get(i)).append("\n"); - } + String quizContent = String.format("**Java Quiz**\n\n%s\n\n%s", + quizQuestion.getQuestion(), + java.util.stream.IntStream.range(0, choices.size()) + .mapToObj(i -> String.format("`%d`) %s", i + 1, choices.get(i))) + .collect(java.util.stream.Collectors.joining("\n")) + ); - String messageId = event.getHook().editOriginal(sb.toString()) + String messageId = event.getHook().editOriginal(quizContent) .setActionRow( Button.primary(COMMAND_NAME + "-1-" + userId, "1"), Button.primary(COMMAND_NAME + "-2-" + userId, "2"), diff --git a/app/src/main/java/com/togetherjava/tjplays/games/game2048/QuizQuestion.java b/app/src/main/java/com/togetherjava/tjplays/trivia/QuizQuestion.java similarity index 94% rename from app/src/main/java/com/togetherjava/tjplays/games/game2048/QuizQuestion.java rename to app/src/main/java/com/togetherjava/tjplays/trivia/QuizQuestion.java index 90436fe..96cf1ed 100644 --- a/app/src/main/java/com/togetherjava/tjplays/games/game2048/QuizQuestion.java +++ b/app/src/main/java/com/togetherjava/tjplays/trivia/QuizQuestion.java @@ -1,8 +1,7 @@ -package com.togetherjava.tjplays.games.game2048; +package com.togetherjava.tjplays.trivia; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; - import java.util.List; /** @@ -32,4 +31,4 @@ public List getChoices() { public int getCorrectAnswerIndex() { return correctAnswerIndex; } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/togetherjava/tjplays/games/game2048/TriviaManager.java b/app/src/main/java/com/togetherjava/tjplays/trivia/TriviaManager.java similarity index 63% rename from app/src/main/java/com/togetherjava/tjplays/games/game2048/TriviaManager.java rename to app/src/main/java/com/togetherjava/tjplays/trivia/TriviaManager.java index 099f837..0291159 100644 --- a/app/src/main/java/com/togetherjava/tjplays/games/game2048/TriviaManager.java +++ b/app/src/main/java/com/togetherjava/tjplays/trivia/TriviaManager.java @@ -1,22 +1,26 @@ -package com.togetherjava.tjplays.games.game2048; +package com.togetherjava.tjplays.trivia; import com.togetherjava.tjplays.services.chatgpt.ChatGptService; import com.fasterxml.jackson.databind.ObjectMapper; - import java.util.Optional; +/** + * Manages Java trivia questions using ChatGptService. + * Used by quiz commands to fetch questions for users. + * Future contributors can use this for any trivia/quiz feature. + */ public class TriviaManager { - private final ChatGptService gpt; + private final ChatGptService chatGptService; private final ObjectMapper mapper = new ObjectMapper(); - public TriviaManager(String openAiKey) { - this.gpt = new ChatGptService(openAiKey); + public TriviaManager(ChatGptService chatGptService) { + this.chatGptService = chatGptService; } public Optional fetchRandomQuestion() { String prompt = "Provide one Java trivia question as a JSON object like" + " {\"question\":\"...\",\"choices\":[\"A\",\"B\",\"C\",\"D\"],\"correct\":}"; - Optional raw = gpt.ask(prompt, "java quiz"); + Optional raw = chatGptService.ask(prompt, "java quiz"); if (raw.isEmpty()) { return Optional.empty(); }