This is a set of "slides" to accompany chapter 9 of Timothy Budd's textbook Understanding Object-Oriented Programming with Java (Addison-Wesley, 1998).
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 CSCI 581 Lecture Notes root document?