µObject Poker: Scoring a Hand

I found I'm not a huge fan of the video. I don't tend to put a lot of time and effort into each post; it's an opportunity for me to share research and experimentation with the world; as well as give myself a searchable brain. :)
The video took WAY too much time. I figure if I'm going to be trying that again; I'll put it off and it'll have a negative impact to actually getting blogs done.
I'm doing the blogging (for ~year now) for me. I want to be doing it; and I need to be careful to not do things that will interfere with getting it to happen.
I switched to a weekly schedule, I'm not doing videos... It's a work in progress; I like what's happening - but ... No more video's for now.

Which is unfortunate for those interested in my PokerScoreing bit - because this is part two - and not a video. :)

Today I'm implementing the scoring of the different poker hands. With the variant that Suits have weight. Hopefully this doesn't make it more complicated for me. :)

My first test is

[TestMethod]
public void ShouldReturnAceOfSpadesHigherThanOtherNonHand()
{
    //Arrange
    IHand aceOfSpadeHigh = AceOfSpaceHigh;
    IHand aceOfClubHigh = AceOfClubsHigh;
    
    //Act
    bool winning = AceOfSpaceHigh.Beats(AceOfClubsHigh);

    //Assert
    winning.Should().BeTrue();
}

I want to make sure that an Ace of Spades high beats an Ace Of Clubs high.

Which... No. That's not what I want to do. How do I know it's an ace high? That's my first task. Creating the Scoring of A hand.

This leads to a simply check of

[TestMethod]
public void ShouldReturnAceOfSpadesHigh()
{
    //Arrange
    //Act
    IScore score = AceOfSpaceHigh.HandScore();

    //Assert
    score.Should().Be(Score.AceSpadeHigh);
}

I'll do a similar for each of the individual cards.
It's a long list of things; but it's what a score could be. Though... Could a hand every be a TWO high? ... I think the lowest single card high you could have would be a 6. To be lower; you'd have 5 cards; 1-4 +1... what's the remaining one? ... Anything but a 6 would give you a run or two of a kind.

Because it's gonna be kinda boring; I'm gonna add all the Score.{RANK}{SUIT}High statics and then the tests. Lot of duplication and copy and paste; but that's OK for tests. Each test will break for one, and only one, reason.
But I'll only go down to 6 for the 'high' scores.
Except Ace, or 1, is high high. So that makes the lowest single high card a 7.

OK. All the tests inplace to ensure that the high card is returned.
In addition I found that I needed to make sure that each rank was processed so that an Ace of Clubs was scored higher than a Two of Spades.
Tests were put in place for that as well.

At this point I have a giant method;

private IScore ScoreSingleCard()
{
    if (_cards.Contains(Deck.Spades.Ace)) return Score.AceSpadeHigh;
    if (_cards.Contains(Deck.Hearts.Ace)) return Score.AceHeartHigh;
    if (_cards.Contains(Deck.Diamonds.Ace)) return Score.AceDiamondHigh;
    if (_cards.Contains(Deck.Clubs.Ace)) return Score.AceClubHigh;

    if (_cards.Contains(Deck.Spades.King)) return Score.KingSpadeHigh;
    if (_cards.Contains(Deck.Hearts.King)) return Score.KingHeartHigh;
    if (_cards.Contains(Deck.Diamonds.King)) return Score.KingDiamondHigh;
    if (_cards.Contains(Deck.Clubs.King)) return Score.KingClubHigh;

    if (_cards.Contains(Deck.Spades.Queen)) return Score.QueenSpadeHigh;
    if (_cards.Contains(Deck.Hearts.Queen)) return Score.QueenHeartHigh;
    if (_cards.Contains(Deck.Diamonds.Queen)) return Score.QueenDiamondHigh;
    if (_cards.Contains(Deck.Clubs.Queen)) return Score.QueenClubHigh;

    if (_cards.Contains(Deck.Spades.Jack)) return Score.JackSpadeHigh;
    if (_cards.Contains(Deck.Hearts.Jack)) return Score.JackHeartHigh;
    if (_cards.Contains(Deck.Diamonds.Jack)) return Score.JackDiamondHigh;
    if (_cards.Contains(Deck.Clubs.Jack)) return Score.JackClubHigh;

    if (_cards.Contains(Deck.Spades.Ten)) return Score.TenSpadeHigh;
    if (_cards.Contains(Deck.Hearts.Ten)) return Score.TenHeartHigh;
    if (_cards.Contains(Deck.Diamonds.Ten)) return Score.TenDiamondHigh;
    if (_cards.Contains(Deck.Clubs.Ten)) return Score.TenClubHigh;

    if (_cards.Contains(Deck.Spades.Nine)) return Score.NineSpadeHigh;
    if (_cards.Contains(Deck.Hearts.Nine)) return Score.NineHeartHigh;
    if (_cards.Contains(Deck.Diamonds.Nine)) return Score.NineDiamondHigh;
    if (_cards.Contains(Deck.Clubs.Nine)) return Score.NineClubHigh;

    if (_cards.Contains(Deck.Spades.Eight)) return Score.EightSpadeHigh;
    if (_cards.Contains(Deck.Hearts.Eight)) return Score.EightHeartHigh;
    if (_cards.Contains(Deck.Diamonds.Eight)) return Score.EightDiamondHigh;
    if (_cards.Contains(Deck.Clubs.Eight)) return Score.EightClubHigh;

    if (_cards.Contains(Deck.Spades.Seven)) return Score.SevenSpadeHigh;
    if (_cards.Contains(Deck.Hearts.Seven)) return Score.SevenHeartHigh;
    if (_cards.Contains(Deck.Diamonds.Seven)) return Score.SevenDiamondHigh;
    if (_cards.Contains(Deck.Clubs.Seven)) return Score.SevenClubHigh;
    throw new NotImplementedException("Working on it");
}

This will need to be broken down into a chain. I'll stick with a chain as it has temporal coupling. The order matters.
But do I want that right now...

I've been pondering this and a couple other points; and it struck me that I don't need to quite chain this. That's A LOT of almost identical components.

What this can do; is an ordered pair; checking one and then returning the result.

This has created the SingleCardScorer class

public class SingleCardHigh : IScorer
{
    private readonly List<KeyValuePair<ICard, IScore>> _singleCardScores;

    public SingleCardHigh() : this(new List<KeyValuePair<ICard, IScore>>
    {

        new KeyValuePair<ICard, IScore>(Deck.Spades.Ace, Score.AceSpadeHigh),
        new KeyValuePair<ICard, IScore>(Deck.Hearts.Ace, Score.AceHeartHigh),
        new KeyValuePair<ICard, IScore>(Deck.Diamonds.Ace, Score.AceDiamondHigh),
        new KeyValuePair<ICard, IScore>(Deck.Clubs.Ace, Score.AceClubHigh),

        new KeyValuePair<ICard, IScore>(Deck.Spades.King, Score.KingSpadeHigh),
        new KeyValuePair<ICard, IScore>(Deck.Hearts.King, Score.KingHeartHigh),
        new KeyValuePair<ICard, IScore>(Deck.Diamonds.King, Score.KingDiamondHigh),
        new KeyValuePair<ICard, IScore>(Deck.Clubs.King, Score.KingClubHigh),

        new KeyValuePair<ICard, IScore>(Deck.Spades.Queen, Score.QueenSpadeHigh),
        new KeyValuePair<ICard, IScore>(Deck.Hearts.Queen, Score.QueenHeartHigh),
        new KeyValuePair<ICard, IScore>(Deck.Diamonds.Queen, Score.QueenDiamondHigh),
        new KeyValuePair<ICard, IScore>(Deck.Clubs.Queen, Score.QueenClubHigh),

        new KeyValuePair<ICard, IScore>(Deck.Spades.Jack, Score.JackSpadeHigh),
        new KeyValuePair<ICard, IScore>(Deck.Hearts.Jack, Score.JackHeartHigh),
        new KeyValuePair<ICard, IScore>(Deck.Diamonds.Jack, Score.JackDiamondHigh),
        new KeyValuePair<ICard, IScore>(Deck.Clubs.Jack, Score.JackClubHigh),

        new KeyValuePair<ICard, IScore>(Deck.Spades.Ten, Score.TenSpadeHigh),
        new KeyValuePair<ICard, IScore>(Deck.Hearts.Ten, Score.TenHeartHigh),
        new KeyValuePair<ICard, IScore>(Deck.Diamonds.Ten, Score.TenDiamondHigh),
        new KeyValuePair<ICard, IScore>(Deck.Clubs.Ten, Score.TenClubHigh),

        new KeyValuePair<ICard, IScore>(Deck.Spades.Nine, Score.NineSpadeHigh),
        new KeyValuePair<ICard, IScore>(Deck.Hearts.Nine, Score.NineHeartHigh),
        new KeyValuePair<ICard, IScore>(Deck.Diamonds.Nine, Score.NineDiamondHigh),
        new KeyValuePair<ICard, IScore>(Deck.Clubs.Nine, Score.NineClubHigh),

        new KeyValuePair<ICard, IScore>(Deck.Spades.Eight, Score.EightSpadeHigh),
        new KeyValuePair<ICard, IScore>(Deck.Hearts.Eight, Score.EightHeartHigh),
        new KeyValuePair<ICard, IScore>(Deck.Diamonds.Eight, Score.EightDiamondHigh),
        new KeyValuePair<ICard, IScore>(Deck.Clubs.Eight, Score.EightClubHigh),

        new KeyValuePair<ICard, IScore>(Deck.Spades.Seven, Score.SevenSpadeHigh),
        new KeyValuePair<ICard, IScore>(Deck.Hearts.Seven, Score.SevenHeartHigh),
        new KeyValuePair<ICard, IScore>(Deck.Diamonds.Seven, Score.SevenDiamondHigh),
        new KeyValuePair<ICard, IScore>(Deck.Clubs.Seven, Score.SevenClubHigh),
    })
    { }

    public SingleCardHigh(List<KeyValuePair<ICard, IScore>> list) => _singleCardScores = list;

    public IScore HandScore(ICard[] cards)
    {
        foreach (KeyValuePair<ICard, IScore> singleCardScore in _singleCardScores)
        {
            if (cards.Contains(singleCardScore.Key)) return singleCardScore.Value;
        }

        throw new NotImplementedException("Ya broke it");
    }
}

Do I LIKE it... ehh; not as much as I could. I think it should be broken into a chain. But not sure the form yet. I don't think a normal chain. Then I'm passing the internals around. The cards of the hand.
But if I pass the hand down the chain; with a "Do you have X?" method. While I dislike it... I think it's better.

I like what I came out with. It's kinda big and dumb as a chain. Long.
I'm not a huge fan of this long of a chain. It is the correct decision process... just looks wrong.

I've moved onto looking at how I'd find Pairs. I'm not sure I like how I'm identifying the "Score". There's going to be WAY too many objects for each of these.

  • PairTwoSpadeHigh
  • PairTwoHeartHigh
  • PairTwoDiamondHigh

Repeat for every additional rank 13*3 = 39. Plus the 34 for singles.

  • ThreeOfTwoSpadeHigh
  • ThreeOfTwoSpadeDiamond

Repeat for every additional rank 13*2 = 26. This puts up to 99 different score objects. gag It's getting very bloated.

I'm feeling that I should have a score object that takes a Score (Single, Pair, ThreeOf), plus a Card. This would be the "high card".
These two combined would give us the result. It's simpler. Then I just need to ask the Hand for the HighCard; or to build a score object given a score slug.
I like this a lot better.

OK; plan for next time - Nuke everything we did today Code. Then do it better!

Show Comments