Skip to content

Commit

Permalink
Merge pull request #5 from TosoxDev/development
Browse files Browse the repository at this point in the history
feat: enhance hangman
  • Loading branch information
Tosox authored Jul 4, 2023
2 parents 6f80c94 + f8f83f5 commit 46a5506
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 20 deletions.
110 changes: 92 additions & 18 deletions src/main/java/de/tosoxdev/tosoxjr/commands/hangman/Hangman.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,52 @@
import java.awt.*;
import java.util.HashSet;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;

enum GameState {
ONGOING("Hangman"),
WIN("You won!"),
DEFEAT("You lost!"),
TIMEOUT("Timeout");

private final String title;

GameState(String title) {
this.title = title;
}

public String getTitle() {
return title;
}
}

public class Hangman {
private static final String API_RANDOM_WORD = "https://random-word-api.vercel.app/api?words=1";
private static final String API_DICTIONARY = "https://www.dictionaryapi.com/api/v3/references/collegiate/json/%s?key=%s";

private static final int REGIONAL_INDICATOR_A_CP = 0x1F1E6;
private static final int REGIONAL_INDICATOR_Z_CP = 0x1F1FF;
private static final int JOKER_CP = 0x1F0CF;
private static final int STOP_SIGN_CP = 0x1F6D1;
private static final int TIMEOUT_MS = 2 * 60 * 1000;

private final Set<Character> guessedLetters = new HashSet<>();
private final MessageChannel channel;
private final String player;
private final boolean coop;

private Timer timer = new Timer();
private String embedMessageId;
private String word;
private String wordDefinition;
private int attempts;

public Hangman(String player, MessageChannel channel) {
public Hangman(String player, MessageChannel channel, boolean coop) {
this.channel = channel;
this.player = player;
this.coop = coop;
}

public boolean initialize() {
Expand All @@ -46,8 +70,16 @@ public boolean initialize() {
return false;
}

// Set timeout timer
timer.schedule(new TimerTask() {
@Override
public void run() {
endGame(GameState.TIMEOUT);
}
}, TIMEOUT_MS);

word = word.toUpperCase();
channel.sendMessageEmbeds(createGameEmbed(false).build()).queue(m -> embedMessageId = m.getId());
channel.sendMessageEmbeds(createGameEmbed(GameState.ONGOING).build()).queue(m -> embedMessageId = m.getId());
return true;
}

Expand All @@ -67,13 +99,25 @@ private void handleMessageReactionAddEvent(MessageReactionAddEvent event) {
User sender = event.getUser();
if (sender == null) return;
if (sender.isBot()) return;
if (!sender.getAsTag().equals(player)) return;
if ((!coop) && (!sender.getAsTag().equals(player))) return;

// Reset timeout timer
resetTimer();

// Get code point from emoji
int codePoint = emoji.getName().codePointAt(0);

// Check if stop
if (codePoint == STOP_SIGN_CP) {
if (!sender.getAsTag().equals(player)) return;

endGame(GameState.DEFEAT);
return;
}

// Check if joker
if (codePoint == JOKER_CP) {
if (!sender.getAsTag().equals(player)) return;
if (wordDefinition != null) return;

// Get joker
Expand All @@ -82,7 +126,7 @@ private void handleMessageReactionAddEvent(MessageReactionAddEvent event) {
// Send embed
channel.retrieveMessageById(embedMessageId).queue(m -> {
m.clearReactions().queue();
m.editMessageEmbeds(createGameEmbed(false).build()).queue();
m.editMessageEmbeds(createGameEmbed(GameState.ONGOING).build()).queue();
});

return;
Expand All @@ -102,28 +146,45 @@ private void handleMessageReactionAddEvent(MessageReactionAddEvent event) {
attempts++;
if (attempts == 7) {
// Player didn't manage to guess the word
endGame();
endGame(GameState.DEFEAT);
return;
}
} else if (!createWordTemplate(false).contains("_")) {
} else if (!showWord().contains("_")) {
// Player managed to guess the word
endGame();
endGame(GameState.WIN);
return;
}

// Send embed
channel.retrieveMessageById(embedMessageId).queue(m -> {
m.clearReactions().queue();
m.editMessageEmbeds(createGameEmbed(false).build()).queue();
m.editMessageEmbeds(createGameEmbed(GameState.ONGOING).build()).queue();
});
}

private void resetTimer() {
timer.cancel();
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
endGame(GameState.TIMEOUT);
}
}, TIMEOUT_MS);
}

private String getWordDefinition() {
JSONArray response = (JSONArray) APIRequest.getJson(String.format(API_DICTIONARY, word, Constants.DICTIONARY_API_KEY));
if ((response == null) || (response.isEmpty())) {
return "_Unknown_";
}

// Check if one definition
if (response.length() == 1) {
return response.getString(0);
}

// If multiple definitions
JSONObject objDefinition = response.getJSONObject(0);
JSONArray shortDefinitions = objDefinition.getJSONArray("shortdef");
if ((shortDefinitions == null) || (shortDefinitions.isEmpty())) {
Expand All @@ -142,10 +203,11 @@ private Character regionalIndicatorCPToChar(int codepoint) {
return (char)((char) relativeChar + 'A');
}

private void endGame() {
private void endGame(GameState state) {
timer.cancel();
channel.retrieveMessageById(embedMessageId).queue(m -> {
m.clearReactions().queue();
m.editMessageEmbeds(createGameEmbed(true).build()).queue();
m.editMessageEmbeds(createGameEmbed(state).build()).queue();
});
HangmanCmd.getInstance().removePlayer(player);
}
Expand All @@ -160,9 +222,15 @@ private String generateWord() {
return response.substring(2, response.length() - 2);
}

private String createWordTemplate(boolean show) {
private String showWord() {
return word.chars()
.mapToObj(c -> (guessedLetters.contains((char) c) ? (char) c + " " : "_ "))
.collect(Collectors.joining());
}

private String revealWord() {
return word.chars()
.mapToObj(c -> show ? ((char) c + " ") : (guessedLetters.contains((char) c) ? (char) c + " " : "_ "))
.mapToObj(c -> ((char) c + " "))
.collect(Collectors.joining());
}

Expand All @@ -186,18 +254,24 @@ private String createDescription() {
+ "```";
}

private EmbedBuilder createGameEmbed(boolean isEndScreen) {
private EmbedBuilder createGameEmbed(GameState state) {
EmbedBuilder gameEmbed = new EmbedBuilder();
gameEmbed.setTitle(!isEndScreen ? "Hangman" : (attempts == 7 ? "You lost!" : "You won!"));
gameEmbed.setTitle(state.getTitle());
gameEmbed.setColor(Color.BLUE);
gameEmbed.setDescription(createDescription());
gameEmbed.addField("Word", "```" + createWordTemplate(isEndScreen) + "```", false);
gameEmbed.addField("Word", "```" + (state == GameState.ONGOING ? showWord() : revealWord()) + "```", false);
gameEmbed.addField("Used Letters", createGuessedWords(), false);
if (!isEndScreen) {
if (state == GameState.ONGOING) {
if (wordDefinition != null) {
gameEmbed.addField("Hint", wordDefinition, false);
}
gameEmbed.addField("How To Play", "React with emojis (e.g. \uD83C\uDDE6, \uD83C\uDDE7) to make a guess\nReact with the joker (🃏) to get a hint", false);
gameEmbed.addField(
"How To Play",
"""
React with emojis (e.g. \uD83C\uDDE6, \uD83C\uDDE7) to make a guess
React with the joker (🃏) to get a hint
React with the stop sign (🛑) to end the game
""", false);
}
return gameEmbed;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
package de.tosoxdev.tosoxjr.commands.hangman;

import de.tosoxdev.tosoxjr.commands.GameBase;
import de.tosoxdev.tosoxjr.utils.ArgumentParser;
import net.dv8tion.jda.api.events.Event;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;

import java.util.HashMap;
import java.util.List;

public class HangmanCmd extends GameBase {
private final HashMap<String, Hangman> games = new HashMap<>();
private static HangmanCmd instance;

public HangmanCmd() {
super("hangman", "Play a game of hangman", null);
super("hangman", "Play a game of hangman", List.of(
new OptionData(OptionType.BOOLEAN, "coop", "Play hangman with all your friends on the server", false)
));
instance = this;
}

Expand All @@ -25,7 +31,8 @@ public void handle(SlashCommandInteractionEvent event) {

event.deferReply().queue(m -> m.deleteOriginal().queue());

Hangman hangman = new Hangman(user, event.getChannel());
boolean coop = ArgumentParser.getBoolean(event.getOption("coop"), false);
Hangman hangman = new Hangman(user, event.getChannel(), coop);
if (hangman.initialize()) {
games.put(user, hangman);
}
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/de/tosoxdev/tosoxjr/utils/ArgumentParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ public static String getStringForced(OptionMapping arg) throws IllegalArgumentEx
}
return argRaw;
}

public static boolean getBoolean(OptionMapping arg, boolean fallback) {
return arg != null ? arg.getAsBoolean() : fallback;
}
}

0 comments on commit 46a5506

Please sign in to comment.