Moved .rps to the new service, reimplemented logic, fixed an unknown bug with 0 amount (?!)

This commit is contained in:
Kwoth
2022-07-13 23:48:03 +02:00
parent 0f1ba400db
commit 17ca609fe9
7 changed files with 198 additions and 139 deletions

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
#nullable disable
namespace NadekoBot.Modules.Gambling;

View File

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

View File

@@ -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<OneOf<BetflipResult, GamblingError>> BetFlipAsync(ulong userId, long amount, byte guess);
Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount);
Task<FlipResult[]> FlipAsync(int count);
Task<OneOf<RpsResult, GamblingError>> RpsAsync(ulong userId, long amount, byte pick);
}

View File

@@ -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<OneOf<WofResult, GamblingError>> 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<OneOf<BetrollResult, GamblingError>> 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<OneOf<BetflipResult, GamblingError>> 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<OneOf<SlotResult, GamblingError>> 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<FlipResult[]> 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<ulong, Deck> _decks = new ConcurrentDictionary<ulong, Deck>();
//
@@ -177,79 +200,41 @@ public sealed class NewGamblingService : IGamblingService, INService
// return Task.FromResult(toReturn);
// }
//
// public override async Task<RpsReply> 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<OneOf<RpsResult, GamblingError>> 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;
}
}