diff --git a/CardDeck/Deck/Card/CardData.cs b/CardDeck/Deck/Card/CardData.cs index 1c1da24..3667092 100644 --- a/CardDeck/Deck/Card/CardData.cs +++ b/CardDeck/Deck/Card/CardData.cs @@ -1,13 +1,11 @@ -using Deck.Extensions; - -namespace Deck.Deck.Card; +namespace Deck.Deck.Card; public readonly struct CardData { - public CardData(CardSubType _subType) + public CardData(CardSubType _subType, Func _scoreMapping) { SubType = _subType; - Score = SubType.MapToScore(); + Score = _scoreMapping(_subType); } /// diff --git a/CardDeck/Deck/Card/CardDescription.cs b/CardDeck/Deck/Card/CardDescription.cs index 64b7a6a..d2d6d25 100644 --- a/CardDeck/Deck/Card/CardDescription.cs +++ b/CardDeck/Deck/Card/CardDescription.cs @@ -1,4 +1,5 @@ -using Deck.Deck.Card.Colour; +using System.Collections.Frozen; +using Deck.Deck.Card.Colour; namespace Deck.Deck.Card; @@ -9,13 +10,13 @@ public CardDescription(CardType _type, IColour _colour, IColourSet _colourSet, D Type = _type; Colour = _colour; ColourSet = _colourSet; - CardCountMapping = _cardCountMapping.AsReadOnly(); + CardCountMapping = _cardCountMapping.ToFrozenDictionary(); TotalCount = CardCountMapping.Values.Sum(x => x); } public CardType Type { get; } public IColour Colour { get; set; } public IColourSet ColourSet { get; } - public IReadOnlyDictionary CardCountMapping { get; } + public FrozenDictionary CardCountMapping { get; } public int TotalCount { get; } } diff --git a/CardDeck/Deck/Card/CardSubType.cs b/CardDeck/Deck/Card/CardSubType.cs index 35ac54e..551a121 100644 --- a/CardDeck/Deck/Card/CardSubType.cs +++ b/CardDeck/Deck/Card/CardSubType.cs @@ -1,25 +1,42 @@ -namespace Deck.Deck.Card; +using System.Diagnostics.CodeAnalysis; -public enum CardSubType : byte +namespace Deck.Deck.Card; + +public readonly struct CardSubType { - // Numeric - Zero, - One, - Two, - Three, - Four, - Five, - Six, - Seven, - Eight, - Nine, + public CardSubType(string _name) + { + SubTypeName = _name; + hash = SubTypeName.GetHashCode(); + } + + public string SubTypeName { get; } + + private readonly int hash; - // Special - PlusTwo, - Skip, - Reverse, + public override int GetHashCode() + { + return hash; + } - // Wild (Special - Wild, - WildPlusFour, + public static bool operator ==(CardSubType _this, CardSubType _other) + { + return _this.SubTypeName == _other.SubTypeName; + } + public static bool operator !=(CardSubType _this, CardSubType _other) + { + return !(_this == _other); + } + public override bool Equals([NotNullWhen(true)] object? obj) + { + if(obj == null) + { + return false; + } + if(obj is not CardSubType _subType) + { + return false; + } + return _subType == this; + } } diff --git a/CardDeck/Deck/Card/CardType.cs b/CardDeck/Deck/Card/CardType.cs index b947f92..adb0e24 100644 --- a/CardDeck/Deck/Card/CardType.cs +++ b/CardDeck/Deck/Card/CardType.cs @@ -1,7 +1,55 @@ -namespace Deck.Deck.Card; +using System.Collections.Frozen; +using System.Diagnostics.CodeAnalysis; -public enum CardType : byte +namespace Deck.Deck.Card; + +public readonly struct CardType { - Numeric, - Special + public CardType(string _name, FrozenSet _typeMembers) + { + TypeName = _name; + TypeMembers = _typeMembers; + var _hash = new HashCode(); + _hash.Add(TypeName); + + for(int i = 0; i < TypeMembers.Count; i++) + { + _hash.Add(TypeMembers.ElementAt(i)); + } + + hash = _hash.ToHashCode(); + } + public CardType(string _name, HashSet _typeMembers) : this(_name, _typeMembers.ToFrozenSet()) { } + public CardType(string _name, CardSubType[] _typeMembers) : this(_name, _typeMembers.ToFrozenSet()) { } + + public string TypeName { get; } + public FrozenSet TypeMembers { get; } + + private readonly int hash; + + public override int GetHashCode() + { + return hash; + } + + public static bool operator ==(CardType _this, CardType _other) + { + return _this.hash == _other.hash; + } + public static bool operator !=(CardType _this, CardType _other) + { + return !(_this.hash == _other.hash); + } + public override bool Equals([NotNullWhen(true)] object? _obj) + { + if(_obj == null) + { + return false; + } + if(_obj is not CardType _cardType) + { + return false; + } + return _cardType == this; + } } \ No newline at end of file diff --git a/CardDeck/Deck/Card/GameCard.cs b/CardDeck/Deck/Card/GameCard.cs index dbfe907..7749932 100644 --- a/CardDeck/Deck/Card/GameCard.cs +++ b/CardDeck/Deck/Card/GameCard.cs @@ -7,21 +7,9 @@ public GameCard(CardDescription _description, CardData _data) Description = _description; Data = _data; - if(Description.Type is CardType.Numeric && - _data.SubType is not (CardSubType.Zero or CardSubType.One - or CardSubType.Two or CardSubType.Three - or CardSubType.Four or CardSubType.Five - or CardSubType.Six or CardSubType.Seven - or CardSubType.Eight or CardSubType.Nine)) + if(!_description.Type.TypeMembers.Contains(_data.SubType)) { - throw new ArgumentException("subType is not a valid numeric type"); - } - if(_description.Type is CardType.Special && - _data.SubType is not (CardSubType.Skip or CardSubType.Reverse - or CardSubType.PlusTwo or CardSubType.WildPlusFour - or CardSubType.Wild)) - { - throw new ArgumentException("subType is not a valid special type"); + throw new ArgumentException($"Member ({_data.SubType}) does not inherit from {_description.Type.TypeName}."); } hash = HashCode.Combine(Description.Colour, Data.SubType, Data.Score); diff --git a/CardDeck/Deck/CardDeckBuilder.cs b/CardDeck/Deck/CardDeckBuilder.cs index bb64352..92ef483 100644 --- a/CardDeck/Deck/CardDeckBuilder.cs +++ b/CardDeck/Deck/CardDeckBuilder.cs @@ -5,14 +5,17 @@ namespace Deck.Deck; public sealed class CardDeckBuilder { - private IRandomizer shuffleOptions = RandomizerFactory.Get(RandomizerType.DefualtRandom); - private DeckOptions options = DeckOptions.Default; - - public CardDeckBuilder WithCustomDeckOptions(DeckOptions _options) + public CardDeckBuilder(DeckOptions _options, Func _scoreMapping) { + scoreMapping = _scoreMapping; options = _options; - return this; } + + private readonly Func scoreMapping; + private readonly DeckOptions options; + + private IRandomizer shuffleOptions = RandomizerFactory.Get(RandomizerType.DefualtRandom); + public CardDeckBuilder WithCustomShuffleOptions(IRandomizer _randomizer) { shuffleOptions = _randomizer; @@ -31,48 +34,33 @@ private Span CreateCards() { var _cards = new GameCard[options.TotalCards].AsSpan(); - var _specialCardOptions = options.SpecialDeckOptions; - var _numericCardOptions = options.NumericDeckOptions; - - CreateSpecialCards(0, _specialCardOptions, _cards, out int _offset); - CreateNumericCards(_offset, _numericCardOptions, _cards, out _); + int _index = 0; + foreach(var _groupDescriptor in options.GroupOptions) + { + CreateCardsOfType(_cards, _index, _groupDescriptor, out _index); + } return _cards; } - - private static void CreateNumericCards(int _initialIndex, DeckDescription _deckOptions, - Span _source, out int _endIndex) - { - CreateCardOfType(_source, _initialIndex, _deckOptions, CardSubType.Zero, out _endIndex); - CreateCardOfType(_source, _endIndex, _deckOptions, CardSubType.One, out _endIndex); - CreateCardOfType(_source, _endIndex, _deckOptions, CardSubType.Two, out _endIndex); - CreateCardOfType(_source, _endIndex, _deckOptions, CardSubType.Three, out _endIndex); - CreateCardOfType(_source, _endIndex, _deckOptions, CardSubType.Four, out _endIndex); - CreateCardOfType(_source, _endIndex, _deckOptions, CardSubType.Five, out _endIndex); - CreateCardOfType(_source, _endIndex, _deckOptions, CardSubType.Six, out _endIndex); - CreateCardOfType(_source, _endIndex, _deckOptions, CardSubType.Seven, out _endIndex); - CreateCardOfType(_source, _endIndex, _deckOptions, CardSubType.Eight, out _endIndex); - CreateCardOfType(_source, _endIndex, _deckOptions, CardSubType.Nine, out _endIndex); - } - private static void CreateSpecialCards(int _initialIndex, DeckDescription _description, - Span _source, out int _endIndex) - { - CreateCardOfType(_source, _initialIndex, _description, CardSubType.PlusTwo, out _endIndex); - CreateCardOfType(_source, _endIndex, _description, CardSubType.Skip, out _endIndex); - CreateCardOfType(_source, _endIndex, _description, CardSubType.Reverse, out _endIndex); - CreateCardOfType(_source, _endIndex, _description, CardSubType.Wild, out _endIndex); - CreateCardOfType(_source, _endIndex, _description, CardSubType.WildPlusFour, out _endIndex); - } - private static void CreateCardOfType(Span _source, int _initialIndex, - DeckDescription _deckDescription, CardSubType _type, out int _endIndex) + private void CreateCardsOfType(Span _source, int _initialIndex, + CardGroupDescription _deckDescription, out int _endIndex) { _endIndex = _initialIndex; + + foreach(var _cardDescription in _deckDescription.CardDescriptions) { - CreateCardType(_source, _endIndex, _cardDescription, _type, out _endIndex); + var _types = _cardDescription.CardCountMapping + .Where(x => x.Value > 0) + .Select(x => x.Key); + + foreach(var _type in _types) + { + CreateCardType(_source, _endIndex, _cardDescription, _type, out _endIndex); + } } } - private static void CreateCardType(Span _source, int _offset, CardDescription _description, + private void CreateCardType(Span _source, int _offset, CardDescription _description, CardSubType _subType, out int _endIndex) { _endIndex = _offset; @@ -82,7 +70,7 @@ private static void CreateCardType(Span _source, int _offset, CardDesc for(int j = 0; j < _count; j++, _endIndex++) { _source[_endIndex] = - new GameCard(_description, new CardData(_subType)); + new GameCard(_description, new CardData(_subType, scoreMapping)); } } } \ No newline at end of file diff --git a/CardDeck/Deck/CardDescriptionBuilder.cs b/CardDeck/Deck/CardDescriptionBuilder.cs index 451246f..f93a9b7 100644 --- a/CardDeck/Deck/CardDescriptionBuilder.cs +++ b/CardDeck/Deck/CardDescriptionBuilder.cs @@ -1,174 +1,66 @@ using System.Runtime.CompilerServices; -using Deck.Deck.Card; using Deck.Deck.Card.Colour; +using Deck.Deck.Card; namespace Deck.Deck; public sealed class CardDescriptionBuilder { + private const byte DEFAULTCOUNT = 0; + public CardDescriptionBuilder(CardType _cardType, IColourSet _set, IColour _cardColour) { set = _set; type = _cardType; colour = _cardColour; - switch(_cardType) - { - case CardType.Numeric: - cardCountPerSubType = new() - { - // Numeric - { CardSubType.Zero, 0 }, - { CardSubType.One, 0 }, - { CardSubType.Two, 0 }, - { CardSubType.Three, 0 }, - { CardSubType.Four, 0 }, - { CardSubType.Five, 0 }, - { CardSubType.Six, 0 }, - { CardSubType.Seven, 0 }, - { CardSubType.Eight, 0 }, - { CardSubType.Nine, 0 }, - }; - break; - case CardType.Special: - cardCountPerSubType = new() - { - // Special - { CardSubType.PlusTwo, 0 }, - { CardSubType.Skip, 0 }, - { CardSubType.Reverse, 0 }, - - // Wild (Special) - { CardSubType.WildPlusFour, 0 }, - { CardSubType.Wild, 0 }, - }; - break; - default: - throw new ArgumentOutOfRangeException(nameof(_cardType)); - } + cardCountPerSubType = _cardType.TypeMembers + .ToDictionary(x => x, x => DEFAULTCOUNT); + + methodCallMap = new(_cardType.TypeMembers.Count + 1); } private readonly CardType type; private readonly IColourSet set; private readonly IColour colour; + private readonly HashSet methodCallMap; private readonly Dictionary cardCountPerSubType; - #region Numeric Cards - public CardDescriptionBuilder WithZero(byte _count) - { - ThrowIfSpecial(); - cardCountPerSubType[CardSubType.Zero] = _count; - return this; - } - public CardDescriptionBuilder WithOne(byte _count) - { - ThrowIfSpecial(); - cardCountPerSubType[CardSubType.One] = _count; - return this; - } - public CardDescriptionBuilder WithTwo(byte _count) - { - ThrowIfSpecial(); - cardCountPerSubType[CardSubType.Two] = _count; - return this; - } - public CardDescriptionBuilder WithThree(byte _count) - { - ThrowIfSpecial(); - cardCountPerSubType[CardSubType.Three] = _count; - return this; - } - public CardDescriptionBuilder WithFour(byte _count) - { - ThrowIfSpecial(); - cardCountPerSubType[CardSubType.Four] = _count; - return this; - } - public CardDescriptionBuilder WithFive(byte _count) - { - ThrowIfSpecial(); - cardCountPerSubType[CardSubType.Five] = _count; - return this; - } - public CardDescriptionBuilder WithSix(byte _count) - { - ThrowIfSpecial(); - cardCountPerSubType[CardSubType.Six] = _count; - return this; - } - public CardDescriptionBuilder WithSeven(byte _count) + public CardDescriptionBuilder WithType(CardSubType _type, byte _count) { - ThrowIfSpecial(); - cardCountPerSubType[CardSubType.Seven] = _count; + ThrowIfNotTypeOrUsed(_type); + cardCountPerSubType[_type] = _count; return this; } - public CardDescriptionBuilder WithEight(byte _count) - { - ThrowIfSpecial(); - cardCountPerSubType[CardSubType.Eight] = _count; - return this; - } - public CardDescriptionBuilder WithNine(byte _count) - { - ThrowIfSpecial(); - cardCountPerSubType[CardSubType.Nine] = _count; - return this; - } - #endregion - #region Special Cards - public CardDescriptionBuilder WithPlusTwo(byte _count) - { - ThrowIfNumeric(); - cardCountPerSubType[CardSubType.PlusTwo] = _count; - return this; - } - public CardDescriptionBuilder WithReverse(byte _count) - { - ThrowIfNumeric(); - cardCountPerSubType[CardSubType.Reverse] = _count; - return this; - } - public CardDescriptionBuilder WithSkip(byte _count) - { - ThrowIfNumeric(); - cardCountPerSubType[CardSubType.Skip] = _count; - return this; - } - public CardDescriptionBuilder WithWild(byte _count) - { - ThrowIfNumeric(); - cardCountPerSubType[CardSubType.Wild] = _count; - return this; - } - public CardDescriptionBuilder WithWildPlusFour(byte _count) - { - ThrowIfNumeric(); - cardCountPerSubType[CardSubType.WildPlusFour] = _count; - return this; - } - #endregion public CardDescription Build() { return new CardDescription(type, colour, set, cardCountPerSubType); } + #region ThrowIf X + private void ThrowIfNotTypeOrUsed(CardSubType _type) + { + ThrowIfAlreadyUsed(_type); + ThrowIfNotType(_type); + } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ThrowIfNumeric() + private void ThrowIfNotType(CardSubType _type) { - if(type != CardType.Special) + if(!type.TypeMembers.Contains(_type)) { - throw new InvalidOperationException($"Invalid operation. Cannot use Numeric Card methods if " + - $"current state is {CardType.Special}"); + throw new InvalidOperationException( + $"Invalid operation. Cannot use CardSubType '{_type.SubTypeName}' if " + + $"current CardType is {type.TypeName}"); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ThrowIfSpecial() + private void ThrowIfAlreadyUsed(CardSubType _type) { - if(type != CardType.Numeric) + if(!methodCallMap.Add(_type)) { - throw new InvalidOperationException($"Invalid operation. Cannot use Special Card methods if " + - $"current state is {CardType.Numeric}"); + throw new Exception($"Cannot use '{_type.SubTypeName}' again."); } } + #endregion } \ No newline at end of file diff --git a/CardDeck/Deck/DeckDescription.cs b/CardDeck/Deck/CardGroupDescription.cs similarity index 52% rename from CardDeck/Deck/DeckDescription.cs rename to CardDeck/Deck/CardGroupDescription.cs index 651000c..502344c 100644 --- a/CardDeck/Deck/DeckDescription.cs +++ b/CardDeck/Deck/CardGroupDescription.cs @@ -2,19 +2,17 @@ namespace Deck.Deck; -public readonly struct DeckDescription +public readonly struct CardGroupDescription { - public DeckDescription(IEnumerable _descriptions) + public CardGroupDescription(string _groupName, IEnumerable _descriptions) { - foreach(var _option in _descriptions) - { - TotalCards += _option.TotalCount; - } - + GroupName = _groupName; CardDescriptions = _descriptions; DescriptionsLength = CardDescriptions.Count(); + TotalCards = _descriptions.Sum(x => x.TotalCount); } + public string GroupName { get; } public int DescriptionsLength { get; } public int TotalCards { get; } public IEnumerable CardDescriptions { get; } diff --git a/CardDeck/Deck/DeckFactory.cs b/CardDeck/Deck/DeckFactory.cs deleted file mode 100644 index ba6710f..0000000 --- a/CardDeck/Deck/DeckFactory.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Deck.Deck.Card.Colour; -using Deck.Deck.Card; - -namespace Deck.Deck; - -public static class DeckFactory -{ - public static DeckDescription GetDefaultSpecialDescription() - { - var _descriptions = new CardDescription[5]; - - ReadOnlySpan _colours = IColourSet.Default.GetColours().ToArray(); - - for(int i = 0; i < _colours.Length - 1; i++) - { - var _cardDescriptionBuilder = - new CardDescriptionBuilder(CardType.Special, IColourSet.Default, _colours[i]); - _descriptions[i] = _cardDescriptionBuilder - .WithPlusTwo(2).WithReverse(2) - .WithSkip(2).Build(); - } - - var _wildCardsBuilder = - new CardDescriptionBuilder(CardType.Special, IColourSet.Default, _colours[^1]); - _descriptions[4] = _wildCardsBuilder - .WithWild(4).WithWildPlusFour(4) - .Build(); - - return new DeckDescription(_descriptions); - } - public static DeckDescription GetDefaultNumericDescription() - { - ReadOnlySpan _colours = IColourSet.Default.GetColours().ToArray(); - - var _descriptions = new CardDescription[_colours.Length - 1]; - - for(int i = 0; i < _descriptions.Length; i++) - { - var _cardDescriptionBuilder = - new CardDescriptionBuilder(CardType.Numeric, IColourSet.Default, _colours[i]); - _descriptions[i] = _cardDescriptionBuilder - .WithZero(1).WithOne(2) - .WithTwo(2).WithThree(2) - .WithFour(2).WithFive(2) - .WithSix(2).WithSeven(2) - .WithEight(2).WithNine(2) - .Build(); - } - - return new DeckDescription(_descriptions); - } -} diff --git a/CardDeck/Deck/DeckOptions.cs b/CardDeck/Deck/DeckOptions.cs index 9e1e321..6924098 100644 --- a/CardDeck/Deck/DeckOptions.cs +++ b/CardDeck/Deck/DeckOptions.cs @@ -2,34 +2,17 @@ public readonly record struct DeckOptions { - public static readonly DeckOptions Default = new(); - - /// - /// Creates a default instance - /// - public DeckOptions() - { - SpecialDeckOptions = DeckFactory.GetDefaultSpecialDescription(); - NumericDeckOptions = DeckFactory.GetDefaultNumericDescription(); - - TotalCards = SpecialDeckOptions.TotalCards + NumericDeckOptions.TotalCards; - - HasSpecialCards = true; - HasNumericCards = true; - } - public DeckOptions( - DeckDescription _specialDeckOptions, - DeckDescription _numericDeckOptions) + CardGroupDescription[] _deckOptions) { - SpecialDeckOptions = _specialDeckOptions; - NumericDeckOptions = _numericDeckOptions; - TotalCards = _specialDeckOptions.TotalCards; + GroupOptions = _deckOptions; + TotalCards = _deckOptions.Sum(x => x.TotalCards); - HasSpecialCards = _specialDeckOptions.TotalCards != 0; - HasNumericCards = _numericDeckOptions.TotalCards != 0; + HasCardTypeMap = _deckOptions + .Select(x => x.GroupName) + .ToHashSet(); - if(!HasSpecialCards && !HasNumericCards) + if(_deckOptions.Length == 0) { throw new Exception("Cannot create a deck with no card types"); } @@ -39,10 +22,13 @@ public DeckOptions( } } - public bool HasSpecialCards { get; } - public bool HasNumericCards { get; } - public int TotalCards { get; } - public DeckDescription SpecialDeckOptions { get; } - public DeckDescription NumericDeckOptions { get; } + public CardGroupDescription[] GroupOptions { get; } + + private readonly HashSet HasCardTypeMap; + + public readonly bool HasCardOfType(CardGroupDescription _descriptor) => + HasCardOfType(_descriptor.GroupName); + public readonly bool HasCardOfType(string _name) => + HasCardTypeMap.Contains(_name); } \ No newline at end of file diff --git a/CardDeck/Extensions/CardExtensions.cs b/CardDeck/Extensions/CardExtensions.cs deleted file mode 100644 index 846738f..0000000 --- a/CardDeck/Extensions/CardExtensions.cs +++ /dev/null @@ -1,68 +0,0 @@ -using Deck.Deck; -using Deck.Deck.Card; -using System.Runtime.CompilerServices; - -namespace Deck.Extensions; - -public static class CardExtensions -{ - private static readonly IReadOnlyDictionary scoreMappingTable = new Dictionary() - { - // Numeric - { CardSubType.Zero, 0 }, - { CardSubType.One, 1 }, - { CardSubType.Two, 2 }, - { CardSubType.Three, 3 }, - { CardSubType.Four, 4 }, - { CardSubType.Five, 5 }, - { CardSubType.Six, 6 }, - { CardSubType.Seven, 7 }, - { CardSubType.Eight, 8 }, - { CardSubType.Nine, 9 }, - - // Special - { CardSubType.PlusTwo, 20 }, - { CardSubType.Reverse, 20 }, - { CardSubType.Skip, 20 }, - - // Wild (Special) - { CardSubType.WildPlusFour, 50 }, - { CardSubType.Wild, 50 }, - }; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte MapToScore(this CardSubType _type) => scoreMappingTable[_type]; - public static bool CanPlay(this GameCard _card, GameCard _other, bool _strictPlay = true) - { - var _discardDescription = _other.Description; - var _desiredDescription = _card.Description; - var _discardData = _other.Data; - var _desiredData = _card.Data; - - if(_card.InUse && _strictPlay) - { - return false; - } - - // Same Colour - return _discardDescription.Colour == _desiredDescription.Colour - // Same Sub Type - || _discardData.SubType == _desiredData.SubType - // Wild Card - || _card.Data.SubType is CardSubType.Wild or CardSubType.WildPlusFour; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool CanPlay(this GameCard _card, CardDeck _deck, bool _strictPlay = true) - { - return _card.CanPlay(_deck.Peek(), _strictPlay); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsNumercic(this CardSubType _subType) - { - return _subType is CardSubType.Zero or CardSubType.One - or CardSubType.Two or CardSubType.Three - or CardSubType.Four or CardSubType.Five - or CardSubType.Six or CardSubType.Seven - or CardSubType.Eight or CardSubType.Nine; - } -} diff --git a/GameOne/Extensions/CardComparison.cs b/GameOne/Extensions/CardComparison.cs new file mode 100644 index 0000000..c270b73 --- /dev/null +++ b/GameOne/Extensions/CardComparison.cs @@ -0,0 +1,12 @@ +using Deck.Extensions; +using Deck.Deck.Card; + +namespace GameOne.Extensions; + +internal sealed class CardComparison : IComparer +{ + public int Compare(CardSubType _x, CardSubType _y) + { + return Math.Clamp(_x.MapToScore() - _y.MapToScore(), -1, 1); + } +} diff --git a/GameOne/Extensions/CardExtensions.cs b/GameOne/Extensions/CardExtensions.cs new file mode 100644 index 0000000..c835eee --- /dev/null +++ b/GameOne/Extensions/CardExtensions.cs @@ -0,0 +1,115 @@ +using System.Runtime.CompilerServices; +using System.Collections.Frozen; +using Deck.Deck.Card; +using Deck.Deck; +using GameOne; + +namespace Deck.Extensions; + +internal static class CardExtensions +{ + private static readonly FrozenDictionary scoreMappingTable = new Dictionary() + { + // Numeric + { Globals.ZeroSubType, 0 }, + { Globals.OneSubType, 1 }, + { Globals.TwoSubType, 2 }, + { Globals.ThreeSubType, 3 }, + { Globals.FourSubType, 4 }, + { Globals.FiveSubType, 5 }, + { Globals.SixSubType, 6 }, + { Globals.SevenSubType, 7 }, + { Globals.EightSubType, 8 }, + { Globals.NineSubType, 9 }, + + // Special + { Globals.PlusTwoSubType, 20 }, + { Globals.SkipSubType, 20 }, + { Globals.ReverseSubType, 20 }, + + // Wild (Special) + { Globals.WildPlusFourSubType, 50 }, + { Globals.WildSubType, 50 }, + }.ToFrozenDictionary(); + private static readonly FrozenDictionary nameMappingTable = new Dictionary() + { + // Numeric + { Globals.ZeroSubType, "Zero" }, + { Globals.OneSubType, "One" }, + { Globals.TwoSubType, "Two" }, + { Globals.ThreeSubType, "Three" }, + { Globals.FourSubType, "Four" }, + { Globals.FiveSubType, "Five" }, + { Globals.SixSubType, "Six" }, + { Globals.SevenSubType, "Seven" }, + { Globals.EightSubType, "Eight" }, + { Globals.NineSubType, "Nine" }, + + // Special + { Globals.PlusTwoSubType, "Plus Two" }, + { Globals.SkipSubType, "Skip" }, + { Globals.ReverseSubType, "Reverse" }, + + // Wild (Special) + { Globals.WildPlusFourSubType, "Wild Plus Four" }, + { Globals.WildSubType, "Wild" }, + }.ToFrozenDictionary(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte MapToScore(this CardSubType _subType) + { + if(!scoreMappingTable.TryGetValue(_subType, out var _score)) + { + throw new ArgumentException($"Provided type does not exist in mapping. CardSubType: {_subType.SubTypeName}"); + } + return _score; + } + public static bool CanPlay(this GameCard _card, GameCard _other, bool _strictPlay = true) + { + var _discardDescription = _other.Description; + var _desiredDescription = _card.Description; + var _discardData = _other.Data; + var _desiredData = _card.Data; + + if(_card.InUse && _strictPlay) + { + return false; + } + + // Same Colour + return _discardDescription.Colour == _desiredDescription.Colour + // Same Sub Type + || _discardData.SubType == _desiredData.SubType + // Wild Card + || _card.Data.SubType == Globals.WildSubType + || _card.Data.SubType == Globals.WildPlusFourSubType; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CanPlay(this GameCard _card, CardDeck _deck, bool _strictPlay = true) + { + return _card.CanPlay(_deck.Peek(), _strictPlay); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNumercic(this CardSubType _subType) + { + return _subType == Globals.ZeroSubType + || _subType == Globals.OneSubType + || _subType == Globals.TwoSubType + || _subType == Globals.ThreeSubType + || _subType == Globals.FourSubType + || _subType == Globals.FiveSubType + || _subType == Globals.SixSubType + || _subType == Globals.SevenSubType + || _subType == Globals.EightSubType + || _subType == Globals.NineSubType; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ReadableNameMapping(this CardSubType _subType) + { + if(!nameMappingTable.TryGetValue(_subType, out var _name)) + { + throw new ArgumentException($"Provided type does not exist in mapping. CardSubType: {_subType.SubTypeName}"); + } + return _name; + } +} \ No newline at end of file diff --git a/GameOne/Game/DeckFactory.cs b/GameOne/Game/DeckFactory.cs new file mode 100644 index 0000000..8ed810f --- /dev/null +++ b/GameOne/Game/DeckFactory.cs @@ -0,0 +1,60 @@ +using Deck.Deck.Card.Colour; +using Deck.Deck.Card; +using GameOne; + +namespace Deck.Deck; + +public static class DeckFactory +{ + public static CardGroupDescription GetDefaultSpecialDescription() + { + var _descriptions = new CardDescription[5]; + + ReadOnlySpan _colours = IColourSet.Default.GetColours().ToArray(); + + for(int i = 0; i < _colours.Length - 1; i++) + { + var _cardDescriptionBuilder = + new CardDescriptionBuilder(Globals.SpecialType, IColourSet.Default, _colours[i]); + _descriptions[i] = _cardDescriptionBuilder + .WithType(Globals.PlusTwoSubType, 2).WithType(Globals.ReverseSubType, 2) + .WithType(Globals.SkipSubType, 2).Build(); + } + + var _wildCardsBuilder = + new CardDescriptionBuilder(Globals.SpecialType, IColourSet.Default, _colours[^1]); + _descriptions[4] = _wildCardsBuilder + .WithType(Globals.WildSubType, 4).WithType(Globals.WildPlusFourSubType, 4) + .Build(); + + return new CardGroupDescription("Special", _descriptions); + } + public static CardGroupDescription GetDefaultNumericDescription() + { + ReadOnlySpan _colours = IColourSet.Default.GetColours().ToArray(); + + var _descriptions = new CardDescription[_colours.Length - 1]; + + for(int i = 0; i < _descriptions.Length; i++) + { + var _cardDescriptionBuilder = + new CardDescriptionBuilder(Globals.NumericType, IColourSet.Default, _colours[i]); + _cardDescriptionBuilder + .WithType(Globals.ZeroSubType, 1); + + foreach(var _type in Globals.NumericType.TypeMembers) + { + if(_type == Globals.ZeroSubType) + { + continue; + } + + _cardDescriptionBuilder.WithType(_type, 2); + } + + _descriptions[i] = _cardDescriptionBuilder.Build(); + } + + return new CardGroupDescription("Numeric", _descriptions); + } +} diff --git a/GameOne/Game/GameManager.cs b/GameOne/Game/GameManager.cs index 89d2681..dea577f 100644 --- a/GameOne/Game/GameManager.cs +++ b/GameOne/Game/GameManager.cs @@ -2,6 +2,7 @@ using GameOne._Player; using Deck.Deck; using Renderer; +using Deck.Extensions; namespace GameOne.Game; @@ -14,9 +15,10 @@ internal sealed class GameManager public GameManager() { - var _deck = new CardDeckBuilder() + var _options = new DeckOptions([DeckFactory.GetDefaultNumericDescription(), DeckFactory.GetDefaultSpecialDescription()]); + + var _deck = new CardDeckBuilder(_options, CardExtensions.MapToScore) .WithCustomShuffleOptions(RandomizerFactory.Get(RandomizerType.KnuthFisher)) - .WithCustomDeckOptions(DeckOptions.Default) .Build(); manager = new(_deck, PLAYERS, STARTINGCARDS); diff --git a/GameOne/Game/RoundManager.cs b/GameOne/Game/RoundManager.cs index fe956ac..4cb48e9 100644 --- a/GameOne/Game/RoundManager.cs +++ b/GameOne/Game/RoundManager.cs @@ -28,7 +28,8 @@ public RoundManager(CardDeck _deck, int _playerCount, int _startingCards) DiscardPile.Add(_card); - while(_card.Description.Type is CardType.Special) + // Make sure first playing card is NUMERICTYPE + while(_card.Description.Type == Globals.SpecialType) { if(!PickupDeck.TryNextFree(out _card)) { @@ -90,7 +91,7 @@ public GameCard GetTopCard() } public void SkipPlayer() { - if(PeekDiscardPileTopCard().Data.SubType is not CardSubType.PlusTwo) + if(PeekDiscardPileTopCard().Data.SubType != Globals.PlusTwoSubType) { return; } @@ -100,7 +101,7 @@ public void SkipPlayer() public void SetWildColour(IColour _colour) { var _card = DiscardPile.Peek(); - if(_card.Data.SubType is not CardSubType.Wild or CardSubType.WildPlusFour) + if(_card.Data.SubType != Globals.WildSubType || _card.Data.SubType != Globals.WildPlusFourSubType) { return; } @@ -191,32 +192,38 @@ public bool ExecuteCardBehaviour(GameCard _card) return false; } - switch(_subType) - { - case CardSubType.PlusTwo: - var _nextPlayer = PeekPlayer(1); - _nextPlayer.GiveCard(GetTopCard()); - SkipPlayer(); - return false; - case CardSubType.Skip: - SkipPlayer(); - return false; - case CardSubType.Reverse: - reverseOrder = !reverseOrder; - return false; - case CardSubType.Wild: - return true; - case CardSubType.WildPlusFour: - _nextPlayer = PeekPlayer(1); - _nextPlayer.GiveCard(GetTopCard()); - _nextPlayer.GiveCard(GetTopCard()); - _nextPlayer.GiveCard(GetTopCard()); - _nextPlayer.GiveCard(GetTopCard()); - SkipPlayer(); - return true; - default: - throw new NotSupportedException(nameof(_subType) + " is not a supported type"); + if(_subType == Globals.PlusTwoSubType) + { + var _nextPlayer = PeekPlayer(1); + _nextPlayer.GiveCard(GetTopCard()); + SkipPlayer(); + return false; + } + if(_subType == Globals.SkipSubType) + { + SkipPlayer(); + return false; + } + if(_subType == Globals.ReverseSubType) + { + reverseOrder = !reverseOrder; + return false; + } + if(_subType == Globals.WildSubType) + { + return true; + } + if(_subType == Globals.WildPlusFourSubType) + { + var _nextPlayer = PeekPlayer(1); + _nextPlayer.GiveCard(GetTopCard()); + _nextPlayer.GiveCard(GetTopCard()); + _nextPlayer.GiveCard(GetTopCard()); + _nextPlayer.GiveCard(GetTopCard()); + SkipPlayer(); + return true; } + throw new NotSupportedException(nameof(_subType) + " is not a supported type"); } private void Shuffle() { diff --git a/GameOne/GameOne.csproj b/GameOne/GameOne.csproj index 06ca5ee..2f65ae5 100644 --- a/GameOne/GameOne.csproj +++ b/GameOne/GameOne.csproj @@ -13,10 +13,6 @@ embedded - - - - diff --git a/GameOne/Globals.cs b/GameOne/Globals.cs new file mode 100644 index 0000000..278d61e --- /dev/null +++ b/GameOne/Globals.cs @@ -0,0 +1,61 @@ +using Deck.Deck.Card; + +namespace GameOne; + +// Do not like this approach +internal static class Globals +{ + static Globals() + { + // SubTypes + // Numeric + ZeroSubType = new("Z"); + OneSubType = new("O"); + TwoSubType = new("T"); + ThreeSubType = new("Th"); + FourSubType = new("F"); + FiveSubType = new("Fi"); + SixSubType = new("S"); + SevenSubType = new("Se"); + EightSubType = new("E"); + NineSubType = new("N"); + + // Special + PlusTwoSubType = new("P"); + ReverseSubType = new("R"); + SkipSubType = new("B"); + WildSubType = new("W"); + WildPlusFourSubType = new("Wp"); + + // Types + NumericType = new("Nu", (CardSubType[]) + [ZeroSubType, OneSubType, TwoSubType, ThreeSubType, + FourSubType, FiveSubType, SixSubType, SevenSubType, + EightSubType, NineSubType]); + SpecialType = new("Sp", (CardSubType[]) + [PlusTwoSubType, SkipSubType, ReverseSubType, WildSubType, WildPlusFourSubType]); + } + + // Types + public static readonly CardType NumericType; + public static readonly CardType SpecialType; + + // SubTypes + // Numeric + public static readonly CardSubType ZeroSubType; + public static readonly CardSubType OneSubType; + public static readonly CardSubType TwoSubType; + public static readonly CardSubType ThreeSubType; + public static readonly CardSubType FourSubType; + public static readonly CardSubType FiveSubType; + public static readonly CardSubType SixSubType; + public static readonly CardSubType SevenSubType; + public static readonly CardSubType EightSubType; + public static readonly CardSubType NineSubType; + //Special + public static readonly CardSubType PlusTwoSubType; + public static readonly CardSubType ReverseSubType; + public static readonly CardSubType SkipSubType; + public static readonly CardSubType WildSubType; + public static readonly CardSubType WildPlusFourSubType; +} diff --git a/GameOne/Renderer/CardRender.cs b/GameOne/Renderer/CardRender.cs index 8c8e148..cf59695 100644 --- a/GameOne/Renderer/CardRender.cs +++ b/GameOne/Renderer/CardRender.cs @@ -1,5 +1,5 @@ -using FastEnumUtility; -using Deck.Deck.Card; +using Deck.Deck.Card; +using Deck.Extensions; using System.Text; namespace Renderer; @@ -29,8 +29,8 @@ public static string Render(GameCard _card) // Middle Row - Offset to previous row + Offset to start of middle row var _centreIndex = CARDLENGTH * cardHeight / 2 - CARDLENGTH + 1; - var _cardType = _card.Data.SubType; - var _cardTypeLength = _cardType.FastToString().Length; + var _cardType = _card.Data.SubType.ReadableNameMapping(); + var _cardTypeLength = _cardType.Length; var _beginIndex = (CARDLENGTH - _cardTypeLength) / 2; diff --git a/GameOne/_Player/GameAI.cs b/GameOne/_Player/GameAI.cs index 7c32502..0de6d92 100644 --- a/GameOne/_Player/GameAI.cs +++ b/GameOne/_Player/GameAI.cs @@ -1,6 +1,6 @@ -using Deck.Deck.Card; -using Deck.Deck.Card.Colour; +using Deck.Deck.Card.Colour; using Deck.Extensions; +using Deck.Deck.Card; using GameOne.Game; namespace GameOne._Player; @@ -12,9 +12,10 @@ public GameAI(string _name, IEnumerable _startingCards, RoundManager _ { Name = _name; manager.OnPlay += OnCardPlace; + } - private Dictionary> playerCardMap = new(3); + private readonly Dictionary> playerCardMap = new(3); public override string Name { get; } @@ -22,9 +23,6 @@ protected override void PlayImpl(Action _render) { var _topDiscardCard = manager.PeekDiscardPileTopCard(); - var _hasWild = cards.Any(x => x.Data.SubType is CardSubType.Wild); - var _hasWildFour = cards.Any(x => x.Data.SubType is CardSubType.WildPlusFour); - var _player = manager.PeekPlayer(1); // TODO Implement better behaviour @@ -39,18 +37,17 @@ protected override void PlayImpl(Action _render) return; } - var _cardColourMap = _cardHistory + var _cardColourAndCount = _cardHistory .GroupBy(x => x.Description.Colour) - .ToDictionary(x => x.Key, x => x.Count()); - - var _ordered = _cardColourMap.OrderBy(x => x.Value); + .Select(x => (x.Key, x.Count())) + .OrderBy(x => x.Item2); - if(Wild(_player, _ordered, x => x.Data.SubType is CardSubType.WildPlusFour)) + if(Wild(_player, _cardColourAndCount, x => x.Data.SubType == Globals.WildPlusFourSubType)) { return; } - if(Wild(_player, _ordered, x => x.Data.SubType is CardSubType.Wild)) + if(Wild(_player, _cardColourAndCount, x => x.Data.SubType == Globals.WildSubType)) { return; } @@ -59,7 +56,7 @@ protected override void PlayImpl(Action _render) return; } } - private bool Wild(Player _player, IOrderedEnumerable> _ordered, + private bool Wild(Player _player, IOrderedEnumerable<(IColour, int)> _ordered, Predicate _findPredicate) { if(_player.Cards.Length >= 4) @@ -75,14 +72,14 @@ private bool Wild(Player _player, IOrderedEnumerable> var _selfMostCommonColour = cards .GroupBy(x => x.Description.Colour) - .ToDictionary(x => x.Key, x => x.Count()); - var _selfOrdered = _selfMostCommonColour.OrderBy(x => x.Value); + .Select(x => (x.Key, x.Count())) + .OrderBy(x => x.Item2); int _index = 0; foreach(var _pair in _ordered) { - var (_colour, _count) = _ordered.ElementAt(_index); - var (_selfColour, _selfCount) = _selfOrdered.ElementAt(_index); + var (_colour, _count) = _pair; + var (_selfColour, _selfCount) = _selfMostCommonColour.ElementAt(_index); if(_selfColour == _colour) { diff --git a/GameOne/_Player/Player.cs b/GameOne/_Player/Player.cs index a2094bd..60d88d9 100644 --- a/GameOne/_Player/Player.cs +++ b/GameOne/_Player/Player.cs @@ -1,5 +1,6 @@ using Deck.Deck.Card; using Deck.Extensions; +using GameOne.Extensions; using GameOne.Game; namespace GameOne._Player; @@ -8,9 +9,8 @@ internal abstract class Player { public Player(IEnumerable _startingCards, RoundManager _manager) { - // Crude way to sort, in future implement IComparer in GameCard and use Sort() for that var _groups = _startingCards.GroupBy(x => x.Description.Colour); - var _whole = _groups.SelectMany(x => x.Select(x => x).OrderBy(x => x.Data.SubType)); + var _whole = _groups.SelectMany(x => x.Select(x => x).OrderBy(x => x.Data.SubType, new CardComparison())); cards = _whole.ToList(); currentCardHandle = cards.Count / 2;