by Aaron Ballman

Have you ever found yourself wondering how to make a card game, but don't want to go through all the hassles of making the graphics and putting all the logic into drawing cards? Then you're a lot like me! There are a lot of fun card games out there that I want to make (because I never seem to find them in commercial products -- like War and Pyramid Solitaire), but I'm a terrible artist. However, I've always found that it's better to avoid reinventing the wheel whenever possible -- and Microsoft has been kind enough to put all the legwork into card games for you!

We're going to leverage the Microsoft cards.dll library in REALbasic in order to create our own set of classes. Using these classes, we'll be able to implement any card game we'd like -- and as an example, we'll make War.

Cards.dll is installed on every Windows OS that REALbasic supports and has a very simple API. You need to initialize the library before you use it, and finalize the library once you're done with it. Then there are three drawing APIs which let you draw a card normally, stretched or animated. Using these simple functions, we'll build a CardManager class, and a Card helper class. First, let's look at the manager.

Since we need to do initialization and finalization operations, the manager is a great place to put a Constructor and Destructor which take care of this for us. So after adding the Constructor method, it's just a matter of calling the proper initialization function:


  Soft Declare Sub cdtInit Lib "Cards" ( ByRef cx as Integer, ByRef cy as Integer )

  if System.IsFunctionAvailable( "cdtInit", "Cards" ) then
    cdtInit( DefaultCardWidth, DefaultCardHeight )
 end if

In this code snippet, the DefaultCardWidth and DefaultCardHeight parameters are actually class properties of the manager object. We want to keep this information around so that we can space cards out properly when drawing them.

We're going to have some simlilar code in the CardManager's Destructor method so that we can tear down the card library.


  Soft Declare Sub cdtTerm Lib "Cards" ()

  if System.IsFunctionAvailable( "cdtTerm", "Cards" ) then
    cdtTerm
  end if

So far, everything's quite easy! We've gotten two out of the five APIs out of the way. So let's take about drawing next.

The drawing API is very simple -- it takes a device context (which is sort of like the Windows equivalent of a Canvas object), as well as position information about where to draw the card. Then it takes the information about what card to draw, as well as what "mode" to draw the card in. Finally, it takes a color parameter, which is only used when drawing in certain modes. Here's what the declare looks like:


  Soft Declare Sub cdtDraw Lib "Cards" ( hdc as Integer, x as Integer, y as Integer, _
  cd as Integer, md as Integer, bkgnd as Color )

  if System.IsFunctionAvailable( "cdtDraw", "Cards" ) then
    cdtDraw( hdc, x, y, cd, md, bk )
  end if

Since this is the main function for the entire library, we're going to spend some time getting familiar with all of the parameters.

The hdc as Integer parameter is going to be explained away with a lot of hand-waving. REALbasic does not expose a way for you to get a device context (also know as an HDC) from a Graphics object, even though it internally has a reference to one. Plugin authors have a way to get access to this information, and so it's entirely feasible to get it via a plugin. I've abstracted this functionality away in a protected Module called Hacks. You don't need to concern yourself with how I wrangle the HDC out of a Graphics object -- I just wanted to make sure this project relied on no third party plugins.

The x and the y Integer parameters seem like they'd be an easy one to explain, but there is one gotcha about them. A REALbasic Canvas object's HDC appears to require you to pass in these parameters relative to the window's origin. So that means you cannot pass in 0, 0 in a Canvas.Paint event to draw at the upper-left of the Canvas, but must pass in me.Left, me.Top instead. As with most drawing APIs, the x and y are relative to the top/left of the image to be drawn.

The cd as Integer parameter is an encoded integer representing a card. Since a card isn't simply a number, but instead a suit and a rank, these properties are stuffed into a single value. The suits range from the value 0 to 3, and the ranks from 0 to 12. So the forumla for encoding the card is quite simple -- you shift the rank information left by two bits, and then add on the suit information. For those of you who are scared by bitwise operations, you can define the "card" as being: card = (rank * 4) + suit But that's not the only thing this parameter represents. The card parameter can also be which card back to draw as well. The meaning of this parameter is actually determined by the next parameter in the list.

The md as Integer determines the drawing mode, and is a simple enumeration of values. It lets you pick drawing things either face up or face down. But it also lets you do some other crazy things -- like draw the card inverted in color, or draw special "cards" (like the "ghost" card, which is really just a graphic meaning "a card goes here", or the X and O images).

Finally, we have the color parameter. This is used only for certain modes and affects how the images are drawn. For instance, in "ghost" mode, it determines the cross-hatch color to draw with. For the hilite mode, it determines the souce inversion color. In "remove" mode, it draws a simple rectangle, much like how Graphics.FillRect works.

Now that we've seen how the drawing routine works, let's briefly discuss the other offshoot routines. There's an "extended" API which allows you to draw the cards scaled. This routine takes two extra parameters (a width and a height), but produces rather ugly results, and so it should be avoided. Then there's an animation API, which draws the parts of the card which are animated. Only certain card backs have animations associated with them, and those animations either have 2 or 4 frames to them. When you call the animation API, it doesn't draw the entire card over, just the part of the card which is animated.

Let's put all our new API knowledge together into the new set of classes we discussed. We've already seen the beginning of the CardManager class. But let's fill it out with some more functionality. A good manager should be responsible for all of the things a simple Card cannot do. So our manager will return an entire deck of cards (as an array of Card objects), as well as do the non-card-specific drawing operations (drawing a background, drawing a ghost card, and drawing the X/O cards). The Card class itself will be responsible for doing all the card-specific tasks. It retains the suit and rank (as well as information about whether the Ace is considered high or low), as well as the ability to draw a card face up, face down or hilited.

At this point, I'd suggest downloading the sample project, which has the CardManager and Card classes already implemented for you. Since the APIs are quite trivial to implement, I'm going to leave the coding details up to the reader as homework, especially since the sample project has them implemented for you.

Special thanks to Brian Rathbone for back-porting the project so that it works in REALbasic 5.5. You can get the 5.5 version of the project here.

Now we're going to spell out how to implement the game of War using our two new classes. War is a quite simple game: you have an opponent and a deck of cards. Two cards are flipped over (one is yours, the other is your opponent's), and the highest card wins. If the two cards match, you get into a "war" to determine who wins the match. In a war, three cards are given to each player, and then a fourth card is dealt to determine who wins (again, the highest card wins). If those final two cards match, then the war is repeated. Eventually, the winner takes all the cards. At the end of the deck, the person with the most cards win.

We'll start off by defining three different canvases. One will show the stack of cards left in the deck. One will be for the player's card, and one will be for the opponent's card. When the player clicks on the deck, the cards are dealt and the outcome is determined.

To draw the stack of cards, you want to draw the card being face down. The trick that I use is if there are "many" cards (more than 6), I draw only 6 cards, offset slightly from one another. And if there are less than 6 cards, I draw the number of cards remaining. In this fashion, it seems like the deck is getting thinner as the game winds down. When you want to draw a card face down, I've found it's easiest to not use a card from the deck. Instead, I make a special card at the beginning of the game, which is kept separate from the deck which the CardManager object stores, and set its Back property to the card back I wish to have drawn.

Drawing the player and opponent Canvases is the same operation, simply done on two different cards. I keep a reference to which card is currently being displayed for each player. If the card is nil, that means there are no cards displayed -- and so I draw a ghost card to show where the cards will be placed. If there is a card, then I display it.

The most interesting part of the application is the logic behind game itself. When the deck Canvas gets a MouseDown event, I deal out two cards. The two cards have their ranks compared. If they're not equal, then everything is easy -- one player gets both cards added to their total. However, if the card ranks match, then I have to deal out the war cards. So what I do is eat up to 6 cards (3 for each player), and then recursively call my DealCards method with the final pair of cards. In order to properly keep track of the score, the DealCards method takes the number of cards being played for as its final parameter. Eventually, a winner of the duel is determined, and their scores are updated. It's as simple as that!

Traditionally, this game is repeated until one player has lost all their cards, but I'll leave that implementation as an exercise for the user and only demonstrate the basis for the game. Likewise, I will leave it up to you to enhance the game by adding multiplayer support, highscore lists, and any other additions you can think of. Hopefully this sample project will inspire you to create your own card games. I certainly enjoy using this code for my own projects because it means I can focus on the logic of the game without having to worry about the graphics aspect of the project.

This sample source code is provided as-is, with no warranties. If you find any bugs with the project, or have questions, feel free to email me at aaron@aaronballman.com. Good luck, and enjoy the code!