refactor (gambling module): some codecleanup and reorganization

This commit is contained in:
Gergő Törcsvári
2016-03-25 12:48:22 +01:00
parent aa589dbc05
commit 05c72ff08a
9 changed files with 351 additions and 227 deletions

View File

@ -1,57 +0,0 @@
using Discord.Commands;
using Discord.Modules;
using NadekoBot.Extensions;
using System.Linq;
using Discord;
using NadekoBot.Commands;
namespace NadekoBot.Modules
{
internal class Gambling : DiscordModule
{
public Gambling() {
commands.Add(new DrawCommand(this));
commands.Add(new FlipCoinCommand(this));
commands.Add(new DiceRollCommand(this));
}
public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Gambling;
public override void Install(ModuleManager manager)
{
manager.CreateCommands("", cgb =>
{
cgb.AddCheck(Classes.Permissions.PermissionChecker.Instance);
commands.ForEach(com => com.Init(cgb));
cgb.CreateCommand(Prefix +"raffle")
.Description("Prints a name and ID of a random user from the online list from the (optional) role.")
.Parameter("role", ParameterType.Optional)
.Do(async e => {
var arg = string.IsNullOrWhiteSpace(e.GetArg("role")) ? "@everyone" : e.GetArg("role");
var role = e.Server.FindRoles(arg).FirstOrDefault();
if (role == null) {
await e.Channel.SendMessage("💢 Role not found.");
return;
}
var members = role.Members.Where(u => u.Status == Discord.UserStatus.Online); // only online
var membersArray = members as User[] ?? members.ToArray();
var usr = membersArray[new System.Random().Next(0, membersArray.Length)];
await e.Channel.SendMessage($"**Raffled user:** {usr.Name} (id: {usr.Id})");
});
cgb.CreateCommand(Prefix + "$$")
.Description("Check how many NadekoFlowers you have.")
.Do(async e => {
var pts = Classes.DbHandler.Instance.GetStateByUserId((long)e.User.Id)?.Value ?? 0;
var str = $"`You have {pts} NadekoFlowers".SnPl((int)pts)+"`\n";
for (var i = 0; i < pts; i++) {
str += "🌸";
}
await e.Channel.SendMessage(str);
});
});
}
}
}

View File

@ -0,0 +1,128 @@
using Discord.Commands;
using NadekoBot.Commands;
using NadekoBot.Extensions;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling
{
internal class DiceRollCommand : DiscordCommand
{
public DiceRollCommand(DiscordModule module) : base(module) { }
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "roll")
.Description("Rolls 2 dice from 0-10. If you supply a number [x] it rolls up to 30 normal dice.\n**Usage**: $roll [x]")
.Parameter("num", ParameterType.Optional)
.Do(Roll0to10Func());
cgb.CreateCommand(Module.Prefix + "nroll")
.Description("Rolls in a given range.\n**Usage**: `$nroll 5` (rolls 0-5) or `$nroll 5-15`")
.Parameter("range", ParameterType.Required)
.Do(Roll0to5Func());
}
private Image GetDice(int num) => Properties.Resources.ResourceManager.GetObject("_" + num) as Image;
private Func<CommandEventArgs, Task> Roll0to10Func()
{
var r = new Random();
return async e =>
{
if (e.Args[0] == "")
{
var num1 = r.Next(0, 10);
var num2 = r.Next(0, 10);
Image[] images;
if (num1 == 0 && num2 == 0 && r.Next(0, 2) == 1)
{
images = new Image[3] { GetDice(1), GetDice(0), GetDice(0) };
}
else {
images = new Image[2] { GetDice(num1), GetDice(num2) };
}
var bitmap = images.Merge();
await e.Channel.SendFile("dice.png", bitmap.ToStream(ImageFormat.Png));
}
else {
try
{
var num = int.Parse(e.Args[0]);
if (num < 1) num = 1;
if (num > 30)
{
await e.Channel.SendMessage("You can roll up to 30 dies at a time.");
num = 30;
}
var dices = new List<Image>(num);
var values = new List<int>(num);
for (var i = 0; i < num; i++)
{
var randomNumber = r.Next(1, 7);
var toInsert = dices.Count;
if (randomNumber == 6 || dices.Count == 0)
toInsert = 0;
else if (randomNumber != 1)
for (var j = 0; j < dices.Count; j++)
{
if (values[j] < randomNumber)
{
toInsert = j;
break;
}
}
dices.Insert(toInsert, GetDice(randomNumber));
values.Insert(toInsert, randomNumber);
}
var bitmap = dices.Merge();
await e.Channel.SendMessage(values.Count + " Dice rolled. Total: **" + values.Sum() + "** Average: **" + (values.Sum() / (1.0f * values.Count)).ToString("N2") + "**");
await e.Channel.SendFile("dice.png", bitmap.ToStream(ImageFormat.Png));
}
catch
{
await e.Channel.SendMessage("Please enter a number of dice to roll.");
}
}
};
}
private Func<CommandEventArgs, Task> Roll0to5Func() =>
async e =>
{
try
{
int rolled;
if (e.GetArg("range").Contains("-"))
{
var arr = e.GetArg("range").Split('-')
.Take(2)
.Select(int.Parse)
.ToArray();
if (arr[0] > arr[1])
throw new ArgumentException("First argument should be bigger than the second one.");
rolled = new Random().Next(arr[0], arr[1] + 1);
}
else {
rolled = new Random().Next(0, int.Parse(e.GetArg("range")) + 1);
}
await e.Channel.SendMessage($"{e.User.Mention} rolled **{rolled}**.");
}
catch (Exception ex)
{
await e.Channel.SendMessage($":anger: {ex.Message}");
}
};
}
}

View File

@ -0,0 +1,91 @@
using Discord.Commands;
using NadekoBot.Commands;
using NadekoBot.Extensions;
using NadekoBot.Modules.Gambling.Helpers;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling
{
internal class DrawCommand : DiscordCommand
{
public DrawCommand(DiscordModule module) : base(module) { }
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "draw")
.Description("Draws a card from the deck.If you supply number [x], she draws up to 5 cards from the deck.\n**Usage**: $draw [x]")
.Parameter("count", ParameterType.Optional)
.Do(DrawCardFunc());
cgb.CreateCommand(Module.Prefix + "shuffle")
.Alias(Module.Prefix + "sh")
.Description("Reshuffles all cards back into the deck.")
.Do(ReshuffleTask());
}
private static readonly ConcurrentDictionary<Discord.Server, Cards> AllDecks = new ConcurrentDictionary<Discord.Server, Cards>();
private static Func<CommandEventArgs, Task> ReshuffleTask()
{
return async e =>
{
AllDecks.AddOrUpdate(e.Server,
(s) => new Cards(),
(s, c) =>
{
c.Restart();
return c;
});
await e.Channel.SendMessage("Deck reshuffled.");
};
}
private Func<CommandEventArgs, Task> DrawCardFunc() => async (e) =>
{
var cards = AllDecks.GetOrAdd(e.Server, (s) => new Cards());
try
{
var num = 1;
var isParsed = int.TryParse(e.GetArg("count"), out num);
if (!isParsed || num < 2)
{
var c = cards.DrawACard();
await e.Channel.SendFile(c.Name + ".jpg", (Properties.Resources.ResourceManager.GetObject(c.Name) as Image).ToStream());
return;
}
if (num > 5)
num = 5;
var images = new List<Image>();
var cardObjects = new List<Cards.Card>();
for (var i = 0; i < num; i++)
{
if (cards.CardPool.Count == 0 && i != 0)
{
await e.Channel.SendMessage("No more cards in a deck.");
break;
}
var currentCard = cards.DrawACard();
cardObjects.Add(currentCard);
images.Add(Properties.Resources.ResourceManager.GetObject(currentCard.Name) as Image);
}
var bitmap = images.Merge();
await e.Channel.SendFile(images.Count + " cards.jpg", bitmap.ToStream());
if (cardObjects.Count == 5)
{
await e.Channel.SendMessage(Cards.GetHandValue(cardObjects));
}
}
catch (Exception ex)
{
Console.WriteLine("Error drawing (a) card(s) " + ex.ToString());
}
};
}
}

View File

@ -0,0 +1,57 @@
using Discord.Commands;
using NadekoBot.Commands;
using NadekoBot.Extensions;
using System;
using System.Drawing;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Gambling
{
internal class FlipCoinCommand : DiscordCommand
{
public FlipCoinCommand(DiscordModule module) : base(module) { }
internal override void Init(CommandGroupBuilder cgb)
{
cgb.CreateCommand(Module.Prefix + "flip")
.Description("Flips coin(s) - heads or tails, and shows an image.\n**Usage**: `$flip` or `$flip 3`")
.Parameter("count", ParameterType.Optional)
.Do(FlipCoinFunc());
}
private readonly Random rng = new Random();
public Func<CommandEventArgs, Task> FlipCoinFunc() => async e =>
{
if (e.GetArg("count") == "")
{
if (rng.Next(0, 2) == 1)
await e.Channel.SendFile("heads.png", Properties.Resources.heads.ToStream(System.Drawing.Imaging.ImageFormat.Png));
else
await e.Channel.SendFile("tails.png", Properties.Resources.tails.ToStream(System.Drawing.Imaging.ImageFormat.Png));
}
else {
int result;
if (int.TryParse(e.GetArg("count"), out result))
{
if (result > 10)
result = 10;
var imgs = new Image[result];
for (var i = 0; i < result; i++)
{
imgs[i] = rng.Next(0, 2) == 0 ?
Properties.Resources.tails :
Properties.Resources.heads;
}
await e.Channel.SendFile($"{result} coins.png", imgs.Merge().ToStream(System.Drawing.Imaging.ImageFormat.Png));
return;
}
await e.Channel.SendMessage("Invalid number");
}
};
}
}

View File

@ -0,0 +1,71 @@
using Discord;
using Discord.Commands;
using Discord.Modules;
using NadekoBot.Extensions;
using System.Linq;
namespace NadekoBot.Modules.Gambling
{
internal class GamblingModule : DiscordModule
{
public GamblingModule()
{
commands.Add(new DrawCommand(this));
commands.Add(new FlipCoinCommand(this));
commands.Add(new DiceRollCommand(this));
}
public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Gambling;
public override void Install(ModuleManager manager)
{
manager.CreateCommands("", cgb =>
{
cgb.AddCheck(Classes.Permissions.PermissionChecker.Instance);
commands.ForEach(com => com.Init(cgb));
cgb.CreateCommand(Prefix + "raffle")
.Description("Prints a name and ID of a random user from the online list from the (optional) role.")
.Parameter("role", ParameterType.Optional)
.Do(RaffleTask());
cgb.CreateCommand(Prefix + "$$")
.Description("Check how many NadekoFlowers you have.")
.Do(NadekoFlowerCheckTask());
});
}
private static System.Func<CommandEventArgs, System.Threading.Tasks.Task> NadekoFlowerCheckTask()
{
return async e =>
{
var pts = Classes.DbHandler.Instance.GetStateByUserId((long)e.User.Id)?.Value ?? 0;
var str = $"`You have {pts} NadekoFlowers".SnPl((int)pts) + "`\n";
for (var i = 0; i < pts; i++)
{
str += "🌸";
}
await e.Channel.SendMessage(str);
};
}
private static System.Func<CommandEventArgs, System.Threading.Tasks.Task> RaffleTask()
{
return async e =>
{
var arg = string.IsNullOrWhiteSpace(e.GetArg("role")) ? "@everyone" : e.GetArg("role");
var role = e.Server.FindRoles(arg).FirstOrDefault();
if (role == null)
{
await e.Channel.SendMessage("💢 Role not found.");
return;
}
var members = role.Members.Where(u => u.Status == Discord.UserStatus.Online); // only online
var membersArray = members as User[] ?? members.ToArray();
var usr = membersArray[new System.Random().Next(0, membersArray.Length)];
await e.Channel.SendMessage($"**Raffled user:** {usr.Name} (id: {usr.Id})");
};
}
}
}

View File

@ -0,0 +1,232 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace NadekoBot.Modules.Gambling.Helpers
{
public class Cards
{
private static readonly Dictionary<int, string> cardNames = new Dictionary<int, string>() {
{ 1, "Ace" },
{ 2, "Two" },
{ 3, "Three" },
{ 4, "Four" },
{ 5, "Five" },
{ 6, "Six" },
{ 7, "Seven" },
{ 8, "Eight" },
{ 9, "Nine" },
{ 10, "Ten" },
{ 11, "Jack" },
{ 12, "Queen" },
{ 13, "King" }
};
private static Dictionary<string, Func<List<Card>, bool>> handValues;
public enum CardSuit
{
Spades = 1,
Hearts = 2,
Diamonds = 3,
Clubs = 4
}
public class Card : IComparable
{
public CardSuit Suit { get; }
public int Number { get; }
public string Name
{
get
{
var str = "";
if (Number <= 10 && Number > 1)
{
str += "_" + Number;
}
else {
str += GetName().ToLower();
}
return str + "_of_" + Suit.ToString().ToLower();
}
}
public Card(CardSuit s, int cardNum)
{
this.Suit = s;
this.Number = cardNum;
}
public string GetName() => cardNames[Number];
public override string ToString() => cardNames[Number] + " Of " + Suit;
public int CompareTo(object obj)
{
if (!(obj is Card)) return 0;
var c = (Card)obj;
return this.Number - c.Number;
}
}
private List<Card> cardPool;
public List<Card> CardPool
{
get { return cardPool; }
set { cardPool = value; }
}
/// <summary>
/// Creates a new instance of the BlackJackGame, this allows you to create multiple games running at one time.
/// </summary>
public Cards()
{
cardPool = new List<Card>(52);
RefillPool();
InitHandValues();
}
/// <summary>
/// Restart the game of blackjack. It will only refill the pool for now. Probably wont be used, unless you want to have only 1 bjg running at one time,
/// then you will restart the same game every time.
/// </summary>
public void Restart() => RefillPool();
/// <summary>
/// Removes all cards from the pool and refills the pool with all of the possible cards. NOTE: I think this is too expensive.
/// We should probably make it so it copies another premade list with all the cards, or something.
/// </summary>
private void RefillPool()
{
cardPool.Clear();
//foreach suit
for (var j = 1; j < 14; j++)
{
// and number
for (var i = 1; i < 5; i++)
{
//generate a card of that suit and number and add it to the pool
// the pool will go from ace of spades,hears,diamonds,clubs all the way to the king of spades. hearts, ...
cardPool.Add(new Card((CardSuit)i, j));
}
}
}
private Random r = new Random();
/// <summary>
/// Take a card from the pool, you either take it from the top if the deck is shuffled, or from a random place if the deck is in the default order.
/// </summary>
/// <returns>A card from the pool</returns>
public Card DrawACard()
{
if (CardPool.Count == 0)
Restart();
//you can either do this if your deck is not shuffled
var num = r.Next(0, cardPool.Count);
var c = cardPool[num];
cardPool.RemoveAt(num);
return c;
// if you want to shuffle when you fill, then take the first one
/*
Card c = cardPool[0];
cardPool.RemoveAt(0);
return c;
*/
}
/// <summary>
/// Shuffles the deck. Use this if you want to take cards from the top of the deck, instead of randomly. See DrawACard method.
/// </summary>
private void Shuffle()
{
if (cardPool.Count <= 1) return;
var orderedPool = cardPool.OrderBy(x => r.Next());
cardPool = cardPool as List<Card> ?? orderedPool.ToList();
}
public override string ToString() => string.Join("", cardPool.Select(c => c.ToString())) + Environment.NewLine;
private static void InitHandValues()
{
Func<List<Card>, bool> hasPair =
cards => cards.GroupBy(card => card.Number)
.Count(group => group.Count() == 2) == 1;
Func<List<Card>, bool> isPair =
cards => cards.GroupBy(card => card.Number)
.Count(group => group.Count() == 3) == 0
&& hasPair(cards);
Func<List<Card>, bool> isTwoPair =
cards => cards.GroupBy(card => card.Number)
.Count(group => group.Count() == 2) == 2;
Func<List<Card>, bool> isStraight =
cards =>
{
if (cards.GroupBy(card => card.Number).Count() != cards.Count())
return false;
var toReturn = (cards.Max(card => (int)card.Number)
- cards.Min(card => (int)card.Number) == 4);
if (toReturn || cards.All(c => c.Number != 1)) return toReturn;
var newCards = cards.Select(c => c.Number == 1 ? new Card(c.Suit, 14) : c);
return (newCards.Max(card => (int)card.Number)
- newCards.Min(card => (int)card.Number) == 4);
};
Func<List<Card>, bool> hasThreeOfKind =
cards => cards.GroupBy(card => card.Number)
.Any(group => group.Count() == 3);
Func<List<Card>, bool> isThreeOfKind =
cards => hasThreeOfKind(cards) && !hasPair(cards);
Func<List<Card>, bool> isFlush =
cards => cards.GroupBy(card => card.Suit).Count() == 1;
Func<List<Card>, bool> isFourOfKind =
cards => cards.GroupBy(card => card.Number)
.Any(group => group.Count() == 4);
Func<List<Card>, bool> isFullHouse =
cards => hasPair(cards) && hasThreeOfKind(cards);
Func<List<Card>, bool> hasStraightFlush =
cards => isFlush(cards) && isStraight(cards);
Func<List<Card>, bool> isRoyalFlush =
cards => cards.Min(card => card.Number) == 1 &&
cards.Max(card => card.Number) == 13
&& hasStraightFlush(cards);
Func<List<Card>, bool> isStraightFlush =
cards => hasStraightFlush(cards) && !isRoyalFlush(cards);
handValues = new Dictionary<string, Func<List<Card>, bool>>
{
{ "Royal Flush", isRoyalFlush },
{ "Straight Flush", isStraightFlush },
{ "Four Of A Kind", isFourOfKind },
{ "Full House", isFullHouse },
{ "Flush", isFlush },
{ "Straight", isStraight },
{ "Three Of A Kind", isThreeOfKind },
{ "Two Pairs", isTwoPair },
{ "A Pair", isPair }
};
}
public static string GetHandValue(List<Card> cards)
{
if (handValues == null)
InitHandValues();
foreach (var kvp in handValues.Where(x => x.Value(cards)))
{
return kvp.Key;
}
return "High card " + cards.Max().GetName();
}
}
}