mirror of
https://github.com/barelyprofessional/KfChatDotNet.git
synced 2026-05-02 04:22:04 -04:00
Implement minimum wager requirement for slots (#61)
* Implement minimum wager requirement for slots Added minimum wager validation for slots command. * Implement house edge and rigged outcomes in SlotsCommand adds house edge to slots if your house edge is greater than 1, HOUSE_EDGE - 100% chance for guaranteed max win chance (spawns all the symbols in the right place, does not guarantee top tier multi) if house edge is less than 1, 100% - HOUSE_EDGE chance for guaranteed loss
This commit is contained in:
@@ -44,9 +44,11 @@ public class SlotsCommand : ICommand
|
|||||||
Window = TimeSpan.FromSeconds(15)
|
Window = TimeSpan.FromSeconds(15)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private decimal HOUSE_EDGE = (decimal)0.98;
|
||||||
public async Task RunCommand(ChatBot botInstance, MessageModel messagen, UserDbModel user,
|
public async Task RunCommand(ChatBot botInstance, MessageModel messagen, UserDbModel user,
|
||||||
GroupCollection arguments, CancellationToken ctx)
|
GroupCollection arguments, CancellationToken ctx)
|
||||||
{
|
{
|
||||||
|
|
||||||
var settings = await SettingsProvider.GetMultipleValuesAsync([
|
var settings = await SettingsProvider.GetMultipleValuesAsync([
|
||||||
BuiltIn.Keys.KasinoGameDisabledMessageCleanupDelay, BuiltIn.Keys.KasinoSlotsEnabled
|
BuiltIn.Keys.KasinoGameDisabledMessageCleanupDelay, BuiltIn.Keys.KasinoSlotsEnabled
|
||||||
]);
|
]);
|
||||||
@@ -82,6 +84,10 @@ public class SlotsCommand : ICommand
|
|||||||
|
|
||||||
|
|
||||||
var wager = Convert.ToDecimal(amount.Value);
|
var wager = Convert.ToDecimal(amount.Value);
|
||||||
|
if (wager < (decimal)0.01){
|
||||||
|
await botInstance.SendChatMessageAsync($"{user.FormatUsername()} you must bet a minimum of $0.01 KKK", true, autoDeleteAfter: TimeSpan.FromSeconds(30));
|
||||||
|
return;
|
||||||
|
}
|
||||||
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}");
|
||||||
@@ -93,12 +99,24 @@ public class SlotsCommand : ICommand
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char rigged = '0';
|
||||||
|
int rigCheck = Money.GetRandomNumber(gambler, 0, 1, 1);
|
||||||
|
if (HOUSE_EDGE > 1)
|
||||||
|
{
|
||||||
|
if (HOUSE_EDGE - rigCheck > 1) rigged = 'W';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (rigCheck - HOUSE_EDGE > 0) rigged = 'L';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
decimal winnings;
|
decimal winnings;
|
||||||
double delayHSec = 0;
|
double delayHSec = 0;
|
||||||
using (var board = new KiwiSlotBoard(wager))
|
using (var board = new KiwiSlotBoard(wager))
|
||||||
{
|
{
|
||||||
board.LoadAssets();
|
board.LoadAssets();
|
||||||
board.ExecuteGameLoop(spins);
|
board.ExecuteGameLoop(spins, rigged);
|
||||||
using (var finalImageStream = board.ExportAndCleanup())
|
using (var finalImageStream = board.ExportAndCleanup())
|
||||||
{
|
{
|
||||||
if (finalImageStream == null)
|
if (finalImageStream == null)
|
||||||
@@ -169,6 +187,7 @@ public class SlotsCommand : ICommand
|
|||||||
private int _activeFeatureTier = 0, _currentFeatureSpin = 0;
|
private int _activeFeatureTier = 0, _currentFeatureSpin = 0;
|
||||||
private bool _showGoldCircle = false;
|
private bool _showGoldCircle = false;
|
||||||
|
|
||||||
|
|
||||||
private readonly RandomShim<StandardRng> _rand = RandomShim.Create(StandardRng.Create());
|
private readonly RandomShim<StandardRng> _rand = RandomShim.Create(StandardRng.Create());
|
||||||
private static readonly List<char> ExpanderWild =
|
private static readonly List<char> ExpanderWild =
|
||||||
['N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2'];
|
['N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2'];
|
||||||
@@ -347,11 +366,12 @@ public class SlotsCommand : ICommand
|
|||||||
AnimatedImage.Frames.RemoveFrame(0);
|
AnimatedImage.Frames.RemoveFrame(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ExecuteGameLoop(int spins, int featureSpins = 0)
|
public void ExecuteGameLoop(int spins, int featureSpins = 0, char rigged = '0')
|
||||||
{
|
{
|
||||||
for (int sp = 0; sp < spins; sp++)
|
for (int sp = 0; sp < spins; sp++)
|
||||||
{
|
{
|
||||||
GeneratePreBoard(featureSpins);
|
|
||||||
|
GeneratePreBoard(featureSpins, rigged);
|
||||||
var fCount = 0;
|
var fCount = 0;
|
||||||
for (var i = 0; i < 5; i++) for (var j = 0; j < 5; j++) if (_preboard[i, j] == FEATURE) fCount++;
|
for (var i = 0; i < 5; i++) for (var j = 0; j < 5; j++) if (_preboard[i, j] == FEATURE) fCount++;
|
||||||
|
|
||||||
@@ -365,7 +385,7 @@ public class SlotsCommand : ICommand
|
|||||||
ProcessReelsAndWins();
|
ProcessReelsAndWins();
|
||||||
var total = _activeFeatureTier switch { 3 => 3, 4 => 5, 5 => 10, _ => 0 };
|
var total = _activeFeatureTier switch { 3 => 3, 4 => 5, 5 => 10, _ => 0 };
|
||||||
if (total > 0 || featureSpins != 0 || spins > 1) AddPause(50);
|
if (total > 0 || featureSpins != 0 || spins > 1) AddPause(50);
|
||||||
if (featureSpins == 0) for (var s = 1; s <= total; s++) ExecuteGameLoop(1,s);
|
if (featureSpins == 0) for (var s = 1; s <= total; s++) ExecuteGameLoop(1,s, rigged);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,10 +446,35 @@ public class SlotsCommand : ICommand
|
|||||||
{
|
{
|
||||||
var fc = 0; HashSet<int> ex = [];
|
var fc = 0; HashSet<int> ex = [];
|
||||||
for (var i = 0; i < 5; i++) {
|
for (var i = 0; i < 5; i++) {
|
||||||
for (var j = 0; j < 5; j++) {
|
for (var j = 0; j < 5; j++)
|
||||||
|
{
|
||||||
|
int loopCounter = 0;
|
||||||
var r = _rand.NextDouble() * 100.6;
|
var r = _rand.NextDouble() * 100.6;
|
||||||
if (f != 0 && j > 2) r *= 1.05;
|
if (f != 0 && j > 2) r *= 1.1;
|
||||||
if (r < 22) _preboard[i, j] = 'A';
|
if (rigged == 'L') r = _rand.NextDouble() * 97.01;
|
||||||
|
|
||||||
|
if (rigged == 'W') // guarantee max win
|
||||||
|
{
|
||||||
|
if (i == 0)
|
||||||
|
{
|
||||||
|
_preboard[i, j] = EXPANDER;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (i < 4)
|
||||||
|
{
|
||||||
|
_preboard[i, j] = WILD;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_preboard[i, j] = FEATURE;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*if (r < 22) _preboard[i, j] = 'A';
|
||||||
else if (r < 44) _preboard[i, j] = 'B';
|
else if (r < 44) _preboard[i, j] = 'B';
|
||||||
else if (r < 52) _preboard[i, j] = 'C';
|
else if (r < 52) _preboard[i, j] = 'C';
|
||||||
else if (r < 66) _preboard[i, j] = 'D';
|
else if (r < 66) _preboard[i, j] = 'D';
|
||||||
@@ -441,10 +486,212 @@ public class SlotsCommand : ICommand
|
|||||||
else if (r < 97) _preboard[i, j] = 'J';
|
else if (r < 97) _preboard[i, j] = 'J';
|
||||||
else if (r < 98.5) _preboard[i, j] = WILD;
|
else if (r < 98.5) _preboard[i, j] = WILD;
|
||||||
else if (r < (j <= 2 ? 99 : 99.5)) { if (!ex.Contains(j)) { _preboard[i, j] = EXPANDER; ex.Add(j); } else _preboard[i, j] = WILD; }
|
else if (r < (j <= 2 ? 99 : 99.5)) { if (!ex.Contains(j)) { _preboard[i, j] = EXPANDER; ex.Add(j); } else _preboard[i, j] = WILD; }
|
||||||
else { if (fc < 5) { _preboard[i, j] = FEATURE; fc++; } else _preboard[i, j] = WILD; }
|
else { if (fc < 5) { _preboard[i, j] = FEATURE; fc++; } else _preboard[i, j] = WILD; }*/
|
||||||
|
|
||||||
|
_preboard[i, j] = PickSlotSymbol(r, i, j);
|
||||||
|
switch (_preboard[i, j])
|
||||||
|
{
|
||||||
|
case EXPANDER: ex.Add(j);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*if (rigged == 'L') //guarantee random losing board
|
||||||
|
{
|
||||||
|
//if i==0 and j==0 pick a random one, aka do nothing
|
||||||
|
if (i == 0 && j == 1)
|
||||||
|
{
|
||||||
|
//first row, just make sure the tiles do not have straight line match for the first three from the left. essentially make sure from first row 0 1 2 3 4, tiles 0, 1, and 2 all need to be different
|
||||||
|
while (_preboard[i, j - 1] == _preboard[i, j])
|
||||||
|
{
|
||||||
|
r = _rand.NextDouble() * 97.01;
|
||||||
|
_preboard[i, j] = PickSlotSymbol(r, i, j);
|
||||||
|
loopCounter++;
|
||||||
|
if (loopCounter > 10000) throw new Exception("Failed to generate a losing board");
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (i == 0 && j == 2)
|
||||||
|
{
|
||||||
|
while (_preboard[i, j - 1] == _preboard[i, j] || _preboard[i, j - 2] == _preboard[i, j])
|
||||||
|
{
|
||||||
|
r = _rand.NextDouble() * 97.01;
|
||||||
|
_preboard[i, j] = PickSlotSymbol(r, i, j);
|
||||||
|
loopCounter++;
|
||||||
|
if (loopCounter > 10000) throw new Exception("Failed to generate a losing board2");
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (i > 0 && j == 0)
|
||||||
|
{
|
||||||
|
//if its the first one in a row check to make sure its not the same as a diagonal on the row above
|
||||||
|
if (_preboard[i - 1, j + 1] == _preboard[i, j])
|
||||||
|
{
|
||||||
|
while (_preboard[i - 1, j + 1] == _preboard[i, j])
|
||||||
|
{
|
||||||
|
r = _rand.NextDouble() * 97.01;
|
||||||
|
_preboard[i, j] = PickSlotSymbol(r, i, j);
|
||||||
|
loopCounter++;
|
||||||
|
if (loopCounter > 10000) throw new Exception("Failed to generate a losing board3");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (i > 0 && j > 0 && j < 3)
|
||||||
|
{
|
||||||
|
//check both diagonals above and one space behind
|
||||||
|
if (j == 2)
|
||||||
|
{
|
||||||
|
if (_preboard[i - 1, j + 1] == _preboard[i, j] ||
|
||||||
|
_preboard[i - 1, j - 1] == _preboard[i, j] ||
|
||||||
|
_preboard[i, j - 1] == _preboard[i, j] ||
|
||||||
|
_preboard[i, j - 2] == _preboard[i, j])
|
||||||
|
{
|
||||||
|
while (_preboard[i - 1, j + 1] == _preboard[i, j] ||
|
||||||
|
_preboard[i - 1, j - 1] == _preboard[i, j] ||
|
||||||
|
_preboard[i, j - 1] == _preboard[i, j] ||
|
||||||
|
_preboard[i, j - 2] == _preboard[i, j])
|
||||||
|
{
|
||||||
|
r = _rand.NextDouble() * 97.01;
|
||||||
|
_preboard[i, j] = PickSlotSymbol(r, i, j);
|
||||||
|
loopCounter++;
|
||||||
|
if (loopCounter > 10000) throw new Exception("Failed to generate a losing board4");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_preboard[i - 1, j + 1] == _preboard[i, j] || _preboard[i - 1, j - 1] == _preboard[i, j] || _preboard[i, j - 1] == _preboard[i, j])
|
||||||
|
{
|
||||||
|
while (_preboard[i - 1, j + 1] == _preboard[i, j] || _preboard[i - 1, j - 1] == _preboard[i, j])
|
||||||
|
{
|
||||||
|
r = _rand.NextDouble() * 97.01;
|
||||||
|
_preboard[i, j] = PickSlotSymbol(r, i, j);
|
||||||
|
loopCounter++;
|
||||||
|
if (loopCounter > 10000) throw new Exception("Failed to generate a losing board5");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rigged == 'L') RigSlotBoard();
|
||||||
|
char PickSlotSymbol(double r, int i, int j)
|
||||||
|
{
|
||||||
|
if (r < 22) return 'A';
|
||||||
|
else if (r < 44) return 'B';
|
||||||
|
else if (r < 52) return 'C';
|
||||||
|
else if (r < 66) return 'D';
|
||||||
|
else if (r < 78) return 'E';
|
||||||
|
else if (r < 84) return 'F';
|
||||||
|
else if (r < 89) return 'G';
|
||||||
|
else if (r < 92) return 'H';
|
||||||
|
else if (r < 95) return 'I';
|
||||||
|
else if (r < 97) return 'J';
|
||||||
|
else if (r < 98.5) return WILD;
|
||||||
|
else if (r < (j <= 2 ? 99 : 99.5)) { if (!ex.Contains(j)) { return EXPANDER; } else return WILD; }
|
||||||
|
else { if (fc < 5) { fc++;
|
||||||
|
return FEATURE;
|
||||||
|
} else return WILD; }
|
||||||
|
}
|
||||||
|
void RigSlotBoard()
|
||||||
|
{
|
||||||
|
int totalRuns = 0;
|
||||||
|
double r;
|
||||||
|
List<(int r, int c)> positionsToCheck;
|
||||||
|
for (int row = 0; row < 5; row++)
|
||||||
|
{
|
||||||
|
for (int col = 0; col < 5; col++)
|
||||||
|
{
|
||||||
|
int loopCounter = 0;
|
||||||
|
if (row == 0 && col == 0) ; //do nothing
|
||||||
|
if (row == 0 && col == 1)
|
||||||
|
{
|
||||||
|
//check 1 slot behind
|
||||||
|
while (_preboard[row, col - 1] == _preboard[row, col])
|
||||||
|
{
|
||||||
|
r = _rand.NextDouble() * 97.01;
|
||||||
|
_preboard[row, col] = PickSlotSymbol(r, row, col);
|
||||||
|
loopCounter++;
|
||||||
|
totalRuns++;
|
||||||
|
if (loopCounter > 10000) throw new Exception($"Failed to rig slot board after 10000 attempts. Got stuck on row {row} col {col}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (row == 0 && col == 2)
|
||||||
|
{
|
||||||
|
//check 2 slots behind
|
||||||
|
while (_preboard[row, col - 1] == _preboard[row, col] || _preboard[row, col - 2] == _preboard[row, col])
|
||||||
|
{
|
||||||
|
r = _rand.NextDouble() * 97.01;
|
||||||
|
_preboard[row, col] = PickSlotSymbol(r, row, col);
|
||||||
|
loopCounter++;
|
||||||
|
totalRuns++;
|
||||||
|
if (loopCounter > 10000) throw new Exception($"Failed to rig slot board after 10000 attempts. Got stuck on row {row} col {col}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (row > 0 && col == 0)
|
||||||
|
{
|
||||||
|
//check the diagonal above and to the right
|
||||||
|
while (_preboard[row - 1, col + 1] == _preboard[row, col])
|
||||||
|
{
|
||||||
|
r = _rand.NextDouble() * 97.01;
|
||||||
|
_preboard[row, col] = PickSlotSymbol(r, row, col);
|
||||||
|
loopCounter++;
|
||||||
|
totalRuns++;
|
||||||
|
if (loopCounter > 10000) throw new Exception($"Failed to rig slot board after 10000 attempts. Got stuck on row {row} col {col}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (row > 1 && col == 0)
|
||||||
|
{
|
||||||
|
//check the diagnoals above and to the right for 2 spaces
|
||||||
|
while (_preboard[row - 1, col + 1] == _preboard[row, col] ||
|
||||||
|
_preboard[row - 2, col + 2] == _preboard[row, col])
|
||||||
|
{
|
||||||
|
r = _rand.NextDouble() * 97.01;
|
||||||
|
_preboard[row, col] = PickSlotSymbol(r, row, col);
|
||||||
|
loopCounter++;
|
||||||
|
totalRuns++;
|
||||||
|
if (loopCounter > 10000) throw new Exception($"Failed to rig slot board after 10000 attempts. Got stuck on row {row} col {col}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (row > 0 && col == 1)
|
||||||
|
{
|
||||||
|
//check both diagonals above for 1 space, and one space behind
|
||||||
|
while (_preboard[row - 1, col - 1] == _preboard[row, col] ||
|
||||||
|
_preboard[row + 1, col + 1] == _preboard[row, col] ||
|
||||||
|
_preboard[row, col - 1] == _preboard[row, col])
|
||||||
|
{
|
||||||
|
r = _rand.NextDouble() * 97.01;
|
||||||
|
_preboard[row, col] = PickSlotSymbol(r, row, col);
|
||||||
|
loopCounter++;
|
||||||
|
totalRuns++;
|
||||||
|
if (loopCounter > 10000) throw new Exception($"Failed to rig slot board after 10000 attempts. Got stuck on row {row} col {col}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (row > 0 && col == 2)
|
||||||
|
{
|
||||||
|
//check both diagonals above for 1 space and 2 spaces behind
|
||||||
|
while (_preboard[row - 1, col - 1] == _preboard[row, col] ||
|
||||||
|
_preboard[row + 1, col + 1] == _preboard[row, col] ||
|
||||||
|
_preboard[row, col - 1] == _preboard[row, col] ||
|
||||||
|
_preboard[row, col - 2] == _preboard[row, col])
|
||||||
|
{
|
||||||
|
r = _rand.NextDouble() * 97.01;
|
||||||
|
_preboard[row, col] = PickSlotSymbol(r, row, col);
|
||||||
|
loopCounter++;
|
||||||
|
totalRuns++;
|
||||||
|
if (loopCounter > 10000) throw new Exception($"Failed to rig slot board after 10000 attempts. Got stuck on row {row} col {col}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user