mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-11 09:48:26 -04:00
Global usings and file scoped namespaces
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using NadekoBot.Extensions;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -9,285 +8,284 @@ using CommandLine;
|
||||
using NadekoBot.Common;
|
||||
using NadekoBot.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Games.Common
|
||||
namespace NadekoBot.Modules.Games.Common;
|
||||
|
||||
public class TicTacToe
|
||||
{
|
||||
public class TicTacToe
|
||||
public class Options : INadekoCommandOptions
|
||||
{
|
||||
public class Options : INadekoCommandOptions
|
||||
public void NormalizeOptions()
|
||||
{
|
||||
public void NormalizeOptions()
|
||||
if (TurnTimer < 5 || TurnTimer > 60)
|
||||
TurnTimer = 15;
|
||||
}
|
||||
|
||||
[Option('t', "turn-timer", Required = false, Default = 15, HelpText = "Turn time in seconds. Default 15.")]
|
||||
public int TurnTimer { get; set; } = 15;
|
||||
}
|
||||
|
||||
enum Phase
|
||||
{
|
||||
Starting,
|
||||
Started,
|
||||
Ended
|
||||
}
|
||||
|
||||
private readonly ITextChannel _channel;
|
||||
private readonly IGuildUser[] _users;
|
||||
private readonly int?[,] _state;
|
||||
private Phase _phase;
|
||||
private int _curUserIndex;
|
||||
private readonly SemaphoreSlim _moveLock;
|
||||
|
||||
private IGuildUser _winner;
|
||||
|
||||
private readonly string[] _numbers = { ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:" };
|
||||
|
||||
public event Action<TicTacToe> OnEnded;
|
||||
|
||||
private IUserMessage _previousMessage;
|
||||
private Timer _timeoutTimer;
|
||||
private readonly IBotStrings _strings;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly Options _options;
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
|
||||
public TicTacToe(IBotStrings strings, DiscordSocketClient client, ITextChannel channel,
|
||||
IGuildUser firstUser, Options options, IEmbedBuilderService eb)
|
||||
{
|
||||
_channel = channel;
|
||||
_strings = strings;
|
||||
_client = client;
|
||||
_options = options;
|
||||
_eb = eb;
|
||||
|
||||
_users = new[] { firstUser, null };
|
||||
_state = new int?[,] {
|
||||
{ null, null, null },
|
||||
{ null, null, null },
|
||||
{ null, null, null },
|
||||
};
|
||||
|
||||
_phase = Phase.Starting;
|
||||
_moveLock = new SemaphoreSlim(1, 1);
|
||||
}
|
||||
|
||||
private string GetText(LocStr key)
|
||||
=> _strings.GetText(key, _channel.GuildId);
|
||||
|
||||
public string GetState()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
for (var i = 0; i < _state.GetLength(0); i++)
|
||||
{
|
||||
for (var j = 0; j < _state.GetLength(1); j++)
|
||||
{
|
||||
if (TurnTimer < 5 || TurnTimer > 60)
|
||||
TurnTimer = 15;
|
||||
sb.Append(_state[i, j] is null ? _numbers[i * 3 + j] : GetIcon(_state[i, j]));
|
||||
if (j < _state.GetLength(1) - 1)
|
||||
sb.Append("┃");
|
||||
}
|
||||
|
||||
[Option('t', "turn-timer", Required = false, Default = 15, HelpText = "Turn time in seconds. Default 15.")]
|
||||
public int TurnTimer { get; set; } = 15;
|
||||
if (i < _state.GetLength(0) - 1)
|
||||
sb.AppendLine("\n──────────");
|
||||
}
|
||||
|
||||
enum Phase
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public IEmbedBuilder GetEmbed(string title = null)
|
||||
{
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithDescription(Environment.NewLine + GetState())
|
||||
.WithAuthor(GetText(strs.vs(_users[0], _users[1])));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(title))
|
||||
embed.WithTitle(title);
|
||||
|
||||
if (_winner is null)
|
||||
{
|
||||
Starting,
|
||||
Started,
|
||||
Ended
|
||||
}
|
||||
|
||||
private readonly ITextChannel _channel;
|
||||
private readonly IGuildUser[] _users;
|
||||
private readonly int?[,] _state;
|
||||
private Phase _phase;
|
||||
private int _curUserIndex;
|
||||
private readonly SemaphoreSlim _moveLock;
|
||||
|
||||
private IGuildUser _winner;
|
||||
|
||||
private readonly string[] _numbers = { ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:" };
|
||||
|
||||
public event Action<TicTacToe> OnEnded;
|
||||
|
||||
private IUserMessage _previousMessage;
|
||||
private Timer _timeoutTimer;
|
||||
private readonly IBotStrings _strings;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly Options _options;
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
|
||||
public TicTacToe(IBotStrings strings, DiscordSocketClient client, ITextChannel channel,
|
||||
IGuildUser firstUser, Options options, IEmbedBuilderService eb)
|
||||
{
|
||||
_channel = channel;
|
||||
_strings = strings;
|
||||
_client = client;
|
||||
_options = options;
|
||||
_eb = eb;
|
||||
|
||||
_users = new[] { firstUser, null };
|
||||
_state = new int?[,] {
|
||||
{ null, null, null },
|
||||
{ null, null, null },
|
||||
{ null, null, null },
|
||||
};
|
||||
|
||||
_phase = Phase.Starting;
|
||||
_moveLock = new SemaphoreSlim(1, 1);
|
||||
}
|
||||
|
||||
private string GetText(LocStr key)
|
||||
=> _strings.GetText(key, _channel.GuildId);
|
||||
|
||||
public string GetState()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
for (var i = 0; i < _state.GetLength(0); i++)
|
||||
{
|
||||
for (var j = 0; j < _state.GetLength(1); j++)
|
||||
{
|
||||
sb.Append(_state[i, j] is null ? _numbers[i * 3 + j] : GetIcon(_state[i, j]));
|
||||
if (j < _state.GetLength(1) - 1)
|
||||
sb.Append("┃");
|
||||
}
|
||||
if (i < _state.GetLength(0) - 1)
|
||||
sb.AppendLine("\n──────────");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public IEmbedBuilder GetEmbed(string title = null)
|
||||
{
|
||||
var embed = _eb.Create()
|
||||
.WithOkColor()
|
||||
.WithDescription(Environment.NewLine + GetState())
|
||||
.WithAuthor(GetText(strs.vs(_users[0], _users[1])));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(title))
|
||||
embed.WithTitle(title);
|
||||
|
||||
if (_winner is null)
|
||||
{
|
||||
if (_phase == Phase.Ended)
|
||||
embed.WithFooter(GetText(strs.ttt_no_moves));
|
||||
else
|
||||
embed.WithFooter(GetText(strs.ttt_users_move(_users[_curUserIndex])));
|
||||
}
|
||||
if (_phase == Phase.Ended)
|
||||
embed.WithFooter(GetText(strs.ttt_no_moves));
|
||||
else
|
||||
embed.WithFooter(GetText(strs.ttt_has_won(_winner)));
|
||||
|
||||
return embed;
|
||||
embed.WithFooter(GetText(strs.ttt_users_move(_users[_curUserIndex])));
|
||||
}
|
||||
else
|
||||
embed.WithFooter(GetText(strs.ttt_has_won(_winner)));
|
||||
|
||||
private static string GetIcon(int? val)
|
||||
return embed;
|
||||
}
|
||||
|
||||
private static string GetIcon(int? val)
|
||||
{
|
||||
switch (val)
|
||||
{
|
||||
switch (val)
|
||||
{
|
||||
case 0:
|
||||
return "❌";
|
||||
case 1:
|
||||
return "⭕";
|
||||
case 2:
|
||||
return "❎";
|
||||
case 3:
|
||||
return "🅾";
|
||||
default:
|
||||
return "⬛";
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Start(IGuildUser user)
|
||||
{
|
||||
if (_phase == Phase.Started || _phase == Phase.Ended)
|
||||
{
|
||||
await _channel.SendErrorAsync(_eb, user.Mention + GetText(strs.ttt_already_running)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
else if (_users[0] == user)
|
||||
{
|
||||
await _channel.SendErrorAsync(_eb, user.Mention + GetText(strs.ttt_against_yourself)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_users[1] = user;
|
||||
|
||||
_phase = Phase.Started;
|
||||
|
||||
_timeoutTimer = new Timer(async (_) =>
|
||||
{
|
||||
await _moveLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (_phase == Phase.Ended)
|
||||
return;
|
||||
|
||||
_phase = Phase.Ended;
|
||||
if (_users[1] != null)
|
||||
{
|
||||
_winner = _users[_curUserIndex ^= 1];
|
||||
var del = _previousMessage?.DeleteAsync();
|
||||
try
|
||||
{
|
||||
await _channel.EmbedAsync(GetEmbed(GetText(strs.ttt_time_expired))).ConfigureAwait(false);
|
||||
if (del != null)
|
||||
await del.ConfigureAwait(false);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
OnEnded?.Invoke(this);
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
_moveLock.Release();
|
||||
}
|
||||
}, null, _options.TurnTimer * 1000, Timeout.Infinite);
|
||||
|
||||
_client.MessageReceived += Client_MessageReceived;
|
||||
|
||||
|
||||
_previousMessage = await _channel.EmbedAsync(GetEmbed(GetText(strs.game_started))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private bool IsDraw()
|
||||
{
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
for (var j = 0; j < 3; j++)
|
||||
{
|
||||
if (_state[i, j] is null)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private Task Client_MessageReceived(SocketMessage msg)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
await _moveLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var curUser = _users[_curUserIndex];
|
||||
if (_phase == Phase.Ended || msg.Author?.Id != curUser.Id)
|
||||
return;
|
||||
|
||||
if (int.TryParse(msg.Content, out var index) &&
|
||||
--index >= 0 &&
|
||||
index <= 9 &&
|
||||
_state[index / 3, index % 3] is null)
|
||||
{
|
||||
_state[index / 3, index % 3] = _curUserIndex;
|
||||
|
||||
// i'm lazy
|
||||
if (_state[index / 3, 0] == _state[index / 3, 1] && _state[index / 3, 1] == _state[index / 3, 2])
|
||||
{
|
||||
_state[index / 3, 0] = _curUserIndex + 2;
|
||||
_state[index / 3, 1] = _curUserIndex + 2;
|
||||
_state[index / 3, 2] = _curUserIndex + 2;
|
||||
|
||||
_phase = Phase.Ended;
|
||||
}
|
||||
else if (_state[0, index % 3] == _state[1, index % 3] && _state[1, index % 3] == _state[2, index % 3])
|
||||
{
|
||||
_state[0, index % 3] = _curUserIndex + 2;
|
||||
_state[1, index % 3] = _curUserIndex + 2;
|
||||
_state[2, index % 3] = _curUserIndex + 2;
|
||||
|
||||
_phase = Phase.Ended;
|
||||
}
|
||||
else if (_curUserIndex == _state[0, 0] && _state[0, 0] == _state[1, 1] && _state[1, 1] == _state[2, 2])
|
||||
{
|
||||
_state[0, 0] = _curUserIndex + 2;
|
||||
_state[1, 1] = _curUserIndex + 2;
|
||||
_state[2, 2] = _curUserIndex + 2;
|
||||
|
||||
_phase = Phase.Ended;
|
||||
}
|
||||
else if (_curUserIndex == _state[0, 2] && _state[0, 2] == _state[1, 1] && _state[1, 1] == _state[2, 0])
|
||||
{
|
||||
_state[0, 2] = _curUserIndex + 2;
|
||||
_state[1, 1] = _curUserIndex + 2;
|
||||
_state[2, 0] = _curUserIndex + 2;
|
||||
|
||||
_phase = Phase.Ended;
|
||||
}
|
||||
var reason = "";
|
||||
|
||||
if (_phase == Phase.Ended) // if user won, stop receiving moves
|
||||
{
|
||||
reason = GetText(strs.ttt_matched_three);
|
||||
_winner = _users[_curUserIndex];
|
||||
_client.MessageReceived -= Client_MessageReceived;
|
||||
OnEnded?.Invoke(this);
|
||||
}
|
||||
else if (IsDraw())
|
||||
{
|
||||
reason = GetText(strs.ttt_a_draw);
|
||||
_phase = Phase.Ended;
|
||||
_client.MessageReceived -= Client_MessageReceived;
|
||||
OnEnded?.Invoke(this);
|
||||
}
|
||||
|
||||
var sendstate = Task.Run(async () =>
|
||||
{
|
||||
var del1 = msg.DeleteAsync();
|
||||
var del2 = _previousMessage?.DeleteAsync();
|
||||
try { _previousMessage = await _channel.EmbedAsync(GetEmbed(reason)).ConfigureAwait(false); } catch { }
|
||||
try { await del1.ConfigureAwait(false); } catch { }
|
||||
try { if (del2 != null) await del2.ConfigureAwait(false); } catch { }
|
||||
});
|
||||
_curUserIndex ^= 1;
|
||||
|
||||
_timeoutTimer.Change(_options.TurnTimer * 1000, Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_moveLock.Release();
|
||||
}
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
case 0:
|
||||
return "❌";
|
||||
case 1:
|
||||
return "⭕";
|
||||
case 2:
|
||||
return "❎";
|
||||
case 3:
|
||||
return "🅾";
|
||||
default:
|
||||
return "⬛";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Start(IGuildUser user)
|
||||
{
|
||||
if (_phase == Phase.Started || _phase == Phase.Ended)
|
||||
{
|
||||
await _channel.SendErrorAsync(_eb, user.Mention + GetText(strs.ttt_already_running)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
else if (_users[0] == user)
|
||||
{
|
||||
await _channel.SendErrorAsync(_eb, user.Mention + GetText(strs.ttt_against_yourself)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_users[1] = user;
|
||||
|
||||
_phase = Phase.Started;
|
||||
|
||||
_timeoutTimer = new Timer(async (_) =>
|
||||
{
|
||||
await _moveLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (_phase == Phase.Ended)
|
||||
return;
|
||||
|
||||
_phase = Phase.Ended;
|
||||
if (_users[1] != null)
|
||||
{
|
||||
_winner = _users[_curUserIndex ^= 1];
|
||||
var del = _previousMessage?.DeleteAsync();
|
||||
try
|
||||
{
|
||||
await _channel.EmbedAsync(GetEmbed(GetText(strs.ttt_time_expired))).ConfigureAwait(false);
|
||||
if (del != null)
|
||||
await del.ConfigureAwait(false);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
OnEnded?.Invoke(this);
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
_moveLock.Release();
|
||||
}
|
||||
}, null, _options.TurnTimer * 1000, Timeout.Infinite);
|
||||
|
||||
_client.MessageReceived += Client_MessageReceived;
|
||||
|
||||
|
||||
_previousMessage = await _channel.EmbedAsync(GetEmbed(GetText(strs.game_started))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private bool IsDraw()
|
||||
{
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
for (var j = 0; j < 3; j++)
|
||||
{
|
||||
if (_state[i, j] is null)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private Task Client_MessageReceived(SocketMessage msg)
|
||||
{
|
||||
var _ = Task.Run(async () =>
|
||||
{
|
||||
await _moveLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var curUser = _users[_curUserIndex];
|
||||
if (_phase == Phase.Ended || msg.Author?.Id != curUser.Id)
|
||||
return;
|
||||
|
||||
if (int.TryParse(msg.Content, out var index) &&
|
||||
--index >= 0 &&
|
||||
index <= 9 &&
|
||||
_state[index / 3, index % 3] is null)
|
||||
{
|
||||
_state[index / 3, index % 3] = _curUserIndex;
|
||||
|
||||
// i'm lazy
|
||||
if (_state[index / 3, 0] == _state[index / 3, 1] && _state[index / 3, 1] == _state[index / 3, 2])
|
||||
{
|
||||
_state[index / 3, 0] = _curUserIndex + 2;
|
||||
_state[index / 3, 1] = _curUserIndex + 2;
|
||||
_state[index / 3, 2] = _curUserIndex + 2;
|
||||
|
||||
_phase = Phase.Ended;
|
||||
}
|
||||
else if (_state[0, index % 3] == _state[1, index % 3] && _state[1, index % 3] == _state[2, index % 3])
|
||||
{
|
||||
_state[0, index % 3] = _curUserIndex + 2;
|
||||
_state[1, index % 3] = _curUserIndex + 2;
|
||||
_state[2, index % 3] = _curUserIndex + 2;
|
||||
|
||||
_phase = Phase.Ended;
|
||||
}
|
||||
else if (_curUserIndex == _state[0, 0] && _state[0, 0] == _state[1, 1] && _state[1, 1] == _state[2, 2])
|
||||
{
|
||||
_state[0, 0] = _curUserIndex + 2;
|
||||
_state[1, 1] = _curUserIndex + 2;
|
||||
_state[2, 2] = _curUserIndex + 2;
|
||||
|
||||
_phase = Phase.Ended;
|
||||
}
|
||||
else if (_curUserIndex == _state[0, 2] && _state[0, 2] == _state[1, 1] && _state[1, 1] == _state[2, 0])
|
||||
{
|
||||
_state[0, 2] = _curUserIndex + 2;
|
||||
_state[1, 1] = _curUserIndex + 2;
|
||||
_state[2, 0] = _curUserIndex + 2;
|
||||
|
||||
_phase = Phase.Ended;
|
||||
}
|
||||
var reason = "";
|
||||
|
||||
if (_phase == Phase.Ended) // if user won, stop receiving moves
|
||||
{
|
||||
reason = GetText(strs.ttt_matched_three);
|
||||
_winner = _users[_curUserIndex];
|
||||
_client.MessageReceived -= Client_MessageReceived;
|
||||
OnEnded?.Invoke(this);
|
||||
}
|
||||
else if (IsDraw())
|
||||
{
|
||||
reason = GetText(strs.ttt_a_draw);
|
||||
_phase = Phase.Ended;
|
||||
_client.MessageReceived -= Client_MessageReceived;
|
||||
OnEnded?.Invoke(this);
|
||||
}
|
||||
|
||||
var sendstate = Task.Run(async () =>
|
||||
{
|
||||
var del1 = msg.DeleteAsync();
|
||||
var del2 = _previousMessage?.DeleteAsync();
|
||||
try { _previousMessage = await _channel.EmbedAsync(GetEmbed(reason)).ConfigureAwait(false); } catch { }
|
||||
try { await del1.ConfigureAwait(false); } catch { }
|
||||
try { if (del2 != null) await del2.ConfigureAwait(false); } catch { }
|
||||
});
|
||||
_curUserIndex ^= 1;
|
||||
|
||||
_timeoutTimer.Change(_options.TurnTimer * 1000, Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_moveLock.Release();
|
||||
}
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user