From a21399aede9bbb5a5bc4710b258ae7128e4ebcb4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 19 Jul 2025 18:13:02 +0000 Subject: [PATCH 1/8] Initial plan From da756a99a547bbe4c7f8809e8f827ff144f98003 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 19 Jul 2025 18:19:38 +0000 Subject: [PATCH 2/8] Implement Rock Paper Scissors command with full game logic Co-authored-by: Pierre-Demessence <1756398+Pierre-Demessence@users.noreply.github.com> --- DiscordBot/Modules/UserSlashModule.cs | 264 ++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) diff --git a/DiscordBot/Modules/UserSlashModule.cs b/DiscordBot/Modules/UserSlashModule.cs index d8d3d74e..554d5b66 100644 --- a/DiscordBot/Modules/UserSlashModule.cs +++ b/DiscordBot/Modules/UserSlashModule.cs @@ -531,4 +531,268 @@ await Context.Interaction.ModifyOriginalResponseAsync(msg => } #endregion + + #region Rock Paper Scissors System + + public enum RPSChoice + { + None = 0, + Rock = 1, + Paper = 2, + Scissors = 3 + } + + public class RPSGame + { + public ulong ChallengerId { get; set; } + public ulong OpponentId { get; set; } + public RPSChoice ChallengerChoice { get; set; } = RPSChoice.None; + public RPSChoice OpponentChoice { get; set; } = RPSChoice.None; + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + } + + private static readonly ConcurrentDictionary _activeRPSGames = new ConcurrentDictionary(); + + [SlashCommand("rps", "Challenge another user to Rock Paper Scissors!")] + public async Task RockPaperScissors([Summary(description: "The user you want to challenge")] IUser opponent) + { + // Prevent self-playing + if (opponent.Id == Context.User.Id) + { + await Context.Interaction.RespondAsync("You cannot challenge yourself to Rock Paper Scissors!", ephemeral: true); + return; + } + + // Prevent challenging bots + if (opponent.IsBot) + { + await Context.Interaction.RespondAsync("You cannot challenge a bot to Rock Paper Scissors!", ephemeral: true); + return; + } + + // Check for active game + string gameKey = $"{Context.User.Id}_{opponent.Id}"; + string reverseGameKey = $"{opponent.Id}_{Context.User.Id}"; + + if (_activeRPSGames.ContainsKey(gameKey) || _activeRPSGames.ContainsKey(reverseGameKey)) + { + await Context.Interaction.RespondAsync("There's already an active Rock Paper Scissors game between you two!", ephemeral: true); + return; + } + + // Create the game + var game = new RPSGame + { + ChallengerId = Context.User.Id, + OpponentId = opponent.Id + }; + _activeRPSGames[gameKey] = game; + + var embed = new EmbedBuilder() + .WithColor(Color.Blue) + .WithTitle("🎮 Rock Paper Scissors Challenge!") + .WithDescription($"{Context.User.Mention} has challenged {opponent.Mention} to Rock Paper Scissors!") + .AddField("How to Play", "Both players choose Rock 🪨, Paper 📄, or Scissors ✂️. You can change your choice until both players have decided.") + .WithFooter("This challenge will expire in 5 minutes"); + + var components = new ComponentBuilder() + .WithButton("🪨 Rock", $"rps_rock:{gameKey}", ButtonStyle.Primary) + .WithButton("📄 Paper", $"rps_paper:{gameKey}", ButtonStyle.Primary) + .WithButton("✂️ Scissors", $"rps_scissors:{gameKey}", ButtonStyle.Primary) + .Build(); + + await Context.Interaction.RespondAsync(embed: embed.Build(), components: components); + + // Store the message reference for timeout + var originalResponse = await Context.Interaction.GetOriginalResponseAsync(); + + // Auto-timeout after 5 minutes + _ = Task.Run(async () => + { + await Task.Delay(300000); // 5 minutes + if (_activeRPSGames.TryGetValue(gameKey, out var gameData)) + { + _activeRPSGames.TryRemove(gameKey, out _); + + try + { + var challenger = await Context.Guild.GetUserAsync(gameData.ChallengerId); + var challengedUser = await Context.Guild.GetUserAsync(gameData.OpponentId); + + string timeoutMessage = "⏰ Rock Paper Scissors challenge expired - no one made their choice in time!"; + + await originalResponse.ModifyAsync(msg => + { + msg.Content = string.Empty; + msg.Embed = new EmbedBuilder() + .WithColor(Color.LightGrey) + .WithTitle("🎮 Game Expired") + .WithDescription(timeoutMessage) + .Build(); + msg.Components = new ComponentBuilder().Build(); + }); + } + catch (Exception ex) + { + await LoggingService.LogChannelAndFile($"Failed to modify RPS timeout message: {ex.Message}", ExtendedLogSeverity.Warning); + } + } + }); + } + + [ComponentInteraction("rps_rock:*")] + public async Task RPSRock(string gameKey) + { + await HandleRPSChoice(gameKey, RPSChoice.Rock); + } + + [ComponentInteraction("rps_paper:*")] + public async Task RPSPaper(string gameKey) + { + await HandleRPSChoice(gameKey, RPSChoice.Paper); + } + + [ComponentInteraction("rps_scissors:*")] + public async Task RPSScissors(string gameKey) + { + await HandleRPSChoice(gameKey, RPSChoice.Scissors); + } + + private async Task HandleRPSChoice(string gameKey, RPSChoice choice) + { + // Check if game is still active + if (!_activeRPSGames.TryGetValue(gameKey, out var game)) + { + await Context.Interaction.RespondAsync("This Rock Paper Scissors game is no longer active!", ephemeral: true); + return; + } + + // Check if user is part of this game + if (Context.User.Id != game.ChallengerId && Context.User.Id != game.OpponentId) + { + await Context.Interaction.RespondAsync("Only the players in this game can make choices!", ephemeral: true); + return; + } + + // Update the player's choice + bool isChallenger = Context.User.Id == game.ChallengerId; + if (isChallenger) + { + game.ChallengerChoice = choice; + } + else + { + game.OpponentChoice = choice; + } + + await Context.Interaction.DeferAsync(); + + // Get user references + var challenger = await Context.Guild.GetUserAsync(game.ChallengerId); + var opponent = await Context.Guild.GetUserAsync(game.OpponentId); + + if (challenger == null || opponent == null) + { + await Context.Interaction.FollowupAsync("One of the players is no longer available!"); + _activeRPSGames.TryRemove(gameKey, out _); + return; + } + + // Check if both players have made their choice + if (game.ChallengerChoice != RPSChoice.None && game.OpponentChoice != RPSChoice.None) + { + // Game is complete, determine winner + _activeRPSGames.TryRemove(gameKey, out _); + + string result = DetermineRPSWinner(game.ChallengerChoice, game.OpponentChoice); + string challengerEmoji = GetRPSEmoji(game.ChallengerChoice); + string opponentEmoji = GetRPSEmoji(game.OpponentChoice); + + var resultEmbed = new EmbedBuilder() + .WithColor(Color.Gold) + .WithTitle("🎮 Rock Paper Scissors Results!") + .AddField($"{challenger.DisplayName}", $"{challengerEmoji} {game.ChallengerChoice}", inline: true) + .AddField("VS", "⚡", inline: true) + .AddField($"{opponent.DisplayName}", $"{opponentEmoji} {game.OpponentChoice}", inline: true) + .AddField("Result", result, inline: false) + .Build(); + + await Context.Interaction.ModifyOriginalResponseAsync(msg => + { + msg.Embed = resultEmbed; + msg.Components = new ComponentBuilder().Build(); + }); + } + else + { + // Show that someone has made their choice + string statusMessage; + if (game.ChallengerChoice != RPSChoice.None && game.OpponentChoice == RPSChoice.None) + { + statusMessage = $"{challenger.Mention} has made their choice! Waiting for {opponent.Mention}..."; + } + else if (game.ChallengerChoice == RPSChoice.None && game.OpponentChoice != RPSChoice.None) + { + statusMessage = $"{opponent.Mention} has made their choice! Waiting for {challenger.Mention}..."; + } + else + { + statusMessage = "Waiting for both players to make their choices..."; + } + + var updatedEmbed = new EmbedBuilder() + .WithColor(Color.Orange) + .WithTitle("🎮 Rock Paper Scissors Challenge!") + .WithDescription($"{challenger.Mention} vs {opponent.Mention}") + .AddField("Status", statusMessage) + .AddField("How to Play", "Both players choose Rock 🪨, Paper 📄, or Scissors ✂️. You can change your choice until both players have decided.") + .WithFooter("This challenge will expire in 5 minutes"); + + var components = new ComponentBuilder() + .WithButton("🪨 Rock", $"rps_rock:{gameKey}", ButtonStyle.Primary) + .WithButton("📄 Paper", $"rps_paper:{gameKey}", ButtonStyle.Primary) + .WithButton("✂️ Scissors", $"rps_scissors:{gameKey}", ButtonStyle.Primary) + .Build(); + + await Context.Interaction.ModifyOriginalResponseAsync(msg => + { + msg.Embed = updatedEmbed.Build(); + msg.Components = components; + }); + } + } + + private string DetermineRPSWinner(RPSChoice challengerChoice, RPSChoice opponentChoice) + { + if (challengerChoice == opponentChoice) + { + return "🤝 It's a tie! Great minds think alike."; + } + + bool challengerWins = (challengerChoice == RPSChoice.Rock && opponentChoice == RPSChoice.Scissors) || + (challengerChoice == RPSChoice.Paper && opponentChoice == RPSChoice.Rock) || + (challengerChoice == RPSChoice.Scissors && opponentChoice == RPSChoice.Paper); + + if (challengerWins) + { + return "🏆 Challenger wins!"; + } + else + { + return "🏆 Opponent wins!"; + } + } + + private string GetRPSEmoji(RPSChoice choice) + { + return choice switch + { + RPSChoice.Rock => "🪨", + RPSChoice.Paper => "📄", + RPSChoice.Scissors => "✂️", + _ => "❓" + }; + } + + #endregion } From 93cd05a18eef7ec4cbb0501e8dbb0daf87a81d73 Mon Sep 17 00:00:00 2001 From: Pierre Demessence Date: Sat, 19 Jul 2025 20:35:47 +0200 Subject: [PATCH 3/8] Addes some line breaks --- DiscordBot/Modules/UserSlashModule.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DiscordBot/Modules/UserSlashModule.cs b/DiscordBot/Modules/UserSlashModule.cs index 554d5b66..25bf748a 100644 --- a/DiscordBot/Modules/UserSlashModule.cs +++ b/DiscordBot/Modules/UserSlashModule.cs @@ -592,7 +592,7 @@ public async Task RockPaperScissors([Summary(description: "The user you want to .WithColor(Color.Blue) .WithTitle("🎮 Rock Paper Scissors Challenge!") .WithDescription($"{Context.User.Mention} has challenged {opponent.Mention} to Rock Paper Scissors!") - .AddField("How to Play", "Both players choose Rock 🪨, Paper 📄, or Scissors ✂️. You can change your choice until both players have decided.") + .AddField("How to Play", "Both players choose Rock 🪨, Paper 📄, or Scissors ✂️.\nYou can change your choice until both players have decided.") .WithFooter("This challenge will expire in 5 minutes"); var components = new ComponentBuilder() @@ -729,11 +729,11 @@ await Context.Interaction.ModifyOriginalResponseAsync(msg => string statusMessage; if (game.ChallengerChoice != RPSChoice.None && game.OpponentChoice == RPSChoice.None) { - statusMessage = $"{challenger.Mention} has made their choice! Waiting for {opponent.Mention}..."; + statusMessage = $"{challenger.Mention} has made their choice!\nWaiting for {opponent.Mention}..."; } else if (game.ChallengerChoice == RPSChoice.None && game.OpponentChoice != RPSChoice.None) { - statusMessage = $"{opponent.Mention} has made their choice! Waiting for {challenger.Mention}..."; + statusMessage = $"{opponent.Mention} has made their choice!\nWaiting for {challenger.Mention}..."; } else { @@ -745,7 +745,7 @@ await Context.Interaction.ModifyOriginalResponseAsync(msg => .WithTitle("🎮 Rock Paper Scissors Challenge!") .WithDescription($"{challenger.Mention} vs {opponent.Mention}") .AddField("Status", statusMessage) - .AddField("How to Play", "Both players choose Rock 🪨, Paper 📄, or Scissors ✂️. You can change your choice until both players have decided.") + .AddField("How to Play", "Both players choose Rock 🪨, Paper 📄, or Scissors ✂️.\nYou can change your choice until both players have decided.") .WithFooter("This challenge will expire in 5 minutes"); var components = new ComponentBuilder() From b9cb25ad9a12a5b5b132e18763c4b6ba031fe0ae Mon Sep 17 00:00:00 2001 From: Pierre Demessence Date: Sat, 19 Jul 2025 20:35:59 +0200 Subject: [PATCH 4/8] Changed one sentence --- DiscordBot/Modules/UserSlashModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DiscordBot/Modules/UserSlashModule.cs b/DiscordBot/Modules/UserSlashModule.cs index 25bf748a..e9a9d282 100644 --- a/DiscordBot/Modules/UserSlashModule.cs +++ b/DiscordBot/Modules/UserSlashModule.cs @@ -619,7 +619,7 @@ public async Task RockPaperScissors([Summary(description: "The user you want to var challenger = await Context.Guild.GetUserAsync(gameData.ChallengerId); var challengedUser = await Context.Guild.GetUserAsync(gameData.OpponentId); - string timeoutMessage = "⏰ Rock Paper Scissors challenge expired - no one made their choice in time!"; + string timeoutMessage = "⏰ Rock Paper Scissors challenge expired!"; await originalResponse.ModifyAsync(msg => { From d7178a368b2778bfe5ff8ab3bf246eb55f09b621 Mon Sep 17 00:00:00 2001 From: Pierre Demessence Date: Sat, 19 Jul 2025 20:40:16 +0200 Subject: [PATCH 5/8] Show name of winner now --- DiscordBot/Modules/UserSlashModule.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/DiscordBot/Modules/UserSlashModule.cs b/DiscordBot/Modules/UserSlashModule.cs index e9a9d282..bc53d0c2 100644 --- a/DiscordBot/Modules/UserSlashModule.cs +++ b/DiscordBot/Modules/UserSlashModule.cs @@ -704,7 +704,7 @@ private async Task HandleRPSChoice(string gameKey, RPSChoice choice) // Game is complete, determine winner _activeRPSGames.TryRemove(gameKey, out _); - string result = DetermineRPSWinner(game.ChallengerChoice, game.OpponentChoice); + string result = DetermineRPSWinner(game.ChallengerChoice, game.OpponentChoice, challenger, opponent); string challengerEmoji = GetRPSEmoji(game.ChallengerChoice); string opponentEmoji = GetRPSEmoji(game.OpponentChoice); @@ -762,7 +762,7 @@ await Context.Interaction.ModifyOriginalResponseAsync(msg => } } - private string DetermineRPSWinner(RPSChoice challengerChoice, RPSChoice opponentChoice) + private string DetermineRPSWinner(RPSChoice challengerChoice, RPSChoice opponentChoice, IGuildUser challenger, IGuildUser opponent) { if (challengerChoice == opponentChoice) { @@ -773,14 +773,8 @@ private string DetermineRPSWinner(RPSChoice challengerChoice, RPSChoice opponent (challengerChoice == RPSChoice.Paper && opponentChoice == RPSChoice.Rock) || (challengerChoice == RPSChoice.Scissors && opponentChoice == RPSChoice.Paper); - if (challengerWins) - { - return "🏆 Challenger wins!"; - } - else - { - return "🏆 Opponent wins!"; - } + var winner = challengerWins ? challenger : opponent; + return $"🏆 {winner.DisplayName} wins!"; } private string GetRPSEmoji(RPSChoice choice) From fac5df6281bb205654e31719df6c23d264398099 Mon Sep 17 00:00:00 2001 From: Pierre Demessence Date: Sat, 19 Jul 2025 20:43:47 +0200 Subject: [PATCH 6/8] Refactor of some code --- DiscordBot/Modules/UserSlashModule.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/DiscordBot/Modules/UserSlashModule.cs b/DiscordBot/Modules/UserSlashModule.cs index bc53d0c2..4f93a44d 100644 --- a/DiscordBot/Modules/UserSlashModule.cs +++ b/DiscordBot/Modules/UserSlashModule.cs @@ -727,17 +727,15 @@ await Context.Interaction.ModifyOriginalResponseAsync(msg => { // Show that someone has made their choice string statusMessage; - if (game.ChallengerChoice != RPSChoice.None && game.OpponentChoice == RPSChoice.None) + if (game.ChallengerChoice == RPSChoice.None && game.OpponentChoice == RPSChoice.None) { - statusMessage = $"{challenger.Mention} has made their choice!\nWaiting for {opponent.Mention}..."; - } - else if (game.ChallengerChoice == RPSChoice.None && game.OpponentChoice != RPSChoice.None) - { - statusMessage = $"{opponent.Mention} has made their choice!\nWaiting for {challenger.Mention}..."; + statusMessage = "Waiting for both players to make their choices..."; } else { - statusMessage = "Waiting for both players to make their choices..."; + var playerReady = game.ChallengerChoice != RPSChoice.None ? challenger : opponent; + var playerWaiting = game.ChallengerChoice != RPSChoice.None ? opponent : challenger; + statusMessage = $"{playerReady.Mention} has made their choice!\nWaiting for {playerWaiting.Mention}..."; } var updatedEmbed = new EmbedBuilder() From c8cc61d8abb661923ddae5caa7e5f6b2a0fae3f5 Mon Sep 17 00:00:00 2001 From: Pierre Demessence Date: Sat, 19 Jul 2025 20:44:03 +0200 Subject: [PATCH 7/8] Mention instead of Display names --- DiscordBot/Modules/UserSlashModule.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DiscordBot/Modules/UserSlashModule.cs b/DiscordBot/Modules/UserSlashModule.cs index 4f93a44d..8c0d19dc 100644 --- a/DiscordBot/Modules/UserSlashModule.cs +++ b/DiscordBot/Modules/UserSlashModule.cs @@ -711,9 +711,9 @@ private async Task HandleRPSChoice(string gameKey, RPSChoice choice) var resultEmbed = new EmbedBuilder() .WithColor(Color.Gold) .WithTitle("🎮 Rock Paper Scissors Results!") - .AddField($"{challenger.DisplayName}", $"{challengerEmoji} {game.ChallengerChoice}", inline: true) + .AddField($"{challenger.Mention}", $"{challengerEmoji} {game.ChallengerChoice}", inline: true) .AddField("VS", "⚡", inline: true) - .AddField($"{opponent.DisplayName}", $"{opponentEmoji} {game.OpponentChoice}", inline: true) + .AddField($"{opponent.Mention}", $"{opponentEmoji} {game.OpponentChoice}", inline: true) .AddField("Result", result, inline: false) .Build(); @@ -772,7 +772,7 @@ private string DetermineRPSWinner(RPSChoice challengerChoice, RPSChoice opponent (challengerChoice == RPSChoice.Scissors && opponentChoice == RPSChoice.Paper); var winner = challengerWins ? challenger : opponent; - return $"🏆 {winner.DisplayName} wins!"; + return $"🏆 {winner.Mention} wins!"; } private string GetRPSEmoji(RPSChoice choice) From b6e5976af7bd2516d206f0a0d830bbab6a772aab Mon Sep 17 00:00:00 2001 From: Pierre Demessence Date: Sat, 19 Jul 2025 20:47:57 +0200 Subject: [PATCH 8/8] Revert part of c8cc61d8, don't use .Mention in field names. --- DiscordBot/Modules/UserSlashModule.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DiscordBot/Modules/UserSlashModule.cs b/DiscordBot/Modules/UserSlashModule.cs index 8c0d19dc..aa7cc535 100644 --- a/DiscordBot/Modules/UserSlashModule.cs +++ b/DiscordBot/Modules/UserSlashModule.cs @@ -711,9 +711,9 @@ private async Task HandleRPSChoice(string gameKey, RPSChoice choice) var resultEmbed = new EmbedBuilder() .WithColor(Color.Gold) .WithTitle("🎮 Rock Paper Scissors Results!") - .AddField($"{challenger.Mention}", $"{challengerEmoji} {game.ChallengerChoice}", inline: true) + .AddField($"{challenger.DisplayName}", $"{challengerEmoji} {game.ChallengerChoice}", inline: true) .AddField("VS", "⚡", inline: true) - .AddField($"{opponent.Mention}", $"{opponentEmoji} {game.OpponentChoice}", inline: true) + .AddField($"{opponent.DisplayName}", $"{opponentEmoji} {game.OpponentChoice}", inline: true) .AddField("Result", result, inline: false) .Build();