discord-bot-game/Bot/Games/SlotMachine.fs

379 lines
18 KiB
Forth
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}
let BigBrother = { index = 0 ; reel1Count = 1 ; reel2Count = 1 ; reel3Count = 1 ; emojiName = "bigbrother" }
let Eye = { index = 1 ; reel1Count = 3 ; reel2Count = 2 ; reel3Count = 1 ; emojiName = "aneye" }
let Obey = { index = 2 ; reel1Count = 2 ; reel2Count = 2 ; reel3Count = 2 ; emojiName = "obey" }
let AnonMask = { index = 3 ; reel1Count = 1 ; reel2Count = 2 ; reel3Count = 4 ; emojiName = "anonmask" }
let Ramen = { index = 5 ; reel1Count = 3 ; reel2Count = 3 ; reel3Count = 1 ; emojiName = "ramen" }
let Sushi = { index = 4 ; reel1Count = 3 ; reel2Count = 2 ; reel3Count = 2 ; emojiName = "sushi" }
let Pizza = { index = 6 ; reel1Count = 2 ; reel2Count = 4 ; reel3Count = 0 ; emojiName = "pizza" }
let Alcohol = { index = 7 ; reel1Count = 1 ; reel2Count = 1 ; reel3Count = 1 ; emojiName = "alcohol" }
let Circuit = { index = 0 ; reel1Count = 0 ; reel2Count = 0 ; reel3Count = 2 ; emojiName = "circuitboard" }
let OldTv = { index = 9 ; reel1Count = 1 ; reel2Count = 2 ; reel3Count = 2 ; emojiName = "oldtv" }
let Pills = { index = 10 ; reel1Count = 2 ; reel2Count = 1 ; reel3Count = 2 ; emojiName = "pills" }
let Rat = { index = 11 ; reel1Count = 2 ; reel2Count = 1 ; reel3Count = 1 ; emojiName = "rat" }
let symbols = [ BigBrother ; Eye ; Obey ; AnonMask ; Sushi ; Ramen ; Pizza ; Alcohol ; Circuit ; OldTv ; Pills ; Rat ]
//let symbols = [ BigBrother ; Eye ; Obey ; AnonMask ; Sushi ; Ramen ; Pizza ; Alcohol ]
let getReel fn = List.fold (fun acc elem -> (List.replicate (fn elem) elem) @ acc) [] symbols |> List.toArray
type Prize =
| Money of int<GBT>
| Jackpot
type Slot =
| Symbol of SlotSymbol
| Any
let prizeTable =
[| Symbol BigBrother , Symbol BigBrother , Symbol BigBrother , Jackpot
Symbol Eye , Symbol Eye , Symbol Eye , Money 500<GBT>
Symbol AnonMask , Symbol AnonMask , Symbol AnonMask , Money 250<GBT>
Symbol Obey , Symbol Obey , Symbol Obey , Money 200<GBT>
Symbol Ramen , Symbol Ramen , Symbol Ramen , Money 150<GBT>
Symbol Alcohol , Symbol Alcohol , Symbol Alcohol , Money 100<GBT>
Symbol OldTv , Symbol OldTv , Symbol OldTv , Money 100<GBT>
Symbol Pills , Symbol Pills , Symbol Pills , Money 100<GBT>
Symbol Rat , Symbol Rat , Symbol Rat , Money 100<GBT>
Symbol Sushi , Symbol Sushi , Any , Money 50<GBT>
Symbol Pizza , Any , Any , Money 20<GBT> |]
let totalPerReel (reel : SlotSymbol -> int) = List.sumBy reel symbols
let calculateOdds prizeIndex =
match prizeTable.[prizeIndex] with
| Symbol s1 , Symbol s2 , Symbol s3 , _ -> s1.reel1Count * s2.reel2Count * s3.reel3Count
| Symbol s1 , Symbol s2 , Any , _ -> s1.reel1Count * s2.reel2Count * totalPerReel (fun s -> s.reel3Count)
| Symbol s1 , Any , Any , _ -> s1.reel1Count * totalPerReel (fun s -> s.reel2Count) * totalPerReel (fun s -> s.reel3Count)
| _ -> 0
let getTotalCombinations () =
(List.sumBy (fun s -> s.reel1Count) symbols)
* (List.sumBy (fun s -> s.reel2Count) symbols)
* (List.sumBy (fun s -> s.reel3Count) symbols)
let getOddsForPrize prizeIndex =
let odds = calculateOdds prizeIndex
let total = getTotalCombinations ()
$"{odds} in {total }"
// getOddsForPrize 0
// getOddsForPrize 1
// getOddsForPrize 5
// getOddsForPrize 10
let getTotalWaysOfWinning () =
[0..prizeTable.Length - 1]
|> List.sumBy calculateOdds
//totalPerReel (fun s -> s.reel1Count)
//totalPerReel (fun s -> s.reel2Count)
//totalPerReel (fun s -> s.reel3Count)
let reel1 = getReel (fun s -> s.reel1Count)
let reel2 = getReel (fun s -> s.reel2Count)
let reel3 = getReel (fun s -> s.reel3Count)
let slots =
[| "https://s7.gifyu.com/images/aneye.png"
"https://s7.gifyu.com/images/anonmask.png"
"https://s7.gifyu.com/images/circuitboard.png"
"https://s7.gifyu.com/images/obey.png"
"https://s7.gifyu.com/images/oldtv.png"
"https://s7.gifyu.com/images/pills.png"
"https://s7.gifyu.com/images/pizza0d47578733961746.png"
"https://s7.gifyu.com/images/ramen0515f00869e1f4eb.png"
"https://s7.gifyu.com/images/rat69609f842a0eb9f5.png"
"https://s7.gifyu.com/images/alcohol.png"
"https://s7.gifyu.com/images/bigbrother.png"
"https://s7.gifyu.com/images/sushi.png" |]
// [| "https://s7.gifyu.com/images/A-bottle-of-pills0a3006d0170e08df.png"
// "https://s7.gifyu.com/images/an-eyec362d8152ae2382b.png"
// "https://s7.gifyu.com/images/anon-face-mask6c7624821c89fc08.png"
// "https://s7.gifyu.com/images/a-piece-of-sushi77071d30f60a89c6.png"
// "https://s7.gifyu.com/images/Circuit-board89056017b80f1d13.png"
// "https://s7.gifyu.com/images/OBEYf2a8234109836c03.png"
// "https://s7.gifyu.com/images/old-tv-screendc6bc9d4b6c1fd65.png"
// "https://s7.gifyu.com/images/pizza030ffc00ff50da0e.png"
// "https://s7.gifyu.com/images/ramen08336d448018c98f.png"
// "https://s7.gifyu.com/images/rat14f65f54f0d75036.png" |]
let slotEmojiNames =
[| "sushi"
"bigbrother"
"pizza"
"ramen"
"circuitboard"
"obey"
"pills"
"oldtv"
"rat"
"aneye"
"alcohol"
"anonmask" |]
let PlayPricex1 = 5<GBT>
let PlayPricex2 = 10<GBT>
let PlayPricex3 = 20<GBT>
let BaseJackpotAmount = 0<GBT>
let sleepTime = 1500
let mutable guildEmojis : Map<string, DiscordEmoji> option = None
let mutable anyEmoji : DiscordEmoji option = None
let getJackpotAmount () =
GuildEnvironment.connectionString
|> Sql.connect
|> Sql.query "SELECT stock FROM item WHERE symbol = 'JACKPOT'"
|> Sql.executeRowAsync (fun read -> (read.int "stock") * 1<GBT>)
|> Async.AwaitTask
let incrementJackpot amount =
GuildEnvironment.connectionString
|> Sql.connect
|> Sql.parameters [ ( "amount" , Sql.int (int amount) ) ]
|> Sql.query "UPDATE item SET stock = stock + @amount WHERE symbol = 'JACKPOT'"
|> Sql.executeNonQueryAsync
|> Async.AwaitTask
let resetJackpot amount =
GuildEnvironment.connectionString
|> Sql.connect
|> Sql.parameters [ ( "amount" , Sql.int (int amount) ) ]
|> Sql.query "UPDATE item SET stock = amount WHERE symbol = 'JACKPOT'"
|> Sql.executeNonQueryAsync
|> Async.AwaitTask
let handlePrizeTable (ctx : IDiscordContext) =
task {
do! Messaging.defer ctx
let embed = DiscordEmbedBuilder()
match guildEmojis , anyEmoji with
| Some emojis , Some any ->
let folder (i,acc,b) elem =
let s1,s2,s3,prize = elem
let prizeTxt = match prize with Money m -> $"**{m}** $GBT" | Jackpot -> $"**JACKPOT**"
let line =
match s1 , s2 , s3 with
| Symbol s1' , Symbol s2' , Symbol s3' ->
$"{Formatter.Emoji(emojis.[s1'.emojiName])}{Formatter.Emoji(emojis.[s2'.emojiName])}{Formatter.Emoji(emojis.[s3'.emojiName])}"
| Symbol s1' , Symbol s2' , Any ->
$"{Formatter.Emoji(emojis.[s1'.emojiName])}{Formatter.Emoji(emojis.[s2'.emojiName])}{Formatter.Emoji(any)}"
| Symbol s1' , Any , Any ->
$"{Formatter.Emoji(emojis.[s1'.emojiName])}{Formatter.Emoji(any)}{Formatter.Emoji(any)}"
| _ -> ""
let text =
if prizeTxt.Contains("100") then
acc
else
$"{acc}\n{line} {prizeTxt}"
(i + 1) , text , ( b || prizeTxt.Contains("100") )
let! jackpot = getJackpotAmount ()
let _ , rows , _ = Array.fold folder (0, "", false) prizeTable
embed.Color <- DiscordColor.Green
embed.Title <- $"CURRENT JACKPOT: `{jackpot} 💰 $GBT`"
embed.Description <- embed.Description + $"\n\n**PRIZE TABLE **\n{rows}\n\n**ANY 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 = async {
do! DbService.updatePlayerCurrency -playAmount player |> Async.Ignore
let random = Random(Guid.NewGuid().GetHashCode())
let symbols = [| reel1.[random.Next(0, reel1.Length)] ; reel2.[random.Next(0, reel2.Length)] ; reel3.[random.Next(0, reel3.Length)] |]
let prize =
prizeTable
|> Array.tryPick (fun (s1,s2,s3,prize) ->
match s1 , s2 , s3 with
| Symbol s1' , Symbol s2' , Symbol s3' when s1'.index = symbols.[0].index && s2'.index = symbols.[1].index && s3'.index = symbols.[2].index -> Some prize
| Symbol s1' , Symbol s2' , Any when s1'.index = symbols.[0].index && s2'.index = symbols.[1].index -> Some prize
| Symbol s1' , Any , Any when s1'.index = symbols.[0].index -> Some prize
| _ -> None)
let builder = DiscordFollowupMessageBuilder()
builder.IsEphemeral <- true
let itx = ctx.GetInteraction()
let! followUpMessage , slotsContent = spinEmojis builder symbols 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 =
let sym = if prize > 0<GBT> then "+" else "-"
embed.AddField("New 💰$GBT Balance", $"`💰` {player.Bank} `💰` {player.Bank + prize} `({sym}{abs prize} $GBT)`") |> ignore
let! result , prizeAmount = async {
match prize with
| Some (Money amount) ->
let prizeWithMultiplier = amount * multiplier
do! DbService.updatePlayerCurrency prizeWithMultiplier player |> Async.Ignore
embed.Color <- DiscordColor.Green
embed.Description <- $"You WIN `💰` **{prizeWithMultiplier}** GBT 🎉"
// embed.AddField("Result", $"{slotsContent}", true) |> ignore
embed.AddField("Bet", $"{playAmount}", true) |> ignore
embed.AddField("Prize", $"{prizeWithMultiplier}") |> ignore
addGBTField embed prizeWithMultiplier
return "WON" , prizeWithMultiplier
| Some (Jackpot) ->
let! jackpot = getJackpotAmount ()
embed.Color <- DiscordColor.Green
embed.Description <- $"🎉🎉 YOU HIT THE JACKPOT 🎉🎉"
// embed.AddField("Result", $"{slotsContent}", true) |> ignore
embed.AddField("Bet", $"{playAmount}", true) |> ignore
embed.AddField("Prize", $"{jackpot}") |> ignore
addGBTField embed jackpot
do! DbService.updatePlayerCurrency jackpot player |> Async.Ignore
do! resetJackpot BaseJackpotAmount |> Async.Ignore
return "JACKPOT" , jackpot
| None ->
do! incrementJackpot playAmount |> Async.Ignore
embed.Description <- $"You LOST `💰` **{playAmount}** $GBT `😭`"
embed.Color <- DiscordColor.Red
embed.AddField("Bet", $"{playAmount}", true) |> ignore
// embed.AddField("New JACKPOT", $"`💰` {jackpot} `$GBT`", true) |> ignore
// embed.AddField("Result", $"{slotsContent}", true) |> ignore
addGBTField embed -playAmount
return "LOST" , 0<GBT>
}
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
do! Analytics.slotPlayed (ctx.GetDiscordMember()) playAmount result prizeAmount
}
PlayerInteractions.executePlayerAction ctx (fun player -> async {
if player.Bank >= playAmount then
match! DbService.getLastPlayedSlotFromPlayer player.DiscordId with
| Some timestamp ->
if DateTime.UtcNow - timestamp > TimeSpan.FromSeconds(8) then
do! DbService.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<mins> ; 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 (_ : DiscordClient) (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 handleGuildDownloadCompleted (_ : DiscordClient) (event : GuildDownloadCompletedEventArgs) =
task {
let ( result , 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)
return ()
} :> Task
let sendInitialEmbed (ctx : IDiscordContext) =
let updateFn (ctx : IDiscordContext) (message : DiscordMessage option) = async {
let channel = ctx.GetGuild().GetChannel(GuildEnvironment.channelSlots)
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 <- "Want to try your luck?"
let! jackpot = getJackpotAmount ()
embed.Title <- $"CURRENT JACKPOT: `{jackpot} 💰 $GBT`"
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(channel, builder) |> Async.AwaitTask
return Some m
| _ -> return None
}
async {
let! message = updateFn ctx None
while true do
do! Async.Sleep 60000
do! updateFn ctx (message) |> Async.Ignore
} |> Async.Start