December 11th, 2007

Euchre Revisited: Fixing bugs and service releases (Matt Gertz)

It’s really embarrassing when a bug gets out the door in a product.  In particular, it’s hard for me to just sit and take it (however well deserved the criticism is) when a Microsoft product gets nailed in the press for a bad error.  It’s much worse when a friend or relative is the one dealing out the grief.  However, worst of all is when your specific product – your specific feature – is getting railed on by your family.

I vividly remember an incident about eleven years ago, when I was a developer on Visual InterDev and my wife was working on her Master’s in Education.  She needed to create a training website as part of her MS project, and I pointed her to ActiveX Control Pad, which my team had just released.  And, oh my goodness.  My wife had an unerring ability to run into bugs which we’d missed before releasing the product, and all I could offer was the feeble, “Oh, yeah, I told so-and-so about that – guess they didn’t fix that right – heh, heh, oops,” as I slunk down further into my chair and hid behind a couple of pillows.

So, let’s step back into the Wayback Machine and revisit my first major series of posts for this blog, which was the nine-part series on Euchre (which starts with this old post).  Being somewhat proud of that program, I’ve been distributing it to family for several years now (I wrote the original using VS2003, updated it in 2005, and then tweaked it again for the blog series earlier this year).  You can imagine my consternation when my dad e-mailed me this past week and said that he’d encountered a problem running my app – not once, but twice.  Apparently, whenever his partner decided to call for a loner when he himself was the dealer, the game would freeze up – he’d be asked to pick up the kitty card, but he couldn’t actually do it.

Well, now my family geek cred was at serious risk – I needed to deal with this fast.  My family is chock full of serious Euchre players who also look to me to be the expert on all things electronic.   I had an idea as to what might be happening, but I’d need a way to not only fix the problem but distribute an update to my family.  I instantly realized that this would be a golden opportunity to talk about updates on this blog and, so, here we go.  I’ve attached the final version to this post which, like the previous version, does not contain the Speech MSMs – they’re too big to attach.   (See part 6 and part 9 of the original series for more information on how to get the speech APIs.)  The new solution is saved in VS2008, incidentally.  You can, of course, make the changes directly in whatever copy you have from my old post — the changes are really easy to make, as you’ll see.

Fixing the Euchre Game:  Finding & Fixing the Bug

The fix can be done in either VS2005 or VS2008.  (I’ll be using the latter).  If you open the solution using VS2008, the first thing that you’ll notice is that the “Visual Studio Conversion Wizard” will pop up to convert your project types to the VS2008 format.  Click “Next” to proceed, and you’ll be asked if you want to create a backup before converting.  It’s always safer to create a backup, so I’ll specify a location for that and click “Next.”  The next page of the wizard just tells me about the things that might be done during the upgrade process – there’s no action to take here, so I’ll click “Finish.”  The upgrade process takes just a few seconds in my case (very little has changed except some metadata in the project file), and after it completes, I’ll click “Close.”  (I could also check a box here which would bring up the conversion log if I wanted to.)

Now, games are notoriously hard to debug without logs (since play is always random), and I don’t have technology built into my game to automatically recreate state — nor do I have the time to build that in.  However, from my Dad’s description of the problem, I have a fairly good idea of what’s going on.  The situation is that the player’s AI partner has decided to go alone, but since the player is also the dealer, he needs to pick up the card in the kitty and discard another card before play can continue.  However, the player also needs to have his/her hand disabled because he/she won’t be participating in it.  If I disable the cards too early, the player won’t be able to respond to the call to “pick it up,” and the game will freeze. 

That’s really all the clues I need.  I know the freezing must be happening during the bidding round (which is concluded either by the dealer picking up a card or the card being turned over), and I know it can only happen in the first bidding round, because in the second bidding round, there is no card to pick up.  I therefore open the file “EuchrePlayer.vb” and navigate to the method “BidFirstRound.”  (I can do this by using the right-hand dropdown at the top of the editor – making sure that “EuchrePlayer” is selected in the left-hand dropdown first.)  Here’s the code – can you spot the bug?

    Public Function BidFirstRound(ByVal Table As EuchreTable) As Boolean

        Dim rv As Boolean = False

        Dim GoingAlone As Boolean = False

 

        If Seat = Seats.Player Then

            Table.BidControl.Reset()

            Table.BidControl.GoingAlone.Enabled = False

            If Table.DealerThisHand = Seats.Partner And Table.UseQuietDealerRule Then

                Table.BidControl.ForceGoAlone(True)

            Else

                Table.BidControl.ForceGoAlone(False)

            End If

 

            Table.PlayerIsBidding = True

            Table.AcceptButton = Table.BidControl.OKButton

            Table.BidControl.Visible = True

            Table.BidControl.Enabled = True

            Table.BidControl.BringToFront()

            Table.BidControl.Update()

 

            ‘ Do a message pump here:

            Do While Table.PlayerIsBidding = True

                My.Application.DoEvents()

                If Table.Exiting = True Then

                    Dim e As New EuchreException(“ExitGame”)

                    Throw e

                End If

                If Table.Restarting = True Then

                    Dim e As New EuchreException(“NewGame”)

                    Throw e

                End If

            Loop

 

            GoingAlone = Table.BidControl.GoingAlone.Checked

            rv = Table.BidControl.PickItUp.Checked

        Else

            Dim bid As Boolean = False

            Dim value As Integer = HandValue(Table.Kitty(0).Suit)

            If Table.DealerThisHand = Seat OrElse Table.DealerThisHand = OppositeSeat() Then

                Dim index As Integer = LowestCardOnReplace(Table.TrumpSuit) ‘ Player would drop this one to get the kitty card

                value = value + Table.Kitty(0).GetValue(Table.Kitty(0).Suit) – CardsHeldThisHand(index).GetValue(Table.Kitty(0).Suit)

            End If

            If value >= Makeable() Then

                If Not (Table.DealerThisHand = OppositeSeat() And Table.UseQuietDealerRule = True) Then

                    bid = True

                End If

                If value >= Loner() Then

                    bid = True

                    GoingAlone = True

                End If

            End If

            rv = bid

        End If

 

        Dim s As New StringBuilder()

        If rv = True Then

            Table.TrumpSuit = Table.Kitty(0).Suit

 

            If GoingAlone = True Then

                Table.Players(OppositeSeat()).SittingOutThisHand = True

                Table.EnableCards(OppositeSeat(), False)

 

                If OppositeSeat() = Table.LeaderThisTrick Then

                    Table.LeaderThisTrick = NextPlayer(Table.LeaderThisTrick)

                End If

                If Seat = Table.DealerThisHand Then

                    s.AppendFormat(My.Resources.Notice_IPickItUpAlone, GetDisplayName(Table), My.Resources.ResourceManager.GetString(EuchreCard.GetSuitDisplayStringResourceName(Table.TrumpSuit)))

                    Table.UpdateStatus(s.ToString)

                    Table.SpeakIPickItUp(Seat)

                Else

                    s.AppendFormat(My.Resources.Notice_PickItUpAlone, GetDisplayName(Table), My.Resources.ResourceManager.GetString(EuchreCard.GetSuitDisplayStringResourceName(Table.TrumpSuit)))

                    Table.UpdateStatus(s.ToString)

                    Table.SpeakPickItUp(Seat)

                End If

                Table.SpeakSuit(Seat)

                Table.SpeakAlone(Seat)

            Else

                If Seat = Table.DealerThisHand Then

                    s.AppendFormat(My.Resources.Notice_IPickItUp, GetDisplayName(Table), My.Resources.ResourceManager.GetString(EuchreCard.GetSuitDisplayStringResourceName(Table.TrumpSuit)))

                    Table.UpdateStatus(s.ToString)

                    Table.SpeakIPickItUp(Seat)

                Else

                    s.AppendFormat(My.Resources.Notice_PickItUp, GetDisplayName(Table), My.Resources.ResourceManager.GetString(EuchreCard.GetSuitDisplayStringResourceName(Table.TrumpSuit)))

                    Table.UpdateStatus(s.ToString)

                    Table.SpeakPickItUp(Seat)

                End If

                Table.SpeakSuit(Seat)

            End If

 

            Table.Players(Table.DealerThisHand).ReplaceACard(Table)

            Table.PickedTrumpThisHand = Seat

        Else

            s.AppendFormat(My.Resources.Notice_Pass, GetDisplayName(Table))

            Table.UpdateStatus(s.ToString)

            Table.SpeakPass(Seat)

        End If

        Return rv

 

The key to solving the problem is to find where I disable the partner’s cards when someone decides to go it alone.  That’s these lines of code:

            If GoingAlone = True Then

                Table.Players(OppositeSeat()).SittingOutThisHand = True

                Table.EnableCards(OppositeSeat(), False)

 

And then a bit later in the code, I then ask the dealer to replace a card, which, if the player is the dealer and their partner called trump, they’re blocked from doing since their cards are disabled:

            Table.Players(Table.DealerThisHand).ReplaceACard(Table)

            Table.PickedTrumpThisHand = Seat

 

Now, before I swap the calls around, it is important for me to disable the cards in that order for any other reason?  Let’s see… nope.  All I’m doing between those calls is just printing out some text, playing some sounds, and setting a variable that will be used much later, so I’m safe to move the card-disabling lines down a bit lower.  Here’s the fixed method:

    Public Function BidFirstRound(ByVal Table As EuchreTable) As Boolean

        Dim rv As Boolean = False

        Dim GoingAlone As Boolean = False

 

        If Seat = Seats.Player Then

            Table.BidControl.Reset()

            Table.BidControl.GoingAlone.Enabled = False

            If Table.DealerThisHand = Seats.Partner And Table.UseQuietDealerRule Then

                Table.BidControl.ForceGoAlone(True)

            Else

                Table.BidControl.ForceGoAlone(False)

            End If

 

            Table.PlayerIsBidding = True

            Table.AcceptButton = Table.BidControl.OKButton

            Table.BidControl.Visible = True

            Table.BidControl.Enabled = True

            Table.BidControl.BringToFront()

            Table.BidControl.Update()

 

            ‘ Do a message pump here:

            Do While Table.PlayerIsBidding = True

                My.Application.DoEvents()

                If Table.Exiting = True Then

                    Dim e As New EuchreException(“ExitGame”)

                    Throw e

                End If

                If Table.Restarting = True Then

                    Dim e As New EuchreException(“NewGame”)

                    Throw e

                End If

            Loop

 

            GoingAlone = Table.BidControl.GoingAlone.Checked

            rv = Table.BidControl.PickItUp.Checked

        Else

            Dim bid As Boolean = False

            Dim value As Integer = HandValue(Table.Kitty(0).Suit)

            If Table.DealerThisHand = Seat OrElse Table.DealerThisHand = OppositeSeat() Then

                Dim index As Integer = LowestCardOnReplace(Table.TrumpSuit) ‘ Player would drop this one to get the kitty card

                value = value + Table.Kitty(0).GetValue(Table.Kitty(0).Suit) – CardsHeldThisHand(index).GetValue(Table.Kitty(0).Suit)

            End If

            If value >= Makeable() Then

                If Not (Table.DealerThisHand = OppositeSeat() And Table.UseQuietDealerRule = True) Then

                    bid = True

                End If

                If value >= Loner() Then

                    bid = True

                    GoingAlone = True

                End If

            End If

            rv = bid

        End If

 

        Dim s As New StringBuilder()

        If rv = True Then

            Table.TrumpSuit = Table.Kitty(0).Suit

 

            If GoingAlone = True Then

                ‘ Bug #1:  I used to disable the opposite player’s cards too early, right here. 

                ‘ Code moved to farther down, fixed 12/10/2007.

 

                If OppositeSeat() = Table.LeaderThisTrick Then

                    Table.LeaderThisTrick = NextPlayer(Table.LeaderThisTrick)

                End If

                If Seat = Table.DealerThisHand Then

                    s.AppendFormat(My.Resources.Notice_IPickItUpAlone, GetDisplayName(Table), My.Resources.ResourceManager.GetString(EuchreCard.GetSuitDisplayStringResourceName(Table.TrumpSuit)))

                    Table.UpdateStatus(s.ToString)

                    Table.SpeakIPickItUp(Seat)

                Else

                    s.AppendFormat(My.Resources.Notice_PickItUpAlone, GetDisplayName(Table), My.Resources.ResourceManager.GetString(EuchreCard.GetSuitDisplayStringResourceName(Table.TrumpSuit)))

                    Table.UpdateStatus(s.ToString)

                    Table.SpeakPickItUp(Seat)

                End If

                Table.SpeakSuit(Seat)

                Table.SpeakAlone(Seat)

            Else

                If Seat = Table.DealerThisHand Then

                    s.AppendFormat(My.Resources.Notice_IPickItUp, GetDisplayName(Table), My.Resources.ResourceManager.GetString(EuchreCard.GetSuitDisplayStringResourceName(Table.TrumpSuit)))

                    Table.UpdateStatus(s.ToString)

                    Table.SpeakIPickItUp(Seat)

                Else

                    s.AppendFormat(My.Resources.Notice_PickItUp, GetDisplayName(Table), My.Resources.ResourceManager.GetString(EuchreCard.GetSuitDisplayStringResourceName(Table.TrumpSuit)))

                    Table.UpdateStatus(s.ToString)

                    Table.SpeakPickItUp(Seat)

                End If

                Table.SpeakSuit(Seat)

            End If

 

            Table.Players(Table.DealerThisHand).ReplaceACard(Table)

            Table.PickedTrumpThisHand = Seat

            ‘ Bug #1:  I shouldn’t disable the opposite seat’s cards

            ‘ until they’ve replaced the card (if relevant).

            ‘ Fixed 12/10/2007 by moving code from higher up to here.

            If GoingAlone = True Then

                Table.Players(OppositeSeat()).SittingOutThisHand = True

                Table.EnableCards(OppositeSeat(), False)

            End If

        Else

            s.AppendFormat(My.Resources.Notice_Pass, GetDisplayName(Table))

            Table.UpdateStatus(s.ToString)

            Table.SpeakPass(Seat)

        End If

        Return rv

 

    End Function

 

Now, for testing – well, it’s kind of brute force because (as noted above) I didn’t have the foresight to add a mechanism for testing different states.   However, to test this case specifically, I can go into the code and temporarily make my partner always go it alone when I’m the dealer, when they get the option to do so.  Otherwise, I play the game for a while and make sure nothing else got messed up with this change. 

Fixing the Euchre Game:  Getting the Fix out to Customers (i.e., Family)

Now, I need to package up my new code and send it to my dad.  I’ll right-click the project in the Solution Explorer and bring up its properties.  Now, I’ll click “Assembly Information…” on the Application tab and, in the resulting dialog, change the assembly version to 2.1 instead of 2.0.  I’ll click OK to apply the changes. Now, that’s just revved the version on the application – I also need to fix up the installer.  Selecting the Installer in the Solution Explorer, I can see its properties in the property grid.  I’ll change the “Version” property to “2.1.0”.  I will then be prompted to change the ProductCode once I commit that change – I’ll select “Yes.”  (I need to do this to give it an identifier that will make it unique from the previous version.  The “UpgradeCode,” on the other hand, shouldn’t change, so that they installers realize that they are different version of the same thing and can upgrade freely.)  Finally, I’ll set “RemovePreviousVersion” to True, and I’m all done.  I can build the solution, burn the resulting setup to disk, and send it to Dad along with his Christmas card.  When he installs it, it’ll replace the old version with the new one automatically.  Whew!  Geek cred restored.

Making mistakes in coding is always a given, and you always need a plan to service your releases.  Fortunately, Visual Studio 2005 and 2008 make this quite easy!

‘Til next time…

–Matt–*

VBEuchre21.zip

0 comments