March 22nd, 2007

Coding a Euchre Game, Part 7: Total Logic (Matt Gertz)

Coding a Euchre Game, Part 7:  Total Logic

Since I’ve been concentrating on specific VB functionality, you may have noticed that the one topic I haven’t really drilled into yet is game logic, and yet it’s central to what a game is all about.  Games have certainly gotten more sophisticated over the years, and yet that sophistication is largely a result of graphical and audio advances.  The actual logic of games itself hasn’t changed nearly as much  – I still have to exercise pretty much the same control over my NWN2 party as I did way back in Pools of Darkness, for example, lest I get blown away by friendly fire.

For card games, the logic is well-defined and ages-old… sort of.  Card games can have a lot of local variations in the rules, and Euchre is no exception.  I grew up playing a particular set of rules in Michigan (24 cards, plus  “Stick the Dealer” if all agreed), but my wife’s family in West Virginia plays with three less cards (getting rid of all 9’s except the nine of hearts) and always advances the deal if trump isn’t called in two bidding rounds.  Obviously, if I wanted my game to be successful for both families, I was going to have to accommodate all of these options.  Further research into Euchre showed me that there are all sorts of variations of Euchre, not only in the U.S. but around the world.  I settled for the two choices I was familiar with (24 vs. 21 cards, “Stick the Dealer” vs. new hand), and added a couple that sounded interesting to me (“SuperEuchre,” which simply changes the number of points awarded if the defending team  takes all of the trumps, and “Quiet Dealer,” which forces a dealer’s partner to play alone if he/she chooses the trump suit) – the latter options would be easy to implement by minor modifications to the logic.

For Euchre, there are two interesting areas of logic:  bidding and playing, both as concern the AI players.  (I could leverage that logic to “tutor” the human player as well, and maybe I’ll do so in a future version.)  As you might guess, in either case the AI needs to understand the value of a given hand or card – I’ll refer to this as the “score,” though it has nothing to do with the actual scoring players receive when winning.  The ordering of the cards for scoring is quite straightforward, but the relative distances between them can vary.  Let me explain…

In Euchre, the ordering of cards (from most powerful to least powerful) goes as follows: the Jack of Trumps (Right Bower), the Jack of the other same-color suit (Left Bower), the Ace of Trumps, the King of Trumps, the Queen of Trumps, the Ten of Trumps, and (if it exists) the Nine of Trumps.  These are followed by the normal ordering (A, K, Q, J, 10, 9) of any other non-trump suit.  So, I could start out with an enum defining this ordering as follows:

    Public Enum Values

        NineNoTrump = 1

        TenNoTrump = 2

        JackNoTrump = 3

        QueenNoTrump = 4

        KingNoTrump = 5

        AceNoTrump = 6

        NineTrump = 7

        TenTrump = 8

        QueenTrump = 9

        KingTrump = 10

        AceTrump = 11

        LeftBower = 12

        RightBower = 13

    End Enum

 

And that would seem to be all right.  However, let’s consider the case where Bob is holding three non-trump aces, the queen trump, and the 10 trump(A A A QT 10T) and Alice is holding both bowers and the nine of trumps, in addition to a couple of non-trump nines (RT LT 9T 9 9).  Bob “hand value” is 35, and Alice’s is 34.  Bob would seem to have a better hand, right?  Wrong!  The goal for the person who declares trump is to win three of the five “tricks” (hands).  Both of Bob’s trumps would be neutralized by Alice’s superior trump cards, and Alice would still have one trump left to neutralize any of the aces in Bob’s hand.  The only missing trumps (AT and KT), even if not owned by Alice’s partner, would also be covered by Alice’s bowers, making it highly certain that Alice would win her needed tricks.  (Alice, of course, doesn’t know Bob’s cards, and so might still be reluctant to make a bid in case he’s got all of the rest of the trump.)  In fact, players of Euchre are lucky if they can take a trick without using a trump or a non-trump ace, since the person who declares trump is likely to be deficient in non-trump cards, so cards other than those should be significantly lower in score.

The AI players, of course, are forbidden from knowing what each others’ cards are (i.e., no peeking!), so I couldn’t very well implement logic that would involve figuring out if high trumps would neutralize specific cards, etc.  So, after some thinking about the probabilities involved, I revised the enum be more reflective of those probabilities.  This is where testing is important – the game has to “feel right” when played.  The user shouldn’t be thinking “why in the world did the AI choose trump with that hand?”  Based on probability, testplay, and a strong familiarity with the game, I ended up with the following enum:

    Public Enum Values

        NineNoTrump = 1

        TenNoTrump = 2

        J ackNoTrump = 3

        QueenNoTrump = 4

        KingNoTrump = 5

        AceNoTrump = 10

        NineTrump = 12

        TenTrump = 15

        QueenTrump = 20

        KingTrump = 25

        AceTrump = 30

        LeftBower = 31

        RightBower = 35

        NoValue = -1

    End Enum

 

and in this case, Bob would have a score of 65 and Alice would have a score of 80, which is much more reflective of the reality of the situation.

Now, we’ll need code to actually leverage this.  Each AI will need to figure out what their hand score would be for a given trump suit.  First, we need to deal with the incongruous case of the Jack of the same-color suit (which I’ll refer to as the Bower suit, even though that’s not technically accurate) being treated as trump suit:

    Public Shared Function GetBowerSuit(ByVal Trump As Suits) As Suits

        Select Case Trump

            Case Suits.Hearts

                Return Suits.Diamonds

            Case Suits.Diamonds

                Return Suits.Hearts

            Case Suits.Clubs

                Return Suits.Spades

            Case Suits.Spades

                Return Suits.Clubs

        End Select

    End Function

 

and then get the value of a hand (per-card)  based on the given potential trump suit:

 

    Public Function GetValue(ByVal Trump As Suits) As Values

        If Suit = Trump Then

            Select Case Rank

                Case Ranks.Nine

                    Return Values.NineTrump

                Case Ranks.Ten

                    Return Values.TenTrump

                Case Ranks.Queen

                    Return Values.QueenTrump

                Case Ranks.King

                    Return Values.KingTrump

                Case Ranks.Ace

                    Return Values.AceTrump

                Case Ranks.Jack

                    Return Values.RightBower

            End Select

        ElseIf Suit = GetBowerSuit(Trump) AndAlso Rank = Ranks.Jack Then

            Return Values.LeftBower

        Else

            Select Case Rank

                Case Ranks.Nine

                    Return Values.NineNoTrump

                Case Ranks.Ten

                    Return Values.TenNoTrump

                Case Ranks.Jack

0 comments