Replaced .wheel with .lula (lucky ladder). It looks nicer but plays the same. Also it is more customizable as you can have more or less multipliers. Cleaned up some trivia code. Sorted lula multipliers in gambling.yml. Improved .slottest

This commit is contained in:
Kwoth
2022-07-14 03:52:30 +02:00
parent 17ca609fe9
commit d9011106ac
15 changed files with 262 additions and 195 deletions

View File

@@ -1,8 +1,9 @@
namespace Nadeko.Econ.Gambling;
public readonly struct WofResult
public readonly struct LuLaResult
{
public int Index { get; init; }
public decimal Multiplier { get; init; }
public decimal Won { get; init; }
public IReadOnlyList<decimal> Multipliers { get; init; }
}

View File

@@ -1,23 +1,23 @@
namespace Nadeko.Econ.Gambling;
public sealed class WofGame
public sealed class LulaGame
{
public static IReadOnlyList<decimal> DEFAULT_MULTIPLIERS = new[] { 1.7M, 1.5M, 0.2M, 0.1M, 0.3M, 0.5M, 1.2M, 2.4M };
private readonly IReadOnlyList<decimal> _multipliers;
private readonly NadekoRandom _rng;
public WofGame(IReadOnlyList<decimal> multipliers)
public LulaGame(IReadOnlyList<decimal> multipliers)
{
_multipliers = multipliers;
_rng = new();
}
public WofGame() : this(DEFAULT_MULTIPLIERS)
public LulaGame() : this(DEFAULT_MULTIPLIERS)
{
}
public WofResult Spin(long bet)
public LuLaResult Spin(long bet)
{
var result = _rng.Next(0, _multipliers.Count);
@@ -28,7 +28,8 @@ public sealed class WofGame
{
Index = result,
Multiplier = multi,
Won = amount
Won = amount,
Multipliers = _multipliers.ToArray(),
};
}
}

View File

@@ -834,7 +834,7 @@ public partial class Gambling : GamblingModule<GamblingService>
new[] { "⬆", "↖", "⬅", "↙", "⬇", "↘", "➡", "↗" }.ToImmutableArray();
[Cmd]
public async partial Task WheelOfFortune(ShmartNumber amount)
public async partial Task LuckyLadder(ShmartNumber amount)
{
if (!await CheckBetMandatory(amount))
return;
@@ -846,13 +846,29 @@ public partial class Gambling : GamblingModule<GamblingService>
return;
}
var wofMultipliers = Config.WheelOfFortune.Multipliers;
await SendConfirmAsync(Format.Bold($@"{ctx.User} won: {N(result.Won)}
var multis = result.Multipliers;
『{wofMultipliers[1]}』 『{wofMultipliers[0]}』 『{wofMultipliers[7]}』
var sb = new StringBuilder();
foreach (var multi in multis)
{
sb.Append($"╠══╣");
『{wofMultipliers[2]}』 {_emojis[result.Index]} 『{wofMultipliers[6]}』
if (multi == result.Multiplier)
sb.Append($"{Format.Bold($"x{multi:0.##}")} ⬅️");
else
sb.Append($"||x{multi:0.##}||");
sb.AppendLine();
}
『{wofMultipliers[3]}』 『{wofMultipliers[4]}』 『{wofMultipliers[5]}』"));
var eb = _eb.Create(ctx)
.WithOkColor()
.WithDescription(sb.ToString())
.AddField(GetText(strs.multiplier), $"{result.Multiplier:0.##}x", true)
.AddField(GetText(strs.won), $"{(long)result.Won}", true)
.WithAuthor(ctx.User);
await ctx.Channel.EmbedAsync(eb);
}
}

View File

@@ -39,8 +39,8 @@ Set 0 for unlimited")]
[Comment(@"How much will each user's owned currency decay over time.")]
public DecayConfig Decay { get; set; }
[Comment(@"Settings for Wheel Of Fortune command.")]
public WheelOfFortuneSettings WheelOfFortune { get; set; }
[Comment(@"Settings for LuckyLadder command")]
public LuckyLadderSettings LuckyLadder { get; set; }
[Comment(@"Settings related to waifus")]
public WaifuConfig Waifu { get; set; }
@@ -59,7 +59,6 @@ This will work only if you've set up VotesApi and correct credentials for topgg
public GamblingConfig()
{
BetRoll = new();
WheelOfFortune = new();
Waifu = new();
Currency = new();
BetFlip = new();
@@ -67,6 +66,7 @@ This will work only if you've set up VotesApi and correct credentials for topgg
Timely = new();
Decay = new();
Slots = new();
LuckyLadder = new();
}
}
@@ -173,13 +173,13 @@ public partial class DecayConfig
}
[Cloneable]
public partial class WheelOfFortuneSettings
public partial class LuckyLadderSettings
{
[Comment(@"Self-Explanatory. Has to have 8 values, otherwise the command won't work.")]
public decimal[] Multipliers { get; set; }
public WheelOfFortuneSettings()
=> Multipliers = new[] { 1.7M, 1.5M, 0.2M, 0.1M, 0.3M, 0.5M, 1.2M, 2.4M };
public LuckyLadderSettings()
=> Multipliers = new[] { 2.4M, 1.7M, 1.5M, 1.2M, 0.5M, 0.3M, 0.2M, 0.1M };
}
[Cloneable]

View File

@@ -174,5 +174,13 @@ public sealed class GamblingConfigService : ConfigServiceBase<GamblingConfig>
c.Version = 5;
});
}
if (data.Version < 6)
{
ModifyConfig(c =>
{
c.Version = 6;
});
}
}
}

View File

@@ -81,6 +81,11 @@ public partial class Gambling
if (tests <= 0)
return;
//multi vs how many times it occured
int streak = 0;
int maxW = 0;
int maxL = 0;
var dict = new Dictionary<decimal, int>();
for (var i = 0; i < tests; i++)
{
@@ -90,16 +95,39 @@ public partial class Gambling
dict[multi] += 1;
else
dict.Add(multi, 1);
}
if (multi == 0)
{
if (streak <= 0)
--streak;
else
streak = -1;
maxL = Math.Max(maxL, -streak);
}
else
{
if (streak >= 0)
++streak;
else
streak = 1;
maxW = Math.Max(maxW, streak);
}
}
var sb = new StringBuilder();
decimal payout = 0;
foreach (var key in dict.Keys.OrderByDescending(x => x))
{
sb.AppendLine($"x{key} occured {dict[key]} times. {dict[key] * 1.0f / tests * 100}%");
sb.AppendLine($"x{key} occured `{dict[key]}` times. {dict[key] * 1.0f / tests * 100}%");
payout += key * dict[key];
}
sb.AppendLine();
sb.AppendLine($"Longest win streak: `{maxW}`");
sb.AppendLine($"Longest lose streak: `{maxL}`");
await SendConfirmAsync("Slot Test Results",
sb.ToString(),
footer: $"Total Bet: {tests} | Payout: {payout:F0} | {payout * 1.0M / tests * 100}%");

View File

@@ -7,7 +7,7 @@ namespace NadekoBot.Modules.Gambling;
public interface IGamblingService
{
Task<OneOf<WofResult, GamblingError>> WofAsync(ulong userId, long amount);
Task<OneOf<LuLaResult, GamblingError>> WofAsync(ulong userId, long amount);
Task<OneOf<BetrollResult, GamblingError>> BetRollAsync(ulong userId, long amount);
Task<OneOf<BetflipResult, GamblingError>> BetFlipAsync(ulong userId, long amount, byte guess);
Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount);

View File

@@ -19,7 +19,7 @@ public sealed class NewGamblingService : IGamblingService, INService
// todo input checks
// todo ladder of fortune
public async Task<OneOf<WofResult, GamblingError>> WofAsync(ulong userId, long amount)
public async Task<OneOf<LuLaResult, GamblingError>> WofAsync(ulong userId, long amount)
{
if (amount < 0)
throw new ArgumentOutOfRangeException(nameof(amount));
@@ -34,7 +34,7 @@ public sealed class NewGamblingService : IGamblingService, INService
}
}
var game = new WofGame(_bcs.Data.WheelOfFortune.Multipliers);
var game = new LulaGame(_bcs.Data.LuckyLadder.Multipliers);
var result = game.Spin(amount);
var won = (long)result.Won;

View File

@@ -94,162 +94,6 @@ public partial class Games
await ReplyErrorLocalizedAsync(strs.trivia_none);
}
private void RegisterEvents(TriviaGame trivia)
{
IEmbedBuilder? questionEmbed = null;
IUserMessage? questionMessage = null;
var showHowToQuit = false;
trivia.OnQuestion += async (_, question) =>
{
try
{
questionEmbed = _eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.trivia_game))
.AddField(GetText(strs.category), question.Category)
.AddField(GetText(strs.question), question.Question);
showHowToQuit = !showHowToQuit;
if (showHowToQuit)
questionEmbed.WithFooter(GetText(strs.trivia_quit($"{prefix}tq")));
if (Uri.IsWellFormedUriString(question.ImageUrl, UriKind.Absolute))
questionEmbed.WithImageUrl(question.ImageUrl);
questionMessage = await ctx.Channel.EmbedAsync(questionEmbed);
}
catch (HttpException ex) when (ex.HttpCode is HttpStatusCode.NotFound
or HttpStatusCode.Forbidden
or HttpStatusCode.BadRequest)
{
Log.Warning("Unable to send trivia questions. Stopping immediately");
trivia.Stop();
}
catch (Exception ex)
{
Log.Warning(ex, "Error sending trivia embed");
await Task.Delay(2000);
}
};
trivia.OnHint += async (_, question) =>
{
try
{
if (questionMessage is null)
{
trivia.Stop();
return;
}
if (questionEmbed is not null)
await questionMessage.ModifyAsync(m
=> m.Embed = questionEmbed.WithFooter(question.GetHint()).Build());
}
catch (HttpException ex) when (ex.HttpCode is HttpStatusCode.NotFound
or HttpStatusCode.Forbidden)
{
Log.Warning("Unable to edit message to show hint. Stopping trivia");
trivia.Stop();
}
catch (Exception ex) { Log.Warning(ex, "Error editing triva message"); }
};
trivia.OnGuess += async (_, user, question, isWin) =>
{
try
{
var embed = _eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.trivia_game))
.WithDescription(GetText(strs.trivia_win(user.Name,
Format.Bold(question.Answer))));
if (Uri.IsWellFormedUriString(question.AnswerImageUrl, UriKind.Absolute))
embed.WithImageUrl(question.AnswerImageUrl);
if (isWin)
{
await ctx.Channel.EmbedAsync(embed);
var reward = _gamesConfig.Data.Trivia.CurrencyReward;
if (reward > 0)
await _cs.AddAsync(user.Id, reward, new("trivia", "win"));
return;
}
embed.WithDescription(GetText(strs.trivia_guess(user.Name,
Format.Bold(question.Answer))));
await ctx.Channel.EmbedAsync(embed);
}
catch
{
// ignored
}
};
trivia.OnEnded += async (game) =>
{
try
{
await ctx.Channel.EmbedAsync(_eb.Create(ctx)
.WithOkColor()
.WithAuthor(GetText(strs.trivia_ended))
.WithTitle(GetText(strs.leaderboard))
.WithDescription(GetLeaderboardString(game)));
}
catch
{
// ignored
}
finally
{
_service.RunningTrivias.TryRemove(ctx.Guild.Id, out _);
}
};
trivia.OnStats += async (game) =>
{
try
{
await SendConfirmAsync(GetText(strs.leaderboard), GetLeaderboardString(game));
}
catch
{
// ignored
}
};
trivia.OnTimeout += async (_, question) =>
{
try
{
var embed = _eb.Create()
.WithErrorColor()
.WithTitle(GetText(strs.trivia_game))
.WithDescription(GetText(strs.trivia_times_up(Format.Bold(question.Answer))));
if (Uri.IsWellFormedUriString(question.AnswerImageUrl, UriKind.Absolute))
embed.WithImageUrl(question.AnswerImageUrl);
await ctx.Channel.EmbedAsync(embed);
}
catch
{
// ignored
}
};
}
private string GetLeaderboardString(TriviaGame tg)
{
@@ -261,5 +105,175 @@ public partial class Games
return sb.ToString();
}
private IEmbedBuilder? questionEmbed = null;
private IUserMessage? questionMessage = null;
private bool showHowToQuit = false;
private void RegisterEvents(TriviaGame trivia)
{
trivia.OnQuestion += OnTriviaOnOnQuestion;
trivia.OnHint += OnTriviaOnOnHint;
trivia.OnGuess += OnTriviaOnOnGuess;
trivia.OnEnded += OnTriviaOnOnEnded;
trivia.OnStats += OnTriviaOnOnStats;
trivia.OnTimeout += OnTriviaOnOnTimeout;
}
private void UnregisterEvents(TriviaGame trivia)
{
trivia.OnQuestion -= OnTriviaOnOnQuestion;
trivia.OnHint -= OnTriviaOnOnHint;
trivia.OnGuess -= OnTriviaOnOnGuess;
trivia.OnEnded -= OnTriviaOnOnEnded;
trivia.OnStats -= OnTriviaOnOnStats;
trivia.OnTimeout -= OnTriviaOnOnTimeout;
}
private async Task OnTriviaOnOnHint(TriviaGame game, TriviaQuestion question)
{
try
{
if (questionMessage is null)
{
game.Stop();
return;
}
if (questionEmbed is not null)
await questionMessage.ModifyAsync(m => m.Embed = questionEmbed.WithFooter(question.GetHint()).Build());
}
catch (HttpException ex) when (ex.HttpCode is HttpStatusCode.NotFound or HttpStatusCode.Forbidden)
{
Log.Warning("Unable to edit message to show hint. Stopping trivia");
game.Stop();
}
catch (Exception ex)
{
Log.Warning(ex, "Error editing triva message");
}
}
private async Task OnTriviaOnOnQuestion(TriviaGame game, TriviaQuestion question)
{
try
{
questionEmbed = _eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.trivia_game))
.AddField(GetText(strs.category), question.Category)
.AddField(GetText(strs.question), question.Question);
showHowToQuit = !showHowToQuit;
if (showHowToQuit)
questionEmbed.WithFooter(GetText(strs.trivia_quit($"{prefix}tq")));
if (Uri.IsWellFormedUriString(question.ImageUrl, UriKind.Absolute))
questionEmbed.WithImageUrl(question.ImageUrl);
questionMessage = await ctx.Channel.EmbedAsync(questionEmbed);
}
catch (HttpException ex) when (ex.HttpCode is HttpStatusCode.NotFound or HttpStatusCode.Forbidden or HttpStatusCode.BadRequest)
{
Log.Warning("Unable to send trivia questions. Stopping immediately");
game.Stop();
}
catch (Exception ex)
{
Log.Warning(ex, "Error sending trivia embed");
await Task.Delay(2000);
}
}
private async Task OnTriviaOnOnTimeout(TriviaGame _, TriviaQuestion question)
{
try
{
var embed = _eb.Create()
.WithErrorColor()
.WithTitle(GetText(strs.trivia_game))
.WithDescription(GetText(strs.trivia_times_up(Format.Bold(question.Answer))));
if (Uri.IsWellFormedUriString(question.AnswerImageUrl, UriKind.Absolute))
embed.WithImageUrl(question.AnswerImageUrl);
await ctx.Channel.EmbedAsync(embed);
}
catch
{
// ignored
}
}
private async Task OnTriviaOnOnStats(TriviaGame game)
{
try
{
await SendConfirmAsync(GetText(strs.leaderboard), GetLeaderboardString(game));
}
catch
{
// ignored
}
}
private async Task OnTriviaOnOnEnded(TriviaGame game)
{
try
{
await ctx.Channel.EmbedAsync(_eb.Create(ctx)
.WithOkColor()
.WithAuthor(GetText(strs.trivia_ended))
.WithTitle(GetText(strs.leaderboard))
.WithDescription(GetLeaderboardString(game)));
}
catch
{
// ignored
}
finally
{
_service.RunningTrivias.TryRemove(ctx.Guild.Id, out _);
}
UnregisterEvents(game);
await Task.Delay(1000);
}
private async Task OnTriviaOnOnGuess(TriviaGame _, TriviaUser user, TriviaQuestion question, bool isWin)
{
try
{
var embed = _eb.Create()
.WithOkColor()
.WithTitle(GetText(strs.trivia_game))
.WithDescription(GetText(strs.trivia_win(user.Name,
Format.Bold(question.Answer))));
if (Uri.IsWellFormedUriString(question.AnswerImageUrl, UriKind.Absolute))
embed.WithImageUrl(question.AnswerImageUrl);
if (isWin)
{
await ctx.Channel.EmbedAsync(embed);
var reward = _gamesConfig.Data.Trivia.CurrencyReward;
if (reward > 0)
await _cs.AddAsync(user.Id, reward, new("trivia", "win"));
return;
}
embed.WithDescription(GetText(strs.trivia_guess(user.Name,
Format.Bold(question.Answer))));
await ctx.Channel.EmbedAsync(embed);
}
catch
{
// ignored
}
}
}
}

View File

@@ -124,10 +124,6 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="Common\Collections" />
<Folder Include="Modules\Gambling\Wheel" />
</ItemGroup>
<PropertyGroup Condition=" '$(Version)' == '' ">
<VersionPrefix Condition=" '$(VersionPrefix)' == '' ">4.0.0</VersionPrefix>

View File

@@ -24,7 +24,7 @@ public static class StringExtensions
{
var spaces = length - str.Length;
var padLeft = (spaces / 2) + str.Length;
return str.PadLeft(padLeft).PadRight(length);
return str.PadLeft(padLeft, '_').PadRight(length, '_');
}
public static T? MapJson<T>(this string str)

View File

@@ -371,9 +371,11 @@ take:
betroll:
- betroll
- br
wheeloffortune:
- wheeloffortune
luckyladder:
- luckyladder
- lula
- wheel
- wof
leaderboard:
- leaderboard
- lb

View File

@@ -1,5 +1,5 @@
# DO NOT CHANGE
version: 5
version: 6
# Currency settings
currency:
# What is the emoji/character which represents the currency
@@ -67,18 +67,18 @@ decay:
minThreshold: 99
# How often, in hours, does the decay run. Default is 24 hours
hourInterval: 24
# Settings for Wheel Of Fortune command.
wheelOfFortune:
# Settings for LuckyLadder command
luckyLadder:
# Self-Explanatory. Has to have 8 values, otherwise the command won't work.
multipliers:
- 2.4
- 1.7
- 1.5
- 1.2
- 0.5
- 0.3
- 0.2
- 0.1
- 0.3
- 0.5
- 1.2
- 2.4
# Settings related to waifus
waifu:
# Minimum price a waifu can have

View File

@@ -703,8 +703,8 @@ betroll:
desc: "Bets a certain amount of currency and rolls a dice. Rolling over 66 yields x2 of your currency, over 90 - x4 and 100 x10."
args:
- "5"
wheeloffortune:
desc: "Bets a certain amount of currency on the wheel of fortune. Wheel can stop on one of many different multipliers. Won amount is rounded down to the nearest whole number."
luckyladder:
desc: "Bets a certain amount of currency on the lucky ladder. You can stop on one of many different multipliers. Won amount is rounded down to the nearest whole number."
args:
- "10"
leaderboard:

View File

@@ -238,6 +238,7 @@
"slot_three": "Wow! Lucky! Three of a kind! x{0}",
"slot_two": "Good job! Two {0} - bet x{1}",
"won": "Won",
"multiplier": "Multiplier",
"tails": "Tail",
"take": "successfully took {0} from {1}",
"take_fail": "was unable to take {0} from {1} because the user doesn't have that much {2}!",