diff --git a/src/Nadeko.Common/NadekoRandom.cs b/src/Nadeko.Common/NadekoRandom.cs index da7688824..af56269ec 100644 --- a/src/Nadeko.Common/NadekoRandom.cs +++ b/src/Nadeko.Common/NadekoRandom.cs @@ -25,6 +25,20 @@ public class NadekoRandom : Random _rng.GetBytes(bytes); 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) { diff --git a/src/Nadeko.Econ/Gambling/Betflip/BetflipGame.cs b/src/Nadeko.Econ/Gambling/Betflip/BetflipGame.cs index 6b9446af9..8e94c21f9 100644 --- a/src/Nadeko.Econ/Gambling/Betflip/BetflipGame.cs +++ b/src/Nadeko.Econ/Gambling/Betflip/BetflipGame.cs @@ -11,9 +11,9 @@ public sealed class BetflipGame _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; if (side == guess) diff --git a/src/Nadeko.Econ/Gambling/Betflip/BetflipResult.cs b/src/Nadeko.Econ/Gambling/Betflip/BetflipResult.cs index 5faf94b8c..e415c73aa 100644 --- a/src/Nadeko.Econ/Gambling/Betflip/BetflipResult.cs +++ b/src/Nadeko.Econ/Gambling/Betflip/BetflipResult.cs @@ -3,5 +3,5 @@ public readonly struct BetflipResult { public decimal Won { get; init; } - public int Side { get; init; } + public byte Side { get; init; } } \ No newline at end of file diff --git a/src/Nadeko.Econ/Gambling/Slot/SlotGame.cs b/src/Nadeko.Econ/Gambling/Slot/SlotGame.cs index ded119208..8d64c51a1 100644 --- a/src/Nadeko.Econ/Gambling/Slot/SlotGame.cs +++ b/src/Nadeko.Econ/Gambling/Slot/SlotGame.cs @@ -2,30 +2,43 @@ namespace Nadeko.Econ.Gambling; public class SlotGame { - private static readonly Random _rng = new NadekoRandom(); + private static readonly NadekoRandom _rng = new NadekoRandom(); 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 winType = SlotWinType.None; - - if (rolls.All(x => x == 5)) + if (a == b && b == c) { - winType = SlotWinType.TrippleJoker; - multi = 30; + if (a == 5) + { + winType = SlotWinType.TrippleJoker; + multi = 30; + } + else + { + winType = SlotWinType.TrippleNormal; + multi = 10; + } } - else if (rolls.All(x => x == rolls[0])) - { - winType = SlotWinType.TrippleNormal; - multi = 10; - } - else if (rolls.Count(x => x == 5) == 2) + else if (a == 5 && (b == 5 || c == 5) + || (b == 5 && c == 5)) { winType = SlotWinType.DoubleJoker; multi = 4; } - else if (rolls.Any(x => x == 5)) + else if (a == 5 || b == 5 || c == 5) { winType = SlotWinType.SingleJoker; multi = 1; @@ -48,4 +61,53 @@ public enum SlotWinType : byte DoubleJoker, TrippleNormal, TrippleJoker, -} \ No newline at end of file +} + +/* +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, + }; + } +*/ \ No newline at end of file diff --git a/src/Nadeko.Econ/Gambling/Slot/SlotResult.cs b/src/Nadeko.Econ/Gambling/Slot/SlotResult.cs index 78bf2ccd1..d756ee5a3 100644 --- a/src/Nadeko.Econ/Gambling/Slot/SlotResult.cs +++ b/src/Nadeko.Econ/Gambling/Slot/SlotResult.cs @@ -3,7 +3,7 @@ public readonly struct SlotResult { public decimal Multiplier { get; init; } - public int[] Rolls { get; init; } + public byte[] Rolls { get; init; } public decimal Won { get; init; } public SlotWinType WinType { get; init; } } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/FlipCoin/FlipCoinCommands.cs b/src/NadekoBot/Modules/Gambling/FlipCoin/FlipCoinCommands.cs index 1a33d8c0d..145e4ebe4 100644 --- a/src/NadekoBot/Modules/Gambling/FlipCoin/FlipCoinCommands.cs +++ b/src/NadekoBot/Modules/Gambling/FlipCoin/FlipCoinCommands.cs @@ -11,16 +11,16 @@ namespace NadekoBot.Modules.Gambling; public partial class Gambling { [Group] - public partial class FlipCoinCommands : GamblingSubmodule + public partial class FlipCoinCommands : GamblingSubmodule { - public enum BetFlipGuess + public enum BetFlipGuess : byte { - H = 1, - Head = 1, - Heads = 1, - T = 2, - Tail = 2, - Tails = 2 + H = 0, + Head = 0, + Heads = 0, + T = 1, + Tail = 1, + Tails = 1 } private static readonly NadekoRandom _rng = new(); @@ -52,11 +52,14 @@ public partial class Gambling var headCount = 0; var tailCount = 0; var imgs = new Image[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(); - var tailsArr = await _images.GetTailsImageAsync(); - if (_rng.Next(0, 10) < 5) + if (result[i].Side == 0) { imgs[i] = Image.Load(headsArr); headCount++; @@ -69,7 +72,7 @@ public partial class Gambling } 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) i.Dispose(); @@ -92,38 +95,37 @@ public partial class Gambling if (!await CheckBetMandatory(amount) || amount == 1) return; - var removed = await _cs.RemoveAsync(ctx.User, amount, new("betflip", "bet")); - if (!removed) + var res = await _service.BetFlipAsync(ctx.User.Id, amount, (byte)guess); + if (!res.TryPickT0(out var result, out _)) { await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); return; } - BetFlipGuess result; Uri imageToSend; var coins = _ic.Data.Coins; - if (_rng.Next(0, 1000) <= 499) + if (result.Side == 0) { imageToSend = coins.Heads[_rng.Next(0, coins.Heads.Length)]; - result = BetFlipGuess.Heads; } else { imageToSend = coins.Tails[_rng.Next(0, coins.Tails.Length)]; - result = BetFlipGuess.Tails; } string str; - if (guess == result) + var won = (long)result.Won; + if (won > 0) { - var toWin = (long)(amount * Config.BetFlip.Multiplier); - str = Format.Bold(ctx.User.ToString()) + " " + GetText(strs.flip_guess(N(toWin))); - await _cs.AddAsync(ctx.User, toWin, new("betflip", "win")); + str = Format.Bold(GetText(strs.flip_guess(N(won)))); } else - str = Format.Bold(ctx.User.ToString()) + " " + GetText(strs.better_luck); + { + str = Format.Bold(GetText(strs.better_luck)); + } await ctx.Channel.EmbedAsync(_eb.Create() + .WithAuthor(ctx.User) .WithDescription(str) .WithOkColor() .WithImageUrl(imageToSend.ToString())); diff --git a/src/NadekoBot/Modules/Gambling/Gambling.cs b/src/NadekoBot/Modules/Gambling/Gambling.cs index 0e25d5537..f92912a09 100644 --- a/src/NadekoBot/Modules/Gambling/Gambling.cs +++ b/src/NadekoBot/Modules/Gambling/Gambling.cs @@ -655,19 +655,26 @@ public partial class Gambling : GamblingModule return; } + var win = (long)result.Won; - var str = Format.Bold(ctx.User.ToString()) + Format.Code(GetText(strs.roll(result.Roll))); + string str; 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")); } 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] diff --git a/src/NadekoBot/Modules/Gambling/Slot/SlotCommands.cs b/src/NadekoBot/Modules/Gambling/Slot/SlotCommands.cs index d3fe544c8..9209c3171 100644 --- a/src/NadekoBot/Modules/Gambling/Slot/SlotCommands.cs +++ b/src/NadekoBot/Modules/Gambling/Slot/SlotCommands.cs @@ -27,7 +27,7 @@ public partial class Gambling private static decimal totalBet; private static decimal totalPaidOut; - private static readonly HashSet _runningUsers = new(); + private static readonly ConcurrentHashSet _runningUsers = new(); //here is a payout chart //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(); for (var i = 0; i < tests; i++) { - var res = SlotMachine.Pull(0); - if (dict.ContainsKey(res.Multiplier)) - dict[res.Multiplier] += 1; + var res = await _service.SlotAsync(ctx.User.Id, 0); + var multi = res.AsT0.Multiplier; + if (dict.ContainsKey(multi)) + dict[multi] += 1; else - dict.Add(res.Multiplier, 1); + dict.Add(multi, 1); } var sb = new StringBuilder(); @@ -157,7 +158,7 @@ public partial class Gambling WrapTextWidth = 140 } }, - result.Won.ToString(), + ((long)result.Won).ToString(), _fonts.DottyFont.CreateFont(65), fontColor, new(227, 92))); @@ -219,50 +220,9 @@ public partial class Gambling } finally { - _ = Task.Run(async () => - { - await Task.Delay(1000); - _runningUsers.Remove(ctx.User.Id); - }); + await Task.Delay(1000); + _runningUsers.TryRemove(ctx.User.Id); } } } -} - -public sealed class SlotMachine -{ - public const int MAX_VALUE = 5; - - private static readonly List> _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, - }; - } } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/~Shared/IGamblingService.cs b/src/NadekoBot/Modules/Gambling/~Shared/IGamblingService.cs index d1b27bb8b..218ff7161 100644 --- a/src/NadekoBot/Modules/Gambling/~Shared/IGamblingService.cs +++ b/src/NadekoBot/Modules/Gambling/~Shared/IGamblingService.cs @@ -8,7 +8,7 @@ public interface IGamblingService { Task> WofAsync(ulong userId, long amount); Task> BetRollAsync(ulong userId, long amount); - Task> BetFlipAsync(ulong userId, long amount, int guess); + Task> BetFlipAsync(ulong userId, long amount, byte guess); Task> SlotAsync(ulong userId, long amount); Task FlipAsync(int count); } \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/~Shared/NewGamblingService.cs b/src/NadekoBot/Modules/Gambling/~Shared/NewGamblingService.cs index f4358309e..3d677cfdb 100644 --- a/src/NadekoBot/Modules/Gambling/~Shared/NewGamblingService.cs +++ b/src/NadekoBot/Modules/Gambling/~Shared/NewGamblingService.cs @@ -17,6 +17,7 @@ public sealed class NewGamblingService : IGamblingService, INService } // todo input checks + // todo ladder of fortune public async Task> WofAsync(ulong userId, long amount) { var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("wof", "bet")); @@ -61,7 +62,7 @@ public sealed class NewGamblingService : IGamblingService, INService return result; } - public async Task> BetFlipAsync(ulong userId, long amount, int guess) + public async Task> BetFlipAsync(ulong userId, long amount, byte guess) { var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("betflip", "bet")); @@ -84,11 +85,14 @@ public sealed class NewGamblingService : IGamblingService, INService public async Task> SlotAsync(ulong userId, long amount) { - var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("slot", "bet")); - - if (!isTakeSuccess) + if (amount > 0) { - return GamblingError.InsufficientFunds; + var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("slot", "bet")); + + if (!isTakeSuccess) + { + return GamblingError.InsufficientFunds; + } } var game = new SlotGame(); diff --git a/src/NadekoBot/data/strings/responses/responses.en-US.json b/src/NadekoBot/data/strings/responses/responses.en-US.json index 9716d9f3a..3db649dcf 100644 --- a/src/NadekoBot/data/strings/responses/responses.en-US.json +++ b/src/NadekoBot/data/strings/responses/responses.en-US.json @@ -231,7 +231,7 @@ "not_enough": "You don't have enough {0}", "no_more_cards": "No more cards in the deck.", "raffled_user": "Raffled user", - "roll": "You rolled {0}.", + "roll2": "Roll", "slot_bet": "Bet", "slot_jackpot": "WOAAHHHHHH!!! Congratulations!!! x{0}", "slot_single": "A single {0}, x{1}",