Skip to content

Commit

Permalink
Added Tic-Tac-Toe
Browse files Browse the repository at this point in the history
  • Loading branch information
beanbeanjuice committed Jul 3, 2024
1 parent 8137f01 commit 62b1344
Show file tree
Hide file tree
Showing 3 changed files with 280 additions and 1 deletion.
6 changes: 5 additions & 1 deletion src/main/java/com/beanbeanjuice/cafebot/CafeBot.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.beanbeanjuice.cafebot.commands.fun.rate.RateCommand;
import com.beanbeanjuice.cafebot.commands.games.CoinFlipCommand;
import com.beanbeanjuice.cafebot.commands.games.DiceRollCommand;
import com.beanbeanjuice.cafebot.commands.games.TicTacToeCommand;
import com.beanbeanjuice.cafebot.commands.games.game.GameCommand;
import com.beanbeanjuice.cafebot.commands.generic.PingCommand;
import com.beanbeanjuice.cafebot.commands.generic.*;
Expand All @@ -28,6 +29,7 @@
import com.beanbeanjuice.cafeapi.wrapper.CafeAPI;
import com.beanbeanjuice.cafeapi.wrapper.requests.RequestLocation;
import com.beanbeanjuice.cafebot.utility.sections.cafe.MenuHandler;
import com.beanbeanjuice.cafebot.utility.sections.game.TicTacToeListener;
import com.beanbeanjuice.cafebot.utility.sections.generic.HelpHandler;
import com.beanbeanjuice.cafebot.utility.sections.generic.HelpListener;
import com.sun.management.OperatingSystemMXBean;
Expand Down Expand Up @@ -188,6 +190,7 @@ private void setupCommands() {
new CoinFlipCommand(this),
new DiceRollCommand(this),
new GameCommand(this),
new TicTacToeCommand(this),

// Social
new MemberCountCommand(this),
Expand Down Expand Up @@ -245,7 +248,8 @@ private void setupListeners() {
new BotAddListener(this),
new BotRemoveListener(this),
new CountingListener(this),
new HelpListener(commandHandler, helpHandler)
new HelpListener(commandHandler, helpHandler),
new TicTacToeListener(cafeAPI.getWinStreaksEndpoint())
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.beanbeanjuice.cafebot.commands.games;

import com.beanbeanjuice.cafebot.CafeBot;
import com.beanbeanjuice.cafebot.utility.commands.Command;
import com.beanbeanjuice.cafebot.utility.commands.CommandCategory;
import com.beanbeanjuice.cafebot.utility.commands.ICommand;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.emoji.Emoji;
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 net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;

public class TicTacToeCommand extends Command implements ICommand {

public TicTacToeCommand(final CafeBot cafeBot) {
super(cafeBot);
}

@Override
public void handle(SlashCommandInteractionEvent event) {
// cafeBot:tictactoe:index:user1:user2
User opponent = event.getOption("opponent").getAsUser();

String playerID = event.getUser().getId();
String opponentID = opponent.getId();

event.getHook()
.sendMessage(String.format("%s vs %s", event.getUser().getAsMention(), opponent.getEffectiveName()))
.addComponents(
ActionRow.of(getButton(0, playerID, opponentID), getButton(1, playerID, opponentID), getButton(2, playerID, opponentID)),
ActionRow.of(getButton(3, playerID, opponentID), getButton(4, playerID, opponentID), getButton(5, playerID, opponentID)),
ActionRow.of(getButton(6, playerID, opponentID), getButton(7, playerID, opponentID), getButton(8, playerID, opponentID))
)
.queue();
}

private Button getButton(final int index, final String user1ID, final String user2ID) {
return Button.secondary(String.format("cafeBot:tictactoe:%d:%s:%s", index, user1ID, user2ID), Emoji.fromFormatted("❓"));
}

@Override
public String getName() {
return "tictactoe";
}

@Override
public String getDescription() {
return "Play tic-tac-toe with someone!";
}

@Override
public CommandCategory getCategory() {
return CommandCategory.GAME;
}

@Override
public OptionData[] getOptions() {
return new OptionData[] {
new OptionData(OptionType.USER, "opponent", "The person you want to play against.", true)
};
}

@Override
public Permission[] getPermissions() {
return new Permission[0];
}

@Override
public boolean isEphemeral() {
return false;
}

@Override
public boolean isNSFW() {
return false;
}

@Override
public boolean allowDM() {
return false;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package com.beanbeanjuice.cafebot.utility.sections.game;

import com.beanbeanjuice.cafeapi.wrapper.endpoints.minigames.winstreaks.MinigameType;
import com.beanbeanjuice.cafeapi.wrapper.endpoints.minigames.winstreaks.WinStreak;
import com.beanbeanjuice.cafeapi.wrapper.endpoints.minigames.winstreaks.WinStreaksEndpoint;
import com.beanbeanjuice.cafebot.CafeBot;
import com.beanbeanjuice.cafebot.utility.helper.Helper;
import lombok.Getter;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.interactions.components.Component;
import net.dv8tion.jda.api.interactions.components.LayoutComponent;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle;
import net.dv8tion.jda.api.requests.restaction.MessageEditAction;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;

public class TicTacToeListener extends ListenerAdapter {

private final WinStreaksEndpoint winStreaksEndpoint;

public TicTacToeListener(final WinStreaksEndpoint winStreaksEndpoint) {
this.winStreaksEndpoint = winStreaksEndpoint;
}

/*
This will be in the format cafeBot:tictactoe:index:user1:user2
*/
@Override
public void onButtonInteraction(ButtonInteractionEvent event) {
if (!event.getComponentId().startsWith("cafeBot:tictactoe:")) return;

User currentPerson = event.getMessage().getMentions().getUsers().getFirst();
if (!event.getUser().getId().equals(currentPerson.getId())) return;

int index = Integer.parseInt(event.getComponentId().split(":")[2]);
String player1ID = event.getComponentId().split(":")[3];
String player2ID = event.getComponentId().split(":")[4];

Guild guild = event.getGuild();
guild.retrieveMembersByIds(player1ID, player2ID).onSuccess((members) -> {
Member player1 = members.get(0);
Member player2 = members.get(1);
boolean isPlayer1 = currentPerson.getId().equals(player1ID);

if (player1 == null || player2 == null) {
event.editMessage(String.format("**Game Cancelled**: Cannot get players %s and %s...", player1ID, player2ID)).setReplace(true).queue();
return;
}

Button button = event.getButton();
ButtonStyle style = (isPlayer1) ? ButtonStyle.SUCCESS : ButtonStyle.DANGER;
Emoji emoji = (isPlayer1) ? Emoji.fromFormatted("U+1F1FD") : Emoji.fromFormatted("U+1F1F4");

Result result = checkGame(index, isPlayer1, event.getMessage().getComponents());
event.editButton(button.withStyle(style).withEmoji(emoji).asDisabled()).queue((ignored) -> {
String formatString = String.format(
"%s (:regional_indicator_x:) vs %s (:regional_indicator_o:) : **%s**",
(isPlayer1) ? player1.getEffectiveName() : player1.getAsMention(),
(isPlayer1) ? player2.getAsMention() : player2.getEffectiveName(),
result.getMessage()
);

MessageEditAction action = event.getMessage().editMessage(formatString);

if (result != Result.ONGOING) {
List<LayoutComponent> components = new ArrayList<>(event.getMessage().getComponents());
components.replaceAll(itemComponents -> itemComponents.withDisabled(true));
action.setComponents(components);

if (result != Result.TIED) {
updateWins(player1, player2, result, event.getMessage());
}
}

action.queue();
});
});

}

private void updateWins(final Member player1, final Member player2, final Result result, final Message message) {
CompletableFuture<WinStreak> player1WinStreakFuture = winStreaksEndpoint.getAndCreateUserWinStreak(player1.getId());
CompletableFuture<WinStreak> player2WinStreakFuture = winStreaksEndpoint.getAndCreateUserWinStreak(player2.getId());

player1WinStreakFuture.thenCombineAsync(player2WinStreakFuture, (player1WinStreak, player2WinStreak) -> {
int player1Wins = (result == Result.PLAYER1) ? player1WinStreak.getWins(MinigameType.TIC_TAC_TOE) + 1: 0;
int player2Wins = (result == Result.PLAYER2) ? player2WinStreak.getWins(MinigameType.TIC_TAC_TOE) + 1: 0;

winStreaksEndpoint.updateUserWinStreak(player1.getId(), MinigameType.TIC_TAC_TOE, player1Wins);
winStreaksEndpoint.updateUserWinStreak(player2.getId(), MinigameType.TIC_TAC_TOE, player2Wins);

EmbedBuilder embedBuilder = new EmbedBuilder();
embedBuilder.setColor(Helper.getRandomColor());
embedBuilder.setDescription(String.format(
"""
# Tic-Tac-Toe Win Streaks
**%s** - *%d Wins*
**%s** - *%d Wins*
""", player1.getEffectiveName(), player1Wins, player2.getEffectiveName(), player2Wins));

message.replyEmbeds(embedBuilder.build()).queue();

return true;
});
}

private Result checkGame(final int index, final boolean isPlayer1, final List<LayoutComponent> componentLayouts) {
List<Square> board = getBoard(index, isPlayer1, componentLayouts);
Result result = (isPlayer1) ? Result.PLAYER1 : Result.PLAYER2;

// Check rows
for (int i = 0; i < 9; i += 3) {
if (board.get(i) != Square.NONE && board.get(i) == board.get(i + 1) && board.get(i) == board.get(i + 2)) {
return result;
}
}

// Check columns
for (int i = 0; i < 3; i++) {
if (board.get(i) != Square.NONE && board.get(i) == board.get(i + 3) && board.get(i) == board.get(i + 6)) {
return result;
}
}

// Check diagonals
if (board.get(0) != Square.NONE && board.get(0) == board.get(4) && board.get(0) == board.get(8)) {
return result;
}

if (board.get(2) != Square.NONE && board.get(2) == board.get(4) && board.get(2) == board.get(6)) {
return result;
}

if (!board.contains(Square.NONE)) return Result.TIED;
return Result.ONGOING;
}

private List<Square> getBoard(final int index, final boolean isPlayer1, final List<LayoutComponent> componentLayouts) {
Square[] game = new Square[9];


componentLayouts.forEach((layout) -> {
layout.getButtons().forEach((button) -> {
int buttonIndex = Integer.parseInt(button.getId().split(":")[2]);
if (!button.isDisabled()) {
game[buttonIndex] = Square.NONE;
return;
}

game[buttonIndex] = button.getStyle().equals(ButtonStyle.SUCCESS) ? Square.PLAYER1 : Square.PLAYER2;
});
});
game[index] = (isPlayer1) ? Square.PLAYER1 : Square.PLAYER2;

return Arrays.stream(game).toList();
}

private enum Result {
PLAYER1 (":regional_indicator_x: WINS"),
PLAYER2 (":regional_indicator_o: WINS"),
TIED ("TIED"),
ONGOING ("ONGOING");

@Getter final String message;

Result(final String message) {
this.message = message;
}
}

private enum Square {
PLAYER1,
PLAYER2,
NONE
}

}

0 comments on commit 62b1344

Please sign in to comment.