module Degenz.SlotMachine open System open System.Threading.Tasks open DSharpPlus open DSharpPlus.Entities open DSharpPlus.EventArgs open Degenz open Degenz.Messaging open Degenz.Types open Npgsql.FSharp type SlotSymbol = { index : int emojiName : string reel1Count : int reel2Count : int reel3Count : int } type Prize = | Money of int | Jackpot type Slot = | Symbol of SlotSymbol | Any let BigBrother = { index = 0 ; reel1Count = 2 ; reel2Count = 2 ; reel3Count = 2 ; emojiName = "bigbrother" } let Eye = { index = 1 ; reel1Count = 2 ; reel2Count = 3 ; reel3Count = 2 ; emojiName = "aneye" } let Obey = { index = 2 ; reel1Count = 3 ; reel2Count = 3 ; reel3Count = 3 ; emojiName = "obey" } let AnonMask = { index = 3 ; reel1Count = 2 ; reel2Count = 2 ; reel3Count = 4 ; emojiName = "anonmask" } let Rat = { index = 4 ; reel1Count = 4 ; reel2Count = 4 ; reel3Count = 4 ; emojiName = "rat" } let Ramen = { index = 5 ; reel1Count = 4 ; reel2Count = 5 ; reel3Count = 5 ; emojiName = "ramen" } let Sushi = { index = 6 ; reel1Count = 5 ; reel2Count = 5 ; reel3Count = 4 ; emojiName = "sushi" } let Pizza = { index = 7 ; reel1Count = 3 ; reel2Count = 3 ; reel3Count = 4 ; emojiName = "pizza" } //let Alcohol = { index = 8 ; reel1Count = 1 ; reel2Count = 1 ; reel3Count = 1 ; emojiName = "alcohol" } //let Circuit = { index = 9 ; reel1Count = 0 ; reel2Count = 0 ; reel3Count = 2 ; emojiName = "circuitboard" } //let OldTv = { index = 10 ; reel1Count = 1 ; reel2Count = 2 ; reel3Count = 2 ; emojiName = "oldtv" } //let Pills = { index = 11 ; reel1Count = 2 ; reel2Count = 1 ; reel3Count = 2 ; emojiName = "pills" } //let symbols = [ BigBrother ; Eye ; Obey ; AnonMask ; Sushi ; Ramen ; Pizza ; Alcohol ; Circuit ; OldTv ; Pills ; Rat ] let symbols = [ BigBrother ; Eye ; Obey ; AnonMask ; Sushi ; Ramen ; Pizza ; Rat ] type PrizeEntry = Slot * Slot * Slot * Prize let prizeTable = [| Symbol BigBrother , Symbol BigBrother , Symbol BigBrother , Jackpot Symbol Eye , Symbol Eye , Symbol Eye , Money 500 Symbol Eye , Symbol Eye , Symbol AnonMask , Money 500 Symbol AnonMask , Symbol AnonMask , Symbol AnonMask , Money 250 Symbol Obey , Symbol Obey , Symbol Obey , Money 250 Symbol Obey , Symbol Obey , Symbol AnonMask , Money 200 Symbol Sushi , Symbol Sushi , Symbol Sushi , Money 100 Symbol Ramen , Symbol Ramen , Symbol Ramen , Money 100 Symbol Pizza , Symbol Pizza , Symbol Pizza , Money 100 // Symbol Alcohol , Symbol Alcohol , Symbol Alcohol , Money 100 // Symbol OldTv , Symbol OldTv , Symbol OldTv , Money 100 // Symbol Pills , Symbol Pills , Symbol Pills , Money 100 Symbol Rat , Symbol Rat , Symbol Rat , Money 100 Symbol Sushi , Symbol Sushi , Any , Money 50 Any , Any , Symbol Pizza , Money 20 |] let calculateOdds (prizeTable : PrizeEntry array) prizeIndex = let totalPerReel (reel : SlotSymbol -> int) = List.sumBy reel symbols let r1 s = s.reel1Count let r2 s = s.reel2Count let r3 s = s.reel3Count let totalForReel1 = function Symbol sym -> r1 sym | Any -> totalPerReel (fun s -> s.reel1Count) let totalForReel2 = function Symbol sym -> r2 sym | Any -> totalPerReel (fun s -> s.reel2Count) let totalForReel3 = function Symbol sym -> r3 sym | Any -> totalPerReel (fun s -> s.reel3Count) let s1 , s2 , s3 , _ = prizeTable.[prizeIndex] totalForReel1 s1 * totalForReel2 s2 * totalForReel3 s3 let getTotalCombinations symbols = (List.sumBy (fun s -> s.reel1Count) symbols) * (List.sumBy (fun s -> s.reel2Count) symbols) * (List.sumBy (fun s -> s.reel3Count) symbols) let getOddsForPrize symbols prizeTable prizeIndex = let odds = calculateOdds prizeTable prizeIndex let total = getTotalCombinations symbols $"{odds} in {total }" let getTotalWaysOfWinning (prizeTable : PrizeEntry array) = [0..prizeTable.Length - 1] |> List.sumBy (calculateOdds prizeTable) let printOddsTable symbols (prizeTable : PrizeEntry array) = ([0..prizeTable.Length - 1] |> List.fold (fun acc elem -> let name = function Symbol s -> s.emojiName | Any -> "Any" let s1 , s2, s3, _ = prizeTable.[elem] acc + $"{name s1} | {name s2} | {name s3} --- {getOddsForPrize symbols prizeTable elem}\n") "") + "-----------------------------\n" + $"Odds of winning something: {getTotalWaysOfWinning prizeTable} out of {getTotalCombinations symbols}" //printOddsTable symbols prizeTable let getReel fn = List.fold (fun acc elem -> (List.replicate (fn elem) elem) @ acc) [] symbols |> List.toArray let reel1 = getReel (fun s -> s.reel1Count) let reel2 = getReel (fun s -> s.reel2Count) let reel3 = getReel (fun s -> s.reel3Count) let slotEmojiNames = [| "sushi" "bigbrother" "pizza" "ramen" "circuitboard" "obey" "pills" "oldtv" "rat" "aneye" "alcohol" "anonmask" |] let PlayPricex1 = 10 let PlayPricex2 = 20 let PlayPricex3 = 30 let BaseJackpotAmount = 0 let sleepTime = 1500 let mutable guildEmojis : Map option = None let mutable anyEmoji : DiscordEmoji option = None let getLastPlayedSlotFromPlayer (did : uint64) = async { let! events = GuildEnvironment.connectionString |> Sql.connect |> Sql.parameters [ "did", Sql.string (string did) ] |> Sql.query """ SELECT player_event.updated_at FROM player_event JOIN "user" u on u.id = player_event.user_id WHERE u.discord_id = @did AND event_type = 'PlayingSlot' """ |> Sql.executeAsync (fun read -> read.dateTime "updated_at" ) |> Async.AwaitTask match events with | [] -> return None | es -> return Some (List.head es) } let updateSlotPlayedFromPlayer (did : uint64) = GuildEnvironment.connectionString |> Sql.connect |> Sql.parameters [ "did", Sql.string (string did) ] |> Sql.query """ WITH usr AS (SELECT id FROM "user" WHERE discord_id = @did) UPDATE player_event SET updated_at = now() at time zone 'utc' FROM usr WHERE usr.id = user_id AND player_event.event_type = 'PlayingSlot'; """ |> Sql.executeNonQueryAsync |> Async.AwaitTask |> Async.Ignore let handlePrizeTable (ctx : IDiscordContext) = task { do! Messaging.defer ctx let embed = DiscordEmbedBuilder() match guildEmojis , anyEmoji with | Some emojis , Some any -> let folder (i,acc) elem = let s1,s2,s3,prize = elem let prizeTxt = match prize with Money m -> $"**{m}** $GBT" | Jackpot -> $"**JACKPOT**" let line = let getEmoji = function Symbol s -> Formatter.Emoji(emojis.[s.emojiName]) | Any -> Formatter.Emoji(any) $"{getEmoji s1}{getEmoji s2}{getEmoji s3}" let text = if prizeTxt.Contains("100") then acc else $"{acc}\n{line} | {prizeTxt}" (i + 1) , text let! jackpot = DbService.getStoreItemBySymbol "JACKPOT" let _ , rows = Array.fold folder (0, "") prizeTable embed.Color <- DiscordColor.Green embed.Title <- $"CURRENT JACKPOT: `{jackpot.Stock} 💰 $GBT`" embed.Description <- embed.Description + $"\n\n**PRIZE TABLE **\n{rows}\n\n**ANY SAME 3** | **100** $GBT" do! ctx.FollowUp(DiscordFollowupMessageBuilder().AsEphemeral().AddEmbed(embed)) do! Analytics.prizeTableViewed (ctx.GetDiscordMember()) | _ , _ -> return () } :> Task let spinEmojis (builder : DiscordFollowupMessageBuilder) (results : SlotSymbol array) (itx : DiscordInteraction) = async { builder.Content <- "**Spinning!**" let! followUp = itx.CreateFollowupMessageAsync(builder) |> Async.AwaitTask match guildEmojis with | Some emojis -> let e1 = Formatter.Emoji(emojis.[results.[0].emojiName]) let e2 = Formatter.Emoji(emojis.[results.[1].emojiName]) let e3 = Formatter.Emoji(emojis.[results.[2].emojiName]) do! Async.Sleep sleepTime let content = $"{e1}" let! _ = itx.EditFollowupMessageAsync(followUp.Id, DiscordWebhookBuilder().WithContent(content)) |> Async.AwaitTask do! Async.Sleep sleepTime let content = $"{e1}{e2}" let! _ = itx.EditFollowupMessageAsync(followUp.Id, DiscordWebhookBuilder().WithContent(content)) |> Async.AwaitTask do! Async.Sleep sleepTime let content = $"{e1}{e2}{e3}" let! _ = itx.EditFollowupMessageAsync(followUp.Id, DiscordWebhookBuilder().WithContent(content)) |> Async.AwaitTask return followUp , content | None -> let! _ = itx.EditFollowupMessageAsync(followUp.Id, DiscordWebhookBuilder().WithContent("An error occurred, please contact an Admin")) |> Async.AwaitTask return followUp , "" } let spin multiplier (ctx : IDiscordContext) = let playAmount = match multiplier with | 1 -> PlayPricex1 | 2 -> PlayPricex2 | _ -> PlayPricex3 let execute (player : PlayerData) = async { do! DbService.updatePlayerCurrency -playAmount player.DiscordId |> Async.Ignore let random = System.Random(System.Guid.NewGuid().GetHashCode()) let results = [| reel1.[random.Next(0, reel1.Length)] ; reel2.[random.Next(0, reel2.Length)] ; reel3.[random.Next(0, reel3.Length)] |] // let results = [| BigBrother ; BigBrother ; BigBrother |] let getPrize (results : SlotSymbol array) = prizeTable |> Array.tryPick (fun (s1,s2,s3,prize) -> let getIndex = function Symbol s -> s.index | Any -> -1 let indices = [ s1 ; s2 ; s3 ] |> List.map getIndex let compare i1 i2 = i1 = i2 || i1 = -1 let s1Result = compare indices.[0] results.[0].index let s2Result = compare indices.[1] results.[1].index let s3Result = compare indices.[2] results.[2].index if s1Result && s2Result && s3Result then Some prize else None) let prize = getPrize results let builder = DiscordFollowupMessageBuilder() builder.IsEphemeral <- true let itx = ctx.GetInteraction() let! followUpMessage , slotsContent = spinEmojis builder results itx do! Async.Sleep 2000 let embed = DiscordEmbedBuilder() embed.Author <- DiscordEmbedBuilder.EmbedAuthor() embed.Author.Name <- player.Name embed.Author.IconUrl <- ctx.GetDiscordMember().AvatarUrl embed.Title <- "Slots Results" let addGBTField (embed : DiscordEmbedBuilder) prize = if prize > 0 then embed.AddField("New $GBT Balance", $"`💰` {player.Bank} ⋙ `💰` {player.Bank - playAmount + prize} `(+{prize - playAmount} $GBT)`") |> ignore else embed.AddField("New $GBT Balance", $"`💰` {player.Bank} ⋙ `💰` {player.Bank + prize} `({prize} $GBT)`") |> ignore let! result , prizeAmount = async { match prize with | Some (Money amount) -> let prizeWithMultiplier = amount * multiplier do! DbService.updatePlayerCurrency prizeWithMultiplier player.DiscordId |> Async.Ignore embed.Color <- DiscordColor.Green embed.Description <- $"You WIN `💰` **{prizeWithMultiplier}** GBT 🎉" embed.AddField("Bet", $"{playAmount}", true) |> ignore // embed.AddField("Jackpot Rake", $"{multiplier}", true) |> ignore embed.AddField("Prize", $"{prizeWithMultiplier}", true) |> ignore addGBTField embed prizeWithMultiplier // do! incrementJackpot multiplier |> Async.Ignore return "WON" , prizeWithMultiplier | Some (Jackpot) -> let! jackpot = DbService.getStoreItemBySymbol "JACKPOT" embed.Color <- DiscordColor.Green embed.Description <- $"🎉🎉 YOU HIT THE JACKPOT 🎉🎉" embed.AddField("Bet", $"{playAmount}", true) |> ignore embed.AddField("Prize", $"{jackpot.Stock}", true) |> ignore addGBTField embed (jackpot.Stock * 1) do! DbService.updatePlayerCurrency (jackpot.Stock * 1) player.DiscordId |> Async.Ignore do! DbService.setItemStock BaseJackpotAmount "JACKPOT" |> Async.Ignore return "JACKPOT" , jackpot.Stock * 1 | None -> do! DbService.incrementItemStock 1 "JACKPOT" |> Async.Ignore embed.Description <- $"You LOST `💰` **{playAmount}** $GBT 😭" embed.Color <- DiscordColor.Red embed.AddField("Bet", $"{playAmount}", true) |> ignore embed.AddField("Jackpot Rake", $"{multiplier}", true) |> ignore addGBTField embed -playAmount return "LOST" , 0 } let dwb = DiscordWebhookBuilder() let button = DiscordButtonComponent(ButtonStyle.Success, $"spin-{multiplier}x", $"🎰 Spin Again") :> DiscordComponent dwb.AddComponents([| button |]).AddEmbed(embed).WithContent(slotsContent) |> ignore do! itx.EditFollowupMessageAsync(followUpMessage.Id, dwb) |> Async.AwaitTask |> Async.Ignore let hopMsg = DiscordMessageBuilder() hopMsg.Content <- let s1 = $"{slotsContent} | {player.Name}" match result with | "WON" -> s1 + $" **WON** {prizeAmount} $GBT playing slots!" | "LOST" -> s1 + $" **LOST** playing slots" | "JACKPOT" -> s1 + $" 🎉🎉🎉 **JUST HIT THE JACKPOT** and **WON** {prizeAmount}!!! 🎉🎉🎉" | _ -> "" let channel = ctx.GetGuild().GetChannel(GuildEnvironment.channelEventsHackerBattle) do! channel.SendMessageAsync(hopMsg) |> Async.AwaitTask |> Async.Ignore do! Analytics.slotPlayed (ctx.GetDiscordMember()) playAmount result prizeAmount } PlayerInteractions.executePlayerAction ctx (fun player -> async { if player.Bank >= playAmount then match! getLastPlayedSlotFromPlayer player.DiscordId with | Some timestamp -> if DateTime.UtcNow - timestamp > TimeSpan.FromSeconds(8) then do! updateSlotPlayedFromPlayer player.DiscordId do! execute player else do! Messaging.sendFollowUpMessage ctx "Wait till you finish the current spin!" | None -> let event = { Type = PlayingSlot ; Cooldown = 1 ; Timestamp = DateTime.UtcNow } do! DbService.addPlayerEvent player.DiscordId event |> Async.Ignore do! execute player else do! Messaging.sendFollowUpMessage ctx "You do not have sufficient funds to play" }) let handleButton _ (event : ComponentInteractionCreateEventArgs) = let ctx = DiscordEventContext event match event.Id with | "spin-1x" -> spin 1 ctx | "spin-2x" -> spin 2 ctx | "spin-3x" -> spin 3 ctx | "prizes" -> handlePrizeTable ctx | _ -> printfn "Wrong Spin ID" Task.CompletedTask |> Async.AwaitTask |> Async.Start Task.CompletedTask let handleMessageCreated _ (event : MessageCreateEventArgs) = task { if event.Channel.Id = GuildEnvironment.channelSlots && event.Author.Id <> GuildEnvironment.botClientSlots.Value.CurrentUser.Id then do! Async.Sleep 1000 do! event.Message.DeleteAsync() } :> Task let sendEmbed (jackpotNumChannel : DiscordChannel) slotsChannel (message : DiscordMessage option) = async { let builder = DiscordMessageBuilder() let embed = DiscordEmbedBuilder() embed.Title <- "Degenz Slot Machine" embed.ImageUrl <- "https://s7.gifyu.com/images/ezgif.com-gif-maker-268ecb6e4d28bd55a0.gif" embed.Description <- "Try Your Hand at a Game of SLOTS!\nEvery Spin Adds to The JACKPOT!\nGood luck Degenz ✊" let! jackpot = DbService.getStoreItemBySymbol "JACKPOT" embed.Title <- $"CURRENT JACKPOT: `{jackpot.Stock} 💰 $GBT`" // Update Jackpot channel // try // do! jackpotNumChannel.ModifyAsync(fun channelEditModel -> channelEditModel.Name <- $"Jackpot: {jackpot} 💰 $GBT") |> Async.AwaitTask // with ex -> printfn $"Error changing name: {ex.Message}" builder.AddEmbed(embed) |> ignore let button1 = DiscordButtonComponent(ButtonStyle.Success, $"spin-1x", $"🎰 Bet {PlayPricex1} $GBT") :> DiscordComponent let button2 = DiscordButtonComponent(ButtonStyle.Success, $"spin-2x", $"🎰 Bet {PlayPricex2} $GBT") :> DiscordComponent let button3 = DiscordButtonComponent(ButtonStyle.Success, $"spin-3x", $"🎰 Bet {PlayPricex3} $GBT") :> DiscordComponent let button4 = DiscordButtonComponent(ButtonStyle.Primary, $"prizes", $"Prize Table") :> DiscordComponent builder.AddComponents([| button1 ; button2 ; button3 ; button4 |]) |> ignore match GuildEnvironment.botClientSlots, message with | _ , Some m -> let! _ = m.ModifyAsync(builder) |> Async.AwaitTask return Some m | Some bot , None -> let! m = bot.SendMessageAsync(slotsChannel, builder) |> Async.AwaitTask do! m.PinAsync() |> Async.AwaitTask return Some m | _ -> return None } let sendEmbedWithLoop (jackpotNumChannel : DiscordChannel) (slotsChannel : DiscordChannel) = async { let! pins = slotsChannel.GetPinnedMessagesAsync() |> Async.AwaitTask let! message = match pins |> Seq.toList with | [] -> sendEmbed jackpotNumChannel slotsChannel None | msg::_ -> async.Return (Some msg) while true do do! Async.Sleep 30000 do! sendEmbed jackpotNumChannel slotsChannel message |> Async.Ignore } |> Async.Start let sendInitialEmbedFromLaunch (client : DiscordClient) = // let jackpotNumChannel = client.Guilds.[GuildEnvironment.guildId].GetChannel(GuildEnvironment.channelJackpotNum) let slotsChannel = client.Guilds.[GuildEnvironment.guildId].GetChannel(GuildEnvironment.channelSlots) sendEmbedWithLoop slotsChannel slotsChannel let sendInitialEmbedFromSlashCommand (ctx : IDiscordContext) = // let jackpotNumChannel = ctx.GetGuild().GetChannel(GuildEnvironment.channelJackpotNum) let slotsChannel = ctx.GetGuild().GetChannel(GuildEnvironment.channelSlots) sendEmbedWithLoop slotsChannel slotsChannel let handleGuildDownloadCompleted (client : DiscordClient) (event : GuildDownloadCompletedEventArgs) = task { let ( _ , guild ) = event.Guilds.TryGetValue(GuildEnvironment.guildId) guildEmojis <- guild.Emojis |> Seq.map (fun kvp -> kvp.Value) |> Seq.toArray |> Seq.filter (fun de -> Array.contains de.Name slotEmojiNames) |> Seq.distinctBy (fun de -> de.Name) |> Seq.map (fun de -> ( de.Name , de )) |> Map.ofSeq |> Some anyEmoji <- guild.Emojis |> Seq.tryPick (fun kvp -> if kvp.Value.Name = "any" then Some kvp.Value else None) sendInitialEmbedFromLaunch client } :> Task