>tictactoe finished, sanity said it seems good to her. Unfortunately it isn't centered and can't be.

This commit is contained in:
Kwoth 2017-02-09 03:09:06 +01:00
parent a8124cf1f2
commit 959f1726c9
3 changed files with 263 additions and 27 deletions

View File

@ -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);
TicTacToe game; try
if (_openGames.TryRemove(channel.Id, out game)) // joining open game
{ {
if (!game.Join((IGuildUser)Context.User)) TicTacToe game;
if (_games.TryGetValue(channel.Id, out game))
{ {
await Context.Channel.SendErrorAsync("You can't play against yourself. Game stopped.").ConfigureAwait(false); var _ = Task.Run(async () =>
{
await game.Start((IGuildUser)Context.User);
});
return; return;
} }
var _ = Task.Run(() => game.Start()); game = new TicTacToe(channel, (IGuildUser)Context.User);
_log.Warn($"User {Context.User} joined a TicTacToe game."); _games.Add(channel.Id, game);
return; await Context.Channel.SendConfirmAsync($"{Context.User.Mention} Created a TicTacToe game.").ConfigureAwait(false);
game.OnEnded += (g) =>
{
_games.Remove(channel.Id);
};
} }
game = new TicTacToe(channel, (IGuildUser)Context.User); finally
if (_openGames.TryAdd(Context.Channel.Id, game))
{ {
_log.Warn($"User {Context.User} created a TicTacToe game."); sem.Release();
await Context.Channel.SendConfirmAsync("Tic Tac Toe game created. Waiting for another user.").ConfigureAwait(false);
} }
} }
} }
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,19 +187,140 @@ 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)
{ {
return Task.CompletedTask; 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);
} }
public void Join(IGuildUser user) 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;
} }
} }
} }

View File

@ -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 &gt;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>

View File

@ -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>&gt;ttt</value>
</data>
</root> </root>