#nullable disable namespace NadekoBot.Modules.Gambling.Common; public class QuadDeck : Deck { protected override void RefillPool() { CardPool = new(52 * 4); for (var j = 1; j < 14; j++) for (var i = 1; i < 5; i++) { CardPool.Add(new((CardSuit)i, j)); CardPool.Add(new((CardSuit)i, j)); CardPool.Add(new((CardSuit)i, j)); CardPool.Add(new((CardSuit)i, j)); } } } public class Deck { public enum CardSuit { Spades = 1, Hearts = 2, Diamonds = 3, Clubs = 4 } private static readonly Dictionary _cardNames = new() { { 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, bool>> _handValues; public List CardPool { get; set; } private readonly Random _r = new NadekoRandom(); static Deck() => InitHandValues(); /// /// Creates a new instance of the BlackJackGame, this allows you to create multiple games running at one time. /// public Deck() => RefillPool(); /// /// 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. /// public void Restart() => RefillPool(); /// /// 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. /// protected virtual void RefillPool() { CardPool = new(52); //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((CardSuit)i, j)); } /// /// 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. /// /// A card from the pool public Card Draw() { 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; */ } /// /// Shuffles the deck. Use this if you want to take cards from the top of the deck, instead of randomly. See DrawACard /// method. /// private void Shuffle() { if (CardPool.Count <= 1) return; var orderedPool = CardPool.Shuffle(); CardPool ??= orderedPool.ToList(); } public override string ToString() => string.Concat(CardPool.Select(c => c.ToString())) + Environment.NewLine; private static void InitHandValues() { bool HasPair(List cards) { return cards.GroupBy(card => card.Number).Count(group => group.Count() == 2) == 1; } bool IsPair(List cards) { return cards.GroupBy(card => card.Number).Count(group => group.Count() == 3) == 0 && HasPair(cards); } bool IsTwoPair(List cards) { return cards.GroupBy(card => card.Number).Count(group => group.Count() == 2) == 2; } bool IsStraight(List cards) { if (cards.GroupBy(card => card.Number).Count() != cards.Count()) return false; var toReturn = cards.Max(card => card.Number) - cards.Min(card => card.Number) == 4; if (toReturn || cards.All(c => c.Number != 1)) return toReturn; var newCards = cards.Select(c => c.Number == 1 ? new(c.Suit, 14) : c); return newCards.Max(card => card.Number) - newCards.Min(card => card.Number) == 4; } bool HasThreeOfKind(List cards) { return cards.GroupBy(card => card.Number).Any(group => group.Count() == 3); } bool IsThreeOfKind(List cards) { return HasThreeOfKind(cards) && !HasPair(cards); } bool IsFlush(List cards) { return cards.GroupBy(card => card.Suit).Count() == 1; } bool IsFourOfKind(List cards) { return cards.GroupBy(card => card.Number).Any(group => group.Count() == 4); } bool IsFullHouse(List cards) { return HasPair(cards) && HasThreeOfKind(cards); } bool HasStraightFlush(List cards) { return IsFlush(cards) && IsStraight(cards); } bool IsRoyalFlush(List cards) { return cards.Min(card => card.Number) == 1 && cards.Max(card => card.Number) == 13 && HasStraightFlush(cards); } bool IsStraightFlush(List cards) { return HasStraightFlush(cards) && !IsRoyalFlush(cards); } _handValues = new() { { "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 cards) { if (_handValues is null) InitHandValues(); foreach (var kvp in _handValues.Where(x => x.Value(cards))) return kvp.Key; return "High card " + (cards.FirstOrDefault(c => c.Number == 1)?.GetValueText() ?? cards.Max().GetValueText()); } public class Card : IComparable { private static readonly IReadOnlyDictionary _suitToSuitChar = new Dictionary { { CardSuit.Diamonds, "♦" }, { CardSuit.Clubs, "♣" }, { CardSuit.Spades, "♠" }, { CardSuit.Hearts, "♥" } }; private static readonly IReadOnlyDictionary _suitCharToSuit = new Dictionary { { "♦", CardSuit.Diamonds }, { "d", CardSuit.Diamonds }, { "♣", CardSuit.Clubs }, { "c", CardSuit.Clubs }, { "♠", CardSuit.Spades }, { "s", CardSuit.Spades }, { "♥", CardSuit.Hearts }, { "h", CardSuit.Hearts } }; private static readonly IReadOnlyDictionary _numberCharToNumber = new Dictionary { { 'a', 1 }, { '2', 2 }, { '3', 3 }, { '4', 4 }, { '5', 5 }, { '6', 6 }, { '7', 7 }, { '8', 8 }, { '9', 9 }, { 't', 10 }, { 'j', 11 }, { 'q', 12 }, { 'k', 13 } }; public CardSuit Suit { get; } public int Number { get; } public string FullName { get { var str = string.Empty; if (Number is <= 10 and > 1) str += "_" + Number; else str += GetValueText().ToLowerInvariant(); return str + "_of_" + Suit.ToString().ToLowerInvariant(); } } private readonly string[] _regIndicators = { "🇦", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:", ":keycap_ten:", "🇯", "🇶", "🇰" }; public Card(CardSuit s, int cardNum) { Suit = s; Number = cardNum; } public string GetValueText() => _cardNames[Number]; public override string ToString() => _cardNames[Number] + " Of " + Suit; public int CompareTo(object obj) { if (obj is not Card card) return 0; return Number - card.Number; } public static Card Parse(string input) { if (string.IsNullOrWhiteSpace(input)) throw new ArgumentNullException(nameof(input)); if (input.Length != 2 || !_numberCharToNumber.TryGetValue(input[0], out var n) || !_suitCharToSuit.TryGetValue(input[1].ToString(), out var s)) throw new ArgumentException("Invalid input", nameof(input)); return new(s, n); } public string GetEmojiString() { var str = string.Empty; str += _regIndicators[Number - 1]; str += _suitToSuitChar[Suit]; return str; } } }