refactored games module

This commit is contained in:
Master Kwoth
2016-04-14 23:30:13 +02:00
parent fe67591081
commit 3e6b2df73d
9 changed files with 104 additions and 58 deletions

View File

@ -1,6 +1,7 @@
using Discord.Commands;
using NadekoBot.Classes.JSONModels;
using NadekoBot.Commands;
using NadekoBot.Modules.Games.Commands;
using System;
using System.Collections.Generic;
using System.Linq;

View File

@ -0,0 +1,128 @@
using Discord.Commands;
using NadekoBot.Commands;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Games.Commands
{
class BetrayGame : DiscordCommand
{
public BetrayGame(DiscordModule module) : base(module) { }
private enum Answers
{
Cooperate,
Betray
}
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "betray")
.Description("BETRAY GAME. Betray nadeko next turn." +
"If Nadeko cooperates - you get extra points, nadeko loses a LOT." +
"If Nadeko betrays - you both lose some points.")
.Do(async e =>
{
await ReceiveAnswer(e, Answers.Betray);
});
cgb.CreateCommand(Module.Prefix + "cooperate")
.Description("BETRAY GAME. Cooperate with nadeko next turn." +
"If Nadeko cooperates - you both get bonus points." +
"If Nadeko betrays - you lose A LOT, nadeko gets extra.")
.Do(async e =>
{
await ReceiveAnswer(e, Answers.Cooperate);
});
}
private int userPoints = 0;
private int UserPoints {
get { return userPoints; }
set {
if (value < 0)
userPoints = 0;
userPoints = value;
}
}
private int nadekoPoints = 0;
private int NadekoPoints {
get { return nadekoPoints; }
set {
if (value < 0)
nadekoPoints = 0;
nadekoPoints = value;
}
}
private int round = 0;
private Answers NextAnswer = Answers.Cooperate;
private async Task ReceiveAnswer(CommandEventArgs e, Answers userAnswer)
{
var response = userAnswer == Answers.Betray
? ":no_entry: `You betrayed nadeko` - you monster."
: ":ok: `You cooperated with nadeko.` ";
var currentAnswer = NextAnswer;
var nadekoResponse = currentAnswer == Answers.Betray
? ":no_entry: `aww Nadeko betrayed you` - she is so cute"
: ":ok: `Nadeko cooperated.`";
NextAnswer = userAnswer;
if (userAnswer == Answers.Betray && currentAnswer == Answers.Betray)
{
NadekoPoints--;
UserPoints--;
}
else if (userAnswer == Answers.Cooperate && currentAnswer == Answers.Cooperate)
{
NadekoPoints += 2;
UserPoints += 2;
}
else if (userAnswer == Answers.Betray && currentAnswer == Answers.Cooperate)
{
NadekoPoints -= 3;
UserPoints += 3;
}
else if (userAnswer == Answers.Cooperate && currentAnswer == Answers.Betray)
{
NadekoPoints += 3;
UserPoints -= 3;
}
await e.Channel.SendMessage($"**ROUND {++round}**\n" +
$"{response}\n" +
$"{nadekoResponse}\n" +
$"--------------------------------\n" +
$"Nadeko has {NadekoPoints} points." +
$"You have {UserPoints} points." +
$"--------------------------------\n");
if (round < 10) return;
if (nadekoPoints == userPoints)
await e.Channel.SendMessage("Its a draw");
else if (nadekoPoints > userPoints)
await e.Channel.SendMessage("Nadeko won.");
else
await e.Channel.SendMessage("You won.");
nadekoPoints = 0;
userPoints = 0;
round = 0;
}
}
public class BetraySetting
{
private string Story = $"{0} have robbed a bank and got captured by a police." +
$"Investigators gave you a choice:\n" +
$"You can either >COOPERATE with your friends and " +
$"not tell them who's idea it was, OR you can >BETRAY your" +
$"friends. Depending on their answers your penalty will vary.";
public int DoubleCoop = 1;
public int DoubleBetray = -1;
public int BetrayCoop_Betrayer = 3;
public int BetrayCoop_Cooperater = -3;
public string GetStory(IEnumerable<string> names) => String.Format(Story, string.Join(", ", names));
}
}

View File

@ -0,0 +1,85 @@
using Discord;
using Discord.Commands;
using NadekoBot.Classes;
using NadekoBot.Commands;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Games.Commands
{
/// <summary>
/// Flower picking/planting idea is given to me by its
/// inceptor Violent Crumble from Game Developers League discord server
/// (he has !cookie and !nom) Thanks a lot Violent!
/// Check out GDL (its a growing gamedev community):
/// https://discord.gg/0TYNJfCU4De7YIk8
/// </summary>
class PlantPick : DiscordCommand
{
public PlantPick(DiscordModule module) : base(module)
{
}
//channelid/messageid pair
ConcurrentDictionary<ulong, Message> plantedFlowerChannels = new ConcurrentDictionary<ulong, Message>();
private object locker = new object();
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "pick")
.Description("Picks a flower planted in this channel.")
.Do(async e =>
{
Message msg;
await e.Message.Delete();
if (!plantedFlowerChannels.TryRemove(e.Channel.Id, out msg))
return;
await msg.Delete();
await FlowersHandler.AddFlowersAsync(e.User, "Picked a flower.", 1, true);
msg = await e.Channel.SendMessage($"**{e.User.Name}** picked a {NadekoBot.Config.CurrencyName}!");
await Task.Delay(10000);
await msg.Delete();
});
cgb.CreateCommand(Module.Prefix + "plant")
.Description("Spend a flower to plant it in this channel. (If bot is restarted or crashes, flower will be lost)")
.Do(async e =>
{
lock (locker)
{
if (plantedFlowerChannels.ContainsKey(e.Channel.Id))
{
e.Channel.SendMessage($"There is already a {NadekoBot.Config.CurrencyName} in this channel.");
return;
}
var removed = FlowersHandler.RemoveFlowers(e.User, "Planted a flower.", 1);
if (!removed)
{
e.Channel.SendMessage($"You don't have any {NadekoBot.Config.CurrencyName}s.").Wait();
return;
}
var rng = new Random();
var file = Directory.GetFiles("data/currency_images").OrderBy(s => rng.Next()).FirstOrDefault();
Message msg;
if (file == null)
msg = e.Channel.SendMessage(NadekoBot.Config.CurrencySign).GetAwaiter().GetResult();
else
msg = e.Channel.SendFile(file).GetAwaiter().GetResult();
plantedFlowerChannels.TryAdd(e.Channel.Id, msg);
}
var vowelFirst = new[] { 'a', 'e', 'i', 'o', 'u' }.Contains(NadekoBot.Config.CurrencyName[0]);
var msg2 = await e.Channel.SendMessage($"Oh how Nice! **{e.User.Name}** planted {(vowelFirst ? "an" : "a")} {NadekoBot.Config.CurrencyName}. Pick it using {Module.Prefix}pick");
await Task.Delay(20000);
await msg2.Delete();
});
}
}
}

View File

@ -0,0 +1,146 @@
using Discord;
using Discord.Commands;
using NadekoBot.Commands;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Games.Commands
{
internal class PollCommand : DiscordCommand
{
public static ConcurrentDictionary<Server, Poll> ActivePolls = new ConcurrentDictionary<Server, Poll>();
public Func<CommandEventArgs, Task> DoFunc()
{
throw new NotImplementedException();
}
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "poll")
.Description("Creates a poll, only person who has manage server permission can do it.\n**Usage**: >poll Question?;Answer1;Answ 2;A_3")
.Parameter("allargs", ParameterType.Unparsed)
.Do(async e =>
{
await Task.Run(async () =>
{
if (!e.User.ServerPermissions.ManageChannels)
return;
if (ActivePolls.ContainsKey(e.Server))
return;
var arg = e.GetArg("allargs");
if (string.IsNullOrWhiteSpace(arg) || !arg.Contains(";"))
return;
var data = arg.Split(';');
if (data.Length < 3)
return;
var poll = new Poll(e, data[0], data.Skip(1));
if (PollCommand.ActivePolls.TryAdd(e.Server, poll))
{
await poll.StartPoll();
}
});
});
cgb.CreateCommand(Module.Prefix + "pollend")
.Description("Stops active poll on this server and prints the results in this channel.")
.Do(async e =>
{
if (!e.User.ServerPermissions.ManageChannels)
return;
if (!ActivePolls.ContainsKey(e.Server))
return;
await ActivePolls[e.Server].StopPoll(e.Channel);
});
}
public PollCommand(DiscordModule module) : base(module) { }
}
internal class Poll
{
private readonly CommandEventArgs e;
private readonly string[] answers;
private ConcurrentDictionary<User, int> participants = new ConcurrentDictionary<User, int>();
private readonly string question;
private DateTime started;
private CancellationTokenSource pollCancellationSource = new CancellationTokenSource();
public Poll(CommandEventArgs e, string question, IEnumerable<string> enumerable)
{
this.e = e;
this.question = question;
this.answers = enumerable as string[] ?? enumerable.ToArray();
}
public async Task StartPoll()
{
started = DateTime.Now;
NadekoBot.Client.MessageReceived += Vote;
var msgToSend =
$"📃**{e.User.Name}** from **{e.Server.Name}** server has created a poll which requires your attention:\n\n" +
$"**{question}**\n";
var num = 1;
msgToSend = answers.Aggregate(msgToSend, (current, answ) => current + $"`{num++}.` **{answ}**\n");
msgToSend += "\n**Private Message me with the corresponding number of the answer.**";
await e.Channel.SendMessage(msgToSend);
}
public async Task StopPoll(Channel ch)
{
NadekoBot.Client.MessageReceived -= Vote;
Poll throwaway;
PollCommand.ActivePolls.TryRemove(e.Server, out throwaway);
try
{
var results = participants.GroupBy(kvp => kvp.Value)
.ToDictionary(x => x.Key, x => x.Sum(kvp => 1))
.OrderBy(kvp => kvp.Value);
var totalVotesCast = results.Sum(kvp => kvp.Value);
if (totalVotesCast == 0)
{
await ch.SendMessage("📄 **No votes have been cast.**");
return;
}
var closeMessage = $"--------------**POLL CLOSED**--------------\n" +
$"📄 , here are the results:\n";
closeMessage = results.Aggregate(closeMessage, (current, kvp) => current + $"`{kvp.Key}.` **[{answers[kvp.Key - 1]}]**" +
$" has {kvp.Value} votes." +
$"({kvp.Value * 1.0f / totalVotesCast * 100}%)\n");
await ch.SendMessage($"📄 **Total votes cast**: {totalVotesCast}\n{closeMessage}");
}
catch (Exception ex)
{
Console.WriteLine($"Error in poll game {ex}");
}
}
private async void Vote(object sender, MessageEventArgs e)
{
try
{
if (!e.Channel.IsPrivate)
return;
if (participants.ContainsKey(e.User))
return;
int vote;
if (!int.TryParse(e.Message.Text, out vote)) return;
if (vote < 1 || vote > answers.Length)
return;
if (participants.TryAdd(e.User, vote))
{
await e.User.SendMessage($"Thanks for voting **{e.User.Name}**.");
}
}
catch { }
}
}
}

View File

@ -0,0 +1,197 @@
using Discord;
using Discord.Commands;
using NadekoBot.Classes;
using NadekoBot.Classes._DataModels;
using NadekoBot.Commands;
using NadekoBot.Extensions;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Games.Commands
{
public static class SentencesProvider
{
internal static string GetRandomSentence()
{
var data = DbHandler.Instance.GetAllRows<TypingArticle>();
try
{
return data.ToList()[new Random().Next(0, data.Count())].Text;
}
catch
{
return "Failed retrieving data from parse. Owner didn't add any articles to type using `typeadd`.";
}
}
}
public class TypingGame
{
public const float WORD_VALUE = 4.5f;
private readonly Channel channel;
public string CurrentSentence;
public bool IsActive;
private readonly Stopwatch sw;
private readonly List<ulong> finishedUserIds;
public TypingGame(Channel channel)
{
this.channel = channel;
IsActive = false;
sw = new Stopwatch();
finishedUserIds = new List<ulong>();
}
public Channel Channell { get; internal set; }
internal async Task<bool> Stop()
{
if (!IsActive) return false;
NadekoBot.Client.MessageReceived -= AnswerReceived;
finishedUserIds.Clear();
IsActive = false;
sw.Stop();
sw.Reset();
await channel.Send("Typing contest stopped");
return true;
}
internal async Task Start()
{
while (true)
{
if (IsActive) return; // can't start running game
IsActive = true;
CurrentSentence = SentencesProvider.GetRandomSentence();
var i = (int)(CurrentSentence.Length / WORD_VALUE * 1.7f);
await channel.SendMessage($":clock2: Next contest will last for {i} seconds. Type the bolded text as fast as you can.");
var msg = await channel.SendMessage("Starting new typing contest in **3**...");
await Task.Delay(1000);
await msg.Edit("Starting new typing contest in **2**...");
await Task.Delay(1000);
await msg.Edit("Starting new typing contest in **1**...");
await Task.Delay(1000);
await msg.Edit($":book:**{CurrentSentence.Replace(" ", " \x200B")}**:book:");
sw.Start();
HandleAnswers();
while (i > 0)
{
await Task.Delay(1000);
i--;
if (!IsActive)
return;
}
await Stop();
}
}
private void HandleAnswers()
{
NadekoBot.Client.MessageReceived += AnswerReceived;
}
private async void AnswerReceived(object sender, MessageEventArgs e)
{
try
{
if (e.Channel == null || e.Channel.Id != channel.Id || e.User.Id == NadekoBot.Client.CurrentUser.Id) return;
var guess = e.Message.RawText;
var distance = CurrentSentence.LevenshteinDistance(guess);
var decision = Judge(distance, guess.Length);
if (decision && !finishedUserIds.Contains(e.User.Id))
{
finishedUserIds.Add(e.User.Id);
await channel.Send($"{e.User.Mention} finished in **{sw.Elapsed.Seconds}** seconds with { distance } errors, **{ CurrentSentence.Length / WORD_VALUE / sw.Elapsed.Seconds * 60 }** WPM!");
if (finishedUserIds.Count % 2 == 0)
{
await e.Channel.SendMessage($":exclamation: `A lot of people finished, here is the text for those still typing:`\n\n:book:**{CurrentSentence}**:book:");
}
}
}
catch { }
}
private bool Judge(int errors, int textLength) => errors <= textLength / 25;
}
internal class SpeedTyping : DiscordCommand
{
public static ConcurrentDictionary<ulong, TypingGame> RunningContests;
public SpeedTyping(DiscordModule module) : base(module)
{
RunningContests = new ConcurrentDictionary<ulong, TypingGame>();
}
public Func<CommandEventArgs, Task> DoFunc() =>
async e =>
{
var game = RunningContests.GetOrAdd(e.User.Server.Id, id => new TypingGame(e.Channel));
if (game.IsActive)
{
await e.Channel.SendMessage(
$"Contest already running in " +
$"{game.Channell.Mention} channel.");
}
else
{
await game.Start();
}
};
private Func<CommandEventArgs, Task> QuitFunc() =>
async e =>
{
TypingGame game;
if (RunningContests.TryRemove(e.User.Server.Id, out game))
{
await game.Stop();
return;
}
await e.Channel.SendMessage("No contest to stop on this channel.");
};
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "typestart")
.Description("Starts a typing contest.")
.Do(DoFunc());
cgb.CreateCommand(Module.Prefix + "typestop")
.Description("Stops a typing contest on the current channel.")
.Do(QuitFunc());
cgb.CreateCommand(Module.Prefix + "typeadd")
.Description("Adds a new article to the typing contest. Owner only.")
.Parameter("text", ParameterType.Unparsed)
.Do(async e =>
{
if (!NadekoBot.IsOwner(e.User.Id) || string.IsNullOrWhiteSpace(e.GetArg("text"))) return;
DbHandler.Instance.InsertData(new TypingArticle
{
Text = e.GetArg("text"),
DateAdded = DateTime.Now
});
await e.Channel.SendMessage("Added new article for typing game.");
});
//todo add user submissions
}
}
}

View File

@ -0,0 +1,63 @@
using Discord.Commands;
using NadekoBot.Commands;
using System.Collections.Concurrent;
using System.Linq;
using TriviaGame = NadekoBot.Classes.Trivia.TriviaGame;
namespace NadekoBot.Modules.Games.Commands
{
internal class Trivia : DiscordCommand
{
public static ConcurrentDictionary<ulong, TriviaGame> RunningTrivias = new ConcurrentDictionary<ulong, TriviaGame>();
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "t")
.Description($"Starts a game of trivia. You can add nohint to prevent hints." +
"First player to get to 10 points wins. 30 seconds per question." +
$"\n**Usage**:`{Module.Prefix}t nohint`")
.Parameter("args", ParameterType.Multiple)
.Do(async e =>
{
TriviaGame trivia;
if (!RunningTrivias.TryGetValue(e.Server.Id, out trivia))
{
var showHints = !e.Args.Contains("nohint");
var triviaGame = new TriviaGame(e, showHints);
if (RunningTrivias.TryAdd(e.Server.Id, triviaGame))
await e.Channel.SendMessage("**Trivia game started!**");
else
await triviaGame.StopGame();
}
else
await e.Channel.SendMessage("Trivia game is already running on this server.\n" + trivia.CurrentQuestion);
});
cgb.CreateCommand(Module.Prefix + "tl")
.Description("Shows a current trivia leaderboard.")
.Do(async e =>
{
TriviaGame trivia;
if (RunningTrivias.TryGetValue(e.Server.Id, out trivia))
await e.Channel.SendMessage(trivia.GetLeaderboard());
else
await e.Channel.SendMessage("No trivia is running on this server.");
});
cgb.CreateCommand(Module.Prefix + "tq")
.Description("Quits current trivia after current question.")
.Do(async e =>
{
TriviaGame trivia;
if (RunningTrivias.TryGetValue(e.Server.Id, out trivia))
{
await trivia.StopGame();
}
else
await e.Channel.SendMessage("No trivia is running on this server.");
});
}
public Trivia(DiscordModule module) : base(module) { }
}
}

View File

@ -4,6 +4,7 @@ using NadekoBot.Classes.JSONModels;
using NadekoBot.Classes.Permissions;
using NadekoBot.Commands;
using NadekoBot.Extensions;
using NadekoBot.Modules.Games.Commands;
using System;
using System.Linq;
using System.Threading.Tasks;