renamed nunchi to countup, added remaining user count to each guess

This commit is contained in:
Toastie 2025-04-18 20:36:58 +12:00
parent e357c78fbe
commit 85f735fcc7
Signed by: toastie_t0ast
GPG key ID: 0861BE54AD481DC7
7 changed files with 148 additions and 138 deletions

View file

@ -2,7 +2,6 @@
using Microsoft.Extensions.Caching.Memory;
using EllieBot.Modules.Games.Common;
using EllieBot.Modules.Games.Common.Acrophobia;
using EllieBot.Modules.Games.Common.Nunchi;
using Newtonsoft.Json;
namespace EllieBot.Modules.Games.Services;
@ -21,7 +20,7 @@ public class GamesService : IEService
public ConcurrentDictionary<ulong, AcrophobiaGame> AcrophobiaGames { get; } = new();
public Dictionary<ulong, TicTacToe> TicTacToeGames { get; } = new();
public ConcurrentDictionary<ulong, TypingGame> RunningContests { get; } = new();
public ConcurrentDictionary<ulong, NunchiGame> NunchiGames { get; } = new();
public ConcurrentDictionary<ulong, CountUpGame> Games { get; } = new();
private readonly GamesConfigService _gamesConfig;

View file

@ -0,0 +1,117 @@
#nullable disable
using EllieBot.Modules.Games.Services;
namespace EllieBot.Modules.Games;
public partial class Games
{
[Group]
public partial class CountUpCommands : EllieModule<GamesService>
{
private readonly DiscordSocketClient _client;
public CountUpCommands(DiscordSocketClient client)
=> _client = client;
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task CountUp()
{
var newGame = new CountUpGame(ctx.User.Id, ctx.User.ToString());
CountUpGame countUp;
// if a game was already active
if ((countUp = _service.Games.GetOrAdd(ctx.Guild.Id, newGame)) != newGame)
{
// join it
// if you failed joining, that means the game is running or just ended
if (!await countUp.Join(ctx.User.Id, ctx.User.ToString()))
return;
await Response().Confirm(strs.countup_joined(countUp.ParticipantCount)).SendAsync();
return;
}
try
{ await Response().Confirm(strs.countup_created).SendAsync(); }
catch { }
countUp.OnGameEnded += CountUpOnGameEnded;
countUp.OnRoundEnded += CountUpOnRoundEnded;
countUp.OnUserGuessed += CountUpOnUserGuessed;
countUp.OnRoundStarted += CountUpOnRoundStarted;
_client.MessageReceived += ClientMessageReceived;
var success = await countUp.Initialize();
if (!success)
{
if (_service.Games.TryRemove(ctx.Guild.Id, out var game))
game.Dispose();
await Response().Confirm(strs.countup_failed_to_start).SendAsync();
}
Task ClientMessageReceived(SocketMessage arg)
{
_ = Task.Run(async () =>
{
if (arg.Channel.Id != ctx.Channel.Id)
return;
if (!int.TryParse(arg.Content, out var number))
return;
try
{
await countUp.Input(arg.Author.Id, arg.Author.ToString(), number);
}
catch
{
}
});
return Task.CompletedTask;
}
Task CountUpOnGameEnded(CountUpGame arg1, string arg2)
{
if (_service.Games.TryRemove(ctx.Guild.Id, out var game))
{
_client.MessageReceived -= ClientMessageReceived;
game.Dispose();
}
if (arg2 is null)
return Response().Confirm(strs.countup_ended_no_winner).SendAsync();
return Response().Confirm(strs.countup_ended(Format.Bold(arg2))).SendAsync();
}
}
private Task CountUpOnRoundStarted(CountUpGame arg, int cur)
=> Response()
.Confirm(strs.countup_round_started(Format.Bold(arg.ParticipantCount.ToString()),
Format.Bold(cur.ToString())))
.SendAsync();
private Task CountUpOnUserGuessed(CountUpGame arg)
=> Response()
.Embed(CreateEmbed()
.WithOkColor()
.WithDescription(GetText(strs.countup_next_number(Format.Bold(arg.CurrentNumber.ToString()))))
.WithFooter($"{arg.PassedCount} / {arg.ParticipantCount}"))
.SendAsync();
private Task CountUpOnRoundEnded(CountUpGame arg1, (ulong Id, string Name)? arg2)
{
if (arg2.HasValue)
return Response().Confirm(strs.countup_round_ended(Format.Bold(arg2.Value.Name))).SendAsync();
return Response()
.Confirm(strs.countup_round_ended_boot(
Format.Bold("\n"
+ string.Join("\n, ",
arg1.Participants.Select(x
=> x.Name)))))
.SendAsync(); // this won't work if there are too many users
}
private Task CountUpOnGameStarted(CountUpGame arg)
=> Response().Confirm(strs.countup_started(Format.Bold(arg.ParticipantCount.ToString()))).SendAsync();
}
}

View file

@ -1,9 +1,9 @@
#nullable disable
using System.Collections.Immutable;
namespace EllieBot.Modules.Games.Common.Nunchi;
namespace EllieBot.Modules.Games;
public sealed class NunchiGame : IDisposable
public sealed class CountUpGame : IDisposable
{
public enum Phase
{
@ -16,11 +16,11 @@ public sealed class NunchiGame : IDisposable
private const int KILL_TIMEOUT = 20 * 1000;
private const int NEXT_ROUND_TIMEOUT = 5 * 1000;
public event Func<NunchiGame, Task> OnGameStarted;
public event Func<NunchiGame, int, Task> OnRoundStarted;
public event Func<NunchiGame, Task> OnUserGuessed;
public event Func<NunchiGame, (ulong Id, string Name)?, Task> OnRoundEnded; // tuple of the user who failed
public event Func<NunchiGame, string, Task> OnGameEnded; // name of the user who won
public event Func<CountUpGame, Task> OnGameStarted;
public event Func<CountUpGame, int, Task> OnRoundStarted;
public event Func<CountUpGame, Task> OnUserGuessed;
public event Func<CountUpGame, (ulong Id, string Name)?, Task> OnRoundEnded; // tuple of the user who failed
public event Func<CountUpGame, string, Task> OnGameEnded; // name of the user who won
public int CurrentNumber { get; private set; } = new EllieRandom().Next(0, 100);
public Phase CurrentPhase { get; private set; } = Phase.Joining;
@ -31,13 +31,16 @@ public sealed class NunchiGame : IDisposable
public int ParticipantCount
=> participants.Count;
public int PassedCount
=> _passed.Count;
private readonly SemaphoreSlim _locker = new(1, 1);
private HashSet<(ulong Id, string Name)> participants = [];
private readonly HashSet<(ulong Id, string Name)> _passed = [];
private Timer killTimer;
public NunchiGame(ulong creatorId, string creatorName)
public CountUpGame(ulong creatorId, string creatorName)
=> participants.Add((creatorId, creatorName));
public async Task<bool> Join(ulong userId, string userName)

View file

@ -1,114 +0,0 @@
#nullable disable
using EllieBot.Modules.Games.Common.Nunchi;
using EllieBot.Modules.Games.Services;
namespace EllieBot.Modules.Games;
public partial class Games
{
[Group]
public partial class NunchiCommands : EllieModule<GamesService>
{
private readonly DiscordSocketClient _client;
public NunchiCommands(DiscordSocketClient client)
=> _client = client;
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task Nunchi()
{
var newNunchi = new NunchiGame(ctx.User.Id, ctx.User.ToString());
NunchiGame nunchi;
//if a game was already active
if ((nunchi = _service.NunchiGames.GetOrAdd(ctx.Guild.Id, newNunchi)) != newNunchi)
{
// join it
// if you failed joining, that means game is running or just ended
if (!await nunchi.Join(ctx.User.Id, ctx.User.ToString()))
return;
await Response().Confirm(strs.nunchi_joined(nunchi.ParticipantCount)).SendAsync();
return;
}
try { await Response().Confirm(strs.nunchi_created).SendAsync(); }
catch { }
nunchi.OnGameEnded += NunchiOnGameEnded;
//nunchi.OnGameStarted += Nunchi_OnGameStarted;
nunchi.OnRoundEnded += Nunchi_OnRoundEnded;
nunchi.OnUserGuessed += Nunchi_OnUserGuessed;
nunchi.OnRoundStarted += Nunchi_OnRoundStarted;
_client.MessageReceived += ClientMessageReceived;
var success = await nunchi.Initialize();
if (!success)
{
if (_service.NunchiGames.TryRemove(ctx.Guild.Id, out var game))
game.Dispose();
await Response().Confirm(strs.nunchi_failed_to_start).SendAsync();
}
Task ClientMessageReceived(SocketMessage arg)
{
_ = Task.Run(async () =>
{
if (arg.Channel.Id != ctx.Channel.Id)
return;
if (!int.TryParse(arg.Content, out var number))
return;
try
{
await nunchi.Input(arg.Author.Id, arg.Author.ToString(), number);
}
catch
{
}
});
return Task.CompletedTask;
}
Task NunchiOnGameEnded(NunchiGame arg1, string arg2)
{
if (_service.NunchiGames.TryRemove(ctx.Guild.Id, out var game))
{
_client.MessageReceived -= ClientMessageReceived;
game.Dispose();
}
if (arg2 is null)
return Response().Confirm(strs.nunchi_ended_no_winner).SendAsync();
return Response().Confirm(strs.nunchi_ended(Format.Bold(arg2))).SendAsync();
}
}
private Task Nunchi_OnRoundStarted(NunchiGame arg, int cur)
=> Response()
.Confirm(strs.nunchi_round_started(Format.Bold(arg.ParticipantCount.ToString()),
Format.Bold(cur.ToString())))
.SendAsync();
private Task Nunchi_OnUserGuessed(NunchiGame arg)
=> Response().Confirm(strs.nunchi_next_number(Format.Bold(arg.CurrentNumber.ToString()))).SendAsync();
private Task Nunchi_OnRoundEnded(NunchiGame arg1, (ulong Id, string Name)? arg2)
{
if (arg2.HasValue)
return Response().Confirm(strs.nunchi_round_ended(Format.Bold(arg2.Value.Name))).SendAsync();
return Response()
.Confirm(strs.nunchi_round_ended_boot(
Format.Bold("\n"
+ string.Join("\n, ",
arg1.Participants.Select(x
=> x.Name)))))
.SendAsync(); // this won't work if there are too many users
}
private Task Nunchi_OnGameStarted(NunchiGame arg)
=> Response().Confirm(strs.nunchi_started(Format.Bold(arg.ParticipantCount.ToString()))).SendAsync();
}
}

View file

@ -442,7 +442,8 @@ race:
joinrace:
- joinrace
- jr
nunchi:
countup:
- countup
- nunchi
connect4:
- connect4

View file

@ -1473,8 +1473,12 @@ joinrace:
params:
- amount:
desc: "The amount to be wagered on the race."
nunchi:
desc: Creates or joins an existing nunchi game. Users have to count up by 1 from the starting number shown by the bot. If someone makes a mistake (types an incorrect number, or repeats the same number) they are out of the game and a new round starts without them. Minimum 3 users required.
countup:
desc: |-
Creates or joins an existing CountUp game.
Bot will show a number - count up from it.
Whoever writes a duplicate number, or is the last person without a number loses, a new round starts!
Minimum 3 users required.
ex:
- ''
params:

View file

@ -806,16 +806,16 @@
"connect4_failed_to_start": "Connect4 game failed to start because nobody joined.",
"connect4_draw": "Connect4 game ended in a draw.",
"connect4_won": "{0} won the game of Connect4 against {1}.",
"nunchi_joined": "Joined nunchi game. {0} users joined so far.",
"nunchi_ended": "Nunchi game ended. {0} won",
"nunchi_ended_no_winner": "Nunchi game ended with no winner.",
"nunchi_started": "Nunchi game started with {0} participants.",
"nunchi_round_ended": "Nunchi round ended. {0} is out of the game.",
"nunchi_round_ended_boot": "Nunchi round ended due to timeout of some users. These users are still in the game: {0}",
"nunchi_round_started": "Nunchi round started with {0} users. Start counting from the number {1}.",
"nunchi_next_number": "Number registered. Last number was {0}.",
"nunchi_failed_to_start": "Nunchi failed to start because there were not enough participants.",
"nunchi_created": "Nunchi game created. Waiting for users to join.",
"countup_joined": "Joined countup game. {0} users joined so far.",
"countup_ended": "CountUp game ended. {0} won",
"countup_ended_no_winner": "CountUp game ended with no winner.",
"countup_started": "CountUp game started with {0} participants.",
"countup_round_ended": "CountUp round ended. {0} is out of the game.",
"countup_round_ended_boot": "CountUp round ended due to timeout of some users. These users are still in the game: {0}",
"countup_round_started": "CountUp round started with {0} users. Start counting from the number {1}.",
"countup_next_number": "Number registered. Last number was {0}.",
"countup_failed_to_start": "CountUp failed to start because there were not enough participants.",
"countup_created": "CountUp game created. Waiting for users to join.",
"stream_role_enabled": "When a user from {0} role starts streaming, I will give them {1} role.",
"stream_role_disabled": "Stream role feature has been disabled.",
"stream_role_kw_set": "Streamers now require {0} keyword in order to receive the role.",
@ -994,7 +994,7 @@
"module_page_empty": "No module on this page.",
"module_description_help": "Get command help, descriptions and usage examples",
"module_description_gambling": "Bet on dice rolls, blackjack, slots, coinflips and others",
"module_description_games": "Play trivia, nunchi, hangman, connect4 and other games",
"module_description_games": "Play trivia, countup, hangman, connect4 and other games",
"module_description_music": "Play music from youtube, local files and radio streams",
"module_description_utility": "Manage custom quotes, repeating messages and check facts about the server",
"module_description_administration": "Moderation, punish users, setup self assignable roles and greet messages",