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: