Java Game Programming For Dummies - W. Holder, D. Bell
.pdf
|
Chapter 6: Blackjack |
95 |
Split |
A player may split a hand consisting of two cards with the |
|
|
same point value into two hands, thereby doubling the |
|
|
chance for a win (or a loss, of course). The player puts the |
|
|
two hands side by side on the table and places a bet on the |
|
|
new hand equal to the original bet. The player then plays |
|
|
each hand normally, except that if a split hand totals 21 |
|
|
after two cards, it doesn't count as a natural. |
|
Designing the game
The first step in designing an object-oriented program (Remember: Java is an object-oriented programming language) is to look at the objects being modeled by the program. Figure 6-1 shows the objects in a real game of Blackjack.
li t
.,tT-lWIT.: IIr
96 Part II: Up to Speed
After you identify the objects your game needs to model, you need to organize the objects according to their relationships and functions in the game. Although programming challenges usually have more than one solution, the elements typically suggest certain logical relationships that, in turn, suggest the most elegant way to program them. The Blackjack game presented in this chapter organizes the elements of Blackjack, as shown in Figure 6-2.
|
Blackjack Applet (Playing Surface) |
|||
|
Player[] |
Dealer |
Deck |
|
|
Hand |
|
||
|
|
|
||
|
d |
Card[] |
Card[] |
|
|
|
|||
|
Card fl |
|
|
|
|
|
User fiterface |
||
Figure 6-2: |
Bet', |
Deal I |
I Stand Split |
|
|
||||
Blackjack |
Bankroll |
|
|
|
object |
Hit |
Double Down |
||
|
||||
relationships.
As Figure 6-2 shows, the Blackjack Applet provides the playing surface and contains an array of player objects, a dealer hand object, a deck object, and a user interface. The deck and dealer hand objects each contain an array of card objects. Each player object contains a player hand object and a bankroll. The player hand contains an array of card objects and a bet. The user interface is a panel component that contains the button objects for the various options available to the player. The fact that the object relationships for Blackjack look similar to the layout of the real-world "objects" is no coincidence: The game design is derived from the real-world objects.
Creating a Reusable Deck a f Cards
A deck of cards is a common element to all card games (bet you didn't know that), and many of the techniques presented in this section can be applied tc any card game you want to program. You can even create specialized decks (for example, Pinochle or Poker with the Joker added) with minor changes here and there, as we explain a bit later in this chapter.
One of the powerful aspects of object-oriented programming is how it facilitates reusing code. This section shows how to create a reusable Deck class that you can use in any card game that uses a standard deck. This section also presents the Ca rd objects that the Deck class shuffles and deals.
|
|
|
Chapter 6: Blackjack |
9 7 |
Shuffling and dealing the deck |
|
|||
The Deck class uses an array to keep track of the cards in the deck. Rather |
|
|||
than actually remove cards from the array as they are dealt, the Deck class |
|
|||
maintains an index to the last card in the card array and deals cards from |
|
|||
the end of the array. As each card is dealt, the index to the last card in the |
|
|||
array is decremented. The complete De c k class is: |
|
|||
i mport java.util.NoSuchElementException; |
|
|
||
public class Deck { |
|
|
|
|
protected Card[] cards; |
|
|
||
protected int |
top; |
|
|
|
protected int |
packs; |
|
|
|
public int getSize t) (return top; I |
/Ilf undealt cards |
|
||
public int getPacks () ( return packs; |
packs in deck |
|
||
public |
i nt packSize () I return 52; } |
cards in pack |
|
|
public |
Deck () C this(1) I |
|
|
|
public |
Deck (int packs) i |
|
|
|
i f ((this.packs = packs) > 0) top = packs * packSize(); cards = new Card[topl;
reshuffle();
// Fills the deck with cards and shuffles public synchronized void reshuffle () {
top = 0;
for (int packs = this.packs; --packs >= 0; )
for (int suit = Card.CLUB: suit <= Card.SPADE; suit-) for (int rank = Card.ACE; rank <= Card.KING; rank++)
cards[top++] = new Card(rank, suit); shuffle();
// Shuffles the undealt cards in the deck public synchronized void shuffle ()
i f (top > 1)
for (int ii = top; --ii >= 0; )
i nt rnd = (int) (Math.random() * top); Card temp = cards[ii];
cards[ii] = cards[rnd]; cards[rndl = temp;
(continued)
98 Part I1: Up to Speed
(continued)
// Deals the top, card from the deck
public Card deal () throws NoSuchElementException I return deal(O);
1
1/ Deals a card from the deck. <pos> is relative to the top of the deck (0 = top, 1 = second from top, etc.)
public synchronized Card deal (int pos) throws NoSuchElementException
Card c = peek(pos);
if (pos > 0) //deal card from middle of deck System.arraycopy(cards, top - pos,
cards, top - pos - 1, pos);',
top--; return c;
//Returns the top card from the deck without dealing it public Card peek () throws NoSuchElementException I
return peek(0);
//Returns a card from the deck without dealing it.
//<pos> is relative to the top of the deck (0 = top)
`public synchronized Card peek -(int pos)
throws NoSuchElementException
if (POs < 0)
throw new NoSuchElementException();
try I return cards[top - (pos + 1)1; l catch (lndexOutOfBoundsException e) { throw new NoSuchElementException();
l
// end class Deck
The algorithm used in Deck. shuffle( ) works by swapping each card in the deck with another randomly selected card. This swapping randomizes the deck after a single pass through the deck and is much simpler than simulating a real shuffle.
Chapter 6: Blackjack 99
The d e a 1 ( ) and pe e k ( ) methods fetch cards from the deck. d e a 1 ( ) removes the fetched cards from the deck, but p e e k ( ) does not. Both d e a 1 ( ) and p e e k ( ) are overloaded methods, meaning that they are given alternate versions with different parameters. One version operates on the top card in the deck; the other version accepts a parameter specifying the card position. The Blackjack applet doesn't use pee k ( ) or the ability to deal from the middle of the deck; neither is necessary for a functioning Blackjack game. However, other computer card games, like Solitaire, use these methods to cycle through the deck multiple times and play cards from arbitrary
positions.
Notice that instead of defining its own subclass of Excepti on when a requested card is unavailable, De c k uses the standard
class included as part of the Java API. Using this standard class eliminates your having to write and the player's web browser having to download extra code to define a class to handle unavailable cards.
Some card games, including Blackjack, use a deck built from combining several packs of cards. The Deck class supports multipack decks by overloading the constructor to accept the number of packs to use.
Building the C a r d class
Each card must remember just two things: its suit (Club, Diamond, Heart, Spade) and its rank (Ace, 2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen, King). You can give cards the following capabilities to make working with them as easy
as possible:
v Assign the suits and ranks to s t a t i c f i n a 1 variables.
-Override 0 b j e c t . t o S t r i n g ( ) so that a card can display its name.
vHave cards draw themselves to a specified G r a p h i c s context.
V Override 0 b j e c t . e q u a l s ( ) so that cards can compare themselves.
The resulting Ca rd class is
import java.awt.*;
i mport java.applet.Applet;
public class Card {
public static final int
CLUB = 0, DIAMOND = l, HEART = 2, SPADE = 3:
(continued)
100 Part II: Up to Speed
(continued)
public static final int
JOKER = 0, ACE = 1, TWO = 2, THREE = 3, FOUR = A,
FIVE = 5, SIX = 6, SEVEN = 7, EIGHT = 8, NINE _ 9,"
TEN = 10, JACK = 11, QUEEN = 12, KING = 13;
private static final String[] suitNames =
I Club , Diamond , Heart , Spade I;
private static final String[] rankNames =
I Joker , Ace , Two , Three , Four , Five
Six , Seven , Eight , Nine , Ten , Jack ,
Queen , King };
private static |
Image cardsImage; |
|
private static |
int |
cardWidth, cardHeight; // in pixels |
private int rank; |
|
|
private int suit; |
|
|
public final int getRank () ( return rank; I public final int getSuit () ( return suit; I public Card (int rank, int suit) I
if ((this.rank = rank) != JOKER) this.suit = suit;
I
public final String getSuitName ()
return rank == JOKER ? : suitNames[suit];
public final String getPluralSuitName () (
return rank == JOKER ? : suitNames[suit] + s-;
public final String getRankName ()
I return rankNames[rank];
public final String getPluralRankName ()
return rankNames[rank] + (rank == SIX ? es : s
I |
|
|
public final String getArticle () I |
|
|
return rank == ACE 1 1 |
rank == EIGHT ? an |
, a |
public final boolean isRed () I |
|
|
return suit == DIAMOND |
1 1 suit == HEART; |
|
I |
|
|
public' final boolean isFace () return rank >= JACK;
I
|
|
|
|
|
Chapter 6: Blackjack |
10 1 |
public |
static int getCardWidth () |
{ return cardWidth; |
|
|||
public |
static int getCardHeight () i return cardHeight; ) |
|
||||
/* If not |
currently loaded, then loads and inits the |
|
||||
cards.gif image. This image is laid out as a 14 wide |
|
|||||
* by |
|
4 tall grid of card images laid out as: |
|
|||
* |
JOKER |
A 2 3 4 5 6 7 8 9 10 J |
0K |
( CLUBS) |
|
|
* |
CARDBACK A 2 3 4 5 6 7 8 9 10 J 0 K |
( DIAMONDS) |
|
|||
* |
BLANK |
A2345678910J0K |
( HEARTS) |
|
||
* |
BLANK |
A2345678910J0K |
( SPADES) |
|
||
public static synchronized void initGraphics(Applet app) if (cardsImage == null) {
MediaTracker tracker = new MediaTracker(app); cardsImage = app.getlmage(app.getCodeBase(),
cards.gif ); tracker.addlmage(cardslmage, 0);
try { tracker.waitForAll(); I catch (InterruptedException e) {}
cardWidth = cardslmage.getWidth(app) l 14; cardHeight'- cardslmage,getHeight(app) / 4;
public static void drawCardBack (Graphics g,
i nt x, int y) f // card back is second card down in first column doDraw(g, x, y, 0, cardHeight);
private static void doDraw (Graphics g, int x, int y, i nt xaff, int yoff) I
if (cardsImage != null) { Graphics gcopy = g.create();
gcopy.clipRect(x, y, cardWidth, cardHeight); gcopy.drawimage(cardslmage, x - xoff, y - yoff, null); gcopy.dispose();
public void draw (Graphics g, int x, int y)
doDraw(g, x, y, rank * cardWidth, suit * cardHeight);
(continued)
102 Part I1: Up to Speed
(continued)
public String toString ()
StringBuffer buf = new StringBuffer(rankNames[rankl); if (rank != JOKER)
buf.append( of ).append(getPluralSuitName(-))-; return buf.toString();
public boolean equals (Object obj) { if (obj instanceof Card) f
Card c = (Card)obj;
return crank == rank && c.suit == suit;
1
return false;
public int hashCode () t
return (rank << 2) + suit;,
)
end, class Card
The Ca rd class supports Jokers even though Blackjack does not use a Joker. Ca rd is a general-purpose class useful for all kinds of games, including those that use a Joker.
Converting cards to strings
Most computer games use an image of a card to represent a card on screen, but sometimes cards need to be displayed as text. For example, if you are trying to develop and debug a game, you may want the cards to appear as text so that you can work out the process of the game before creating the images for the cards.
To convert cards to strings, override 0 b j e c t. t o S t r i n g ( ) so that it implicitly converts an object to a string when using the + string concatenation operator. A Ca rd object has methods for returning the plural and singular names of its suit and rank. C a r d uses these methods in t o S t r i n g ( ) to construct and return the name of the card.
For example, the following code displays The card is an Ace of Spades:
Card c = new Card(Card.ACE, Card.SPADE);
System.out.println( The card is |
+ c.getArticlet) + |
Chapter 6: Blackjack 103
Overriding equal s ( )
Notice that Card overrides both Object. equals() and Object. has hCode(). The
Vector, Hashtabl e, and other data struc-
ture classes use equal s( ). A Vector is
like an array that automatically grows as new objects are added to it. A Hashtabl e is a vector where the objects are located by using a key object, such as a string, instead of an index. Ha s ht a bl e uses a hash code I an integer) -calculated by calling the hashCode ( ) method for the key object-to find the object ~n the hash table. You need to maintain certain elationships between equals( and
=zhCode( ) in order for an object to work orrectly with these data structures. While the -d objects work fine with the array used by -ie Deck class, the games that use the Ca rd
Card objects in
So whenever you override
-_~ 's() or hashCode( ),you must make
Z- , a that the following expressions remain _,_ Q
a,equals(b) == b.equals(a) i f (a,equals(b))
a.hashCode() == b.hashCode()
I n addition, both |
equals( and |
|
hashCode( ) need to return consistent results throughout the lifetime of the object. The data structures use equals( and hashCode ( ) to organize and find the objects placed in the data structure. If the result of
or
changes,theob-jectcouldbecome"lost"inthedatastructure. You use immutable (unchangeable) attributes to calculate the hash code and perform the equality comparison consistently. For ex-
ample, the Card class uses the suit |
and |
r a n k attributes in both eq u a 1 s ( ) |
and |
but never changes their values constructor runs.
Extracting card graphics from a composite image
Ca rd contains several methods that load and display card graphics. Instead of loading 54 individual images (52 cards plus a joker and a card back), the methods in C a r d use a composite image (a single large image that contains all the individual images) and then extract a piece of the composite image to draw the particular card to the screen, as shown in Figure 6-3. Think of the composite image as a quilt of card "patches" from which the code extracts the necessary patch to display.
A single large image downloads faster, requires less memory to store, and is easier to create and edit than multiple small images. It also keeps all the
i mages for a deck of cards encapsulated in a single object, and that fits well with an object-oriented design.
Part II: Up to Speed
10 4
|
|
|
2 |
|
3 |
; |
44 |
|
54 ; |
|
7 6 |
|
~ 48 |
|
14,4 |
J |
|
|
|
|
|
|
||
|
J(MtBlt |
|
|
; |
|
|
|
|
|
|
|
|
|
|
||||||||||
|
|
4 |
|
|
|
4 |
|
|
4 |
4 4 |
,, 4 4 |
4 4 |
4 |
|
4 |
4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*4* 4 |
|
•*' |
|
|
|
|
|
|
|
|
|||||||
|
a8 M0f |
|
|
i |
|
* s |
! • ; |
1 **S |
t •• |
'' • *L |
I **•• |
9 t • |
a |
w |
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
s |
s |
t |
e |
|
|
|
|
|
|
|
|
|
|
||||
|
|
|
2 |
|
3 |
• |
44 4 |
5• 4 |
6 |
|
84 4 |
9• 4 |
q 4,b |
j |
• |
C7 |
|
|
|
|||||
|
Sti |
|
|
|
|
4 |
|
|
|
4 4 |
• 4 |
4 |
~4~ |
|
|
|
|
|
|
|
|
|
o• |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
w |
• |
3 |
• |
4• • |
;• • |
6• • |
~• • |
8• i |
9.• • |
|
~••• |
:i |
'',., |
|
Q~ry.. • |
Y |
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
|
|
|
|
|
|
|
|
|
Figure 6-3: |
• |
i |
|
|
1 |
|
|
• |
• • |
• 4 |
••• |
|
|
|
|
|
|
|
|
|
|
|
|
|
The |
|
|
~ 'z |
|
|
|
|
• •S |
• •g |
• •L |
|
|
|
|
|
|
|
- |
•' =~, I |
|
|
|
||
y |
, |
|
• q |
• +j+ |
• •'g |
• •6 |
• • •01 |
|
|
y |
|
|||||||||||||
"cards.gif" |
|
|
|
: r |
4V |
|
*;•w7,~~;V |
V;•* |
|
|
~,f*I |
|
r |
: |
~.:.: |
|
|
|||||||
composite |
|
|
Z |
|
£ |
|
*4 |
S |
4 |
L |
8 |
|
|
|
|
|
|
|
|
I |
|
|
|
|
image. |
|
|
|
|
|
6 |
|
|
A01 |
|
|
f |
|
g |
|
|||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|||||||||||||
Four rows of 14 card images - one row for each suit - combine to form the composite image. You call the static method i n i t G r a p h i c s ( ) to load the combined image. i n i t G r a p h i c s ( ) uses the Medi aTracker class discussed i n Chapter 5 to load the cardsImage image. i n i t G r a p h i c s ( ) calculates the width (ca rdWi dth) of an individual card by dividing the composite image's width by 14 and the height (c a rd H e i g h t) by dividing the composite's height by 4.
To draw a single card rather than the entire c a rd s I ma g e graphic, you need to define a clipping region to restrict where the graphics context draws pixels. To understand how a clipping region works, imagine that the graphics context is a piece of paper. To create a clipping region take another piece of paper the same size, cut a rectangular hole in it, and position it over the first piece of paper. Now when you draw on the graphics context, you can only draw within the clipping region. To draw a card from the c a r d s I m a g e graphic, you make the clipping region the size of a single card, then position c a r d s I ma g e to line up the desired card with the clipping region and call
d r a w I m a g e ( ) to draw the card. Figure 6-4 shows what this clipping image looks like if c a r d s I m a g e is a third piece of paper and you place it between the other two sheets of paper.
The draw( ) and drawCardBack( ) methods use card Wi dth and
c a r d H e i g h t to calculate the x,y pixel coordinates of the upper-left corner of the image to extract from c a r d s I ma ge. These methods pass the x,y pixel coordinates to d o D r a w ( ), which uses them as the offsets for positioning
c a rd s I ma g e so that the card to be drawn is in the correct place relative to the clipping region. d o D r a w ( ) sets the clipping region and draws the card using the following code:
Graphics gcopy = g.create();
gcopy.clipRect(x, y, cardWidth, cardHeight); gcopy.drawImage(cardsImage, x - xoff, y - yoff, null); gcopy.dispose();
