-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8 from TosoxDev/development
feat: add scramble game
- Loading branch information
Showing
7 changed files
with
309 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
src/main/java/de/tosoxdev/tosoxjr/commands/hangman/GameState.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
src/main/java/de/tosoxdev/tosoxjr/commands/scramble/GameState.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
170
src/main/java/de/tosoxdev/tosoxjr/commands/scramble/Scramble.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
75 changes: 75 additions & 0 deletions
75
src/main/java/de/tosoxdev/tosoxjr/commands/scramble/ScrambleCmd.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters