/ microObject

µ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!

Quinn Gil

Quinn Gil

Seattle Code Crafter. Quinn beats the drum of FAST Agile, Extreme Programming and Object Oriented Design through MicroObjects. He blogs for fun and frustration of exploring new concepts.

Read More
µObject Poker: Scoring a Hand
Share this