diff --git a/src/Nadeko.Econ/Gambling/Betroll/BetrollGame.cs b/src/Nadeko.Econ/Gambling/Betroll/BetrollGame.cs index 6cfdb5b91..dc8b17c26 100644 --- a/src/Nadeko.Econ/Gambling/Betroll/BetrollGame.cs +++ b/src/Nadeko.Econ/Gambling/Betroll/BetrollGame.cs @@ -2,10 +2,10 @@ namespace Nadeko.Econ.Gambling; public sealed class BetrollGame { - private readonly (decimal WhenAbove, decimal MultiplyBy)[] _thresholdPairs; - private readonly Random _rng; + private readonly (int WhenAbove, decimal MultiplyBy)[] _thresholdPairs; + private readonly NadekoRandom _rng; - public BetrollGame(IReadOnlyList<(decimal WhenAbove, decimal MultiplyBy)> pairs) + public BetrollGame(IReadOnlyList<(int WhenAbove, decimal MultiplyBy)> pairs) { _thresholdPairs = pairs.OrderByDescending(x => x.WhenAbove).ToArray(); _rng = new(); diff --git a/src/Nadeko.Econ/Gambling/Rps/RpsGame.cs b/src/Nadeko.Econ/Gambling/Rps/RpsGame.cs new file mode 100644 index 000000000..c9f2b5824 --- /dev/null +++ b/src/Nadeko.Econ/Gambling/Rps/RpsGame.cs @@ -0,0 +1,75 @@ +namespace Nadeko.Econ.Gambling.Rps; + +public sealed class RpsGame +{ + private static readonly NadekoRandom _rng = new NadekoRandom(); + + const decimal WIN_MULTI = 1.95m; + const decimal DRAW_MULTI = 1m; + const decimal LOSE_MULTI = 0m; + + public RpsGame() + { + + } + + public RpsResult Play(RpsPick pick, decimal amount) + { + var compPick = (RpsPick)_rng.Next(0, 3); + if (compPick == pick) + { + return new() + { + Won = amount * DRAW_MULTI, + Multiplier = DRAW_MULTI, + ComputerPick = compPick, + Result = RpsResultType.Draw, + }; + } + + if ((compPick == RpsPick.Paper && pick == RpsPick.Rock) + || (compPick == RpsPick.Rock && pick == RpsPick.Scissors) + || (compPick == RpsPick.Scissors && pick == RpsPick.Paper)) + { + return new() + { + Won = amount * LOSE_MULTI, + Multiplier = LOSE_MULTI, + Result = RpsResultType.Lose, + ComputerPick = compPick, + }; + } + + return new() + { + Won = amount * WIN_MULTI, + Multiplier = WIN_MULTI, + Result = RpsResultType.Win, + ComputerPick = compPick, + }; + } +} + +public enum RpsPick : byte +{ + Rock = 0, + Paper = 1, + Scissors = 2, +} + +public enum RpsResultType : byte +{ + Win, + Draw, + Lose +} + + + +public readonly struct RpsResult +{ + public decimal Won { get; init; } + public decimal Multiplier { get; init; } + public RpsResultType Result { get; init; } + public RpsPick ComputerPick { get; init; } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Gambling.cs b/src/NadekoBot/Modules/Gambling/Gambling.cs index f92912a09..00d508682 100644 --- a/src/NadekoBot/Modules/Gambling/Gambling.cs +++ b/src/NadekoBot/Modules/Gambling/Gambling.cs @@ -13,6 +13,7 @@ using NadekoBot.Services.Database.Models; using System.Collections.Immutable; using System.Globalization; using System.Text; +using Nadeko.Econ.Gambling.Rps; namespace NadekoBot.Modules.Gambling; @@ -759,65 +760,72 @@ public partial class Gambling : GamblingModule opts.Clean); } + public enum InputRpsPick : byte + { + R = 0, + Rock = 0, + Rocket = 0, + P = 1, + Paper = 1, + Paperclip = 1, + S = 2, + Scissors = 2 + } + // todo check if trivia is being disposed [Cmd] - public async partial Task Rps(RpsPick pick, ShmartNumber amount = default) + public async partial Task Rps(InputRpsPick pick, ShmartNumber amount = default) { - if (!await CheckBetOptional(amount) || amount == 1) - { - return; - } - - string GetRpsPick(RpsPick p) + static string GetRpsPick(InputRpsPick p) { switch (p) { - case RpsPick.R: + case InputRpsPick.R: return "🚀"; - case RpsPick.P: + case InputRpsPick.P: return "📎"; default: return "✂️"; } } + + if (!await CheckBetOptional(amount) || amount == 1) + return; - var embed = _eb.Create(); + var res = await _gs.RpsAsync(ctx.User.Id, amount, (byte)pick); - var nadekoPick = (RpsPick)new NadekoRandom().Next(0, 3); - - if (amount > 0) + if (!res.TryPickT0(out var result, out _)) { - if (!await _cs.RemoveAsync(ctx.User.Id, amount, new("rps", "bet"))) - { - await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); - return; - } + await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign)); + return; } - + + var embed = _eb.Create(); + string msg; - if (pick == nadekoPick) + if (result.Result == RpsResultType.Draw) { - await _cs.AddAsync(ctx.User.Id, amount, new("rps", "draw")); - embed.WithOkColor(); msg = GetText(strs.rps_draw(GetRpsPick(pick))); } - else if ((pick == RpsPick.Paper && nadekoPick == RpsPick.Rock) - || (pick == RpsPick.Rock && nadekoPick == RpsPick.Scissors) - || (pick == RpsPick.Scissors && nadekoPick == RpsPick.Paper)) + else if (result.Result == RpsResultType.Win) { - amount = (long)(amount * Config.BetFlip.Multiplier); - await _cs.AddAsync(ctx.User.Id, amount, new("rps", "win")); - embed.WithOkColor(); - embed.AddField(GetText(strs.won), N(amount.Value)); - msg = GetText(strs.rps_win(ctx.User.Mention, GetRpsPick(pick), GetRpsPick(nadekoPick))); + if((long)result.Won > 0) + embed.AddField(GetText(strs.won), N(amount.Value)); + + msg = GetText(strs.rps_win(ctx.User.Mention, + GetRpsPick(pick), + GetRpsPick((InputRpsPick)result.ComputerPick))); } else { - embed.WithErrorColor(); - msg = GetText(strs.rps_win(ctx.Client.CurrentUser.Mention, GetRpsPick(nadekoPick), GetRpsPick(pick))); + msg = GetText(strs.rps_win(ctx.Client.CurrentUser.Mention, + GetRpsPick((InputRpsPick)result.ComputerPick), + GetRpsPick(pick))); } - embed.WithDescription(msg); + embed + .WithOkColor() + .WithDescription(msg); await ctx.Channel.EmbedAsync(embed); } diff --git a/src/NadekoBot/Modules/Gambling/InputRpsPick.cs b/src/NadekoBot/Modules/Gambling/InputRpsPick.cs new file mode 100644 index 000000000..17359afc6 --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/InputRpsPick.cs @@ -0,0 +1,3 @@ +#nullable disable +namespace NadekoBot.Modules.Gambling; + diff --git a/src/NadekoBot/Modules/Gambling/RpsPick.cs b/src/NadekoBot/Modules/Gambling/RpsPick.cs deleted file mode 100644 index e50eed3cb..000000000 --- a/src/NadekoBot/Modules/Gambling/RpsPick.cs +++ /dev/null @@ -1,14 +0,0 @@ -#nullable disable -namespace NadekoBot.Modules.Gambling; - -public enum RpsPick -{ - R = 0, - Rock = 0, - Rocket = 0, - P = 1, - Paper = 1, - Paperclip = 1, - S = 2, - Scissors = 2 -} \ 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 218ff7161..4e7346d59 100644 --- a/src/NadekoBot/Modules/Gambling/~Shared/IGamblingService.cs +++ b/src/NadekoBot/Modules/Gambling/~Shared/IGamblingService.cs @@ -1,5 +1,6 @@ #nullable disable using Nadeko.Econ.Gambling; +using Nadeko.Econ.Gambling.Rps; using OneOf; namespace NadekoBot.Modules.Gambling; @@ -11,4 +12,5 @@ public interface IGamblingService Task> BetFlipAsync(ulong userId, long amount, byte guess); Task> SlotAsync(ulong userId, long amount); Task FlipAsync(int count); + Task> RpsAsync(ulong userId, long amount, byte pick); } \ 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 3d677cfdb..8455f7448 100644 --- a/src/NadekoBot/Modules/Gambling/~Shared/NewGamblingService.cs +++ b/src/NadekoBot/Modules/Gambling/~Shared/NewGamblingService.cs @@ -1,5 +1,6 @@ #nullable disable using Nadeko.Econ.Gambling; +using Nadeko.Econ.Gambling.Rps; using NadekoBot.Modules.Gambling.Services; using OneOf; @@ -20,11 +21,17 @@ public sealed class NewGamblingService : IGamblingService, INService // todo ladder of fortune public async Task> WofAsync(ulong userId, long amount) { - var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("wof", "bet")); - - if (!isTakeSuccess) + if (amount < 0) + throw new ArgumentOutOfRangeException(nameof(amount)); + + if (amount > 0) { - return GamblingError.InsufficientFunds; + var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("wof", "bet")); + + if (!isTakeSuccess) + { + return GamblingError.InsufficientFunds; + } } var game = new WofGame(_bcs.Data.WheelOfFortune.Multipliers); @@ -41,16 +48,23 @@ public sealed class NewGamblingService : IGamblingService, INService public async Task> BetRollAsync(ulong userId, long amount) { - var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("betroll", "bet")); + if (amount < 0) + throw new ArgumentOutOfRangeException(nameof(amount)); - if (!isTakeSuccess) + if (amount > 0) { - return GamblingError.InsufficientFunds; + var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("betroll", "bet")); + + if (!isTakeSuccess) + { + return GamblingError.InsufficientFunds; + } } var game = new BetrollGame(_bcs.Data.BetRoll.Pairs - .Select(x => ((decimal)x.WhenAbove, (decimal)x.MultiplyBy)) + .Select(x => (x.WhenAbove, (decimal)x.MultiplyBy)) .ToList()); + var result = game.Roll(amount); var won = (long)result.Won; @@ -64,11 +78,17 @@ public sealed class NewGamblingService : IGamblingService, INService public async Task> BetFlipAsync(ulong userId, long amount, byte guess) { - var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("betflip", "bet")); + if (amount < 0) + throw new ArgumentOutOfRangeException(nameof(amount)); - if (!isTakeSuccess) + if (amount > 0) { - return GamblingError.InsufficientFunds; + var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("betflip", "bet")); + + if (!isTakeSuccess) + { + return GamblingError.InsufficientFunds; + } } var game = new BetflipGame(_bcs.Data.BetFlip.Multiplier); @@ -85,6 +105,9 @@ public sealed class NewGamblingService : IGamblingService, INService public async Task> SlotAsync(ulong userId, long amount) { + if (amount < 0) + throw new ArgumentOutOfRangeException(nameof(amount)); + if (amount > 0) { var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("slot", "bet")); @@ -106,7 +129,7 @@ public sealed class NewGamblingService : IGamblingService, INService return result; } - + public Task FlipAsync(int count) { var game = new BetflipGame(0); @@ -123,8 +146,8 @@ public sealed class NewGamblingService : IGamblingService, INService return Task.FromResult(results); } - // todo deck draw black/white? - + // // todo deck draw black/white? + // // // private readonly ConcurrentDictionary _decks = new ConcurrentDictionary(); // @@ -177,79 +200,41 @@ public sealed class NewGamblingService : IGamblingService, INService // return Task.FromResult(toReturn); // } // - // public override async Task Rps(RpsRequest request, ServerCallContext context) - // { - // if (request.Amount > 0) - // { - // var res = await _currency.TransferCurrencyAsync(new TransferCurrencyRequest - // { - // Amount = request.Amount, - // FromId = request.UserId, - // Type = "rps", - // Subtype = "bet", - // }); - // - // if (!res.Success) - // { - // return new RpsReply - // { - // Result = RpsReply.Types.ResultType.NotEnough - // }; - // } - // } - // - // var botPick = _rng.Next(0, 3); - // var userPick = (int) request.Pick; - // - // if (botPick == userPick) - // { - // if (request.Amount > 0) - // { - // await _currency.GrantToUserAsync(new GrantToUserRequest - // { - // Amount = request.Amount, - // GranterId = 0, - // Type = "rps", - // Subtype = "draw", - // UserId = request.UserId, - // }); - // } - // - // return new RpsReply - // { - // BotPick = (RpsPick) botPick, - // WonAmount = request.Amount, - // Result = RpsReply.Types.ResultType.Draw - // }; - // } - // - // if ((botPick == 1 && userPick == 2) || (botPick == 2 && userPick == 0) || (botPick == 0 && userPick == 1)) - // { - // if (request.Amount > 0) - // { - // await _currency.GrantToUserAsync(new GrantToUserRequest - // { - // Amount = (long) (request.Amount * 1.95f), - // GranterId = 0, - // Type = "rps", - // Subtype = "draw", - // UserId = request.UserId, - // }); - // } - // - // return new RpsReply - // { - // BotPick = (RpsPick) botPick, - // WonAmount = (long) (request.Amount * 1.95f), - // Result = RpsReply.Types.ResultType.Won - // }; - // } - // - // return new RpsReply - // { - // BotPick = (RpsPick) botPick, - // WonAmount = 0, - // Result = RpsReply.Types.ResultType.Lost - // }; - // } + + public async Task> RpsAsync(ulong userId, long amount, byte pick) + { + if (amount < 0) + throw new ArgumentOutOfRangeException(nameof(amount)); + + if (pick > 2) + throw new ArgumentOutOfRangeException(nameof(pick)); + + if (amount > 0) + { + var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("rps", "bet")); + + if (!isTakeSuccess) + { + return GamblingError.InsufficientFunds; + } + } + + var rps = new RpsGame(); + var result = rps.Play((RpsPick)pick, amount); + + var won = (long)result.Won; + if (won > 0) + { + var extra = result.Result switch + { + RpsResultType.Draw => "draw", + RpsResultType.Win => "win", + _ => "lose" + }; + + await _cs.AddAsync(userId, won, new("rps", extra)); + } + + return result; + } } \ No newline at end of file