Introduction to Object Oriented Programming, 3rd Ed

Timothy A. Budd

Chapter 9

The Solitaire Game

Outline

  1. The Solitaire Game
  2. The class PlayingCard
    1. Some Features to Note
  3. Separation of Data and View
    1. The CardView is Eventually Implemented
  4. The Game -- Klondike
  5. Card Piles
    1. The Class CardPile
    2. Some Features to Note
    3. Power of Inheritance
    4. Suit Piles
    5. Deck Pile
    6. Discard Pile
    7. Tableau Piles
  6. The Polymorphic Game
    1. Some Features to Note
  7. The Main Program
  8. Chapter summary

Source

Other Material

Intro OOP, Chapter 9, Outline

The Solitare Game

Intro OOP, Chapter 9, Slide 01

The Class PlayingCard

public class PlayingCard 
{
	public PlayingCard (Suits sv, int rv)
		{ s = sv; r = rv; faceUp = false; }

	public bool isFaceUp 
	{
		get { return faceUp; }
	}
	
	public void flip () 
	{ 
		faceUp = ! faceUp;
	}

	public int rank
	{
		get { return r; }
	}

	public Suits suit
	{
		get { return s; }
	}
		
	public Color color
	{
		get
		{
			if ( suit == Suits.Heart || suit == Suits.Diamond )
				{ return Color.Red; }
			return Color.Black;
		}
	}

	private bool faceUp;
	private int r;
	private Suits s;
}
Intro OOP, Chapter 9, Slide 02

Some Features to Note

Intro OOP, Chapter 9, Slide 03

Separation of Data and View

We reduce the complexity of card and card piles by separating out the data and view classes.
public abstract class CardView
{
	public abstract void display (PlayingCard aCard, int x, int y);

	public static int Width = 50;
	public static int Height = 70;
}
Intro OOP, Chapter 9, Slide 04

The CardView is Eventually Implemented

The abstract class CardView will eventually be matched with an implementation. This implementation can encapsulate information about the specific graphics operations of the system.
public class WinFormsCardView : CardView {
	public WinFormsCardView (Graphics aGraphicsObject) {
		g = aGraphicsObject;
	}

	public override void display (PlayingCard aCard,int x,int y) {
		if  (aCard == null) {
			Pen myPen = new Pen(Color.Black,2);
			Brush myBrush = new SolidBrush (Color.White);
			g.FillRectangle(myBrush,x,y,CardView.Width,CardView.Height);
			g.DrawRectangle(myPen,x,y,CardView.Width,CardView.Height);
		} else {
			paintCard (aCard,x,y);
		}
	}

	private void paintCard (PlayingCard aCard,int x,int y) {
		...
	}
}
Intro OOP, Chapter 9, Slide 05

The Game -- Klondike

picture of game layout
Intro OOP, Chapter 9, Slide 06

Card Piles

Note that most of the game consists of different types of card piles.
Intro OOP, Chapter 9, Slide 07

The Class CardPile

public class CardPile {
	public CardPile (int xl, int yl )
		{ x = xl; y = yl; pile = new Stack(); }

	public PlayingCard top 
		{ get { return (PlayingCard) pile.Peek (); } }

	public bool isEmpty 
		{ get { return pile.Count == 0; }	}

	public PlayingCard pop 
		{ get { return (PlayingCard) pile.Pop (); } }
		
		// the following are sometimes overridden
	public virtual bool includes (int tx, int ty ) {
		return( ( x <= tx ) && ( tx <= x + CardView.Width ) &&
		    ( y <= ty ) && ( ty <= y + CardView.Height ) );
	}

	public virtual void select (int tx, int ty ) {
		// do nothing--override
	}

	public virtual void addCard (PlayingCard aCard )
		{ pile.Push(aCard); }

	public virtual void display (CardView cv) {
		if ( isEmpty ) {
			cv.display(null, x, y);
		} else {
			cv.display((PlayingCard) pile.Peek(), x, y );
		}
	}

	public virtual bool canTake (PlayingCard aCard)
		{ return false; }

	protected int x, y; // coordinates of the card pile
	protected Stack pile; // card pile data
}
Intro OOP, Chapter 9, Slide 008

Some Features to Note

Intro OOP, Chapter 9, Slide 09

Power of Inheritance

By using the inherited default behavior, only a small number of the possible methods must actually be implemented.

CardPile SuitPile DeckPile DiscardPile TableauPile
includesXX
canTakeXXX
addCardXX
displayXX
selectXXXX

Intro OOP, Chapter 9, Slide 10

Suit Piles

public class SuitPile : CardPile {
	public SuitPile (int x, int y) : base(x, y) {	}

	public override bool canTake (PlayingCard aCard ) {
		if( isEmpty ) 
			{ return( aCard.rank == 0 ); }
		PlayingCard topCard = top;
		return( ( aCard.suit == topCard.suit ) &&
		   ( aCard.rank == topCard.rank + 1 ) );
	}
}
Note use of keyword base in constructor, keyword override in canTake, call on properties.
Intro OOP, Chapter 9, Slide 11

DeckPile

public class DeckPile : CardPile {
	public DeckPile (int x, int y) : base(x, y) {
		// create the new deck
		// first put cards into a local array
		ArrayList aList = new ArrayList ();
		for( int i = 0; i <= 12; i++) {
			aList.Add(new PlayingCard(Suits.Heart, i));
			aList.Add(new PlayingCard(Suits.Diamond, i));
			aList.Add(new PlayingCard(Suits.Spade, i));
			aList.Add(new PlayingCard(Suits.Club, i));
		}
			// then pull them out randomly
		Random myRandom = new Random( );
		for(int count = 0; count < 52; count++) {
			int index = myRandom.Next(aList.Count);
			addCard( (PlayingCard) aList [index] );
			aList.RemoveAt(index);
		}
	}

	public override void select (int tx, int ty) {
		if ( isEmpty ) { return; }
		Game.discardPile().addCard( pop );
	}
}
Note use of system provided resources, such as ArrayList, Random.
Intro OOP, Chapter 9, Slide 12

The Discard Pile

public class DiscardPile : CardPile {
	public DiscardPile (int x, int y ) : base(x, y) { }

	public override void addCard (PlayingCard aCard) {
		if( ! aCard.isFaceUp )
			{ aCard.flip(); }
		base.addCard( aCard );
	}

	public override void select (int tx, int ty) {
		if( isEmpty ) { return; }
		PlayingCard topCard = pop;
		for( int i = 0; i < 4; i++ ) {
			if( Game.suitPile(i).canTake( topCard ) ) {
				Game.suitPile(i).addCard( topCard );
				return;
			}
		}

		for( int i = 0; i < 7; i++ ) {
			if( Game.tableau(i).canTake( topCard ) ) {
				Game.tableau(i).addCard( topCard );
				return;
			}
		}
		// nobody can use it, put it back on our stack
		addCard(topCard);
	}
}
Two different types of overriding, replacement and refinement.
Intro OOP, Chapter 9, Slide 13

Tableau Piles

public class TablePile : CardPile {
	public TablePile (int x, int y, int c) : base(x, y) {
		// initialize our pile of cards
		for(int i = 0; i < c; i++ ) {
			addCard(Game.deckPile().pop);
		}	
		top.flip();
	}

	public override bool canTake (PlayingCard aCard ) {
		if( isEmpty ) { return(aCard.rank == 12); }
		PlayingCard topCard = top;
		return( ( aCard.color != topCard.color ) &&
			( aCard.rank    == topCard.rank - 1 ) );
	}

	public override bool includes (int tx, int ty) {
		return( ( x <= tx ) && ( tx <= x + CardView.Width ) &&
				( y <= ty ) );
	}

	public override void select (int tx, int ty) {
		if( isEmpty ) { return; }
		// if face down, then flip
		PlayingCard topCard = top;
		if( ! topCard.isFaceUp ) {
			topCard.flip();
			return;
		}
		// else see if any suit pile can take card
		topCard = pop;
		for(int i = 0; i < 4; i++ ) {
			if( Game.suitPile(i).canTake( topCard ) ) {
				Game.suitPile(i).addCard( topCard );
				return;
			}
		}
		// else see if any other table pile can take card
		for(int i = 0; i < 7; i++ ) {
			if( Game.tableau(i).canTake( topCard ) ) {
				Game.tableau(i).addCard( topCard );
				return;
			}
		}
		addCard( topCard );
	}

	public override void display (CardView cv) {
		Object [ ] cardArray = pile.ToArray();
		int size = pile.Count;
		int hs = CardView.Height / 2; // half size
		int ty = y;
		for (int i = pile.Count - 1; i >= 0; i--) {
			cv.display((PlayingCard) cardArray[i], x, ty);
			ty += hs;
		}
	}
}
Intro OOP, Chapter 9, Slide 14

The Polymorphic Game

public class Game {
	static Game () {
		allPiles = new CardPile[ 13 ];
		allPiles[0] = new DeckPile(335, 5 );
		allPiles[1] = new DiscardPile(268, 5 );
		for( int i = 0; i < 4; i++ ) {
			allPiles[2 + i] = new SuitPile(15 + 60 * i, 5);
		}
		for( int i = 0; i < 7; i++ ) {
			allPiles[6+i] = new TablePile(5+55*i, 80, i+1);
		}	
	}

	public static void paint (CardView cv) {
		for( int i = 0; i < 13; i++ ) {
			allPiles[i].display(cv );
		}
	}

	public static void mouseDown (int x, int y) {
		for( int i = 0; i < 13; i++ ) {
			if( allPiles[i].includes(x, y) ) { 
				allPiles [i].select(x, y);
			}	
		}
	}

	public static CardPile deckPile () { return allPiles[0]; }

	public static CardPile discardPile () { return allPiles[1]; }

	public static CardPile tableau (int index) { return allPiles[6+index]; }

	public static CardPile suitPile (int index) { return allPiles[2+index]; }

	private static CardPile[] allPiles;
}
Intro OOP, Chapter 9, Slide 15

Features to Note

Intro OOP, Chapter 9, Slide 16

The Main Program

Details of the user interface are isolated in the main program. Much of this is automatically generated by the IDE.
public class Solitaire : System.WinForms.Form {
		// start of automatically generated code
        private System.ComponentModel.Container components;

        public Solitaire() { 
            InitializeComponent();
        }

        public override void Dispose() { 
            base.Dispose();
            components.Dispose();
        }

        private void InitializeComponent() { 
		this.components = new System.ComponentModel.Container ();
		this.Text = "Solitaire";
		this.AutoScaleBaseSize = new System.Drawing.Size (5, 13);
		this.ClientSize = new System.Drawing.Size (392, 373);
	}
		// end of automatically generated code

	protected override void OnMouseDown (MouseEventArgs e ) { 
		Game.mouseDown(e.X, e.Y); 
		this.Invalidate(); // force screen redraw
		}
		
	protected override void OnPaint (PaintEventArgs pe ) {
		Graphics g = pe.Graphics;
		CardView cv = new WinFormsCardView(g);
		Game.paint(cv);
	}

	public static void Main(string[] args) 
		{ Application.Run(new Solitaire()); }
}
Intro OOP, Chapter 9, Slide 17

Chapter Summary

The most important features of this case study:
Intro OOP, Chapter 9, Slide 18