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