Introduction
When I first posted this application back in 2002, I challenged everyone to improve it and provided a list of enhancements I thought were needed. Nobody took me up on the challenge so I finally got back to adding some of those features myself.
First among those was insurance. Whenever the dealer shows an ace, the players are given the opportunity to buy insurance which usually costs half the original bet. The dealer then looks at his hole card and, if he has Blackjack, you are insured and lose nothing. If he does not have Blackjack, you lose your insurance bet and play continues as usual. This feature was missing from the original game and if the dealer had blackjack, you just lost immediately. Of course, if the dealer has an ace in the hole, you'll still lose immediately...sorry, that's the rules.
Then I found an article called AquaButton (see Readme.doc for credits) which subclassed the button control and made it glow when it was the default button. I thought this would be cool if the button for the 'correct' move glowed to show the user what to do. But I also wanted my button to be able to take images so I wrote my own version of the button. Feel free to use this button on its own in your applications.
If you snooped at the code carefully, you would have noticed that there was a 'Draw' method in the Strategy class that was never used. This method could create a graph of the correct moves. Anyone who's been to Vegas has probably seen one of these; They even hand them out right at the tables and it is legal to refer to it during play. My version is available by right-clicking on the playing field and selecting the 'Strategy Window' option from the popup menu. You'll notice that the graph changes for each player depending on the strategy chosen. And if the player is using the High-Low strategy, the graph can change depending on the card count (High-Low is the only strategy that does this).
Player controls have been moved to a popup menu because I added so many that the little form window became unmanageable. Right-click on each player circle to get player-specific settings. Right-click anywhere else to get general settings like the strategy window.
Initial player setup has been moved from the code to the app.config file so you can set the initial number of players, methods and strategies
The code has also been cleaned up a lot. I first wrote this game to learn .NET windows forms programming (I did mostly web stuff then). And since I was learning, I tried to use every feature and nuance just to see how it worked. A lot of that stuff wasn't really needed and made the code more difficult to figure out and maintain. So I've streamlined it quite a bit.
Here is the original article:
When programmers start creating object oriented designs, several questions pop up consistently:
- How do I know what objects to create?
- What properties should my objects have?
- What methods do I need to create?
- How do I know when to overload an operator?
- How do I structure my classes for inheritance?
- Etc.
The Objects
Let's take a look at a real world example and a fun one as well. The game Blackjack lends itself well to object oriented design because it has physical objects that can be modeled in object-oriented code, i.e. players, a dealer, cards, etc.
These objects have relationships to one another as well. Players have hands that have cards. The dealer also has a hand that has cards. And there's a shoe from which the cards are dealt into the hands.
public class Player
public class Dealer
public class Hand
public class Card
public class Shoe
For our Blackjack game, we're going to have computer-controlled players as well as human ones. For that, we'll have to have a strategy that the computer players use. So we can create another object, albeit not a physical one, called Strategy
that takes some input and gives advice on what move to make. The Strategy
object is going to belong to the Player
objects and each player will need an array of Hand
objects (players can split pairs so they may have more than one hand).
public class Player
{
private Strategy plyrStrategy;
private Hand[] hands;
_
A hand is just an array of Card
objects:
public class Hand
{
private Card[] cards;
_
A shoe is also just an array of Card
objects:
public class Shoe
{
private Card[] cards;
�
Now when we deal the cards, we just go around the table taking cards from the Shoe
object and adding them to the Hand
objects for each of the players and the dealer.
for( int k=0; k<2; k++ )
{
foreach( Player player in players )
{
player.GetHands()[0].Add( shoe.Next() );
}
dealer.Hand.Add(shoe.Next() );
}
Inheriting Interfaces
When a player splits a pair of aces, each ace receives only one more card:
if( CurrentPlayer.CurrentHand[0].FaceValue == Card.CardType.Ace )
{
NextCard();
NextHand();
NextPlayer();
}
Nice code huh? Well that's because there is a lot of supporting code underneath this, particularly to implement the line:
if( CurrentPlayer.CurrentHand[0].FaceValue == Card.CardType.Ace )
How does the compiler know what CurrentHand[0]
means? To use this kind of syntax, we must implement the IList
interface. This is the same interface used by the ArrayList
class and others that you might be familiar with. This is easily done by changing our class declaration slightly:
public class Hand : IList
Now there's a little more work to do. When you inherit an interface, you must provide the implementation for all the methods of that interface. For IList
, we need to add:
IsFixedSize
IsReadOnly
Add
Clear
Contains
IndexOf
Insert
Remove
RemoveAt
But the most important method to implement is called Item
and it looks like this:
Object IList.this[int index]
{
get { return cards[index]; }
set { cards[index] = (Card)value; }
}
This allows us to use array syntax like CurrentHand[0]
which really means nothing until we tell the compiler that this means the card at position 0 in the array of cards in the hand. Without implementing IList
, we would probably have to write something like CurrentHand.GetCard(0)
which isn't nearly as cool!
What methods do I create?
Notice that the players and the dealer are responsible for drawing their own hands. This makes it convenient to add code to the form's Paint
event like this:
dealer.DrawHand( drawingSurface, showDealerDownCard );
foreach( Player player in players
{
player.DrawHands( drawingSurface );
}
The players and dealer then loop through each hand, asking the cards to draw themselves:
foreach( Hand hand in hands )
{
foreach( Card card in hand )
{
card.Draw( drawingSurface );
}
}
Sometimes it's easier to envision the code you would like to write and then model your objects to allow it.
Summary
Going back to the design of the objects, you might be wondering why the Dealer
and the Player
objects don't inherit from some common object. Well, you could do that. But I felt the dealer and the players didn't have enough in common to justify it. The dealer can only have one hand, has no bank, no bet, no strategy, no card counting. This will have to be a judgment call on your part and that's why they pay you the big bucks.
You might also wonder where the Deck
object is. Shouldn't the Shoe
be composed of many Deck
objects which are composed of many Card
objects? That may be the case in the real world, but this is an example of where the real world and OOD might better part ways. A Deck
object would have just introduced an unneeded layer of complexity. The shoe is easier to implement as an array of cards, though it must be a multiple of the number of cards in a deck (52).
Take a look at the code for this article. This is a full-featured Blackjack game with strategies and graphics and even card counting. But don't let it intimidate you. It really just boils down to the few objects outlined above with a lot of fancy code added to make the game more appealing.
Have fun with this game. Take a look at the Readme.doc file for ideas on how you could improve this application.