mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-11-03 08:14:28 -05:00
Improved how .bf and .br look like. Improved .slot result calculation performance (because of .slottest). Some string changes
This commit is contained in:
@@ -25,6 +25,20 @@ public class NadekoRandom : Random
|
|||||||
_rng.GetBytes(bytes);
|
_rng.GetBytes(bytes);
|
||||||
return Math.Abs(BitConverter.ToInt32(bytes, 0)) % maxValue;
|
return Math.Abs(BitConverter.ToInt32(bytes, 0)) % maxValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte Next(byte minValue, byte maxValue)
|
||||||
|
{
|
||||||
|
if (minValue > maxValue)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(maxValue));
|
||||||
|
|
||||||
|
if (minValue == maxValue)
|
||||||
|
return minValue;
|
||||||
|
|
||||||
|
var bytes = new byte[1];
|
||||||
|
_rng.GetBytes(bytes);
|
||||||
|
|
||||||
|
return (byte)((bytes[0] % (maxValue - minValue)) + minValue);
|
||||||
|
}
|
||||||
|
|
||||||
public override int Next(int minValue, int maxValue)
|
public override int Next(int minValue, int maxValue)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ public sealed class BetflipGame
|
|||||||
_rng = new NadekoRandom();
|
_rng = new NadekoRandom();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BetflipResult Flip(int guess, decimal amount)
|
public BetflipResult Flip(byte guess, decimal amount)
|
||||||
{
|
{
|
||||||
var side = _rng.Next(0, 1);
|
var side = _rng.Next(0, 2);
|
||||||
decimal won = 0;
|
decimal won = 0;
|
||||||
|
|
||||||
if (side == guess)
|
if (side == guess)
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
public readonly struct BetflipResult
|
public readonly struct BetflipResult
|
||||||
{
|
{
|
||||||
public decimal Won { get; init; }
|
public decimal Won { get; init; }
|
||||||
public int Side { get; init; }
|
public byte Side { get; init; }
|
||||||
}
|
}
|
||||||
@@ -2,30 +2,43 @@ namespace Nadeko.Econ.Gambling;
|
|||||||
|
|
||||||
public class SlotGame
|
public class SlotGame
|
||||||
{
|
{
|
||||||
private static readonly Random _rng = new NadekoRandom();
|
private static readonly NadekoRandom _rng = new NadekoRandom();
|
||||||
|
|
||||||
public SlotResult Spin(decimal bet)
|
public SlotResult Spin(decimal bet)
|
||||||
{
|
{
|
||||||
var rolls = new[] { _rng.Next(0, 6), _rng.Next(0, 6), _rng.Next(0, 6) };
|
var rolls = new[]
|
||||||
|
{
|
||||||
|
_rng.Next(0, 6),
|
||||||
|
_rng.Next(0, 6),
|
||||||
|
_rng.Next(0, 6)
|
||||||
|
};
|
||||||
|
|
||||||
|
ref var a = ref rolls[0];
|
||||||
|
ref var b = ref rolls[1];
|
||||||
|
ref var c = ref rolls[2];
|
||||||
|
|
||||||
var multi = 0;
|
var multi = 0;
|
||||||
var winType = SlotWinType.None;
|
var winType = SlotWinType.None;
|
||||||
|
if (a == b && b == c)
|
||||||
if (rolls.All(x => x == 5))
|
|
||||||
{
|
{
|
||||||
winType = SlotWinType.TrippleJoker;
|
if (a == 5)
|
||||||
multi = 30;
|
{
|
||||||
|
winType = SlotWinType.TrippleJoker;
|
||||||
|
multi = 30;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
winType = SlotWinType.TrippleNormal;
|
||||||
|
multi = 10;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (rolls.All(x => x == rolls[0]))
|
else if (a == 5 && (b == 5 || c == 5)
|
||||||
{
|
|| (b == 5 && c == 5))
|
||||||
winType = SlotWinType.TrippleNormal;
|
|
||||||
multi = 10;
|
|
||||||
}
|
|
||||||
else if (rolls.Count(x => x == 5) == 2)
|
|
||||||
{
|
{
|
||||||
winType = SlotWinType.DoubleJoker;
|
winType = SlotWinType.DoubleJoker;
|
||||||
multi = 4;
|
multi = 4;
|
||||||
}
|
}
|
||||||
else if (rolls.Any(x => x == 5))
|
else if (a == 5 || b == 5 || c == 5)
|
||||||
{
|
{
|
||||||
winType = SlotWinType.SingleJoker;
|
winType = SlotWinType.SingleJoker;
|
||||||
multi = 1;
|
multi = 1;
|
||||||
@@ -48,4 +61,53 @@ public enum SlotWinType : byte
|
|||||||
DoubleJoker,
|
DoubleJoker,
|
||||||
TrippleNormal,
|
TrippleNormal,
|
||||||
TrippleJoker,
|
TrippleJoker,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
var rolls = new[]
|
||||||
|
{
|
||||||
|
_rng.Next(default(byte), 6),
|
||||||
|
_rng.Next(default(byte), 6),
|
||||||
|
_rng.Next(default(byte), 6)
|
||||||
|
};
|
||||||
|
|
||||||
|
var multi = 0;
|
||||||
|
var winType = SlotWinType.None;
|
||||||
|
|
||||||
|
ref var a = ref rolls[0];
|
||||||
|
ref var b = ref rolls[1];
|
||||||
|
ref var c = ref rolls[2];
|
||||||
|
if (a == b && b == c)
|
||||||
|
{
|
||||||
|
if (a == 5)
|
||||||
|
{
|
||||||
|
winType = SlotWinType.TrippleJoker;
|
||||||
|
multi = 30;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
winType = SlotWinType.TrippleNormal;
|
||||||
|
multi = 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (a == 5 && (b == 5 || c == 5)
|
||||||
|
|| (b == 5 && c == 5))
|
||||||
|
{
|
||||||
|
winType = SlotWinType.DoubleJoker;
|
||||||
|
multi = 4;
|
||||||
|
}
|
||||||
|
else if (rolls.Any(x => x == 5))
|
||||||
|
{
|
||||||
|
winType = SlotWinType.SingleJoker;
|
||||||
|
multi = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Won = bet * multi,
|
||||||
|
WinType = winType,
|
||||||
|
Multiplier = multi,
|
||||||
|
Rolls = rolls,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
*/
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
public readonly struct SlotResult
|
public readonly struct SlotResult
|
||||||
{
|
{
|
||||||
public decimal Multiplier { get; init; }
|
public decimal Multiplier { get; init; }
|
||||||
public int[] Rolls { get; init; }
|
public byte[] Rolls { get; init; }
|
||||||
public decimal Won { get; init; }
|
public decimal Won { get; init; }
|
||||||
public SlotWinType WinType { get; init; }
|
public SlotWinType WinType { get; init; }
|
||||||
}
|
}
|
||||||
@@ -11,16 +11,16 @@ namespace NadekoBot.Modules.Gambling;
|
|||||||
public partial class Gambling
|
public partial class Gambling
|
||||||
{
|
{
|
||||||
[Group]
|
[Group]
|
||||||
public partial class FlipCoinCommands : GamblingSubmodule<GamblingService>
|
public partial class FlipCoinCommands : GamblingSubmodule<IGamblingService>
|
||||||
{
|
{
|
||||||
public enum BetFlipGuess
|
public enum BetFlipGuess : byte
|
||||||
{
|
{
|
||||||
H = 1,
|
H = 0,
|
||||||
Head = 1,
|
Head = 0,
|
||||||
Heads = 1,
|
Heads = 0,
|
||||||
T = 2,
|
T = 1,
|
||||||
Tail = 2,
|
Tail = 1,
|
||||||
Tails = 2
|
Tails = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly NadekoRandom _rng = new();
|
private static readonly NadekoRandom _rng = new();
|
||||||
@@ -52,11 +52,14 @@ public partial class Gambling
|
|||||||
var headCount = 0;
|
var headCount = 0;
|
||||||
var tailCount = 0;
|
var tailCount = 0;
|
||||||
var imgs = new Image<Rgba32>[count];
|
var imgs = new Image<Rgba32>[count];
|
||||||
for (var i = 0; i < count; i++)
|
var headsArr = await _images.GetHeadsImageAsync();
|
||||||
|
var tailsArr = await _images.GetTailsImageAsync();
|
||||||
|
|
||||||
|
var result = await _service.FlipAsync(count);
|
||||||
|
|
||||||
|
for (var i = 0; i < result.Length; i++)
|
||||||
{
|
{
|
||||||
var headsArr = await _images.GetHeadsImageAsync();
|
if (result[i].Side == 0)
|
||||||
var tailsArr = await _images.GetTailsImageAsync();
|
|
||||||
if (_rng.Next(0, 10) < 5)
|
|
||||||
{
|
{
|
||||||
imgs[i] = Image.Load(headsArr);
|
imgs[i] = Image.Load(headsArr);
|
||||||
headCount++;
|
headCount++;
|
||||||
@@ -69,7 +72,7 @@ public partial class Gambling
|
|||||||
}
|
}
|
||||||
|
|
||||||
using var img = imgs.Merge(out var format);
|
using var img = imgs.Merge(out var format);
|
||||||
await using var stream = img.ToStream(format);
|
await using var stream = await img.ToStreamAsync(format);
|
||||||
foreach (var i in imgs)
|
foreach (var i in imgs)
|
||||||
i.Dispose();
|
i.Dispose();
|
||||||
|
|
||||||
@@ -92,38 +95,37 @@ public partial class Gambling
|
|||||||
if (!await CheckBetMandatory(amount) || amount == 1)
|
if (!await CheckBetMandatory(amount) || amount == 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var removed = await _cs.RemoveAsync(ctx.User, amount, new("betflip", "bet"));
|
var res = await _service.BetFlipAsync(ctx.User.Id, amount, (byte)guess);
|
||||||
if (!removed)
|
if (!res.TryPickT0(out var result, out _))
|
||||||
{
|
{
|
||||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BetFlipGuess result;
|
|
||||||
Uri imageToSend;
|
Uri imageToSend;
|
||||||
var coins = _ic.Data.Coins;
|
var coins = _ic.Data.Coins;
|
||||||
if (_rng.Next(0, 1000) <= 499)
|
if (result.Side == 0)
|
||||||
{
|
{
|
||||||
imageToSend = coins.Heads[_rng.Next(0, coins.Heads.Length)];
|
imageToSend = coins.Heads[_rng.Next(0, coins.Heads.Length)];
|
||||||
result = BetFlipGuess.Heads;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
imageToSend = coins.Tails[_rng.Next(0, coins.Tails.Length)];
|
imageToSend = coins.Tails[_rng.Next(0, coins.Tails.Length)];
|
||||||
result = BetFlipGuess.Tails;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string str;
|
string str;
|
||||||
if (guess == result)
|
var won = (long)result.Won;
|
||||||
|
if (won > 0)
|
||||||
{
|
{
|
||||||
var toWin = (long)(amount * Config.BetFlip.Multiplier);
|
str = Format.Bold(GetText(strs.flip_guess(N(won))));
|
||||||
str = Format.Bold(ctx.User.ToString()) + " " + GetText(strs.flip_guess(N(toWin)));
|
|
||||||
await _cs.AddAsync(ctx.User, toWin, new("betflip", "win"));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
str = Format.Bold(ctx.User.ToString()) + " " + GetText(strs.better_luck);
|
{
|
||||||
|
str = Format.Bold(GetText(strs.better_luck));
|
||||||
|
}
|
||||||
|
|
||||||
await ctx.Channel.EmbedAsync(_eb.Create()
|
await ctx.Channel.EmbedAsync(_eb.Create()
|
||||||
|
.WithAuthor(ctx.User)
|
||||||
.WithDescription(str)
|
.WithDescription(str)
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithImageUrl(imageToSend.ToString()));
|
.WithImageUrl(imageToSend.ToString()));
|
||||||
|
|||||||
@@ -655,19 +655,26 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var win = (long)result.Won;
|
var win = (long)result.Won;
|
||||||
var str = Format.Bold(ctx.User.ToString()) + Format.Code(GetText(strs.roll(result.Roll)));
|
string str;
|
||||||
if (win > 0)
|
if (win > 0)
|
||||||
{
|
{
|
||||||
str += GetText(strs.br_win(N(win), result.Threshold + (result.Roll == 100 ? " 👑" : "")));
|
str = GetText(strs.br_win(N(win), result.Threshold + (result.Roll == 100 ? " 👑" : "")));
|
||||||
await _cs.AddAsync(ctx.User, win, new("betroll", "win"));
|
await _cs.AddAsync(ctx.User, win, new("betroll", "win"));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
str += GetText(strs.better_luck);
|
str = GetText(strs.better_luck);
|
||||||
}
|
}
|
||||||
|
|
||||||
await SendConfirmAsync(str);
|
var eb = _eb.Create(ctx)
|
||||||
|
.WithAuthor(ctx.User)
|
||||||
|
.WithFooter(str)
|
||||||
|
.AddField(GetText(strs.roll2), result.Roll.ToString(CultureInfo.InvariantCulture))
|
||||||
|
.WithOkColor();
|
||||||
|
|
||||||
|
await ctx.Channel.EmbedAsync(eb);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public partial class Gambling
|
|||||||
private static decimal totalBet;
|
private static decimal totalBet;
|
||||||
private static decimal totalPaidOut;
|
private static decimal totalPaidOut;
|
||||||
|
|
||||||
private static readonly HashSet<ulong> _runningUsers = new();
|
private static readonly ConcurrentHashSet<ulong> _runningUsers = new();
|
||||||
|
|
||||||
//here is a payout chart
|
//here is a payout chart
|
||||||
//https://lh6.googleusercontent.com/-i1hjAJy_kN4/UswKxmhrbPI/AAAAAAAAB1U/82wq_4ZZc-Y/DE6B0895-6FC1-48BE-AC4F-14D1B91AB75B.jpg
|
//https://lh6.googleusercontent.com/-i1hjAJy_kN4/UswKxmhrbPI/AAAAAAAAB1U/82wq_4ZZc-Y/DE6B0895-6FC1-48BE-AC4F-14D1B91AB75B.jpg
|
||||||
@@ -84,11 +84,12 @@ public partial class Gambling
|
|||||||
var dict = new Dictionary<decimal, int>();
|
var dict = new Dictionary<decimal, int>();
|
||||||
for (var i = 0; i < tests; i++)
|
for (var i = 0; i < tests; i++)
|
||||||
{
|
{
|
||||||
var res = SlotMachine.Pull(0);
|
var res = await _service.SlotAsync(ctx.User.Id, 0);
|
||||||
if (dict.ContainsKey(res.Multiplier))
|
var multi = res.AsT0.Multiplier;
|
||||||
dict[res.Multiplier] += 1;
|
if (dict.ContainsKey(multi))
|
||||||
|
dict[multi] += 1;
|
||||||
else
|
else
|
||||||
dict.Add(res.Multiplier, 1);
|
dict.Add(multi, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
@@ -157,7 +158,7 @@ public partial class Gambling
|
|||||||
WrapTextWidth = 140
|
WrapTextWidth = 140
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
result.Won.ToString(),
|
((long)result.Won).ToString(),
|
||||||
_fonts.DottyFont.CreateFont(65),
|
_fonts.DottyFont.CreateFont(65),
|
||||||
fontColor,
|
fontColor,
|
||||||
new(227, 92)));
|
new(227, 92)));
|
||||||
@@ -219,50 +220,9 @@ public partial class Gambling
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_ = Task.Run(async () =>
|
await Task.Delay(1000);
|
||||||
{
|
_runningUsers.TryRemove(ctx.User.Id);
|
||||||
await Task.Delay(1000);
|
|
||||||
_runningUsers.Remove(ctx.User.Id);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class SlotMachine
|
|
||||||
{
|
|
||||||
public const int MAX_VALUE = 5;
|
|
||||||
|
|
||||||
private static readonly List<Func<int[], int>> _winningCombos = new()
|
|
||||||
{
|
|
||||||
//three flowers
|
|
||||||
arr => arr.All(a => a == MAX_VALUE) ? 30 : 0,
|
|
||||||
//three of the same
|
|
||||||
arr => arr.All(a => a == arr[0]) ? 10 : 0,
|
|
||||||
//two flowers
|
|
||||||
arr => arr.Count(a => a == MAX_VALUE) == 2 ? 4 : 0,
|
|
||||||
//one flower
|
|
||||||
arr => arr.Any(a => a == MAX_VALUE) ? 1 : 0
|
|
||||||
};
|
|
||||||
|
|
||||||
public static SlotResult Pull(decimal amount)
|
|
||||||
{
|
|
||||||
var numbers = new int[3];
|
|
||||||
for (var i = 0; i < numbers.Length; i++)
|
|
||||||
numbers[i] = new NadekoRandom().Next(0, MAX_VALUE + 1);
|
|
||||||
var multi = 0M;
|
|
||||||
foreach (var t in _winningCombos)
|
|
||||||
{
|
|
||||||
multi = t(numbers);
|
|
||||||
if (multi != 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new()
|
|
||||||
{
|
|
||||||
Rolls = numbers,
|
|
||||||
Multiplier = multi,
|
|
||||||
Won = multi * amount,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ public interface IGamblingService
|
|||||||
{
|
{
|
||||||
Task<OneOf<WofResult, GamblingError>> WofAsync(ulong userId, long amount);
|
Task<OneOf<WofResult, GamblingError>> WofAsync(ulong userId, long amount);
|
||||||
Task<OneOf<BetrollResult, GamblingError>> BetRollAsync(ulong userId, long amount);
|
Task<OneOf<BetrollResult, GamblingError>> BetRollAsync(ulong userId, long amount);
|
||||||
Task<OneOf<BetflipResult, GamblingError>> BetFlipAsync(ulong userId, long amount, int guess);
|
Task<OneOf<BetflipResult, GamblingError>> BetFlipAsync(ulong userId, long amount, byte guess);
|
||||||
Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount);
|
Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount);
|
||||||
Task<FlipResult[]> FlipAsync(int count);
|
Task<FlipResult[]> FlipAsync(int count);
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,7 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// todo input checks
|
// todo input checks
|
||||||
|
// todo ladder of fortune
|
||||||
public async Task<OneOf<WofResult, GamblingError>> WofAsync(ulong userId, long amount)
|
public async Task<OneOf<WofResult, GamblingError>> WofAsync(ulong userId, long amount)
|
||||||
{
|
{
|
||||||
var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("wof", "bet"));
|
var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("wof", "bet"));
|
||||||
@@ -61,7 +62,7 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<OneOf<BetflipResult, GamblingError>> BetFlipAsync(ulong userId, long amount, int guess)
|
public async Task<OneOf<BetflipResult, GamblingError>> BetFlipAsync(ulong userId, long amount, byte guess)
|
||||||
{
|
{
|
||||||
var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("betflip", "bet"));
|
var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("betflip", "bet"));
|
||||||
|
|
||||||
@@ -84,11 +85,14 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
|
|
||||||
public async Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount)
|
public async Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount)
|
||||||
{
|
{
|
||||||
var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("slot", "bet"));
|
if (amount > 0)
|
||||||
|
|
||||||
if (!isTakeSuccess)
|
|
||||||
{
|
{
|
||||||
return GamblingError.InsufficientFunds;
|
var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("slot", "bet"));
|
||||||
|
|
||||||
|
if (!isTakeSuccess)
|
||||||
|
{
|
||||||
|
return GamblingError.InsufficientFunds;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var game = new SlotGame();
|
var game = new SlotGame();
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
"not_enough": "You don't have enough {0}",
|
"not_enough": "You don't have enough {0}",
|
||||||
"no_more_cards": "No more cards in the deck.",
|
"no_more_cards": "No more cards in the deck.",
|
||||||
"raffled_user": "Raffled user",
|
"raffled_user": "Raffled user",
|
||||||
"roll": "You rolled {0}.",
|
"roll2": "Roll",
|
||||||
"slot_bet": "Bet",
|
"slot_bet": "Bet",
|
||||||
"slot_jackpot": "WOAAHHHHHH!!! Congratulations!!! x{0}",
|
"slot_jackpot": "WOAAHHHHHH!!! Congratulations!!! x{0}",
|
||||||
"slot_single": "A single {0}, x{1}",
|
"slot_single": "A single {0}, x{1}",
|
||||||
|
|||||||
Reference in New Issue
Block a user