mirror of
https://github.com/barelyprofessional/KfChatDotNet.git
synced 2026-05-02 04:22:04 -04:00
Mines (#60)
* Add MinesCommand Add MinesCommand parses user input and submits it to mines service * Add KasinoMines service to bot services Add KasinoMines service to bot services * kasinomines service code kasinomines service code holds all the game information so that games can be ongoing, you can leave your game and come back to it later, * Update MinesCommand.cs * Update KasinoMines.cs * Update MinesCommand.cs * add house edge to limbo add house edge to limbo * add house edge to keno add house edge to keno * Update BotServices.cs forgot to add kasino mines item * Update BuiltIn.cs add kasinomines cleanup delay setting * Update KenoCommand.cs add difficulty options to keno, classic low medium high default high * Update PlanesCommand.cs adds house edge to planes if your buffs cause house edge to be greater than 1, you have a HOUSE_EDGE - 1.0 % chance to get a guaranteed win, if house edge is less than 1, 1-HOUSE EDGE chance for a guaranteed loss * Update PlanesCommand.cs missed a counter update * Update PlinkoCommand.cs plinko house edge update changes vacuum strength based on house edge
This commit is contained in:
@@ -14,6 +14,8 @@ namespace KfChatDotNetBot.Commands.Kasino;
|
|||||||
public class KenoCommand : ICommand
|
public class KenoCommand : ICommand
|
||||||
{
|
{
|
||||||
public List<Regex> Patterns => [
|
public List<Regex> Patterns => [
|
||||||
|
new Regex(@"^keno (?<difficulty>classic|low|medium|high) (?<amount>\d+) (?<numbers>\d+)$", RegexOptions.IgnoreCase),
|
||||||
|
new Regex(@"^keno (?<difficulty>classic|low|medium|high) (?<amount>\d+\.\d+) (?<numbers>\d+)$", RegexOptions.IgnoreCase),
|
||||||
new Regex(@"^keno (?<amount>\d+) (?<numbers>\d+)$", RegexOptions.IgnoreCase),
|
new Regex(@"^keno (?<amount>\d+) (?<numbers>\d+)$", RegexOptions.IgnoreCase),
|
||||||
new Regex(@"^keno (?<amount>\d+\.\d+) (?<numbers>\d+)$", RegexOptions.IgnoreCase),
|
new Regex(@"^keno (?<amount>\d+\.\d+) (?<numbers>\d+)$", RegexOptions.IgnoreCase),
|
||||||
new Regex(@"^keno (?<amount>\d+)$", RegexOptions.IgnoreCase),
|
new Regex(@"^keno (?<amount>\d+)$", RegexOptions.IgnoreCase),
|
||||||
@@ -29,6 +31,9 @@ public class KenoCommand : ICommand
|
|||||||
Window = TimeSpan.FromSeconds(10)
|
Window = TimeSpan.FromSeconds(10)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private List<int> playerNumbers;
|
||||||
|
private List<int> casinoNumbers;
|
||||||
|
private decimal HOUSE_EDGE = (decimal)0.98;
|
||||||
private const string PlayerNumberDisplay = "⬜";
|
private const string PlayerNumberDisplay = "⬜";
|
||||||
private const string CasinoNumberDisplay = "🔶";
|
private const string CasinoNumberDisplay = "🔶";
|
||||||
private const string MatchRevealDisplay = "💠";
|
private const string MatchRevealDisplay = "💠";
|
||||||
@@ -48,7 +53,8 @@ public class KenoCommand : ICommand
|
|||||||
var kenoEnabled = (settings[BuiltIn.Keys.KasinoKenoEnabled]).ToBoolean();
|
var kenoEnabled = (settings[BuiltIn.Keys.KasinoKenoEnabled]).ToBoolean();
|
||||||
if (!kenoEnabled)
|
if (!kenoEnabled)
|
||||||
{
|
{
|
||||||
var gameDisabledCleanupDelay= TimeSpan.FromMilliseconds(settings[BuiltIn.Keys.KasinoGameDisabledMessageCleanupDelay].ToType<int>());
|
var gameDisabledCleanupDelay =
|
||||||
|
TimeSpan.FromMilliseconds(settings[BuiltIn.Keys.KasinoGameDisabledMessageCleanupDelay].ToType<int>());
|
||||||
await botInstance.SendChatMessageAsync(
|
await botInstance.SendChatMessageAsync(
|
||||||
$"{user.FormatUsername()}, keno is currently disabled.",
|
$"{user.FormatUsername()}, keno is currently disabled.",
|
||||||
true, autoDeleteAfter: gameDisabledCleanupDelay);
|
true, autoDeleteAfter: gameDisabledCleanupDelay);
|
||||||
@@ -64,8 +70,20 @@ public class KenoCommand : ICommand
|
|||||||
true, autoDeleteAfter: cleanupDelay);
|
true, autoDeleteAfter: cleanupDelay);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string difficultyString;
|
||||||
|
if (!arguments.TryGetValue("difficulty", out var difficultyArg))
|
||||||
|
{
|
||||||
|
difficultyString = "high";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
difficultyString = difficultyArg.Value;
|
||||||
|
}
|
||||||
var wager = Convert.ToDecimal(amount.Value);
|
var wager = Convert.ToDecimal(amount.Value);
|
||||||
var numbers = !arguments.TryGetValue("numbers", out var userNumbers) ? 10 : Convert.ToInt32(userNumbers.Value); //if user just enters !keno <wager>
|
var numbers = !arguments.TryGetValue("numbers", out var userNumbers)
|
||||||
|
? 10
|
||||||
|
: Convert.ToInt32(userNumbers.Value); //if user just enters !keno <wager>
|
||||||
var gambler = await Money.GetGamblerEntityAsync(user.Id, ct: ctx);
|
var gambler = await Money.GetGamblerEntityAsync(user.Id, ct: ctx);
|
||||||
if (gambler == null)
|
if (gambler == null)
|
||||||
throw new InvalidOperationException($"Caught a null when retrieving gambler for {user.KfUsername}");
|
throw new InvalidOperationException($"Caught a null when retrieving gambler for {user.KfUsername}");
|
||||||
@@ -84,23 +102,73 @@ public class KenoCommand : ICommand
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var payoutMultipliers = new[,]//stole the payout multis from stake keno and re added the RTP, except for the 1000x
|
var payoutMultipliersHigh =
|
||||||
|
new[,] //stole the payout multis from stake keno and re added the RTP, except for the 1000x
|
||||||
{
|
{
|
||||||
{0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, // 1 selection
|
{ 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // 1 selection
|
||||||
{0.0, 0.0, 17.27, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, // 2 selections
|
{ 0.0, 0.0, 17.27, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // 2 selections
|
||||||
{0.0, 0.0, 0.0, 82.32, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, // 3 selections
|
{ 0.0, 0.0, 0.0, 82.32, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // 3 selections
|
||||||
{0.0, 0.0, 0.0, 10.1, 261.61, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, // 4 selections
|
{ 0.0, 0.0, 0.0, 10.1, 261.61, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // 4 selections
|
||||||
{0.0, 0.0, 0.0, 4.5, 48.48, 454.54, 0.0, 0.0, 0.0, 0.0, 0.0}, // 5 selections
|
{ 0.0, 0.0, 0.0, 4.5, 48.48, 454.54, 0.0, 0.0, 0.0, 0.0, 0.0 }, // 5 selections
|
||||||
{0.0, 0.0, 0.0, 0.0, 11.11, 353.53, 717.17, 0.0, 0.0, 0.0, 0.0}, // 6 selections
|
{ 0.0, 0.0, 0.0, 0.0, 11.11, 353.53, 717.17, 0.0, 0.0, 0.0, 0.0 }, // 6 selections
|
||||||
{0.0, 0.0, 0.0, 0.0, 7.07, 90.90, 404.04, 808.08, 0.0, 0.0, 0.0}, // 7 selections
|
{ 0.0, 0.0, 0.0, 0.0, 7.07, 90.90, 404.04, 808.08, 0.0, 0.0, 0.0 }, // 7 selections
|
||||||
{0.0, 0.0, 0.0, 0.0, 5.05, 20.20, 272.72, 606.06, 909.09, 0.0, 0.0}, // 8 selections
|
{ 0.0, 0.0, 0.0, 0.0, 5.05, 20.20, 272.72, 606.06, 909.09, 0.0, 0.0 }, // 8 selections
|
||||||
{0.0, 0.0, 0.0, 0.0, 4.04, 11.11, 56.56, 505.05, 808.08, 1000.0, 0.0}, // 9 selections
|
{ 0.0, 0.0, 0.0, 0.0, 4.04, 11.11, 56.56, 505.05, 808.08, 1000.0, 0.0 }, // 9 selections
|
||||||
{0.0, 0.0, 0.0, 0.0, 3.53, 8.08, 13.13, 63.63, 505.05, 808.08, 1000.0} // 10 selections
|
{ 0.0, 0.0, 0.0, 0.0, 3.53, 8.08, 13.13, 63.63, 505.05, 808.08, 1000.0 } // 10 selections
|
||||||
};
|
};
|
||||||
var playerNumbers = GenerateKenoNumbers(numbers, gambler);
|
var payoutMultipliersClassic =
|
||||||
var casinoNumbers = GenerateKenoNumbers(10, gambler);
|
new[,] //stole the payout multis from stake keno and re added the RTP, except for the 1000x
|
||||||
|
{
|
||||||
|
{ 0.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // 1 selection
|
||||||
|
{ 0.0, 1.93, 4.59, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // 2 selections
|
||||||
|
{ 0.0, 1.02, 3.16, 10.6, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // 3 selections
|
||||||
|
{ 0.0, 0.81, 1.83, 10.1, 5.1, 22.96, 0.0, 0.0, 0.0, 0.0, 0.0 }, // 4 selections
|
||||||
|
{ 0.0, 0.26, 1.42, 4.18, 16.83, 36.73, 0.0, 0.0, 0.0, 0.0, 0.0 }, // 5 selections
|
||||||
|
{ 0.0, 0.0, 1.02, 3.75, 7.14, 16.83, 40.81, 0.0, 0.0, 0.0, 0.0 }, // 6 selections
|
||||||
|
{ 0.0, 0.0, 0.46, 3.06, 4.59, 14.28, 31.63, 61.22, 0.0, 0.0, 0.0 }, // 7 selections
|
||||||
|
{ 0.0, 0.0, 0.0, 2.24, 4.08, 13.26, 22.44, 56.12, 71.42, 0.0, 0.0 }, // 8 selections
|
||||||
|
{ 0.0, 0.0, 0.0, 1.58, 3.06, 8.16, 15.30, 44.89, 61.22, 86.73, 0.0 }, // 9 selections
|
||||||
|
{ 0.0, 0.0, 0.0, 1.42, 2.29, 4.59, 8.16, 17.34, 51.02, 81.63, 102.04 } // 10 selections
|
||||||
|
};
|
||||||
|
var payoutMultipliersLow =
|
||||||
|
new[,] //stole the payout multis from stake keno and re added the RTP, except for the 1000x
|
||||||
|
{
|
||||||
|
{ 0.7, 1.85, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // 1 selection
|
||||||
|
{ 0.0, 2.04, 3.87, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // 2 selections
|
||||||
|
{ 0.0, 1.12, 1.4, 26.53, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // 3 selections
|
||||||
|
{ 0.0, 0.0, 2.24, 8.06, 91.83, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // 4 selections
|
||||||
|
{ 0.0, 0.0, 1.53, 4.28, 13.26, 306.12, 0.0, 0.0, 0.0, 0.0, 0.0 }, // 5 selections
|
||||||
|
{ 0.0, 0.0, 1.12, 2.04, 6.32, 102.04, 714.28, 0.0, 0.0, 0.0, 0.0 }, // 6 selections
|
||||||
|
{ 0.0, 0.0, 1.12, 1.63, 3.57, 15.3, 229.59, 714.28, 0.0, 0.0, 0.0 }, // 7 selections
|
||||||
|
{ 0.0, 0.0, 1.12, 1.53, 2.04, 5.61, 39.79, 102.04, 816.32, 0.0, 0.0 }, // 8 selections
|
||||||
|
{ 0.0, 0.0, 1.12, 1.32, 1.73, 2.55, 7.65, 51.02, 255.1, 1000.0, 0.0 }, // 9 selections
|
||||||
|
{ 0.0, 0.0, 1.12, 1.22, 1.32, 1.83, 3.57, 13.26, 51.02, 255.1, 1000.0 } // 10 selections
|
||||||
|
};
|
||||||
|
var payoutMultipliersMedium =
|
||||||
|
new[,] //stole the payout multis from stake keno and re added the RTP, except for the 1000x
|
||||||
|
{
|
||||||
|
{ 0.4, 2.8, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // 1 selection
|
||||||
|
{ 0.0, 1.83, 5.2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // 2 selections
|
||||||
|
{ 0.0, 0.0, 2.85, 51.02, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // 3 selections
|
||||||
|
{ 0.0, 0.0, 1.73, 10.2, 102.04, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }, // 4 selections
|
||||||
|
{ 0.0, 0.0, 1.42, 4.08, 14.28, 397.95, 0.0, 0.0, 0.0, 0.0, 0.0 }, // 5 selections
|
||||||
|
{ 0.0, 0.0, 0.0, 3.06, 9.18, 183.67, 724.48, 0.0, 0.0, 0.0, 0.0 }, // 6 selections
|
||||||
|
{ 0.0, 0.0, 0.0, 2.04, 7.14, 30.61, 408.16, 816.32, 0.0, 0.0, 0.0 }, // 7 selections
|
||||||
|
{ 0.0, 0.0, 0.0, 2.04, 4.08, 11.22, 68.36, 408.16, 918.36, 0.0, 0.0 }, // 8 selections
|
||||||
|
{ 0.0, 0.0, 0.0, 2.04, 2.55, 11.11, 56.56, 505.05, 808.08, 1000.0, 0.0 }, // 9 selections
|
||||||
|
{ 0.0, 0.0, 0.0, 0.0, 3.53, 5.1, 15.3, 63.63, 102.04, 510.2, 1000.0 } // 10 selections
|
||||||
|
};
|
||||||
|
Dictionary<string, double[,]> payoutMultipliers = new Dictionary<string, double[,]>{
|
||||||
|
{ "high", payoutMultipliersHigh },
|
||||||
|
{ "low", payoutMultipliersLow},
|
||||||
|
{ "medium", payoutMultipliersMedium},
|
||||||
|
{ "classic", payoutMultipliersClassic}
|
||||||
|
};
|
||||||
|
|
||||||
|
playerNumbers = GenerateKenoNumbers(numbers, gambler);
|
||||||
|
casinoNumbers = GenerateKenoNumbers(10, gambler, true);
|
||||||
var matches = playerNumbers.Intersect(casinoNumbers).ToList();
|
var matches = playerNumbers.Intersect(casinoNumbers).ToList();
|
||||||
var payoutMulti = payoutMultipliers[numbers - 1, matches.Count];
|
var payoutMulti = payoutMultipliers[difficultyString][numbers - 1, matches.Count];
|
||||||
|
|
||||||
await AnimatedDisplayTable(playerNumbers, casinoNumbers, matches, botInstance);
|
await AnimatedDisplayTable(playerNumbers, casinoNumbers, matches, botInstance);
|
||||||
var colors =
|
var colors =
|
||||||
@@ -202,7 +270,7 @@ public class KenoCommand : ICommand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<int> GenerateKenoNumbers(int size, GamblerDbModel gambler)
|
private List<int> GenerateKenoNumbers(int size, GamblerDbModel gambler, bool kasino = false)
|
||||||
{
|
{
|
||||||
var numbers = new List<int>();
|
var numbers = new List<int>();
|
||||||
for (var i = 0; i < size; i++)
|
for (var i = 0; i < size; i++)
|
||||||
@@ -212,6 +280,8 @@ public class KenoCommand : ICommand
|
|||||||
{
|
{
|
||||||
var randomNum = Money.GetRandomNumber(gambler, 1, 40);
|
var randomNum = Money.GetRandomNumber(gambler, 1, 40);
|
||||||
if (numbers.Contains(randomNum)) continue;
|
if (numbers.Contains(randomNum)) continue;
|
||||||
|
if (kasino && Money.GetRandomDouble(gambler) > (double)HOUSE_EDGE &&
|
||||||
|
playerNumbers.Contains(randomNum)) continue; //rigging function
|
||||||
numbers.Add(randomNum);
|
numbers.Add(randomNum);
|
||||||
repeatNum = false;
|
repeatNum = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ public class LimboCommand : ICommand
|
|||||||
|
|
||||||
private const double Min = 1;
|
private const double Min = 1;
|
||||||
private const double Max = 10000;
|
private const double Max = 10000;
|
||||||
|
private decimal HOUSE_EDGE = (decimal)0.98;
|
||||||
|
|
||||||
public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments,
|
public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments,
|
||||||
CancellationToken ctx)
|
CancellationToken ctx)
|
||||||
@@ -119,10 +120,10 @@ public class LimboCommand : ICommand
|
|||||||
|
|
||||||
//returns a distribution with a 1/multi chance of getting a number below or above sqr(min * max) (so max should basically be multi^2). basically gives you a 1/x fair chance to win
|
//returns a distribution with a 1/multi chance of getting a number below or above sqr(min * max) (so max should basically be multi^2). basically gives you a 1/x fair chance to win
|
||||||
//then scales the number using the number scaling function
|
//then scales the number using the number scaling function
|
||||||
private static decimal[] Get1XWeightedRandomNumber(double minValue, double maxValue, decimal multi)
|
private decimal[] Get1XWeightedRandomNumber(double minValue, double maxValue, decimal multi)
|
||||||
{
|
{
|
||||||
var random = RandomShim.Create(StandardRng.Create());
|
var random = RandomShim.Create(StandardRng.Create());
|
||||||
var skew = 1.0 / (double)(multi * (decimal)1.01);
|
var skew = 1.0 / (double)(multi);
|
||||||
var gamma = Math.Log(0.5) / Math.Log(skew);
|
var gamma = Math.Log(0.5) / Math.Log(skew);
|
||||||
var r = random.NextDouble();
|
var r = random.NextDouble();
|
||||||
var rP = 1 - Math.Pow(1 - r, gamma);
|
var rP = 1 - Math.Pow(1 - r, gamma);
|
||||||
@@ -130,7 +131,7 @@ public class LimboCommand : ICommand
|
|||||||
var lnMax = Math.Log(maxValue);
|
var lnMax = Math.Log(maxValue);
|
||||||
var exponent = lnMin + rP * (lnMax - lnMin);
|
var exponent = lnMin + rP * (lnMax - lnMin);
|
||||||
var result = new decimal[2];
|
var result = new decimal[2];
|
||||||
result[0] = (decimal)Math.Exp(exponent);
|
result[0] = (decimal)Math.Exp(exponent) * HOUSE_EDGE;
|
||||||
result[1] = GetScaledNumber(lnMin, lnMax, exponent, result[0], multi);
|
result[1] = GetScaledNumber(lnMin, lnMax, exponent, result[0], multi);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
225
KfChatDotNetBot/Commands/Kasino/MinesCommand.cs
Normal file
225
KfChatDotNetBot/Commands/Kasino/MinesCommand.cs
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using KfChatDotNetBot.Extensions;
|
||||||
|
using KfChatDotNetBot.Models;
|
||||||
|
using KfChatDotNetBot.Models.DbModels;
|
||||||
|
using KfChatDotNetBot.Services;
|
||||||
|
using KfChatDotNetBot.Settings;
|
||||||
|
using KfChatDotNetWsClient.Models.Events;
|
||||||
|
|
||||||
|
namespace KfChatDotNetBot.Commands.Kasino;
|
||||||
|
|
||||||
|
|
||||||
|
public class MinesCommand : ICommand
|
||||||
|
{
|
||||||
|
public List<Regex> Patterns => [
|
||||||
|
//attempting to continue a game below here
|
||||||
|
new Regex(@"^mines (?<betString>.+) (?<cashout>cashout|)$", RegexOptions.IgnoreCase),
|
||||||
|
new Regex(@"^mines (?<picks>\d+) (?<cashout>cashout|)$", RegexOptions.IgnoreCase),
|
||||||
|
//attempting to start a game below here
|
||||||
|
new Regex(@"^mines (?<bet>\d+\.\d+) (?<size>\d+) (?<mines>\d+) (?<betString>.+) (?<cashout>cashout|)$", RegexOptions.IgnoreCase),
|
||||||
|
new Regex(@"^mines (?<bet>\d+) (?<size>\d+) (?<mines>\d+) (?<betString>.+) (?<cashout>cashout|)$", RegexOptions.IgnoreCase),
|
||||||
|
new Regex(@"^mines (?<bet>\d+\.\d+) (?<size>\d+) (?<mines>\d+) (?<picks>\d+) (?<cashout>cashout|)$", RegexOptions.IgnoreCase),
|
||||||
|
new Regex(@"^mines (?<bet>\d+) (?<size>\d+) (?<mines>\d+) (?<picks>\d+) (?<cashout>cashout|)$", RegexOptions.IgnoreCase),
|
||||||
|
//cashout
|
||||||
|
new Regex(@"^mines (?<cashout>cashout)$", RegexOptions.IgnoreCase),
|
||||||
|
//refresh
|
||||||
|
new Regex(@"^mines (?<refresh>refresh)$", RegexOptions.IgnoreCase),
|
||||||
|
//get info
|
||||||
|
new Regex("^mines")
|
||||||
|
];
|
||||||
|
public string? HelpText => "!mines <bet> <board size> <number of mines> <picks> to play simple mines. !mines <bet> <board size> <number of mines> <betString> for advanced mines. Tool: https://i.ddos.lgbt/raw/UJ9Dty.html";
|
||||||
|
public UserRight RequiredRight => UserRight.Loser;
|
||||||
|
public TimeSpan Timeout => TimeSpan.FromSeconds(30);
|
||||||
|
|
||||||
|
private const string betPattern = @"(?<row>\d+),(?<col>\d+)";
|
||||||
|
private const string toolUrl = "https://i.ddos.lgbt/raw/Kasino%20Mines%20Interface.html";
|
||||||
|
|
||||||
|
public RateLimitOptionsModel? RateLimitOptions => new RateLimitOptionsModel
|
||||||
|
{
|
||||||
|
MaxInvocations = 1,
|
||||||
|
Window = TimeSpan.FromSeconds(10)
|
||||||
|
};
|
||||||
|
|
||||||
|
public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments,
|
||||||
|
CancellationToken ctx)
|
||||||
|
{
|
||||||
|
|
||||||
|
var settings = await SettingsProvider.GetMultipleValuesAsync([
|
||||||
|
BuiltIn.Keys.KasinoMinesCleanupDelay, BuiltIn.Keys.KiwiFarmsGreenColor, BuiltIn.Keys.KiwiFarmsRedColor,
|
||||||
|
BuiltIn.Keys.KasinoMinesEnabled, BuiltIn.Keys.KasinoGameDisabledMessageCleanupDelay
|
||||||
|
]);
|
||||||
|
var cleanupDelay = TimeSpan.FromMilliseconds(settings[BuiltIn.Keys.KasinoMinesCleanupDelay].ToType<int>());
|
||||||
|
if (!settings[BuiltIn.Keys.KasinoMinesEnabled].ToBoolean())
|
||||||
|
{
|
||||||
|
var gameDisabledCleanupDelay= TimeSpan.FromMilliseconds(settings[BuiltIn.Keys.KasinoGameDisabledMessageCleanupDelay].ToType<int>());
|
||||||
|
await botInstance.SendChatMessageAsync(
|
||||||
|
$"{user.FormatUsername()}, mines is currently disabled.",
|
||||||
|
true, autoDeleteAfter: gameDisabledCleanupDelay);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var gambler = await Money.GetGamblerEntityAsync(user.Id, ct: ctx);
|
||||||
|
if (gambler == null)
|
||||||
|
throw new InvalidOperationException($"Caught a null when retrieving gambler for {user.KfUsername}");
|
||||||
|
bool cashout = false;
|
||||||
|
if (message.Message.Contains("cashout")) cashout = true;
|
||||||
|
//check if user has an existing game already
|
||||||
|
if (!botInstance.BotServices.KasinoMines.activeGames.ContainsKey(gambler.Id))
|
||||||
|
{
|
||||||
|
if (arguments.TryGetValue("refresh", out var refresh))
|
||||||
|
{
|
||||||
|
await botInstance.SendChatMessageAsync(
|
||||||
|
$"{user.FormatUsername()}, you don't have a game running. !mines <bet> <board size> <number of mines> <picks> to play simple mines. !mines <bet> <board size> <number of mines> <betString> for advanced mines. Tool: {toolUrl}",
|
||||||
|
true, autoDeleteAfter: cleanupDelay);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//if there is no game currently running
|
||||||
|
if (!arguments.TryGetValue("bet", out var bet))
|
||||||
|
{
|
||||||
|
await botInstance.SendChatMessageAsync(
|
||||||
|
$"{user.FormatUsername()}, not enough arguments. !mines <bet> <board size> <number of mines> <picks> to play simple mines. !mines <bet> <board size> <number of mines> <betString> for advanced mines. Tool: {toolUrl}",
|
||||||
|
true, autoDeleteAfter: cleanupDelay);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
decimal wager = Convert.ToDecimal(bet.Value);
|
||||||
|
if (gambler.Balance < wager)
|
||||||
|
{
|
||||||
|
await botInstance.SendChatMessageAsync(
|
||||||
|
$"{user.FormatUsername()}, your balance is too low. Balance: {gambler.Balance.FormatKasinoCurrencyAsync()}", true, autoDeleteAfter: cleanupDelay);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!arguments.TryGetValue("size", out var size) || !arguments.TryGetValue("mines", out var mines))
|
||||||
|
{
|
||||||
|
await botInstance.SendChatMessageAsync(
|
||||||
|
$"{user.FormatUsername()}, not enough arguments. !mines <bet> <board size> <number of mines> <picks> to play simple mines. !mines <bet> <board size> <number of mines> <betString> for advanced mines. Tool: {toolUrl}",
|
||||||
|
true, autoDeleteAfter: cleanupDelay);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pick = 0;
|
||||||
|
List<(int r, int c)> precisePicks = new();
|
||||||
|
if (arguments.TryGetValue("picks", out var picks)) //if they are using picks to randomly select squares to reveal
|
||||||
|
{
|
||||||
|
pick = Convert.ToInt32(picks.Value);
|
||||||
|
}
|
||||||
|
else if (arguments.TryGetValue("betString", out var betString)) //if they are using precise picks manually or from the tool to select specific squares to reveal
|
||||||
|
{
|
||||||
|
var matches = Regex.Matches(message.Message, betPattern);
|
||||||
|
if (matches.Count == 0 || matches == null) //if invalid bet string
|
||||||
|
{
|
||||||
|
await botInstance.SendChatMessageAsync(
|
||||||
|
$"{user.FormatUsername()}, invalid bet string. Example: !mines 100 10 10 1,3 1,5 2,6 - or use the tool: {toolUrl}", true, autoDeleteAfter: cleanupDelay);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach (Match match in matches)
|
||||||
|
{
|
||||||
|
precisePicks.Add((Convert.ToInt32(match.Groups["row"].Value), Convert.ToInt32(match.Groups["col"].Value)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else //if they didn't put anything
|
||||||
|
{
|
||||||
|
await botInstance.SendChatMessageAsync(
|
||||||
|
$"{user.FormatUsername()}, not enough arguments. !mines <bet> <board size> <number of mines> <picks> to play simple mines. !mines <bet> <board size> <number of mines> <betString> for advanced mines. Tool: {toolUrl}",
|
||||||
|
true, autoDeleteAfter: cleanupDelay);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int boardSize = Convert.ToInt32(size.Value);
|
||||||
|
if (boardSize < 2 || boardSize > 10)
|
||||||
|
{
|
||||||
|
await botInstance.SendChatMessageAsync($"{user.FormatUsername()}, board size must be between 2 and 10.",true, autoDeleteAfter: cleanupDelay);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int minesCount = Convert.ToInt32(mines.Value);
|
||||||
|
if (minesCount < 1 || minesCount > (boardSize * boardSize) - 1)
|
||||||
|
{
|
||||||
|
await botInstance.SendChatMessageAsync($"{user.FormatUsername()}, number of mines must be between 1 and {boardSize * boardSize - 1}(boardSize * boardSize - 1).",true, autoDeleteAfter: cleanupDelay);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//at this point all valid values so good to continue making the game
|
||||||
|
await botInstance.BotServices.KasinoMines.CreateGame(gambler, wager, boardSize, minesCount);
|
||||||
|
var msg = await botInstance.SendChatMessageAsync(
|
||||||
|
$"{botInstance.BotServices.KasinoMines.activeGames[gambler.Id].ToString()}", true);
|
||||||
|
|
||||||
|
if (pick == 0) //if using coordinates
|
||||||
|
{
|
||||||
|
var game = botInstance.BotServices.KasinoMines.activeGames[gambler.Id];
|
||||||
|
foreach (var coord in precisePicks)
|
||||||
|
{
|
||||||
|
if (game.betsPlaced.Contains(coord) || coord.r <= 0 || coord.r > game.size || coord.c <= 0 || coord.c > game.size)
|
||||||
|
{
|
||||||
|
await botInstance.SendChatMessageAsync($"{user.FormatUsername()}, you can't place duplicate or invalid bets. Use the tool: {toolUrl}", true, autoDeleteAfter: cleanupDelay);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await botInstance.BotServices.KasinoMines.Bet(gambler.Id, precisePicks, msg, cashout);
|
||||||
|
}
|
||||||
|
else //if using picks
|
||||||
|
{
|
||||||
|
await botInstance.BotServices.KasinoMines.Bet(gambler.Id, pick, msg, cashout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//if there is a game already running
|
||||||
|
if (arguments.TryGetValue("refresh", out var refresh))
|
||||||
|
{
|
||||||
|
await botInstance.BotServices.KasinoMines.RefreshGameMessage(gambler.Id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int pick = 0;
|
||||||
|
List<(int r, int c)> precisePicks = new();
|
||||||
|
if (arguments.TryGetValue("picks", out var picks)) //if they are using picks to randomly select squares to reveal
|
||||||
|
{
|
||||||
|
pick = Convert.ToInt32(picks.Value);
|
||||||
|
}
|
||||||
|
else if (arguments.TryGetValue("betString", out var betString)) //if they are using precise picks manually or from the tool to select specific squares to reveal
|
||||||
|
{
|
||||||
|
var matches = Regex.Matches(message.Message, betPattern);
|
||||||
|
if (matches.Count == 0 || matches == null) //if invalid bet string
|
||||||
|
{
|
||||||
|
await botInstance.SendChatMessageAsync(
|
||||||
|
$"{user.FormatUsername()}, invalid bet string. Example: !mines 100 10 10 1,3 1,5 2,6 - or use the tool: {toolUrl}", true, autoDeleteAfter: cleanupDelay);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach (Match match in matches)
|
||||||
|
{
|
||||||
|
precisePicks.Add((Convert.ToInt32(match.Groups["row"].Value), Convert.ToInt32(match.Groups["col"].Value)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else //if they didn't put anything
|
||||||
|
{
|
||||||
|
if (cashout)
|
||||||
|
{
|
||||||
|
await botInstance.BotServices.KasinoMines.Cashout(botInstance.BotServices.KasinoMines.activeGames[gambler.Id]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await botInstance.SendChatMessageAsync(
|
||||||
|
$"{user.FormatUsername()}, you already have a game running. !mines <picks> to reveal more spaces, !mines cashout to cash out, !mines <bet string> to place precise picks. Tool: {toolUrl}",
|
||||||
|
true, autoDeleteAfter: cleanupDelay);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var msg = await botInstance.SendChatMessageAsync(
|
||||||
|
$"{botInstance.BotServices.KasinoMines.activeGames[gambler.Id].ToString()}", true);
|
||||||
|
|
||||||
|
if (pick == 0) //if using coordinates
|
||||||
|
{
|
||||||
|
var game = botInstance.BotServices.KasinoMines.activeGames[gambler.Id];
|
||||||
|
foreach (var coord in precisePicks)
|
||||||
|
{
|
||||||
|
if (game.betsPlaced.Contains(coord) || coord.r <= 0 || coord.r > game.size || coord.c <= 0 || coord.c > game.size)
|
||||||
|
{
|
||||||
|
await botInstance.SendChatMessageAsync($"{user.FormatUsername()}, you can't place duplicate or invalid bets. Use the tool: {toolUrl}", true, autoDeleteAfter: cleanupDelay);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await botInstance.BotServices.KasinoMines.Bet(gambler.Id, precisePicks, msg, cashout);
|
||||||
|
|
||||||
|
}
|
||||||
|
else //if using picks
|
||||||
|
{
|
||||||
|
await botInstance.BotServices.KasinoMines.Bet(gambler.Id, pick, msg, cashout);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,8 +39,10 @@ public class Planes : ICommand
|
|||||||
private const string Water = "🌊";
|
private const string Water = "🌊";
|
||||||
private const string Air = "\u2B1C"; // White square
|
private const string Air = "\u2B1C"; // White square
|
||||||
private const string BlankSpace = "⠀"; //need 35?
|
private const string BlankSpace = "⠀"; //need 35?
|
||||||
private bool _rigged;
|
private bool _rigged = false;
|
||||||
private bool _superRigged;
|
private bool _riggedWin = false;
|
||||||
|
private const int CarrierCount = 6;
|
||||||
|
private decimal HOUSE_EDGE = (decimal)0.98;
|
||||||
public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments,
|
public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments,
|
||||||
CancellationToken ctx)
|
CancellationToken ctx)
|
||||||
{
|
{
|
||||||
@@ -81,21 +83,30 @@ public class Planes : ICommand
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int carrierCount = 6;
|
if (HOUSE_EDGE < 1)
|
||||||
|
{
|
||||||
|
if (Money.GetRandomDouble(gambler, 1) > (double)HOUSE_EDGE)
|
||||||
|
{
|
||||||
|
_rigged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ((double)HOUSE_EDGE - Money.GetRandomDouble(gambler, 1) > 1)
|
||||||
|
{
|
||||||
|
_riggedWin = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var planesBoard = CreatePlanesBoard(gambler,0);
|
var planesBoard = CreatePlanesBoard(gambler,0);
|
||||||
var planesBoard2 = CreatePlanesBoard(gambler);
|
var planesBoard2 = CreatePlanesBoard(gambler);
|
||||||
var planesBoard3 = CreatePlanesBoard(gambler);
|
var planesBoard3 = CreatePlanesBoard(gambler);
|
||||||
if (_rigged)
|
|
||||||
{
|
|
||||||
planesBoard2 = RigPlanesBoard(planesBoard2, carrierCount, 0);
|
|
||||||
planesBoard3 = RigPlanesBoard(planesBoard3, carrierCount, 0);
|
|
||||||
}
|
|
||||||
List<int[,]> planesBoards = [planesBoard, planesBoard2, planesBoard3];
|
List<int[,]> planesBoards = [planesBoard, planesBoard2, planesBoard3];
|
||||||
var plane = new Plane(gambler);
|
var plane = new Plane(gambler);
|
||||||
const double frameLength = 1000.0;
|
const double frameLength = 1000.0;
|
||||||
var fullCounter = 0;
|
var fullCounter = 0;
|
||||||
var noseUp = true;
|
var noseUp = true;
|
||||||
var planesDisplay = GetPreGameBoard(-3, planesBoard2, plane, carrierCount, noseUp);
|
var planesDisplay = GetPreGameBoard(-3, planesBoard2, plane, CarrierCount, noseUp);
|
||||||
var msgId = await botInstance.SendChatMessageAsync(planesDisplay, true);
|
var msgId = await botInstance.SendChatMessageAsync(planesDisplay, true);
|
||||||
var num = 0;
|
var num = 0;
|
||||||
while (msgId.ChatMessageId == null)
|
while (msgId.ChatMessageId == null)
|
||||||
@@ -113,13 +124,13 @@ public class Planes : ICommand
|
|||||||
*/
|
*/
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
var counter = (fullCounter - 3) % 20;
|
var counter = (fullCounter - 3) % 24;
|
||||||
|
|
||||||
await Task.Delay(TimeSpan.FromMilliseconds(frameLength / 3), ctx);
|
await Task.Delay(TimeSpan.FromMilliseconds(frameLength / 3), ctx);
|
||||||
|
|
||||||
if (fullCounter >= 3)
|
if (fullCounter >= 3)
|
||||||
{
|
{
|
||||||
planesDisplay = GetGameBoard(fullCounter, planesBoards, plane, carrierCount, noseUp);
|
planesDisplay = GetGameBoard(fullCounter, planesBoards, plane, CarrierCount, noseUp);
|
||||||
planesDisplay += $"[br]Multi: {plane.MultiTracker}x";
|
planesDisplay += $"[br]Multi: {plane.MultiTracker}x";
|
||||||
for (var i = 0; i < 10; i++)
|
for (var i = 0; i < 10; i++)
|
||||||
{
|
{
|
||||||
@@ -136,8 +147,8 @@ public class Planes : ICommand
|
|||||||
{
|
{
|
||||||
while (fullCounter < 3)
|
while (fullCounter < 3)
|
||||||
{
|
{
|
||||||
counter = fullCounter % 23 - 3;
|
counter = (fullCounter - 3) % 24;
|
||||||
planesDisplay = GetPreGameBoard(fullCounter, planesBoard2, plane, carrierCount, noseUp);
|
planesDisplay = GetPreGameBoard(fullCounter, planesBoard2, plane, CarrierCount, noseUp);
|
||||||
await botInstance.KfClient.EditMessageAsync(msgId.ChatMessageId!.Value, planesDisplay);
|
await botInstance.KfClient.EditMessageAsync(msgId.ChatMessageId!.Value, planesDisplay);
|
||||||
await Task.Delay(TimeSpan.FromMilliseconds(frameLength), ctx);
|
await Task.Delay(TimeSpan.FromMilliseconds(frameLength), ctx);
|
||||||
fullCounter++;
|
fullCounter++;
|
||||||
@@ -200,7 +211,7 @@ public class Planes : ICommand
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
planesDisplay = GetGameBoard(fullCounter, planesBoards, plane, carrierCount, noseUp);
|
planesDisplay = GetGameBoard(fullCounter, planesBoards, plane, CarrierCount, noseUp);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -216,36 +227,21 @@ public class Planes : ICommand
|
|||||||
var winnings = plane.MultiTracker * wager;
|
var winnings = plane.MultiTracker * wager;
|
||||||
planesDisplay += $"Winnings: {await winnings.FormatKasinoCurrencyAsync()}";
|
planesDisplay += $"Winnings: {await winnings.FormatKasinoCurrencyAsync()}";
|
||||||
await botInstance.KfClient.EditMessageAsync(msgId.ChatMessageId!.Value, planesDisplay);
|
await botInstance.KfClient.EditMessageAsync(msgId.ChatMessageId!.Value, planesDisplay);
|
||||||
if (plane.Height >= 6)
|
if (plane.Height > 5)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
//maybe fuckery around here
|
//maybe fuckery around here
|
||||||
}
|
}
|
||||||
fullCounter++;
|
fullCounter++;
|
||||||
}
|
if ((fullCounter - 3) % 24 == 0 && fullCounter != 3)
|
||||||
plane.Gravity();
|
|
||||||
if ((fullCounter - 3) % 20 == 0 && fullCounter != 3)//removes old planesboard, adds new planeboard when necessary **********************************************************************NEEDS MORE UPDATES
|
|
||||||
{
|
{
|
||||||
if (Money.GetRandomNumber(gambler, 0, 100) == 0 && settings[BuiltIn.Keys.KasinoPlanesRandomRiggeryEnabled].ToBoolean()) _rigged = true;
|
|
||||||
if (settings[BuiltIn.Keys.KasinoPlanesTargetedRiggeryEnabled].ToBoolean() &&
|
|
||||||
settings[BuiltIn.Keys.KasinoPlanesTargetedRiggeryVictims].JsonDeserialize<List<int>>()!.Contains(user.KfId))
|
|
||||||
{
|
|
||||||
_rigged = true;
|
|
||||||
}
|
|
||||||
logger.Info($"Switching planes boards. FullCounter: {fullCounter} | Counter: {counter}");
|
|
||||||
planesBoards.RemoveAt(0);
|
planesBoards.RemoveAt(0);
|
||||||
planesBoards.Add(CreatePlanesBoard(gambler));
|
planesBoards.Add(CreatePlanesBoard(gambler));
|
||||||
if (_rigged && Money.GetRandomNumber(gambler, 0, 100) == 0) {
|
|
||||||
planesBoards[1] = CreatePlanesBoard(gambler, 1); //1% chance to update to a board full of rockets if rigged
|
|
||||||
_superRigged = true;
|
|
||||||
}
|
|
||||||
else if (_rigged)
|
|
||||||
{
|
|
||||||
planesBoards[1] = RigPlanesBoard(planesBoards[1], carrierCount, fullCounter);
|
|
||||||
planesBoards[2] = RigPlanesBoard(planesBoards[2], carrierCount, fullCounter);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
plane.Gravity();
|
||||||
|
//maybe need to add one more frame here?***************
|
||||||
} while (plane.Height < 6);
|
} while (plane.Height < 6);
|
||||||
//now plane is too low so you have either won or lost depending on your position
|
//now plane is too low so you have either won or lost depending on your position
|
||||||
var colors =
|
var colors =
|
||||||
@@ -253,11 +249,11 @@ public class Planes : ICommand
|
|||||||
BuiltIn.Keys.KiwiFarmsGreenColor, BuiltIn.Keys.KiwiFarmsRedColor
|
BuiltIn.Keys.KiwiFarmsGreenColor, BuiltIn.Keys.KiwiFarmsRedColor
|
||||||
]);
|
]);
|
||||||
decimal newBalance;
|
decimal newBalance;
|
||||||
if ((fullCounter - 3) % carrierCount == 0) //if you landed on the carrier
|
if ((fullCounter - 3) % CarrierCount == 0) //if you landed on the carrier
|
||||||
{
|
{
|
||||||
var win = plane.MultiTracker * wager;
|
var win = plane.MultiTracker * wager;
|
||||||
newBalance = await Money.NewWagerAsync(gambler.Id, wager, win, WagerGame.Planes, ct: ctx);
|
newBalance = await Money.NewWagerAsync(gambler.Id, wager, win, WagerGame.Planes, ct: ctx);
|
||||||
planesDisplay = GetGameBoard(fullCounter, planesBoards, plane, carrierCount, noseUp);
|
planesDisplay = GetGameBoard(fullCounter, planesBoards, plane, CarrierCount, noseUp);
|
||||||
await botInstance.KfClient.EditMessageAsync(msgId.ChatMessageId!.Value, planesDisplay);
|
await botInstance.KfClient.EditMessageAsync(msgId.ChatMessageId!.Value, planesDisplay);
|
||||||
await botInstance.SendChatMessageAsync(
|
await botInstance.SendChatMessageAsync(
|
||||||
$"{user.FormatUsername()}, you [color={colors[BuiltIn.Keys.KiwiFarmsGreenColor].Value}]successfully landed with {await win.FormatKasinoCurrencyAsync()} from a total {plane.MultiTracker:N2}x multi![/color]. Your balance is now: {await newBalance.FormatKasinoCurrencyAsync()}",
|
$"{user.FormatUsername()}, you [color={colors[BuiltIn.Keys.KiwiFarmsGreenColor].Value}]successfully landed with {await win.FormatKasinoCurrencyAsync()} from a total {plane.MultiTracker:N2}x multi![/color]. Your balance is now: {await newBalance.FormatKasinoCurrencyAsync()}",
|
||||||
@@ -267,7 +263,7 @@ public class Planes : ICommand
|
|||||||
}
|
}
|
||||||
plane.Crash();
|
plane.Crash();
|
||||||
newBalance = await Money.NewWagerAsync(gambler.Id, wager, -wager, WagerGame.Planes, ct: ctx);
|
newBalance = await Money.NewWagerAsync(gambler.Id, wager, -wager, WagerGame.Planes, ct: ctx);
|
||||||
planesDisplay = GetGameBoard(fullCounter, planesBoards, plane, carrierCount, noseUp);
|
planesDisplay = GetGameBoard(fullCounter, planesBoards, plane, CarrierCount, noseUp);
|
||||||
await Task.Delay(TimeSpan.FromMilliseconds(frameLength), ctx);
|
await Task.Delay(TimeSpan.FromMilliseconds(frameLength), ctx);
|
||||||
await botInstance.KfClient.EditMessageAsync(msgId.ChatMessageId!.Value, planesDisplay);
|
await botInstance.KfClient.EditMessageAsync(msgId.ChatMessageId!.Value, planesDisplay);
|
||||||
await botInstance.SendChatMessageAsync(
|
await botInstance.SendChatMessageAsync(
|
||||||
@@ -279,7 +275,7 @@ public class Planes : ICommand
|
|||||||
private string GetPreGameBoard(int fullCounter, int[,] planesBoard, Plane plane, int carrierCount, bool noseUp)
|
private string GetPreGameBoard(int fullCounter, int[,] planesBoard, Plane plane, int carrierCount, bool noseUp)
|
||||||
{
|
{
|
||||||
//counter < 5
|
//counter < 5
|
||||||
var counter = fullCounter % 23 - 3;
|
var counter = (fullCounter - 3) % 24;
|
||||||
var output = "";
|
var output = "";
|
||||||
for (var row = 0; row < 8; row++)
|
for (var row = 0; row < 8; row++)
|
||||||
{
|
{
|
||||||
@@ -335,7 +331,6 @@ public class Planes : ICommand
|
|||||||
}
|
}
|
||||||
|
|
||||||
output += "[br]";
|
output += "[br]";
|
||||||
|
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
@@ -343,76 +338,75 @@ public class Planes : ICommand
|
|||||||
private string GetGameBoard(int fullCounter, List<int[,]> planesBoards, Plane plane, int carrierCount, bool noseUp)
|
private string GetGameBoard(int fullCounter, List<int[,]> planesBoards, Plane plane, int carrierCount, bool noseUp)
|
||||||
{
|
{
|
||||||
var output = "";
|
var output = "";
|
||||||
|
// worldXPlane is the absolute distance the plane has traveled from the start.
|
||||||
|
int worldXPlane = fullCounter - 3;
|
||||||
|
|
||||||
for (var row = 0; row < 8; row++)
|
for (var row = 0; row < 8; row++)
|
||||||
{
|
{
|
||||||
for (var column = -3;
|
for (var column = -3; column < 10; column++)
|
||||||
column < 10;
|
|
||||||
column++) //plane starts out 3 space behind to give some space to the view,
|
|
||||||
{
|
{
|
||||||
var useBoard = 1;
|
// worldXTile is the absolute coordinate of the specific tile we are currently drawing.
|
||||||
int counter;
|
int worldXTile = worldXPlane + column;
|
||||||
if (fullCounter < 23) counter = fullCounter % 23 - 3;
|
|
||||||
else counter = (fullCounter - 3) % 20;
|
// 1. WATER & CARRIER ROW (Row 7)
|
||||||
//---
|
if (row == 7)
|
||||||
if (counter + column < 0)
|
|
||||||
{
|
{
|
||||||
counter = 20 + counter;
|
// We use worldXTile so the carrier stays pinned to a global position.
|
||||||
useBoard = 0;
|
if (worldXTile >= 0 && worldXTile % carrierCount == 0) output += Carrier;
|
||||||
}
|
else output += Water;
|
||||||
else if (counter + column > 19)
|
continue;
|
||||||
{
|
|
||||||
useBoard = 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//---actual game board displays below here
|
// 2. THE PLANE (At Column 0 relative to the camera)
|
||||||
|
if (row == plane.Height && column == 0)
|
||||||
|
{
|
||||||
|
if (plane.Crashed) output += PlaneExplosion;
|
||||||
|
else output += noseUp ? PlaneUp : PlaneDown;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. BOOST EFFECT
|
||||||
if (row == plane.Height && column == -1 && plane.JustHitMulti > 1)
|
if (row == plane.Height && column == -1 && plane.JustHitMulti > 1)
|
||||||
{
|
{
|
||||||
output += Boost;
|
output += Boost;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
else if (row == 7) //water/carrier row
|
|
||||||
|
// 4. THE SKY & GAME OBJECTS (Rows 0-6)
|
||||||
|
// Row 6 is always Air. Any tile with a negative world coordinate is also Air.
|
||||||
|
if (row == 6 || worldXTile < 0)
|
||||||
{
|
{
|
||||||
if (((fullCounter - 3)+ column) % carrierCount == 0) output += Carrier;
|
|
||||||
else output += Water;
|
|
||||||
}
|
|
||||||
else if (row == plane.Height && column == 0)
|
|
||||||
{
|
|
||||||
if (plane.Crashed) output += PlaneExplosion;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
switch (noseUp)
|
|
||||||
{
|
|
||||||
case true:
|
|
||||||
output += PlaneUp;
|
|
||||||
break;
|
|
||||||
case false:
|
|
||||||
output += PlaneDown;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (row == 6) output += Air;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//logger.Info($"GetGameBoard: attempting to access planeboard index [{row},{(column + counter) % 20}]. RawCounter: {fullCounter} | Counter: {counter} | UseBoard: {useBoard}");
|
|
||||||
switch (planesBoards[useBoard][row, (counter + column) % 20])
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
output += Air;
|
output += Air;
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
output += Bomb;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
output += Multi;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Calculate which BOARD the tile belongs to (0, 1, 2, 3...)
|
||||||
|
int boardNumber = worldXTile / 24;
|
||||||
|
int localX = worldXTile % 24;
|
||||||
|
|
||||||
}
|
// Map the boardNumber to our sliding window (List of 3 boards).
|
||||||
|
// Our list always contains: [Board N-1, Board N, Board N+1]
|
||||||
|
// relative to where the plane is currently flying.
|
||||||
|
int planeBoardNumber = worldXPlane / 24;
|
||||||
|
int listIndex = boardNumber - (planeBoardNumber - 1);
|
||||||
|
|
||||||
|
if (listIndex >= 0 && listIndex < planesBoards.Count)
|
||||||
|
{
|
||||||
|
int tileValue = planesBoards[listIndex][row, localX];
|
||||||
|
output += tileValue switch
|
||||||
|
{
|
||||||
|
1 => Bomb,
|
||||||
|
2 => Multi,
|
||||||
|
_ => Air
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Fallback if the tile is beyond our current 3-board window
|
||||||
|
output += Air;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Was https://i.postimg.cc/rmX59qtV/avelloonaircall2.webp previously
|
|
||||||
if (_superRigged && row == 0) output += "[img]https://i.ddos.lgbt/u/6v8WJ5.webp[/img]";
|
|
||||||
output += "[br]";
|
output += "[br]";
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
@@ -420,13 +414,26 @@ public class Planes : ICommand
|
|||||||
|
|
||||||
private int[,] CreatePlanesBoard(GamblerDbModel gambler, int forceTiles = -1)
|
private int[,] CreatePlanesBoard(GamblerDbModel gambler, int forceTiles = -1)
|
||||||
{
|
{
|
||||||
var board = new int [6, 20];
|
var board = new int [6, 24];
|
||||||
|
|
||||||
for (var row = 0; row < 6; row++)
|
for (var row = 0; row < 6; row++)
|
||||||
{
|
{
|
||||||
for (var column = 0; column < 20; column++)
|
for (var column = 0; column < 24; column++)
|
||||||
{
|
{
|
||||||
var randomNum = Money.GetRandomNumber(gambler, 1, 100);
|
var randomNum = Money.GetRandomNumber(gambler, 0, 100);
|
||||||
if (forceTiles != -1) board[row, column] = forceTiles;
|
if (forceTiles != -1) board[row, column] = forceTiles;
|
||||||
|
else if (_rigged && (column == 5 || column == 11 || column == 17 || column == 23) && row == 5)
|
||||||
|
{
|
||||||
|
board[row, column] = 2;
|
||||||
|
}
|
||||||
|
else if (_riggedWin && (column == 5 || column == 11 || column == 17 || column == 23) && row == 5)
|
||||||
|
{
|
||||||
|
board[row, column] = 0;
|
||||||
|
}
|
||||||
|
else if (_riggedWin && row == 5 && (column != 5 && column != 11 && column != 17 && column != 23))
|
||||||
|
{
|
||||||
|
board[row, column] = 2;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
board[row, column] = randomNum switch
|
board[row, column] = randomNum switch
|
||||||
{
|
{
|
||||||
@@ -436,42 +443,9 @@ public class Planes : ICommand
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return board;
|
return board;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int[,] RigPlanesBoard(int[,] planesBoard, int carrierCount, int fullCounter)
|
|
||||||
{
|
|
||||||
var returnBoard = new int[6,20];
|
|
||||||
bool startUpdating;
|
|
||||||
var spaceToUpdate = (fullCounter-3) % 20; //how far along is the game into the current board
|
|
||||||
if (spaceToUpdate > 0) startUpdating = false;
|
|
||||||
|
|
||||||
for (var row = 0; row < 6; row++)
|
|
||||||
{
|
|
||||||
for (var column = 0; column < 20; column++)
|
|
||||||
{
|
|
||||||
if (column >= spaceToUpdate) startUpdating = true;
|
|
||||||
else startUpdating = false;
|
|
||||||
if (startUpdating)
|
|
||||||
{
|
|
||||||
if (row == 5 && column+1 == (fullCounter-3) % carrierCount)
|
|
||||||
{
|
|
||||||
returnBoard[row, column] = 2; //force set as multi
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
returnBoard[row, column] = planesBoard[row, column];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
returnBoard[row, column] = planesBoard[row, column];
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return returnBoard;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Plane(GamblerDbModel gambler)
|
public class Plane(GamblerDbModel gambler)
|
||||||
@@ -522,7 +496,7 @@ public class Plane(GamblerDbModel gambler)
|
|||||||
private int WeightedRandomNumber(int min, int max)
|
private int WeightedRandomNumber(int min, int max)
|
||||||
{
|
{
|
||||||
var range = max - min + 1;
|
var range = max - min + 1;
|
||||||
var weight = 6.25 + Height;
|
var weight = 6.55 + Height;
|
||||||
var r = _random.NextDouble();
|
var r = _random.NextDouble();
|
||||||
var exp = -Math.Log(1 - r) / weight;
|
var exp = -Math.Log(1 - r) / weight;
|
||||||
var returnVal = min + (int)Math.Round(exp * range);
|
var returnVal = min + (int)Math.Round(exp * range);
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ public class PlinkoCommand : ICommand
|
|||||||
private const string BIGWINSPACE = "💲";
|
private const string BIGWINSPACE = "💲";
|
||||||
|
|
||||||
private const int DIFFICULTY = 8;//maybe plan to allow user to change difficulty of plinko in future updates, would need to change the payout logic though
|
private const int DIFFICULTY = 8;//maybe plan to allow user to change difficulty of plinko in future updates, would need to change the payout logic though
|
||||||
private static readonly double VACUUM = 0.27;
|
private static double VACUUM = 0.25;
|
||||||
|
private decimal HOUSE_EDGE = (decimal)0.98;
|
||||||
|
|
||||||
private static Dictionary<decimal, string> PAYOUTSTOSTRING = new Dictionary<decimal, string>()
|
private static Dictionary<decimal, string> PAYOUTSTOSTRING = new Dictionary<decimal, string>()
|
||||||
{
|
{
|
||||||
@@ -69,7 +70,7 @@ public class PlinkoCommand : ICommand
|
|||||||
public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments,
|
public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments,
|
||||||
CancellationToken ctx)
|
CancellationToken ctx)
|
||||||
{
|
{
|
||||||
|
VACUUM += 1 - (double)HOUSE_EDGE;
|
||||||
validPositions = new List<(int row, int col)>() { (0, DIFFICULTY-1) };
|
validPositions = new List<(int row, int col)>() { (0, DIFFICULTY-1) };
|
||||||
validColumnsForRow = new Dictionary<int, List<int>>(){{0, new List<int>(){DIFFICULTY-1}}};
|
validColumnsForRow = new Dictionary<int, List<int>>(){{0, new List<int>(){DIFFICULTY-1}}};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using KfChatDotNetBot.Extensions;
|
using KfChatDotNetBot.Extensions;
|
||||||
using KfChatDotNetBot.Models;
|
using KfChatDotNetBot.Models;
|
||||||
@@ -41,6 +41,7 @@ public class BotServices
|
|||||||
private ShuffleDotUs? _shuffleDotUs;
|
private ShuffleDotUs? _shuffleDotUs;
|
||||||
private YouTubePubSub? _youTubePubSub;
|
private YouTubePubSub? _youTubePubSub;
|
||||||
public KasinoRain? KasinoRain;
|
public KasinoRain? KasinoRain;
|
||||||
|
public KasinoMines KasinoMines;
|
||||||
|
|
||||||
private Task? _websocketWatchdog;
|
private Task? _websocketWatchdog;
|
||||||
private Task? _howlggGetUserTimer;
|
private Task? _howlggGetUserTimer;
|
||||||
@@ -93,7 +94,8 @@ public class BotServices
|
|||||||
BuildOwncastLiveStatusCheck(),
|
BuildOwncastLiveStatusCheck(),
|
||||||
BuildShuffleDotUs(),
|
BuildShuffleDotUs(),
|
||||||
BuildYouTubePubSub(),
|
BuildYouTubePubSub(),
|
||||||
BuildKasinoRain()
|
BuildKasinoRain(),
|
||||||
|
BuildKasinoMines()
|
||||||
];
|
];
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -116,6 +118,12 @@ public class BotServices
|
|||||||
KasinoRain = new KasinoRain(_chatBot, _cancellationToken);
|
KasinoRain = new KasinoRain(_chatBot, _cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task BuildKasinoMines()
|
||||||
|
{
|
||||||
|
_logger.Debug("Building the Kasino mines service");
|
||||||
|
KasinoMines = new KasinoMines(_chatBot, _cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task BuildShuffle()
|
private async Task BuildShuffle()
|
||||||
{
|
{
|
||||||
_logger.Debug("Building Shuffle");
|
_logger.Debug("Building Shuffle");
|
||||||
|
|||||||
379
KfChatDotNetBot/Services/KasinoMines.cs
Normal file
379
KfChatDotNetBot/Services/KasinoMines.cs
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using KfChatDotNetBot.Extensions;
|
||||||
|
using KfChatDotNetBot.Models;
|
||||||
|
using KfChatDotNetBot.Models.DbModels;
|
||||||
|
using KfChatDotNetBot.Settings;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NLog;
|
||||||
|
using SixLabors.Fonts;
|
||||||
|
using StackExchange.Redis;
|
||||||
|
|
||||||
|
namespace KfChatDotNetBot.Services;
|
||||||
|
|
||||||
|
public class KasinoMines : IDisposable
|
||||||
|
{
|
||||||
|
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||||
|
private Task? _minesTimerTask;
|
||||||
|
private IDatabase? _redisDb;
|
||||||
|
private static ChatBot _kfChatBot;
|
||||||
|
private CancellationToken _ct;
|
||||||
|
private CancellationTokenSource _minesCts = new();
|
||||||
|
public Dictionary<int, KasinoMinesGame>? activeGames;
|
||||||
|
private decimal HOUSE_EDGE = (decimal)0.98; //used to rig win rate, payout is 100% fair. with shop i plan to implement a sort of kasino shop profile holding the investments and buffs and tracking the gamblers current house edge
|
||||||
|
public class KasinoMinesGame
|
||||||
|
{
|
||||||
|
public GamblerDbModel creator { get; set; }
|
||||||
|
public DateTime lastInteracted = DateTime.UtcNow;
|
||||||
|
public char[,] minesBoard;
|
||||||
|
public decimal wager { get; set; }
|
||||||
|
public int size { get; set; }
|
||||||
|
public int mines { get; set; }
|
||||||
|
public List<(int r, int c)> betsPlaced = new();
|
||||||
|
public SentMessageTrackerModel? lastMessage;
|
||||||
|
|
||||||
|
|
||||||
|
public KasinoMinesGame(GamblerDbModel creator, decimal wager, int size, int mines)
|
||||||
|
{
|
||||||
|
this.creator = creator;
|
||||||
|
this.size = size;
|
||||||
|
this.mines = mines;
|
||||||
|
this.wager = wager;
|
||||||
|
minesBoard = CreateBoard();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ResetMessage(SentMessageTrackerModel msg)
|
||||||
|
{
|
||||||
|
await _kfChatBot.KfClient.DeleteMessageAsync(lastMessage.ChatMessageId.Value);
|
||||||
|
lastMessage = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RigBoard((int r, int c) coord) //moves one of the mines to a specified coordinate for house edge rigging
|
||||||
|
{
|
||||||
|
//find the first mine
|
||||||
|
(int r, int c) originalMine = (11, 11);
|
||||||
|
for (int r = 0; r < size; r++)
|
||||||
|
{
|
||||||
|
for (int c = 0; c < size; c++)
|
||||||
|
{
|
||||||
|
if (minesBoard[r, c] == 'M') originalMine = (r, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
minesBoard[coord.r, coord.c] = 'M';
|
||||||
|
if (originalMine.r == 11)
|
||||||
|
{
|
||||||
|
_logger.Error("Rigboard failed to find a mine somehow?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
minesBoard[originalMine.r, originalMine.c] = 'G';
|
||||||
|
|
||||||
|
}
|
||||||
|
public async Task Explode((int r, int c) mineLocation, SentMessageTrackerModel msg)
|
||||||
|
{
|
||||||
|
if (lastMessage != msg)
|
||||||
|
{
|
||||||
|
await ResetMessage(msg);
|
||||||
|
}
|
||||||
|
int frames = mineLocation.c;
|
||||||
|
if (size - mineLocation.c > frames) frames = size - mineLocation.c;
|
||||||
|
string str;
|
||||||
|
bool revealedSpace;
|
||||||
|
int yellowWave = 1;
|
||||||
|
int orangeWave = 2;
|
||||||
|
int redWave = 3;
|
||||||
|
int whiteWave = 0;
|
||||||
|
for (int f = 0; f < frames; f++)
|
||||||
|
{
|
||||||
|
str = "";
|
||||||
|
for (int r = 0; r < size; r++)
|
||||||
|
{
|
||||||
|
await Task.Delay(100);
|
||||||
|
revealedSpace = false;
|
||||||
|
for (int c = 0; c < size; c++)
|
||||||
|
{
|
||||||
|
foreach (var bet in betsPlaced)
|
||||||
|
{
|
||||||
|
if (bet.r == r && bet.c == c) revealedSpace = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mineLocation.r == r && mineLocation.c == c)
|
||||||
|
{
|
||||||
|
str += "💣";
|
||||||
|
}
|
||||||
|
else if (revealedSpace)
|
||||||
|
{
|
||||||
|
str += "💎";
|
||||||
|
}
|
||||||
|
else if (DistanceFromMine((r, c)).vertical == yellowWave || DistanceFromMine((r, c)).horizontal == yellowWave)
|
||||||
|
{
|
||||||
|
str += "🟨";
|
||||||
|
}
|
||||||
|
else if (DistanceFromMine((r, c)).vertical == orangeWave ||
|
||||||
|
DistanceFromMine((r, c)).horizontal == orangeWave)
|
||||||
|
{
|
||||||
|
str += "🟧";
|
||||||
|
}
|
||||||
|
else if (DistanceFromMine((r, c)).vertical == redWave ||
|
||||||
|
DistanceFromMine((r, c)).horizontal == redWave)
|
||||||
|
{
|
||||||
|
str += "🟥";
|
||||||
|
}
|
||||||
|
else if (DistanceFromMine((r, c)).vertical == whiteWave ||
|
||||||
|
DistanceFromMine((r, c)).horizontal == whiteWave)
|
||||||
|
{
|
||||||
|
str += "⬜";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
str += "⬜";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _kfChatBot.KfClient.EditMessageAsync(msg.ChatMessageId.Value, $"{str}[br]{creator.User.FormatUsername()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(10));
|
||||||
|
await _kfChatBot.KfClient.DeleteMessageAsync(msg.ChatMessageId.Value);
|
||||||
|
|
||||||
|
(int vertical, int horizontal) DistanceFromMine((int r, int c) coord)
|
||||||
|
{
|
||||||
|
return (Math.Abs(coord.r - mineLocation.r), Math.Abs(coord.c - mineLocation.c));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ToString()
|
||||||
|
{
|
||||||
|
string value = "";
|
||||||
|
bool revealedSpace;
|
||||||
|
for (int r = 0; r < size; r++)
|
||||||
|
{
|
||||||
|
revealedSpace = false;
|
||||||
|
for (int c = 0; c < size; c++)
|
||||||
|
{
|
||||||
|
foreach (var bet in betsPlaced)
|
||||||
|
{
|
||||||
|
if (bet.r == r && bet.c == c) revealedSpace = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!revealedSpace)
|
||||||
|
{
|
||||||
|
value += "⬜";
|
||||||
|
}
|
||||||
|
else if (minesBoard[r, c] == 'M') value += "💣";
|
||||||
|
else value += "💎";
|
||||||
|
}
|
||||||
|
|
||||||
|
value += "[br]";
|
||||||
|
}
|
||||||
|
|
||||||
|
value += $"{creator.User.FormatUsername()}";
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public char[,] CreateBoard()
|
||||||
|
{
|
||||||
|
char[,] board = new char[size, size];
|
||||||
|
List<(int r, int c)> minesCoords = new List<(int r, int c)>();
|
||||||
|
(int r, int c) coord;
|
||||||
|
int counter = 0;
|
||||||
|
bool gems = !(mines < (size * size)/2); //if there are more mines than gems, generate list of gem locations instead since thats less generations
|
||||||
|
int coordsCounter;
|
||||||
|
if (gems) coordsCounter = size * size - mines;
|
||||||
|
else coordsCounter = mines;
|
||||||
|
while (minesCoords.Count != coordsCounter)
|
||||||
|
{
|
||||||
|
coord = (Money.GetRandomNumber(creator, 0, size), Money.GetRandomNumber(creator, 0, size));
|
||||||
|
if (!minesCoords.Contains(coord)) minesCoords.Add(coord);
|
||||||
|
else counter++;
|
||||||
|
if (counter >= 100000) throw new Exception($"mines failed to generate mines coordinates. Mines: {mines} | Board size: {size} | Current count of mines list {minesCoords.Count}");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var coords in minesCoords)
|
||||||
|
{
|
||||||
|
if (gems) board[coords.r, coords.c] = 'G';
|
||||||
|
else board[coords.r, coords.c] = 'M';
|
||||||
|
}
|
||||||
|
for (int r = 0; r < size; r++)
|
||||||
|
{
|
||||||
|
for (int c = 0; c < size; c++)
|
||||||
|
{
|
||||||
|
if (gems)
|
||||||
|
{
|
||||||
|
if (!(board[r,c] == 'G')) board[r, c] = 'M';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!(board[r,c] == 'M')) board[r, c] = 'G';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return board;
|
||||||
|
}
|
||||||
|
public async Task DeleteMessage(SentMessageTrackerModel msg)
|
||||||
|
{
|
||||||
|
await _kfChatBot.KfClient.DeleteMessageAsync(msg.ChatMessageId.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public KasinoMines(ChatBot kfChatBot, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
_kfChatBot = kfChatBot;
|
||||||
|
_ct = ct;
|
||||||
|
var connectionString = SettingsProvider.GetValueAsync(BuiltIn.Keys.BotRedisConnectionString).Result;
|
||||||
|
if (string.IsNullOrEmpty(connectionString.Value))
|
||||||
|
{
|
||||||
|
_logger.Error($"Can't initialize the Kasino Mines service as Redis isn't configured in {BuiltIn.Keys.BotRedisConnectionString}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var redis = ConnectionMultiplexer.Connect(connectionString.Value);
|
||||||
|
_redisDb = redis.GetDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RefreshGameMessage(int gamblerId)
|
||||||
|
{
|
||||||
|
await GetSavedGames();
|
||||||
|
var game = activeGames[gamblerId];
|
||||||
|
game.lastInteracted = DateTime.UtcNow;
|
||||||
|
var msg = await _kfChatBot.SendChatMessageAsync($"{game.ToString()}", true);
|
||||||
|
await game.ResetMessage(msg);
|
||||||
|
activeGames[gamblerId] = game;
|
||||||
|
await SaveActiveGames();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task GetSavedGames()
|
||||||
|
{
|
||||||
|
if (_redisDb == null) throw new InvalidOperationException("Kasino mines service isn't initialized");
|
||||||
|
var json = await _redisDb.StringGetAsync("Mines.State");
|
||||||
|
if (string.IsNullOrEmpty(json)) return;
|
||||||
|
activeGames = JsonSerializer.Deserialize<Dictionary<int, KasinoMinesGame>>(json.ToString());
|
||||||
|
if (activeGames == null)
|
||||||
|
{
|
||||||
|
_logger.Error("Potentially failed to deserialize active mines games in GetSavedGames() in KasinoMines in Services");
|
||||||
|
activeGames = new Dictionary<int, KasinoMinesGame>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public async Task SaveActiveGames()
|
||||||
|
{
|
||||||
|
if (_redisDb == null) throw new InvalidOperationException("Kasino mines service isn't initialized");
|
||||||
|
var json = JsonSerializer.Serialize(activeGames);
|
||||||
|
await _redisDb.StringSetAsync("Mines.State", json, null, When.Always);
|
||||||
|
}
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveGame(int gamblerId)
|
||||||
|
{
|
||||||
|
await GetSavedGames();
|
||||||
|
activeGames?.Remove(gamblerId);
|
||||||
|
await SaveActiveGames();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Cashout(KasinoMinesGame game)
|
||||||
|
{
|
||||||
|
decimal payout = 0;
|
||||||
|
decimal possiblePicks = game.size * game.size - game.mines;
|
||||||
|
for (int i = 0; i < game.betsPlaced.Count; i++)
|
||||||
|
{
|
||||||
|
payout += game.wager * (possiblePicks / game.betsPlaced.Count);
|
||||||
|
possiblePicks--;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newBalance = await Money.NewWagerAsync(game.creator.Id, game.wager, payout, WagerGame.Mines);
|
||||||
|
await _kfChatBot.SendChatMessageAsync(
|
||||||
|
$"{game.creator.User.FormatUsername()}, you won {payout.FormatKasinoCurrencyAsync()} from your {game.wager.FormatKasinoCurrencyAsync()} bet on mines, collecting {game.betsPlaced.Count} gems while avoiding {game.mines} mines. Net: {(payout - game.wager).FormatKasinoCurrencyAsync()}. Balance: {newBalance.FormatKasinoCurrencyAsync()}");
|
||||||
|
await RemoveGame(game.creator.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Bet(int gamblerId, int count, SentMessageTrackerModel msg, bool cashOut = false) //returns false if you hit a bomb, true if you didn't
|
||||||
|
{
|
||||||
|
await GetSavedGames();
|
||||||
|
var game = activeGames[gamblerId];
|
||||||
|
game.lastInteracted = DateTime.UtcNow;
|
||||||
|
if (game.lastMessage != msg)
|
||||||
|
{
|
||||||
|
await game.ResetMessage(msg);
|
||||||
|
}
|
||||||
|
List<(int r, int c)> betCoords = new();
|
||||||
|
(int r, int c) coord;
|
||||||
|
while (betCoords.Count != count)//creates a list of coordinates to bet on using the coordinate bet function
|
||||||
|
{
|
||||||
|
coord = (Money.GetRandomNumber(game.creator, 0, game.size), Money.GetRandomNumber(game.creator, 0, game.size));
|
||||||
|
if (!betCoords.Contains(coord) && !game.betsPlaced.Contains(coord)) betCoords.Add(coord);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await Bet(gamblerId, betCoords, msg, cashOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Bet(int gamblerId, List<(int r, int c)> coords, SentMessageTrackerModel msg, bool cashOut = false)
|
||||||
|
{
|
||||||
|
await GetSavedGames();
|
||||||
|
var game = activeGames[gamblerId];
|
||||||
|
game.lastInteracted = DateTime.UtcNow;
|
||||||
|
if (game.lastMessage != msg)
|
||||||
|
{
|
||||||
|
await game.ResetMessage(msg);
|
||||||
|
}
|
||||||
|
foreach (var coord in coords) //the main portion of the game
|
||||||
|
{
|
||||||
|
await Task.Delay(100);
|
||||||
|
if (game.minesBoard[coord.r, coord.c] == 'M')
|
||||||
|
{
|
||||||
|
game.betsPlaced.Add(coord);
|
||||||
|
await _kfChatBot.KfClient.EditMessageAsync(msg.ChatMessageId!.Value, game.ToString());
|
||||||
|
game.Explode((coord.r, coord.c), msg);
|
||||||
|
var newBalance = await Money.NewWagerAsync(game.creator.Id, game.wager, -game.wager, WagerGame.Mines);
|
||||||
|
await _kfChatBot.SendChatMessageAsync(
|
||||||
|
$"{game.creator.User.FormatUsername()}, you lost your {game.wager.FormatKasinoCurrencyAsync()} bet on mines, collecting {game.betsPlaced.Count} gems until you hit one of {game.mines} mines. Net: {(-game.wager).FormatKasinoCurrencyAsync()}. Balance: {newBalance.FormatKasinoCurrencyAsync()}",
|
||||||
|
true, autoDeleteAfter: TimeSpan.FromSeconds(15));
|
||||||
|
await RemoveGame(gamblerId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Money.GetRandomNumber(game.creator, 0, 100) < 100 * HOUSE_EDGE)//if you didn't lose, check to see if the switch was flipped
|
||||||
|
{
|
||||||
|
game.betsPlaced.Add(coord);
|
||||||
|
await _kfChatBot.KfClient.EditMessageAsync(msg.ChatMessageId!.Value, game.ToString());
|
||||||
|
await game.RigBoard(coord);
|
||||||
|
await Task.Delay(50);
|
||||||
|
await _kfChatBot.KfClient.EditMessageAsync(msg.ChatMessageId!.Value, game.ToString());
|
||||||
|
game.Explode(coord, msg);
|
||||||
|
var newBalance = await Money.NewWagerAsync(game.creator.Id, game.wager, -game.wager, WagerGame.Mines);
|
||||||
|
await _kfChatBot.SendChatMessageAsync(
|
||||||
|
$"{game.creator.User.FormatUsername()}, you lost your {game.wager.FormatKasinoCurrencyAsync()} bet on mines, collecting {game.betsPlaced.Count} gems until you hit one of {game.mines} mines. Net: {(-game.wager).FormatKasinoCurrencyAsync()}. Balance: {newBalance.FormatKasinoCurrencyAsync()}",
|
||||||
|
true, autoDeleteAfter: TimeSpan.FromSeconds(15));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
game.betsPlaced.Add(coord);
|
||||||
|
}
|
||||||
|
await _kfChatBot.KfClient.EditMessageAsync(msg.ChatMessageId!.Value, game.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
activeGames[gamblerId] = game;
|
||||||
|
if (cashOut) await Cashout(game);
|
||||||
|
else await SaveActiveGames();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsInitialized()
|
||||||
|
{
|
||||||
|
return _redisDb != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CreateGame(GamblerDbModel gambler, decimal bet, int size, int mines)
|
||||||
|
{
|
||||||
|
await GetSavedGames();
|
||||||
|
activeGames?.Add(gambler.Id, new KasinoMinesGame(gambler, bet, size, mines));
|
||||||
|
await SaveActiveGames();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -414,7 +414,9 @@ public static class BuiltIn
|
|||||||
public static string KasinoGuessWhatNumberCleanupDelay = "Kasino.GuessWhatNumber.CleanupDelay";
|
public static string KasinoGuessWhatNumberCleanupDelay = "Kasino.GuessWhatNumber.CleanupDelay";
|
||||||
[BuiltInSetting("Delay in milliseconds before cleaning up the Keno board", SettingValueType.Text, "30000", WholeNumberRegex)]
|
[BuiltInSetting("Delay in milliseconds before cleaning up the Keno board", SettingValueType.Text, "30000", WholeNumberRegex)]
|
||||||
public static string KasinoKenoCleanupDelay = "Kasino.Keno.CleanupDelay";
|
public static string KasinoKenoCleanupDelay = "Kasino.Keno.CleanupDelay";
|
||||||
[BuiltInSetting("Delay in milliseconds before cleaning up the Planes board and result", SettingValueType.Text, "60000", WholeNumberRegex)]
|
[BuiltInSetting("Delay in milliseconds before cleaning up the keno board and result", SettingValueType.Text, "60000", WholeNumberRegex)]
|
||||||
|
public static string KasinoMinesCleanupDelay = "Kasino.Mines.CleanupDelay";
|
||||||
|
[BuiltInSetting("Delay in milliseconds before cleaning up the mines command messages", SettingValueType.Text, "60000", WholeNumberRegex)]
|
||||||
public static string KasinoPlanesCleanupDelay = "Kasino.Planes.CleanupDelay";
|
public static string KasinoPlanesCleanupDelay = "Kasino.Planes.CleanupDelay";
|
||||||
[BuiltInSetting("Delay in milliseconds between each check to see whether tehre's messages to be deleted", SettingValueType.Text, "1000", WholeNumberRegex)]
|
[BuiltInSetting("Delay in milliseconds between each check to see whether tehre's messages to be deleted", SettingValueType.Text, "1000", WholeNumberRegex)]
|
||||||
public static string BotScheduledDeletionInterval = "Bot.ScheduledDeletionInterval";
|
public static string BotScheduledDeletionInterval = "Bot.ScheduledDeletionInterval";
|
||||||
|
|||||||
Reference in New Issue
Block a user