mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
Moved .rps to the new service, reimplemented logic, fixed an unknown bug with 0 amount (?!)
This commit is contained in:
@@ -2,10 +2,10 @@ namespace Nadeko.Econ.Gambling;
|
|||||||
|
|
||||||
public sealed class BetrollGame
|
public sealed class BetrollGame
|
||||||
{
|
{
|
||||||
private readonly (decimal WhenAbove, decimal MultiplyBy)[] _thresholdPairs;
|
private readonly (int WhenAbove, decimal MultiplyBy)[] _thresholdPairs;
|
||||||
private readonly Random _rng;
|
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();
|
_thresholdPairs = pairs.OrderByDescending(x => x.WhenAbove).ToArray();
|
||||||
_rng = new();
|
_rng = new();
|
||||||
|
75
src/Nadeko.Econ/Gambling/Rps/RpsGame.cs
Normal file
75
src/Nadeko.Econ/Gambling/Rps/RpsGame.cs
Normal 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; }
|
||||||
|
}
|
@@ -13,6 +13,7 @@ using NadekoBot.Services.Database.Models;
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Nadeko.Econ.Gambling.Rps;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Gambling;
|
namespace NadekoBot.Modules.Gambling;
|
||||||
|
|
||||||
@@ -759,65 +760,72 @@ public partial class Gambling : GamblingModule<GamblingService>
|
|||||||
opts.Clean);
|
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
|
// todo check if trivia is being disposed
|
||||||
[Cmd]
|
[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)
|
static string GetRpsPick(InputRpsPick p)
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string GetRpsPick(RpsPick p)
|
|
||||||
{
|
{
|
||||||
switch (p)
|
switch (p)
|
||||||
{
|
{
|
||||||
case RpsPick.R:
|
case InputRpsPick.R:
|
||||||
return "🚀";
|
return "🚀";
|
||||||
case RpsPick.P:
|
case InputRpsPick.P:
|
||||||
return "📎";
|
return "📎";
|
||||||
default:
|
default:
|
||||||
return "✂️";
|
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 (!res.TryPickT0(out var result, out _))
|
||||||
|
|
||||||
if (amount > 0)
|
|
||||||
{
|
{
|
||||||
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;
|
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)));
|
msg = GetText(strs.rps_draw(GetRpsPick(pick)));
|
||||||
}
|
}
|
||||||
else if ((pick == RpsPick.Paper && nadekoPick == RpsPick.Rock)
|
else if (result.Result == RpsResultType.Win)
|
||||||
|| (pick == RpsPick.Rock && nadekoPick == RpsPick.Scissors)
|
|
||||||
|| (pick == RpsPick.Scissors && nadekoPick == RpsPick.Paper))
|
|
||||||
{
|
{
|
||||||
amount = (long)(amount * Config.BetFlip.Multiplier);
|
if((long)result.Won > 0)
|
||||||
await _cs.AddAsync(ctx.User.Id, amount, new("rps", "win"));
|
embed.AddField(GetText(strs.won), N(amount.Value));
|
||||||
embed.WithOkColor();
|
|
||||||
embed.AddField(GetText(strs.won), N(amount.Value));
|
msg = GetText(strs.rps_win(ctx.User.Mention,
|
||||||
msg = GetText(strs.rps_win(ctx.User.Mention, GetRpsPick(pick), GetRpsPick(nadekoPick)));
|
GetRpsPick(pick),
|
||||||
|
GetRpsPick((InputRpsPick)result.ComputerPick)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
embed.WithErrorColor();
|
msg = GetText(strs.rps_win(ctx.Client.CurrentUser.Mention,
|
||||||
msg = GetText(strs.rps_win(ctx.Client.CurrentUser.Mention, GetRpsPick(nadekoPick), GetRpsPick(pick)));
|
GetRpsPick((InputRpsPick)result.ComputerPick),
|
||||||
|
GetRpsPick(pick)));
|
||||||
}
|
}
|
||||||
|
|
||||||
embed.WithDescription(msg);
|
embed
|
||||||
|
.WithOkColor()
|
||||||
|
.WithDescription(msg);
|
||||||
|
|
||||||
await ctx.Channel.EmbedAsync(embed);
|
await ctx.Channel.EmbedAsync(embed);
|
||||||
}
|
}
|
||||||
|
3
src/NadekoBot/Modules/Gambling/InputRpsPick.cs
Normal file
3
src/NadekoBot/Modules/Gambling/InputRpsPick.cs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#nullable disable
|
||||||
|
namespace NadekoBot.Modules.Gambling;
|
||||||
|
|
@@ -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
|
|
||||||
}
|
|
@@ -1,5 +1,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Nadeko.Econ.Gambling;
|
using Nadeko.Econ.Gambling;
|
||||||
|
using Nadeko.Econ.Gambling.Rps;
|
||||||
using OneOf;
|
using OneOf;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Gambling;
|
namespace NadekoBot.Modules.Gambling;
|
||||||
@@ -11,4 +12,5 @@ public interface IGamblingService
|
|||||||
Task<OneOf<BetflipResult, GamblingError>> BetFlipAsync(ulong userId, long amount, byte 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);
|
||||||
|
Task<OneOf<RpsResult, GamblingError>> RpsAsync(ulong userId, long amount, byte pick);
|
||||||
}
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using Nadeko.Econ.Gambling;
|
using Nadeko.Econ.Gambling;
|
||||||
|
using Nadeko.Econ.Gambling.Rps;
|
||||||
using NadekoBot.Modules.Gambling.Services;
|
using NadekoBot.Modules.Gambling.Services;
|
||||||
using OneOf;
|
using OneOf;
|
||||||
|
|
||||||
@@ -20,11 +21,17 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
// todo ladder of fortune
|
// 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"));
|
if (amount < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(amount));
|
||||||
if (!isTakeSuccess)
|
|
||||||
|
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);
|
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)
|
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
|
var game = new BetrollGame(_bcs.Data.BetRoll.Pairs
|
||||||
.Select(x => ((decimal)x.WhenAbove, (decimal)x.MultiplyBy))
|
.Select(x => (x.WhenAbove, (decimal)x.MultiplyBy))
|
||||||
.ToList());
|
.ToList());
|
||||||
|
|
||||||
var result = game.Roll(amount);
|
var result = game.Roll(amount);
|
||||||
|
|
||||||
var won = (long)result.Won;
|
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)
|
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);
|
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)
|
public async Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount)
|
||||||
{
|
{
|
||||||
|
if (amount < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(amount));
|
||||||
|
|
||||||
if (amount > 0)
|
if (amount > 0)
|
||||||
{
|
{
|
||||||
var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("slot", "bet"));
|
var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("slot", "bet"));
|
||||||
@@ -106,7 +129,7 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<FlipResult[]> FlipAsync(int count)
|
public Task<FlipResult[]> FlipAsync(int count)
|
||||||
{
|
{
|
||||||
var game = new BetflipGame(0);
|
var game = new BetflipGame(0);
|
||||||
@@ -123,8 +146,8 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
return Task.FromResult(results);
|
return Task.FromResult(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo deck draw black/white?
|
// // todo deck draw black/white?
|
||||||
|
//
|
||||||
//
|
//
|
||||||
// private readonly ConcurrentDictionary<ulong, Deck> _decks = new ConcurrentDictionary<ulong, Deck>();
|
// private readonly ConcurrentDictionary<ulong, Deck> _decks = new ConcurrentDictionary<ulong, Deck>();
|
||||||
//
|
//
|
||||||
@@ -177,79 +200,41 @@ public sealed class NewGamblingService : IGamblingService, INService
|
|||||||
// return Task.FromResult(toReturn);
|
// return Task.FromResult(toReturn);
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// public override async Task<RpsReply> Rps(RpsRequest request, ServerCallContext context)
|
|
||||||
// {
|
public async Task<OneOf<RpsResult, GamblingError>> RpsAsync(ulong userId, long amount, byte pick)
|
||||||
// if (request.Amount > 0)
|
{
|
||||||
// {
|
if (amount < 0)
|
||||||
// var res = await _currency.TransferCurrencyAsync(new TransferCurrencyRequest
|
throw new ArgumentOutOfRangeException(nameof(amount));
|
||||||
// {
|
|
||||||
// Amount = request.Amount,
|
if (pick > 2)
|
||||||
// FromId = request.UserId,
|
throw new ArgumentOutOfRangeException(nameof(pick));
|
||||||
// Type = "rps",
|
|
||||||
// Subtype = "bet",
|
if (amount > 0)
|
||||||
// });
|
{
|
||||||
//
|
var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("rps", "bet"));
|
||||||
// if (!res.Success)
|
|
||||||
// {
|
if (!isTakeSuccess)
|
||||||
// return new RpsReply
|
{
|
||||||
// {
|
return GamblingError.InsufficientFunds;
|
||||||
// Result = RpsReply.Types.ResultType.NotEnough
|
}
|
||||||
// };
|
}
|
||||||
// }
|
|
||||||
// }
|
var rps = new RpsGame();
|
||||||
//
|
var result = rps.Play((RpsPick)pick, amount);
|
||||||
// var botPick = _rng.Next(0, 3);
|
|
||||||
// var userPick = (int) request.Pick;
|
var won = (long)result.Won;
|
||||||
//
|
if (won > 0)
|
||||||
// if (botPick == userPick)
|
{
|
||||||
// {
|
var extra = result.Result switch
|
||||||
// if (request.Amount > 0)
|
{
|
||||||
// {
|
RpsResultType.Draw => "draw",
|
||||||
// await _currency.GrantToUserAsync(new GrantToUserRequest
|
RpsResultType.Win => "win",
|
||||||
// {
|
_ => "lose"
|
||||||
// Amount = request.Amount,
|
};
|
||||||
// GranterId = 0,
|
|
||||||
// Type = "rps",
|
await _cs.AddAsync(userId, won, new("rps", extra));
|
||||||
// Subtype = "draw",
|
}
|
||||||
// UserId = request.UserId,
|
|
||||||
// });
|
return result;
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// 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
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
}
|
}
|
Reference in New Issue
Block a user