>tictactoe finished, sanity said it seems good to her. Unfortunately it isn't centered and can't be.
This commit is contained in:
parent
a8124cf1f2
commit
959f1726c9
@ -8,17 +8,19 @@ using System.Collections.Concurrent;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace NadekoBot.Modules.Games
|
namespace NadekoBot.Modules.Games
|
||||||
{
|
{
|
||||||
public partial class Games
|
public partial class Games
|
||||||
{
|
{
|
||||||
|
//todo timeout
|
||||||
[Group]
|
[Group]
|
||||||
public class TicTacToeCommands : ModuleBase
|
public class TicTacToeCommands : ModuleBase
|
||||||
{
|
{
|
||||||
//channelId/game
|
//channelId/game
|
||||||
private static readonly ConcurrentDictionary<ulong, TicTacToe> _openGames = new ConcurrentDictionary<ulong, TicTacToe>();
|
private static readonly Dictionary<ulong, TicTacToe> _games = new Dictionary<ulong, TicTacToe>();
|
||||||
private readonly Logger _log;
|
private readonly Logger _log;
|
||||||
|
|
||||||
public TicTacToeCommands()
|
public TicTacToeCommands()
|
||||||
@ -26,41 +28,69 @@ namespace NadekoBot.Modules.Games
|
|||||||
_log = LogManager.GetCurrentClassLogger();
|
_log = LogManager.GetCurrentClassLogger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly SemaphoreSlim sem = new SemaphoreSlim(1, 1);
|
||||||
|
private readonly object tttLockObj = new object();
|
||||||
|
|
||||||
[NadekoCommand, Usage, Description, Aliases]
|
[NadekoCommand, Usage, Description, Aliases]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
public async Task Ttt(IGuildUser secondUser)
|
public async Task TicTacToe()
|
||||||
{
|
{
|
||||||
var channel = (ITextChannel)Context.Channel;
|
var channel = (ITextChannel)Context.Channel;
|
||||||
|
|
||||||
|
await sem.WaitAsync(1000);
|
||||||
|
try
|
||||||
|
{
|
||||||
TicTacToe game;
|
TicTacToe game;
|
||||||
if (_openGames.TryRemove(channel.Id, out game)) // joining open game
|
if (_games.TryGetValue(channel.Id, out game))
|
||||||
{
|
{
|
||||||
if (!game.Join((IGuildUser)Context.User))
|
var _ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await Context.Channel.SendErrorAsync("You can't play against yourself. Game stopped.").ConfigureAwait(false);
|
await game.Start((IGuildUser)Context.User);
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
var _ = Task.Run(() => game.Start());
|
|
||||||
_log.Warn($"User {Context.User} joined a TicTacToe game.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
game = new TicTacToe(channel, (IGuildUser)Context.User);
|
game = new TicTacToe(channel, (IGuildUser)Context.User);
|
||||||
if (_openGames.TryAdd(Context.Channel.Id, game))
|
_games.Add(channel.Id, game);
|
||||||
|
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} Created a TicTacToe game.").ConfigureAwait(false);
|
||||||
|
|
||||||
|
game.OnEnded += (g) =>
|
||||||
{
|
{
|
||||||
_log.Warn($"User {Context.User} created a TicTacToe game.");
|
_games.Remove(channel.Id);
|
||||||
await Context.Channel.SendConfirmAsync("Tic Tac Toe game created. Waiting for another user.").ConfigureAwait(false);
|
};
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
sem.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TicTacToe
|
public class TicTacToe
|
||||||
{
|
{
|
||||||
|
enum Phase
|
||||||
|
{
|
||||||
|
Starting,
|
||||||
|
Started,
|
||||||
|
Ended
|
||||||
|
}
|
||||||
|
|
||||||
private readonly ITextChannel _channel;
|
private readonly ITextChannel _channel;
|
||||||
private readonly Logger _log;
|
private readonly Logger _log;
|
||||||
private readonly IGuildUser[] _users;
|
private readonly IGuildUser[] _users;
|
||||||
private readonly int?[,] _state;
|
private readonly int?[,] _state;
|
||||||
|
private Phase _phase;
|
||||||
|
private readonly Func<IUserMessage, Task> _playMove;
|
||||||
|
int curUserIndex = 0;
|
||||||
|
private readonly SemaphoreSlim moveLock;
|
||||||
|
|
||||||
|
private IGuildUser _winner = null;
|
||||||
|
|
||||||
|
private readonly string[] numbers = { ":one:", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:" };
|
||||||
|
|
||||||
|
public Action<TicTacToe> OnEnded;
|
||||||
|
|
||||||
|
private IUserMessage previousMessage = null;
|
||||||
|
private Timer timeoutTimer;
|
||||||
|
|
||||||
public TicTacToe(ITextChannel channel, IGuildUser firstUser)
|
public TicTacToe(ITextChannel channel, IGuildUser firstUser)
|
||||||
{
|
{
|
||||||
@ -68,11 +98,44 @@ namespace NadekoBot.Modules.Games
|
|||||||
_users = new IGuildUser[2] { firstUser, null };
|
_users = new IGuildUser[2] { firstUser, null };
|
||||||
_state = new int?[3, 3] {
|
_state = new int?[3, 3] {
|
||||||
{ null, null, null },
|
{ null, null, null },
|
||||||
{ null, 1, 1 },
|
{ null, null, null },
|
||||||
{ 0, null, 0 },
|
{ null, null, null },
|
||||||
};
|
};
|
||||||
|
|
||||||
_log = LogManager.GetCurrentClassLogger();
|
_log = LogManager.GetCurrentClassLogger();
|
||||||
|
_log.Warn($"User {firstUser} created a TicTacToe game.");
|
||||||
|
_phase = Phase.Starting;
|
||||||
|
moveLock = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
|
timeoutTimer = new Timer(async (_) =>
|
||||||
|
{
|
||||||
|
await moveLock.WaitAsync();
|
||||||
|
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("Time Expired!")).ConfigureAwait(false);
|
||||||
|
await del.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
OnEnded?.Invoke(this);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
moveLock.Release();
|
||||||
|
}
|
||||||
|
}, null, 15000, Timeout.Infinite);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetState()
|
public string GetState()
|
||||||
@ -82,7 +145,7 @@ namespace NadekoBot.Modules.Games
|
|||||||
{
|
{
|
||||||
for (int j = 0; j < _state.GetLength(1); j++)
|
for (int j = 0; j < _state.GetLength(1); j++)
|
||||||
{
|
{
|
||||||
sb.Append(GetIcon(_state[i, j]));
|
sb.Append(_state[i, j] == null ? numbers[i * 3 + j] : GetIcon(_state[i, j]));
|
||||||
if (j < _state.GetLength(1) - 1)
|
if (j < _state.GetLength(1) - 1)
|
||||||
sb.Append("┃");
|
sb.Append("┃");
|
||||||
}
|
}
|
||||||
@ -93,12 +156,28 @@ namespace NadekoBot.Modules.Games
|
|||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public EmbedBuilder GetEmbed() =>
|
public EmbedBuilder GetEmbed(string title = null)
|
||||||
new EmbedBuilder()
|
{
|
||||||
|
var embed = new EmbedBuilder()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithDescription(GetState())
|
.WithDescription(Environment.NewLine + GetState())
|
||||||
.WithAuthor(eab => eab.WithName("Tic Tac Toe"))
|
.WithAuthor(eab => eab.WithName($"{_users[0]} vs {_users[1]}"));
|
||||||
.WithTitle($"{_users[0]} vs {_users[1]}");
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(title))
|
||||||
|
embed.WithTitle(title);
|
||||||
|
|
||||||
|
if (_winner == null)
|
||||||
|
{
|
||||||
|
if (_phase == Phase.Ended)
|
||||||
|
embed.WithFooter(efb => efb.WithText($"No moves left!"));
|
||||||
|
else
|
||||||
|
embed.WithFooter(efb => efb.WithText($"{_users[curUserIndex]}'s move"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
embed.WithFooter(efb => efb.WithText($"{_winner} Won!"));
|
||||||
|
|
||||||
|
return embed;
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetIcon(int? val)
|
private static string GetIcon(int? val)
|
||||||
{
|
{
|
||||||
@ -108,20 +187,141 @@ namespace NadekoBot.Modules.Games
|
|||||||
return "❌";
|
return "❌";
|
||||||
case 1:
|
case 1:
|
||||||
return "⭕";
|
return "⭕";
|
||||||
|
case 2:
|
||||||
|
return "❎";
|
||||||
|
case 3:
|
||||||
|
return "🅾";
|
||||||
default:
|
default:
|
||||||
return "⬛";
|
return "⬛";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Start()
|
public async Task Start(IGuildUser user)
|
||||||
{
|
{
|
||||||
|
if (_phase == Phase.Started || _phase == Phase.Ended)
|
||||||
|
{
|
||||||
|
await _channel.SendErrorAsync(user.Mention + " TicTacToe Game is already running in this channel.").ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (_users[0] == user)
|
||||||
|
{
|
||||||
|
await _channel.SendErrorAsync(user.Mention + " You can't play against yourself.").ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_users[1] = user;
|
||||||
|
_log.Warn($"User {user} joined a TicTacToe game.");
|
||||||
|
|
||||||
|
_phase = Phase.Started;
|
||||||
|
|
||||||
|
NadekoBot.Client.MessageReceived += Client_MessageReceived;
|
||||||
|
|
||||||
|
previousMessage = await _channel.EmbedAsync(GetEmbed("Game Started")).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsDraw()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < 3; j++)
|
||||||
|
{
|
||||||
|
if (_state[i, j] == null)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task Client_MessageReceived(Discord.WebSocket.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;
|
||||||
|
|
||||||
|
int index;
|
||||||
|
if (int.TryParse(msg.Content, out index) &&
|
||||||
|
--index >= 0 &&
|
||||||
|
index <= 9 &&
|
||||||
|
_state[index / 3, index % 3] == 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;
|
||||||
|
}
|
||||||
|
string reason = "";
|
||||||
|
|
||||||
|
if (_phase == Phase.Ended) // if user won, stop receiving moves
|
||||||
|
{
|
||||||
|
reason = "Matched three!";
|
||||||
|
_winner = _users[curUserIndex];
|
||||||
|
NadekoBot.Client.MessageReceived -= Client_MessageReceived;
|
||||||
|
OnEnded?.Invoke(this);
|
||||||
|
}
|
||||||
|
else if (IsDraw())
|
||||||
|
{
|
||||||
|
reason = "A draw!";
|
||||||
|
_phase = Phase.Ended;
|
||||||
|
NadekoBot.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)); } catch { }
|
||||||
|
try { await del1; } catch { }
|
||||||
|
try { await del2; } catch { }
|
||||||
|
});
|
||||||
|
curUserIndex ^= 1;
|
||||||
|
|
||||||
|
timeoutTimer.Change(15000, Timeout.Infinite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
moveLock.Release();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Join(IGuildUser user)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
27
src/NadekoBot/Resources/CommandStrings.Designer.cs
generated
27
src/NadekoBot/Resources/CommandStrings.Designer.cs
generated
@ -7727,6 +7727,33 @@ namespace NadekoBot.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to tictactoe ttt.
|
||||||
|
/// </summary>
|
||||||
|
public static string tictactoe_cmd {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("tictactoe_cmd", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Starts a game of tic tac toe. Another user must run the command in the same channel in order to accept the challenge. Use numbers 1-9 to play. 15 seconds per move..
|
||||||
|
/// </summary>
|
||||||
|
public static string tictactoe_desc {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("tictactoe_desc", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to >ttt.
|
||||||
|
/// </summary>
|
||||||
|
public static string tictactoe_usage {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("tictactoe_usage", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to tl.
|
/// Looks up a localized string similar to tl.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -3078,4 +3078,13 @@
|
|||||||
<data name="shardid_usage" xml:space="preserve">
|
<data name="shardid_usage" xml:space="preserve">
|
||||||
<value>`{0}shardid 117523346618318850`</value>
|
<value>`{0}shardid 117523346618318850`</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="tictactoe_cmd" xml:space="preserve">
|
||||||
|
<value>tictactoe ttt</value>
|
||||||
|
</data>
|
||||||
|
<data name="tictactoe_desc" xml:space="preserve">
|
||||||
|
<value>Starts a game of tic tac toe. Another user must run the command in the same channel in order to accept the challenge. Use numbers 1-9 to play. 15 seconds per move.</value>
|
||||||
|
</data>
|
||||||
|
<data name="tictactoe_usage" xml:space="preserve">
|
||||||
|
<value>>ttt</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
Loading…
Reference in New Issue
Block a user