440 likes | 592 Views
Engr 691 Special Topics in Engineering Science Software Architecture Spring Semester 2004 Lecture Notes. Solitaire Case Study (Budd's UOOPJ, Ch. 9). This is a set of slides to accompany chapter 9 of Timothy Budd's textbook Understanding Object-Oriented Programming with Java,
E N D
Engr 691Special Topics in Engineering ScienceSoftware ArchitectureSpring Semester 2004Lecture Notes
Solitaire Case Study (Budd's UOOPJ, Ch. 9) 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)
The Solitaire Game • Good example of inheritance in action • Constructed from classes for cards, card piles, and the game application
Class 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;
Class Card (continued) // constructors public Card (int sv, int rv) { s = sv; r = rv; faceup = false; } // mutators public final void flip() { faceup = ! faceup; }
Class Card (continued) // accessors public final int rank () { return r; } public final int suit() { return s; } public final boolean faceUp() { return faceup; }
Class Card (continued) 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 }
Notes on Class Card • Class implements playing card abstraction • Textbook code rearranged into our standard format • import makes package java.awt available to program—part of Java API • Variables and classes can be used before defined (except local variables in methods)
Notes on Class Card (continued) Java has no global variables, constants, or enumerated data types • static data fields (i.e., class variables) used for global • final data fields used for constants – assigned once, then not redefined • Three internal data fields • 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() • cannot be overridden in subclasses • can be optimized – expanded inline like a macro – efficient but inhibits reuse! • draw is not final – implementation may be somewhat platform dependent
Card Images See Figure 9.3 on page 145 of Budd's UOOPJ.
Method 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());
Method draw (continued) 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); } }
Method draw (continued) 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); } } }
Notes on Method draw • Displays the card on screen • Parameter is current graphics context • argument is object of class java.awt.Graphics • class Graphics in Java API provides many drawing primitives • methods for printing bit-maps available for more sophisticated visuals • Constants such as Color.red and Color.black defined in class java.awt.Color • draw is instance method – every card responsible for drawing itself
The Game • See Figure 9.4 on page 145 of Budd's UOOPJ.
Class 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(); }
Class CardPile (continued) //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 */ }
Class CardPile (continued) // 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); }
Class CardPile (continued) // protected data fields // coordinates of the card pile protected int x; // (x,y) is upper left corner protected int y; // cards in the pile protected Stack thePile; }
Notes on Class CardPile • Implementation of card deck abstraction – base class extended for specific behaviors • Uses generic container Stack from Java API for pile of cards • empty initially • Stack operations implement Card operations – adapter pattern • Cast objects back to Card when taken from stack • Uses global symbolic constants 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 accessible by subclasses • Instructor's comment: • 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)
Informal Specification of CardPile's Non-final Methods Methods may need to be overridden to give the card deck the appropriate behaviors. • addCard(c) • adds card c to the card pile • select(x,y) • performs an action in response to mouse click at (x,y) • includes(x,y) • determines whether (x,y) within boundary of the pile • canTake(c) • determines whether the pile can take card c (according to the rules governing the pile) • display(g) • displays the pile using graphics context g
Class 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()); } }
Notes on Class SuitPile • Represents one of the (4) piles of cards at top of playing surface – built up in suit from Ace (1) to King (13) • Keyword extends indicates inheritance from CardPile • super indicates parent – in constructor used to call parent's constructor for initialization – first statement only • Method canTake overridden by replacement – canTake if pile empty or card is next higher of same suit
Class 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()); } }
Notes on Class DeckPile • Represents the original deck of cards • Uses super in constructor for basic initialization, new code for specific initialization • Accesses static (class) methods – absolute value function Math.abs() • Overrides method select by replacement – if pile nonempty, add top card to discard pile • Java API Stack extends Vector – select uses the "ranked sequence" methods of Vector • Instructor's comment: • 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
Notes on Class DeckPile (continued) • Accesses utility class java.util.Random to generate pseudo-random numbers • Accesses static (class) variables to share single copies • Solitaire.discardPile • Author has only one discard pile, so made this class access that instance directly • Instructor's comment: • Hard-coding of reference to static discard pile inhibits reusability of the deck • Probably better to pass discard deck to constructor of DeckPile, store reference, manipulate it
Notes on Class DeckPile(continued) • Manipulates protected field thePile from parent class CardPile Possible alternatives to use of protected data fields: • Add (protected?) "ranked sequence" get and set operations to CardPile abstraction? • Add constructor or (protected?) set operation to CardPile that takes a sequence (vector, array, etc.) of cards to (re)initialize pile? get method? • Add shuffle operation to CardPile abstraction?
Class 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); }
Class DiscardPile (continued) //mutators 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); } }
Notes on Class DiscardPile • Represents the discard pile in a Solitaire game • Constructor refines parent's constructor – uses 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 • Hard-coded access to static variables for the (4) suit piles and (7) tableau piles • probably better to pass these to constructor, store references, and manipulate via local references
Class 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(); }
Class TablePile (continued) // 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); }
Class TablePile (continued) // 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; } } }
Notes on Class TablePile • Represents one of the (7) tableau piles on the lower part of the playing surface • Initialized with cards from deck – constructor argument gives number needed • Top card is face up • Add card to empty pile only if King • Add card to nonempty pile only if opposite color to top and one rank lower • Selection action moves any cards from this table pile to suit pile or another table pile • Uses super in constructor for basic initialization, new code for specific initialization • Methods select, canTake, includes, and display override and replace ones in parent • Method display • Uses java.util.Enumeration object returned by the Stack to iterate through the tableau pile • Displays each card slightly offset from the one below it • Accesses 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)
The Game Class: 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 -- aids polymorphism static public CardPile allPiles [ ];
The Game Class: Solitaire (continued) // application entry point static public void main (String[] args) { Solitaire world = new Solitaire(); } // constructors public Solitaire () { window = new SolitaireFrame(); init(); window.show(); }
The Game Class: Solitaire (continued) //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); }
The Game Class: Solitaire (continued) // inner classes private class SolitaireFrame extends Frame { /* expanded later */ } // internal data fields private Frame window;// the application window }
Notes on Class Solitaire • Class declares and allocates class (static) variables for piles of cards on playing surface • Class sets up allpiles array to "alias" all other piles, regardless of subclass • Control goes initially to class method 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 class (from the glossary to Budd's UOOPJ textbook): • a class definition that appears inside another class. Inner classes are allowed access to both the private data fields and the methods of the surrounding class. Inner classes are frequently used in building listener objects for handling events. • SolitaireFrame manages the application window
Inner Class SolitaireFrame // part of Class Solitaire private class SolitaireFrame extends Frame { private class RestartButtonListener implements ActionListener { public void actionPerformed (ActionEvent e) { init(); window.repaint(); } }
Inner Class SolitaireFrame(continued) 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(); } } }
Inner Class SolitaireFrame(continued) 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); } }
Notes on Class SolitaireFrame • GUI window for the Solitaire game application • Extends java.awt.Frame class • Event-driven program – system GUI manager in control, user code "called back" in response to GUI events • SolitaireFrame constructor • sets window size, title • adds mouse listener "callback" code to respond to mouse events • adds restart button at bottom of window ("South") and "callback" code to respond to button events
Notes on Class SolitaireFrame (continued) • Inner class MouseKeeper's method mousePressed called on mouse click event • determines which pile selected • performs select operation for that pile • note use of polymorphism • action depends on pile type (i.e., on which subclass) • repaints window • Inner class RestartButtonListener method actionPerformed called on button event • reinitializes game • repaints window • Method paint of SolitaireFrame called by GUI when frame needs to be displayed • note use of polymorphism