Improved how .bf and .br look like. Improved .slot result calculation performance (because of .slottest). Some string changes

This commit is contained in:
Kwoth
2022-07-13 06:22:39 +02:00
parent f3ed14de5b
commit 2b8daa2177
11 changed files with 151 additions and 102 deletions

View File

@@ -26,6 +26,20 @@ public class NadekoRandom : Random
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)
{ {
if (minValue > maxValue) if (minValue > maxValue)

View File

@@ -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)

View File

@@ -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; }
} }

View File

@@ -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;
@@ -49,3 +62,52 @@ public enum SlotWinType : byte
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,
};
}
*/

View File

@@ -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; }
} }

View File

@@ -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()));

View File

@@ -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]

View File

@@ -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,
};
}
}

View File

@@ -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);
} }

View File

@@ -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();

View File

@@ -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}",