discord-bot-game/Bot/Games/SlotMachine.fs
2022-04-20 13:26:29 +07:00

354 lines
17 KiB
Forth
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.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 Eye , Symbol Eye , Symbol Obey , Money 500<GBT>
Symbol AnonMask , Symbol AnonMask , Symbol AnonMask , Money 250<GBT>
Symbol AnonMask , Symbol AnonMask , Symbol Eye , Money 250<GBT>
Symbol Ramen , Symbol Ramen , Symbol Ramen , Money 100<GBT>
Symbol Ramen , Symbol Ramen , Symbol Eye , 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 8
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 acc 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)}"
| _ -> ""
$"{acc}\n{line} {prizeTxt}"
let! jackpot = getJackpotAmount ()
let rows = Array.fold folder "" prizeTable
embed.Color <- DiscordColor.Green
embed.Title <- "Degenz Slots Prize Table"
embed.Description <- $"Current Jackpot At: **{jackpot}** $GBT\n\n**Combo** **Prize**\n{rows}"
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! You got **{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.Purple
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 <- $"Better luck next time! You paid {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 betText mult = if mult = multiplier then "Spin Again" else "Bet"
let button1 = DiscordButtonComponent(ButtonStyle.Success, $"spin-1x", $"{betText 1} {PlayPricex1} $GBT 🎰") :> DiscordComponent
let button2 = DiscordButtonComponent(ButtonStyle.Success, $"spin-2x", $"{betText 2} {PlayPricex2} $GBT 🎰") :> DiscordComponent
let button3 = DiscordButtonComponent(ButtonStyle.Success, $"spin-3x", $"{betText 3} {PlayPricex3} $GBT 🎰") :> DiscordComponent
dwb.AddComponents([| button1 ; button2 ; button3 |]).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 ->
if player.Bank >= playAmount then
execute player
else
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) =
async {
try
let channel = ctx.GetGuild().GetChannel(GuildEnvironment.channelSlots)
let builder = DiscordMessageBuilder()
let embed = DiscordEmbedBuilder()
embed.Title <- "Degenz Slot Machine"
embed.Description <- "Want to try your luck?"
embed.ImageUrl <- "https://s7.gifyu.com/images/ezgif.com-gif-maker-268ecb6e4d28bd55a0.gif"
builder.AddEmbed(embed) |> ignore
let button1 = DiscordButtonComponent(ButtonStyle.Success, $"spin-1x", $"Bet 1x - {PlayPricex1} $GBT 🎰") :> DiscordComponent
let button2 = DiscordButtonComponent(ButtonStyle.Success, $"spin-2x", $"Bet 2x - {PlayPricex2} $GBT 🎰") :> DiscordComponent
let button3 = DiscordButtonComponent(ButtonStyle.Success, $"spin-3x", $"Bet 3x - {PlayPricex3} $GBT 🎰") :> DiscordComponent
let button4 = DiscordButtonComponent(ButtonStyle.Primary, $"prizes", $"Show Prizes") :> DiscordComponent
builder.AddComponents([| button1 ; button2 ; button3 ; button4 |]) |> ignore
do! GuildEnvironment.botClientSlots.Value.SendMessageAsync(channel, builder)
|> Async.AwaitTask
|> Async.Ignore
with e ->
printfn $"Error trying to get channel Training Dojo\n\n{e.Message}"
} |> Async.RunSynchronously