Engr 691-10: Special Topics in Engineering Science
Software Architecture
Spring Semester 2002
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, Updated Edition (Addison-Wesley, 2000).


The Solitaire Game


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;

  // 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
}


Notes on Class Card


Card Images

See Figure 9.3 on page 143 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());
        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);
        }
    }
}


Notes on Method draw


The Game

See Figure 9.4 on page 143 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(); }

  // 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
}


Notes on Class CardPile


Informal Specification of CardPile's Non-final Methods

The following 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


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


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);
    }

    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


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();
    }

  // 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;
        }
    }
}


Notes on Class TablePile


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 -- 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
}


Notes on Class Solitaire


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();
            }
        }

        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);
        }
    }


Notes on Class SolitaireFrame


UP to ENGR 691 Lecture Notes root document?


Copyright © 2002, H. Conrad Cunningham
Last modified: Sun 12 Oct 2003