Skip to content

Commit

Permalink
Merge pull request #8 from TosoxDev/development
Browse files Browse the repository at this point in the history
feat: add scramble game
  • Loading branch information
Tosox authored Jul 6, 2023
2 parents de1f913 + d8de4ba commit 90958e2
Show file tree
Hide file tree
Showing 7 changed files with 309 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import de.tosoxdev.tosoxjr.commands.joke.JokeCmd;
import de.tosoxdev.tosoxjr.commands.quote.QuoteCmd;
import de.tosoxdev.tosoxjr.commands.say.SayCmd;
import de.tosoxdev.tosoxjr.commands.scramble.ScrambleCmd;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;

import java.util.ArrayList;
Expand All @@ -20,7 +21,9 @@ public CommandManager() {
addCommand(new QuoteCmd());
addCommand(new JokeCmd());
addCommand(new CSStatsCmd());

addCommand(new HangmanCmd());
addCommand(new ScrambleCmd());
}

public List<CommandBase> getCommands() {
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/de/tosoxdev/tosoxjr/commands/hangman/GameState.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package de.tosoxdev.tosoxjr.commands.hangman;

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;
}
}
47 changes: 17 additions & 30 deletions src/main/java/de/tosoxdev/tosoxjr/commands/hangman/Hangman.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,23 @@

import java.awt.*;
import java.util.*;
import java.util.List;
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 {
public static final HashMap<String, String> RANDOM_WORD_APIS = new HashMap<>(Map.of(
"en", "https://random-word-api.vercel.app/api?words=1",
"de", "https://alex-riedel.de/randV2.php?anz=1"
"en", "https://capitalizemytitle.com/wp-content/tools/random-word/en/nouns.txt",
"de", "https://capitalizemytitle.com/wp-content/tools/random-word/de/nouns.txt"
));
private static final HashMap<String, List<String>> RANDOM_WORD_LIST = new HashMap<>();

static {
for (Map.Entry<String, String> entry : RANDOM_WORD_APIS.entrySet()) {
String response = APIRequest.getString(entry.getValue());
RANDOM_WORD_LIST.put(entry.getKey(), response != null ? List.of(response.split(",")) : null);
}
}

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;
Expand Down Expand Up @@ -219,14 +211,9 @@ private void endGame(GameState state) {
}

private String generateWord() {
String lang = RANDOM_WORD_APIS.getOrDefault(language.toLowerCase(), RANDOM_WORD_APIS.get("en"));
String response = APIRequest.getString(lang);
if (response == null) {
return null;
}

// Remove brackets and quotation marks
return response.substring(2, response.length() - 2);
List<String> words = RANDOM_WORD_LIST.getOrDefault(language.toLowerCase(), RANDOM_WORD_LIST.get("en"));
int randomIdx = ThreadLocalRandom.current().nextInt(words.size());
return words.get(randomIdx);
}

private String showWord() {
Expand Down Expand Up @@ -275,9 +262,9 @@ private EmbedBuilder createGameEmbed(GameState state) {
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
- 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
18 changes: 18 additions & 0 deletions src/main/java/de/tosoxdev/tosoxjr/commands/scramble/GameState.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package de.tosoxdev.tosoxjr.commands.scramble;

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

private final String title;

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

public String getTitle() {
return title;
}
}
170 changes: 170 additions & 0 deletions src/main/java/de/tosoxdev/tosoxjr/commands/scramble/Scramble.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package de.tosoxdev.tosoxjr.commands.scramble;

import de.tosoxdev.tosoxjr.utils.APIRequest;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.entities.emoji.EmojiUnion;
import net.dv8tion.jda.api.events.Event;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent;

import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

public class Scramble {
public static final HashMap<String, String> RANDOM_WORD_APIS = new HashMap<>(Map.of(
"en", "https://capitalizemytitle.com/wp-content/tools/random-word/en/nouns.txt",
"de", "https://capitalizemytitle.com/wp-content/tools/random-word/de/nouns.txt"
));
private static final HashMap<String, List<String>> RANDOM_WORD_LIST = new HashMap<>();

static {
for (Map.Entry<String, String> entry : RANDOM_WORD_APIS.entrySet()) {
String response = APIRequest.getString(entry.getValue());
RANDOM_WORD_LIST.put(entry.getKey(), response != null ? List.of(response.split(",")) : null);
}
}

private static final int TIMEOUT_MS = 2 * 60 * 1000;
private static final int STOP_SIGN_CP = 0x1F6D1;

private final MessageChannel channel;
private final String player;
private final boolean coop;
private final String language;

private Timer tmTimeout = new Timer();
private String embedMessageId;
private long timer;
private int attempts;
private String word;
private String scrambledWord;

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

public boolean initialize() {
word = generateWord();
if (word == null) {
channel.sendMessage("I'm unable to generate a random word").queue();
return false;
}

// Shuffle word
List<String> chars = new ArrayList<>(word.toUpperCase().chars().mapToObj(c -> String.valueOf((char) c)).toList());
Collections.shuffle(chars);
scrambledWord = String.join("", chars);

// Start timers
tmTimeout.schedule(new TimerTask() {
@Override
public void run() {
endGame(GameState.TIMEOUT, null);
}
}, TIMEOUT_MS);
timer = System.currentTimeMillis();

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

public void handleEvent(Event event) {
if (event instanceof MessageReceivedEvent e) {
handleMessageReceivedEvent(e);
} else if (event instanceof MessageReactionAddEvent e) {
handleMessageReactionAddEvent(e);
}
}

private void handleMessageReactionAddEvent(MessageReactionAddEvent event) {
EmojiUnion emoji = event.getEmoji();
if (!event.getMessageId().equals(embedMessageId)) return;
if (emoji.getType() == Emoji.Type.CUSTOM) return;

// Check sender
User sender = event.getUser();
if (sender == null) return;
if (sender.isBot()) return;
if (!sender.getAsTag().equals(player)) return;

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

endGame(GameState.DEFEAT, null);
}

private void handleMessageReceivedEvent(MessageReceivedEvent event) {
if (event.isWebhookMessage()) return;
if (!event.getChannel().getId().equals(channel.getId())) return;

User sender = event.getAuthor();
if (sender.isBot()) return;
if ((!coop) && (!sender.getAsTag().equals(player))) return;

// Reset timeout timer
resetTimer();

attempts++;

if (event.getMessage().getContentDisplay().equalsIgnoreCase(word)) {
endGame(GameState.WIN, sender.getName());
}
}

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

private void endGame(GameState state, String sender) {
tmTimeout.cancel();
channel.retrieveMessageById(embedMessageId).queue(m -> m.clearReactions().queue());
channel.sendMessageEmbeds(createGameEmbed(state, sender).build()).queue();
ScrambleCmd.getInstance().removePlayer(player);
}

private String generateWord() {
List<String> words = RANDOM_WORD_LIST.getOrDefault(language.toLowerCase(), RANDOM_WORD_LIST.get("en"));
int randomIdx = ThreadLocalRandom.current().nextInt(words.size());
return words.get(randomIdx);
}

private EmbedBuilder createGameEmbed(GameState state, String sender) {
EmbedBuilder gameEmbed = new EmbedBuilder();
gameEmbed.setTitle(state.getTitle());
gameEmbed.setColor(Color.CYAN);
gameEmbed.addField(state == GameState.ONGOING ? "Word" : "The word was", state == GameState.ONGOING ? scrambledWord : word, false);
if (state == GameState.WIN) {
double time = (double)(System.currentTimeMillis() - timer) / 1000;
String results = coop
? String.format("%s guessed the word first after %.2fs", sender, time)
: String.format("You guessed the word after %.2fs and %d attempts", time, attempts);
gameEmbed.addField("Results", results, false);
}
if (state == GameState.ONGOING) {
gameEmbed.addField(
"How To Play",
"""
- Try to unscramble the word
- Write your guess in this channel
- React with the stop sign (🛑) to end the game
""", false);
}
return gameEmbed;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package de.tosoxdev.tosoxjr.commands.scramble;

import de.tosoxdev.tosoxjr.commands.GameBase;
import de.tosoxdev.tosoxjr.commands.hangman.Hangman;
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 ScrambleCmd extends GameBase {
private static final int MAX_GAMES = 10;
private static ScrambleCmd instance;
private final HashMap<String, Scramble> games = new HashMap<>();
private final String languages;

public ScrambleCmd() {
super("scramble", "Play a game of Scramble", List.of(
new OptionData(OptionType.STRING, "lang", "Decide the langauge of the word. Use 'list' to list all available ones", false),
new OptionData(OptionType.BOOLEAN, "coop", "Play Scramble with all your friends on the server", false)
));
instance = this;

StringBuilder sb = new StringBuilder();
Hangman.RANDOM_WORD_APIS.forEach((key, value) -> sb.append(String.format("- %s\n", key)));
languages = sb.toString();
}

@Override
public void handle(SlashCommandInteractionEvent event) {
String lang = ArgumentParser.getString(event.getOption("lang"), "");
if (lang.equalsIgnoreCase("list")) {
String msg = String.format("Available languages\n%s", languages);
event.reply(msg).queue();
return;
}

String user = event.getUser().getAsTag();
if (games.containsKey(user)) {
event.reply("You already started a game of Scramble").queue();
return;
}

if (games.size() > MAX_GAMES) {
event.reply("Sorry, there are to many games of Scramble already").queue();
return;
}

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

boolean coop = ArgumentParser.getBoolean(event.getOption("coop"), false);
Scramble scramble = new Scramble(user, event.getChannel(), coop, lang);
if (scramble.initialize()) {
games.put(user, scramble);
}
}

@Override
public void handleEvent(Event event) {
for (Scramble scramble : games.values()) {
new Thread(() -> scramble.handleEvent(event)).start();
}
}

public void removePlayer(String user) {
games.remove(user);
}

public static ScrambleCmd getInstance() {
return instance;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import de.tosoxdev.tosoxjr.Main;
import de.tosoxdev.tosoxjr.commands.hangman.HangmanCmd;
import de.tosoxdev.tosoxjr.commands.scramble.ScrambleCmd;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull;
Expand All @@ -16,5 +18,11 @@ public void onSlashCommandInteraction(@NotNull SlashCommandInteractionEvent even
@Override
public void onMessageReactionAdd(@NotNull MessageReactionAddEvent event) {
HangmanCmd.getInstance().handleEvent(event);
ScrambleCmd.getInstance().handleEvent(event);
}

@Override
public void onMessageReceived(@NotNull MessageReceivedEvent event) {
ScrambleCmd.getInstance().handleEvent(event);
}
}

0 comments on commit 90958e2

Please sign in to comment.