open System.IO open System.Threading.Tasks open DSharpPlus open DSharpPlus.Entities open DSharpPlus.SlashCommands open Emzi0767.Utilities type Move = | Rock | Paper | Scissor type RoundResult = | P1Win | P2Win | Draw type Player = { name : string health : string } type Turn = | WaitingForBoth | WaitingForOne of Move | WaitingForTwo of Move | BothCompleted of Move * Move type Match = { player1 : DiscordUser player2 : DiscordUser round : int scorePlayer1 : int scorePlayer2 : int turn : Turn } type JoeBot() = inherit ApplicationCommandModule () static let mutable currentMatch : Match option = None [] member _.StartMatch (ctx : InteractionContext, [] player : DiscordUser) = async { currentMatch <- Some { player1 = ctx.User player2 = player round = 0 scorePlayer1 = 0 scorePlayer2 = 0 turn = WaitingForBoth } // We won't be able to find the user if they are Away or Sleeping apparently let ( result , discordMember ) = ctx.Guild.Members.TryGetValue(player.Id) if result then let yes = DiscordButtonComponent( ButtonStyle.Primary, "first_button", "I do") let no = DiscordButtonComponent( ButtonStyle.Danger, "second_button", "No thank you") let builder = DiscordMessageBuilder() let builder = builder.AddComponents(yes, no) use img = new FileStream("challenge.jpg", FileMode.Open) builder.WithFile(img) |> ignore builder.Content <- $"You have been challenged by {player.Username}, do you accept?" for channel in ctx.Guild.Channels do if channel.Value.Name = "battle-1" then do! channel.Value.SendMessageAsync builder |> Async.AwaitTask |> Async.Ignore let builder = DiscordInteractionResponseBuilder() builder.IsEphemeral <- true builder.Content <- $"Sending challenge to {player.Username}" do! ctx.CreateResponseAsync (builder) |> Async.AwaitTask else let builder = DiscordInteractionResponseBuilder() builder.IsEphemeral <- true builder.Content <- $"Unable to find user in this server" do! ctx.CreateResponseAsync (builder) |> Async.AwaitTask } |> Async.StartAsTask :> Task [] member _.SendMove (ctx : InteractionContext, [] moveString : string) = async { let move = match moveString.ToLower() with | "rock" -> Some Rock | "paper" -> Some Paper | "scissors" -> Some Scissor | _ -> None match currentMatch , move with | Some mtc , Some move -> let updatedTurn = match mtc.turn with | WaitingForBoth -> match ctx.User with | mem when mem = mtc.player1 -> WaitingForTwo move | mem when mem = mtc.player2 -> WaitingForOne move | _ -> mtc.turn | WaitingForOne p1m -> match ctx.User with | mem when mem = mtc.player1 -> BothCompleted ( p1m , move ) | mem when mem = mtc.player2 -> mtc.turn | _ -> mtc.turn | WaitingForTwo p2m -> match ctx.User with | mem when mem = mtc.player1 -> mtc.turn | mem when mem = mtc.player2 -> BothCompleted ( p2m , move ) | _ -> mtc.turn | _ -> mtc.turn match updatedTurn with | BothCompleted ( p1m , p2m ) -> let result = match p1m , p2m with | Rock , Paper -> P2Win | Rock , Scissor -> P1Win | Paper , Rock -> P1Win | Paper , Scissor -> P2Win | Scissor , Rock -> P2Win | Scissor , Paper -> P1Win | _ -> Draw match result with | P1Win | P2Win -> let winner = match result with P1Win -> mtc.player1 | P2Win -> mtc.player2 | Draw -> mtc.player1 let winningMove = match result with P1Win -> p1m | P2Win -> p2m | Draw -> p1m let losingMove = match result with P1Win -> p2m | P2Win -> p1m | Draw -> p1m let message = $"{winningMove} beats {losingMove}! {winner.Username} takes the round!" currentMatch <- Some { mtc with round = mtc.round + 1 scorePlayer1 = mtc.scorePlayer1 + (match result with P1Win -> 1 | _ -> 0) scorePlayer2 = mtc.scorePlayer2 + (match result with P2Win -> 1 | _ -> 0) turn = WaitingForBoth } if mtc.round >= 3 then let winnerScore = match result with P1Win -> mtc.scorePlayer1 | P2Win -> mtc.scorePlayer2 | Draw -> 0 let loserScore = match result with P1Win -> mtc.scorePlayer2 | P2Win -> mtc.scorePlayer1 | Draw -> 0 currentMatch <- None do! ctx.CreateResponseAsync $"{winner} wins the match {winnerScore} to {loserScore}! Awarding a 100 genz!" |> Async.AwaitTask else do! ctx.CreateResponseAsync message |> Async.AwaitTask | Draw -> currentMatch <- Some { mtc with turn = WaitingForBoth } do! ctx.CreateResponseAsync $"{mtc.player1.Username} and {mtc.player2.Username} both did {p1m}. Round was a draw! Go again!" |> Async.AwaitTask | _ -> if updatedTurn <> mtc.turn then currentMatch <- Some { mtc with turn = updatedTurn } let builder = DiscordInteractionResponseBuilder() do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder.WithContent(($"Move received by {ctx.Member.DisplayName}!"))) |> Async.AwaitTask else do! async{ return () } | None , _ -> do! ctx.CreateResponseAsync ("No match has been found, please use the '/start-match' command and mention the two players") |> Async.AwaitTask |> Async.Ignore | _ , None -> do! ctx.CreateResponseAsync $"Could not recognize move '{moveString}', please try again. Valid moves are 'rock', 'paper', or 'scissor'" |> Async.AwaitTask |> Async.Ignore } |> Async.StartAsTask :> Task [] member _.Status (ctx : InteractionContext) = async { let builder = DiscordInteractionResponseBuilder() builder.IsEphemeral <- true do! ctx.CreateResponseAsync(InteractionResponseType.ChannelMessageWithSource, builder.WithContent(sprintf "%A" currentMatch)) |> Async.AwaitTask } |> Async.StartAsTask :> Task let config = DiscordConfiguration() config.Token <- "OTIyNDIyMDIyMTI1MDEwOTU1.YcBOcw.JxfW1CSIwEO7j6RbRFCnPZ-HoTk" config.TokenType <- TokenType.Bot config.Intents <- DiscordIntents.All // config.MinimumLogLevel <- LogLevel.Trace let client = new DiscordClient(config) client.add_ComponentInteractionCreated(AsyncEventHandler( fun client event -> async { return () } |> Async.StartAsTask :> Task)) let slash = client.UseSlashCommands() slash.RegisterCommands(); client.ConnectAsync () |> Async.AwaitTask |> Async.RunSynchronously Task.Delay(-1) |> Async.AwaitTask |> Async.RunSynchronously client.DisconnectAsync () |> Async.AwaitTask |> Async.RunSynchronously