This is a set of "slides" to accompany chapter 9 of Timothy Budd's textbook Understanding Object-Oriented Programming with Java, Updated Edition (Addison-Wesley, 2000).
Card
import java.awt.*;
public class Card
{
// public constants for card dimensions
public final static int width = 50;
public final static int height = 70;
// public constants for card suits
public final static int heart = 0;
public final static int spade = 1;
public final static int diamond = 2;
public final static int club = 3;
// constructors
public Card (int sv, int rv)
{ s = sv; r = rv; faceup = false; }
// mutators
public final void flip() { faceup = ! faceup; }
// accessors
public final int rank () { return r; }
public final int suit() { return s; }
public final boolean faceUp() { return faceup; }
public final Color color()
{ if (faceUp())
if (suit() == heart || suit() == diamond)
return Color.red;
else
return Color.black;
return Color.yellow;
}
public void draw (Graphics g, int x, int y)
{ /* ... */ }
// internal data fields
private boolean faceup; // card face exposed?
private int r; // card rank
private int s; // card suit
}
Card
import makes package java.awt available
to program -- part of Java API
static data fields (i.e., class variables) used for
global
final data fields used for constants -- assigned
once, then not redefined
rank and suit are read-only --
accessors but no mutators
faceup can be modified -- flip()
mutator
draw is complex method -- accessor with side effects
final methods -- accessors rank(),
suit(), faceup(), and color(),
and mutator flip()
draw is not final -- implementation may
be somewhat platform dependent
See Figure 9.3 on page 143 of Budd's UOOPJ.
draw
import java.awt.*;
public class Card
{ // ...
public void draw (Graphics g, int x, int y)
{ String names[] = {"A", "2", "3", "4", "5", "6",
"7", "8", "9", "10", "J", "Q", "K"};
// clear rectangle, draw border
g.clearRect(x, y, width, height);
g.setColor(Color.blue);
g.drawRect(x, y, width, height);
// draw body of card
g.setColor(color());
if (faceUp())
{ g.drawString(names[rank()], x+3, y+15);
if (suit() == heart)
{ g.drawLine(x+25, y+30, x+35, y+20);
g.drawLine(x+35, y+20, x+45, y+30);
g.drawLine(x+45, y+30, x+25, y+60);
g.drawLine(x+25, y+60, x+5, y+30);
g.drawLine(x+5, y+30, x+15, y+20);
g.drawLine(x+15, y+20, x+25, y+30);
}
else if (suit() == spade)
{ g.drawLine(x+25, y+20, x+40, y+50);
g.drawLine(x+40, y+50, x+10, y+50);
g.drawLine(x+10, y+50, x+25, y+20);
g.drawLine(x+23, y+45, x+20, y+60);
g.drawLine(x+20, y+60, x+30, y+60);
g.drawLine(x+30, y+60, x+27, y+45);
}
else if (suit() == diamond)
{ g.drawLine(x+25, y+20, x+40, y+40);
g.drawLine(x+40, y+40, x+25, y+60);
g.drawLine(x+25, y+60, x+10, y+40);
g.drawLine(x+10, y+40, x+25, y+20);
}
else if (suit() == club)
{ g.drawOval(x+20, y+25, 10, 10);
g.drawOval(x+25, y+35, 10, 10);
g.drawOval(x+15, y+35, 10, 10);
g.drawLine(x+23, y+45, x+20, y+55);
g.drawLine(x+20, y+55, x+30, y+55);
g.drawLine(x+30, y+55, x+27, y+45);
}
}
else
{ // face down
g.drawLine(x+15, y+5, x+15, y+65);
g.drawLine(x+35, y+5, x+35, y+65);
g.drawLine(x+5, y+20, x+45, y+20);
g.drawLine(x+5, y+35, x+45, y+35);
g.drawLine(x+5, y+50, x+45, y+50);
}
}
}
draw
java.awt.Graphics
Graphics in Java API provides many drawing
primitives
Color.red and
Color.black defined in class java.awt.Color
draw is instance method -- every card responsible
for drawing itself
See Figure 9.4 on page 143 of Budd's UOOPJ.
CardPile
import java.awt.*;
import java.util.Stack;
import java.util.EmptyStackException;
public class CardPile
{
// constructors
public CardPile (int xl, int yl)
{ x = xl; y = yl; thePile = new Stack(); }
// mutators
public final Card pop()
{ try { return (Card) thePile.pop(); }
catch (EmptyStackException e) { return null; }
}
public void addCard (Card aCard) // sometimes overridden
{ thePile.push(aCard); }
public void select (int tx, int ty) // sometimes overridden
{ /* do nothing */ }
// accessors
public final Card top()
{ return (Card) thePile.peek(); }
public final boolean isEmpty()
{ return thePile.empty(); }
public boolean includes (int tx, int ty) // sometimes overridden
{ return x <= tx && tx <= x + Card.width &&
y <= ty && ty <= y + Card.height;
}
public boolean canTake (Card aCard) // sometimes overridden
{ return false; }
public void display (Graphics g) // sometimes overridden
{ g.setColor(Color.blue);
if (isEmpty())
g.drawRect(x, y, Card.width, Card.height);
else
top().draw(g, x, y);
}
// protected data fields
protected int x; // coordinates of the card pile
protected int y; // (x,y) is upper left corner
protected Stack thePile; // cards in the pile
}
CardPile
Stack from Java API for pile
of cards
Stack operations implement Card
operations -- adapter pattern
Card when taken from stack
Card.width and
Card.height
-- access to static fields using class name, not instance name (actually, either works)
pop, top, and isEmpty
implementation for subclasses -- final methods
addCard, select, includes,
canTake, and display part of abstraction,
but implementation varies among subclasses
protected data fields usually bad practice -- trusts
subclasses (perhaps in different package) to manipulate internal
fields correctly
Safer to provide appropriate protected mutators and accessors
instead (perhaps final)
CardPile's
Non-final MethodsThe following methods may need to be overridden to give the card deck the appropriate behaviors.
addCard(c)
c to the card pile
select(x,y)
x,y)
includes(x,y)
x,y) within
boundary of the pile
canTake(c)
c
(according to the rules governing the pile)
display(g)
g
SuitPile
class SuitPile extends CardPile
{
// constructors
public SuitPile (int x, int y) { super(x, y); }
// accessors
public boolean canTake (Card aCard) // overrides parent
{ if (isEmpty())
return aCard.rank() == 0;
Card topCard = top();
return (aCard.suit() == topCard.suit()) &&
(aCard.rank() == 1 + topCard.rank());
}
}
SuitPile
extends indicates inheritance from
CardPile
super indicates parent -- in constructor used to
call parent's constructor for initialization -- first statement only
canTake overridden by replacement --
canTake if pile empty or card is next higher of same suit
DeckPile
import java.util.Random ;
class DeckPile extends CardPile
{
// constructors
public DeckPile (int x, int y)
{ // first initialize parent
super(x, y);
// then create the new deck
// first put them into a local pile
for (int i = 0; i < 4; i++)
for (int j = 0; j <= 12; j++)
addCard(new Card(i, j));
// then shuffle the cards
Random generator = new Random();
for (int i = 0; i < 52; i++)
{ int j = Math.abs(generator.nextInt() % 52);
// swap the two card values
Object temp = thePile.elementAt(i);
thePile.setElementAt(thePile.elementAt(j), i);
thePile.setElementAt(temp, j);
}
}
// mutators
public void select(int tx, int ty)
{ if (isEmpty())
return;
Solitaire.discardPile.addCard(pop());
}
}
DeckPile
super in constructor for basic initialization,
new code for specific initialization
Math.abs()
select by replacement -- if pile
nonempty, add top card to discard pile
Stack extends Vector --
select uses the "ranked sequence" methods of
Vector
Stack extending Vector is poor design
in the Java API! Similarly, the choice of Stack is a
questionable design.
Better to just use Vector directly
java.util.Random to generate
pseudo-random numbers
Solitaire.discardPile
Author has only one discard pile, so made this class access that instance directly
Probably better to pass discard deck to constructor of
DeckPile, store reference, manipulate it
protected field thePile
from parent class CardPile
Possible alternatives to use of protected data fields:
protected?) "ranked sequence" get and set
operations to CardPile abstraction?
protected?) set operation to
CardPile that takes a sequence (vector, array, etc.) of
cards to (re)initialize pile? get method?
CardPile abstraction?
DiscardPile
import java.util.Random;
class DiscardPile extends CardPile
{
// constructors
public DiscardPile (int x, int y) { super (x, y); }
// mutators
public void addCard (Card aCard)
{ if (! aCard.faceUp())
aCard.flip();
super.addCard(aCard);
}
public void select (int tx, int ty)
{ if (isEmpty())
return;
Card topCard = pop();
for (int i = 0; i < 4; i++)
if (Solitaire.suitPile[i].canTake(topCard))
{ Solitaire.suitPile[i].addCard(topCard);
return;
}
for (int i = 0; i < 7; i++)
if (Solitaire.tableau[i].canTake(topCard))
{ Solitaire.tableau[i].addCard(topCard);
return;
}
// nobody can use it, put it back on our list
addCard(topCard);
}
}
DiscardPile
super call in initialization
select method overrides and replaces one in
parent
Checks whether topmost card can be played any suit pile or tableau pile
addCard method overrides and refines one in
parent
Executes parent method (super.addCard), but also adds new behavior
Flips card faceup when put on discard pile
Probably better to pass these to constructor, store references, and manipulate via local references
TablePile
import java.util.Enumeration ;
class TablePile extends CardPile
{ // constructors
public TablePile (int x, int y, int c)
{ // initialize the parent class
super(x, y);
// then initialize our pile of cards
for (int i = 0; i < c; i++)
{ addCard(Solitaire.deckPile.pop()); }
// flip topmost card face up
top().flip();
}
// mutators
public void select (int tx, int ty)
{ if (isEmpty())
return;
// if face down, then flip
Card topCard = top();
if (! topCard.faceUp())
{ topCard.flip();
return;
}
// else see if any suit pile can take card
topCard = pop();
for (int i = 0; i < 4; i++)
if (Solitaire.suitPile[i].canTake(topCard))
{ Solitaire.suitPile[i].addCard(topCard);
return;
}
// else see if any other table pile can take card
for (int i = 0; i < 7; i++)
if (Solitaire.tableau[i].canTake(topCard))
{ Solitaire.tableau[i].addCard(topCard);
return;
}
// else put it back on our pile
addCard(topCard);
}
// accessors
public boolean canTake (Card aCard)
{ if (isEmpty())
return aCard.rank() == 12;
Card topCard = top();
return (aCard.color() != topCard.color()) &&
(aCard.rank() == topCard.rank() - 1);
}
public boolean includes (int tx, int ty)
{ // don't test bottom of card
return x <= tx && tx <= x + Card.width && y <= ty;
}
public void display (Graphics g)
{ int localy = y;
for (Enumeration e = thePile.elements(); e.hasMoreElements();)
{ Card aCard = (Card) e.nextElement();
aCard.draw (g, x, localy);
localy += 35;
}
}
}
TablePile
super in constructor for basic initialization,
new code for specific initialization
select, canTake,
includes, and display override and
replace ones in parent
display
java.util.Enumeration object returned by the
Stack to iterate through the tableau pile
protected field thePile
from parent class CardPile
Perhaps define (protected?) elements()
method in CardPile to return a
pile-Enumeration object -- to avoid
protected fields
Another alternative: method to return cards in pile as sequence (e.g., array or vector)
Solitaire
import java.awt.*;
import java.awt.event.*;
public class Solitaire
{
// public class variables for the various card piles of game
static public DeckPile deckPile;
static public DiscardPile discardPile;
static public TablePile tableau [ ];
static public SuitPile suitPile [ ];
// single array to alias all above piles -- to aid polymorphism
static public CardPile allPiles [ ];
// application entry point
static public void main (String[] args)
{ Solitaire world = new Solitaire(); }
// constructors
public Solitaire ()
{ window = new SolitaireFrame();
init();
window.show();
}
// mutators
public void init ()
{ // first allocate the arrays
allPiles = new CardPile[13];
suitPile = new SuitPile[4];
tableau = new TablePile[7];
// then fill them in
allPiles[0] = deckPile = new DeckPile(335, 30);
allPiles[1] = discardPile = new DiscardPile(268, 30);
for (int i = 0; i < 4; i++)
allPiles[2+i] = suitPile[i] =
new SuitPile(15 + (Card.width+10) * i, 30);
for (int i = 0; i < 7; i++)
allPiles[6+i] = tableau[i] =
new TablePile(15 + (Card.width+5) * i, Card.height + 35, i+1);
}
// inner classes
private class SolitaireFrame extends Frame
{ /* expanded later */ }
// internal data fields
private Frame window; // the application window
}
Solitaire
static)
variables for piles of cards on playing surface
allpiles array to "alias" all other
piles, regardless of subclass
main
main creates an instance of Solitaire
application (its only action)
Solitaire constructor creates window for
application, initializes the playing surface, and displays window
SolitaireFrame is an inner class
Inner classes are frequently used in building listener objects for handling events.
SolitaireFrame manages the application window
SolitaireFrame
// part of Class Solitaire
private class SolitaireFrame extends Frame
{
private class RestartButtonListener implements ActionListener
{
public void actionPerformed (ActionEvent e)
{ init();
window.repaint();
}
}
private class MouseKeeper extends MouseAdapter
{
public void mousePressed (MouseEvent e)
{ int x = e.getX();
int y = e.getY();
for (int i = 0; i < 13; i++)
if (allPiles[i].includes(x, y))
{ allPiles[i].select(x, y);
repaint();
}
}
}
public SolitaireFrame()
{ setSize(600, 500);
setTitle("Solitaire Game");
addMouseListener (new MouseKeeper());
Button restartButton = new Button("New Game");
restartButton.addActionListener(new RestartButtonListener());
add("South", restartButton);
}
public void paint(Graphics g)
{ for (int i = 0; i < 13; i++)
allPiles[i].display(g);
}
}
SolitaireFrame
java.awt.Frame class
SolitaireFrame constructor
MouseKeeper's method mousePressed
called on mouse click event
select operation for that pile
RestartButtonListener method
actionPerformed called on button event
paint of SolitaireFrame called
by GUI when frame needs to be displayed
UP to ENGR 691 Lecture Notes root document?