Nunchi game added. A bit confusing now. Will be polished further tomorrow.
This commit is contained in:
parent
e0be610ec0
commit
f3984c824e
170
src/NadekoBot/Modules/Games/Common/Nunchi/Nunchi.cs
Normal file
170
src/NadekoBot/Modules/Games/Common/Nunchi/Nunchi.cs
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
using NadekoBot.Common;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Games.Common.Nunchi
|
||||||
|
{
|
||||||
|
public class Nunchi : IDisposable
|
||||||
|
{
|
||||||
|
public enum Phase
|
||||||
|
{
|
||||||
|
Joining,
|
||||||
|
Playing,
|
||||||
|
Ended,
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CurrentNumber { get; private set; } = new NadekoRandom().Next(0, 100);
|
||||||
|
public Phase CurrentPhase { get; private set; } = Phase.Joining;
|
||||||
|
|
||||||
|
public event Func<Nunchi, Task> OnGameStarted;
|
||||||
|
public event Func<Nunchi, Task> OnRoundStarted;
|
||||||
|
public event Func<Nunchi, Task> OnUserGuessed;
|
||||||
|
public event Func<Nunchi, (ulong Id, string Name)?, Task> OnRoundEnded; // tuple of the user who failed
|
||||||
|
public event Func<Nunchi, string, Task> OnGameEnded; // name of the user who won
|
||||||
|
|
||||||
|
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
|
private HashSet<(ulong Id, string Name)> _participants = new HashSet<(ulong Id, string Name)>();
|
||||||
|
private HashSet<(ulong Id, string Name)> _passed = new HashSet<(ulong Id, string Name)>();
|
||||||
|
|
||||||
|
public ImmutableArray<(ulong Id, string Name)> Participants => _participants.ToImmutableArray();
|
||||||
|
public int ParticipantCount => _participants.Count;
|
||||||
|
|
||||||
|
private const int _killTimeout = 20 * 1000;
|
||||||
|
private Timer _killTimer;
|
||||||
|
|
||||||
|
public Nunchi(ulong creatorId, string creatorName)
|
||||||
|
{
|
||||||
|
_participants.Add((creatorId, creatorName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Join(ulong userId, string userName)
|
||||||
|
{
|
||||||
|
await _locker.WaitAsync().ConfigureAwait(false);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (CurrentPhase != Phase.Joining)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return _participants.Add((userId, userName));
|
||||||
|
}
|
||||||
|
finally { _locker.Release(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Initialize()
|
||||||
|
{
|
||||||
|
CurrentPhase = Phase.Joining;
|
||||||
|
await Task.Delay(30000).ConfigureAwait(false);
|
||||||
|
await _locker.WaitAsync().ConfigureAwait(false);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_participants.Count < 2)
|
||||||
|
{
|
||||||
|
CurrentPhase = Phase.Ended;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_killTimer = new Timer(async state =>
|
||||||
|
{
|
||||||
|
await _locker.WaitAsync().ConfigureAwait(false);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (CurrentPhase != Phase.Playing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//if some players took too long to type a number, boot them all out and start a new round
|
||||||
|
_participants = new HashSet<(ulong, string)>(_passed);
|
||||||
|
EndRound();
|
||||||
|
}
|
||||||
|
finally { _locker.Release(); }
|
||||||
|
}, null, _killTimeout, _killTimeout);
|
||||||
|
|
||||||
|
CurrentPhase = Phase.Playing;
|
||||||
|
var _ = OnGameStarted?.Invoke(this);
|
||||||
|
var __ = OnRoundStarted?.Invoke(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
finally { _locker.Release(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Input(ulong userId, string userName, int input)
|
||||||
|
{
|
||||||
|
await _locker.WaitAsync().ConfigureAwait(false);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (CurrentPhase != Phase.Playing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var userTuple = (Id: userId, Name: userName);
|
||||||
|
|
||||||
|
// if the user is not a member of the race,
|
||||||
|
// or he already successfully typed the number
|
||||||
|
// ignore the input
|
||||||
|
if (!_participants.Contains(userTuple) || !_passed.Add(userTuple))
|
||||||
|
return;
|
||||||
|
|
||||||
|
//if the number is correct
|
||||||
|
if (CurrentNumber == input - 1)
|
||||||
|
{
|
||||||
|
//increment current number
|
||||||
|
++CurrentNumber;
|
||||||
|
if (_passed.Count == _participants.Count - 1)
|
||||||
|
{
|
||||||
|
// if only n players are left, and n - 1 type the correct number, round is over
|
||||||
|
|
||||||
|
// if only 2 players are left, game is over
|
||||||
|
if (_participants.Count == 2)
|
||||||
|
{
|
||||||
|
CurrentPhase = Phase.Ended;
|
||||||
|
var _ = OnGameEnded?.Invoke(this, userTuple.Name);
|
||||||
|
}
|
||||||
|
else // else just start the new round without the user who was the last
|
||||||
|
{
|
||||||
|
var failure = _participants.Except(_passed).First();
|
||||||
|
EndRound(failure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var __ = OnUserGuessed?.Invoke(this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//if the user failed
|
||||||
|
|
||||||
|
EndRound(userTuple);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally { _locker.Release(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EndRound((ulong, string)? failure = null)
|
||||||
|
{
|
||||||
|
_killTimer.Change(_killTimeout, _killTimeout);
|
||||||
|
CurrentNumber = new NadekoRandom().Next(0, 100); // reset the counter
|
||||||
|
_passed.Clear(); // reset all users who passed (new round starts)
|
||||||
|
if(failure != null)
|
||||||
|
_participants.Remove(failure.Value); // remove the dude who failed from the list of players
|
||||||
|
|
||||||
|
var __ = OnRoundEnded?.Invoke(this, failure);
|
||||||
|
if (_participants.Count <= 1) // means we have a winner or everyone was booted out
|
||||||
|
{
|
||||||
|
CurrentPhase = Phase.Ended;
|
||||||
|
var _ = OnGameEnded?.Invoke(this, _participants.Count > 0 ? _participants.First().Name : null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var ___ = OnRoundStarted?.Invoke(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
OnGameEnded = null;
|
||||||
|
OnGameStarted = null;
|
||||||
|
OnRoundEnded = null;
|
||||||
|
OnRoundStarted = null;
|
||||||
|
OnUserGuessed = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,6 @@ namespace NadekoBot.Modules.Games
|
|||||||
- Shiritori
|
- Shiritori
|
||||||
- Simple RPG adventure
|
- Simple RPG adventure
|
||||||
- The nunchi game
|
- The nunchi game
|
||||||
- Wheel of fortune
|
|
||||||
- Connect 4
|
- Connect 4
|
||||||
*/
|
*/
|
||||||
public partial class Games : NadekoTopLevelModule<GamesService>
|
public partial class Games : NadekoTopLevelModule<GamesService>
|
||||||
|
121
src/NadekoBot/Modules/Games/NunchiCommands.cs
Normal file
121
src/NadekoBot/Modules/Games/NunchiCommands.cs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
using Discord;
|
||||||
|
using Discord.Commands;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using NadekoBot.Common.Attributes;
|
||||||
|
using NadekoBot.Modules.Games.Common.Nunchi;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Games
|
||||||
|
{
|
||||||
|
public partial class Games
|
||||||
|
{
|
||||||
|
public class NunchiCommands : NadekoSubmodule
|
||||||
|
{
|
||||||
|
public static readonly ConcurrentDictionary<ulong, Nunchi> Games = new ConcurrentDictionary<ulong, Common.Nunchi.Nunchi>();
|
||||||
|
private readonly DiscordSocketClient _client;
|
||||||
|
|
||||||
|
public NunchiCommands(DiscordSocketClient client)
|
||||||
|
{
|
||||||
|
_client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
[NadekoCommand, Usage, Description, Aliases]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
public async Task Nunchi()
|
||||||
|
{
|
||||||
|
var newNunchi = new Nunchi(Context.User.Id, Context.User.ToString());
|
||||||
|
Nunchi nunchi;
|
||||||
|
|
||||||
|
//if a game was already active
|
||||||
|
if ((nunchi = Games.GetOrAdd(Context.Guild.Id, newNunchi)) != newNunchi)
|
||||||
|
{
|
||||||
|
// join it
|
||||||
|
if (!await nunchi.Join(Context.User.Id, Context.User.ToString()))
|
||||||
|
{
|
||||||
|
// if you failed joining, that means game is running or just ended
|
||||||
|
// await ReplyErrorLocalized("nunchi_already_started").ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ReplyConfirmLocalized("nunchi_joined", nunchi.ParticipantCount).ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try { await ReplyConfirmLocalized("nunchi_created").ConfigureAwait(false); } catch { }
|
||||||
|
|
||||||
|
nunchi.OnGameEnded += Nunchi_OnGameEnded;
|
||||||
|
//nunchi.OnGameStarted += Nunchi_OnGameStarted;
|
||||||
|
nunchi.OnRoundEnded += Nunchi_OnRoundEnded;
|
||||||
|
nunchi.OnUserGuessed += Nunchi_OnUserGuessed;
|
||||||
|
nunchi.OnRoundStarted += Nunchi_OnRoundStarted;
|
||||||
|
_client.MessageReceived += _client_MessageReceived;
|
||||||
|
|
||||||
|
var success = await nunchi.Initialize().ConfigureAwait(false);
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
if (Games.TryRemove(Context.Guild.Id, out var game))
|
||||||
|
game.Dispose();
|
||||||
|
await ReplyErrorLocalized("nunchi_failed_to_start").ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task _client_MessageReceived(SocketMessage arg)
|
||||||
|
{
|
||||||
|
if (arg.Channel.Id != Context.Channel.Id)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!int.TryParse(arg.Content, out var number))
|
||||||
|
return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await nunchi.Input(arg.Author.Id, arg.Author.ToString(), number).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task Nunchi_OnRoundStarted(Nunchi arg)
|
||||||
|
{
|
||||||
|
return ReplyConfirmLocalized("nunchi_round_started", Format.Bold(arg.CurrentNumber.ToString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task Nunchi_OnUserGuessed(Nunchi arg)
|
||||||
|
{
|
||||||
|
return ReplyConfirmLocalized("nunchi_next_number", Format.Bold(arg.CurrentNumber.ToString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task Nunchi_OnRoundEnded(Nunchi arg1, (ulong Id, string Name)? arg2)
|
||||||
|
{
|
||||||
|
if(arg2.HasValue)
|
||||||
|
return ReplyConfirmLocalized("nunchi_round_ended", Format.Bold(arg2.Value.Name));
|
||||||
|
else
|
||||||
|
return ReplyConfirmLocalized("nunchi_round_ended_boot",
|
||||||
|
Format.Bold("\n" + string.Join("\n, ", arg1.Participants.Select(x => x.Name)))); // this won't work if there are too many users
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task Nunchi_OnGameStarted(Nunchi arg)
|
||||||
|
{
|
||||||
|
return ReplyConfirmLocalized("nunchi_started", Format.Bold(arg.ParticipantCount.ToString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task Nunchi_OnGameEnded(Nunchi arg1, string arg2)
|
||||||
|
{
|
||||||
|
if (Games.TryRemove(Context.Guild.Id, out var game))
|
||||||
|
game.Dispose();
|
||||||
|
|
||||||
|
if(arg2 == null)
|
||||||
|
return ReplyConfirmLocalized("nunchi_ended_no_winner", Format.Bold(arg2));
|
||||||
|
else
|
||||||
|
return ReplyConfirmLocalized("nunchi_ended", Format.Bold(arg2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1269,6 +1269,15 @@
|
|||||||
<data name="joinrace_usage" xml:space="preserve">
|
<data name="joinrace_usage" xml:space="preserve">
|
||||||
<value>`{0}jr` or `{0}jr 5`</value>
|
<value>`{0}jr` or `{0}jr 5`</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="nunchi_cmd" xml:space="preserve">
|
||||||
|
<value>nunchi</value>
|
||||||
|
</data>
|
||||||
|
<data name="nunchi_desc" xml:space="preserve">
|
||||||
|
<value>Creates or joins a nunchi game. Minimum 3 users required.</value>
|
||||||
|
</data>
|
||||||
|
<data name="nunchi_usage" xml:space="preserve">
|
||||||
|
<value>`{0}nunchi`</value>
|
||||||
|
</data>
|
||||||
<data name="raffle_cmd" xml:space="preserve">
|
<data name="raffle_cmd" xml:space="preserve">
|
||||||
<value>raffle</value>
|
<value>raffle</value>
|
||||||
</data>
|
</data>
|
||||||
|
@ -363,6 +363,16 @@
|
|||||||
"games_leaderboard": "Leaderboard",
|
"games_leaderboard": "Leaderboard",
|
||||||
"games_not_enough": "You don't have enough {0}",
|
"games_not_enough": "You don't have enough {0}",
|
||||||
"games_no_results": "No results",
|
"games_no_results": "No results",
|
||||||
|
"games_nunchi_joined": "Joined nunchi game. {0} users joined so far.",
|
||||||
|
"games_nunchi_ended": "Nunchi game ended. {0} won",
|
||||||
|
"games_nunchi_ended_no_winner": "Nunchi game ended with no winner.",
|
||||||
|
"games_nunchi_started": "Nunchi game started with {0} participants.",
|
||||||
|
"games_nunchi_round_ended": "Nunchi round ended. {0} is out of the game.",
|
||||||
|
"games_nunchi_round_ended_boot": "Nunchi round ended due to timeout of some users. These users are still in the game: {0}",
|
||||||
|
"games_nunchi_round_started": "Nunchi round started. Start counting from the number {0}.",
|
||||||
|
"games_nunchi_next_number": "Number registered. Last number was {0}.",
|
||||||
|
"games_nunchi_failed_to_start": "Nunchi failed to start because there were not enough participants.",
|
||||||
|
"games_nunchi_created": "Nunchi game created. Waiting for users to join.",
|
||||||
"games_picked": "picked {0}",
|
"games_picked": "picked {0}",
|
||||||
"games_planted": "{0} planted {1}",
|
"games_planted": "{0} planted {1}",
|
||||||
"games_trivia_already_running": "Trivia game is already running on this server.",
|
"games_trivia_already_running": "Trivia game is already running on this server.",
|
||||||
|
Loading…
Reference in New Issue
Block a user