Outline
Source
Other Material
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; }
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; }
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) { ... } }
Note that most of the game consists of different types of card piles.
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 }
By using the inherited default behavior, only a small number of the possible methods must actually be implemented.
CardPile | SuitPile | DeckPile | DiscardPile | TableauPile | |
includes | X | X | |||
canTake | X | X | X | ||
addCard | X | X | |||
display | X | X | |||
select | X | X | X | X |
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.
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.
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.
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; } } }
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; }
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()); } }
The most important features of this case study: