RichardBerg : FinalProgrammerManual

FavoriteLinksCondensed :: PageIndex :: RecentChanges :: RecentlyCommented :: UserSettings

Blackjack Programmer's Manual


Overview

The main program flow is in game.cpp (main does absolutely nothing, as you'll soon see). One game cycle occurs in play(): we get the number of players from the user, their game-specific attributes, we play, we show statistics, and we ask whether we want to play again.

The middle three parts are overloaded by BJGame. GetPlayers() is long but very straightforward; doGame() is where the action is, so much so that I believe it will showcase how everything else fits into place. The first loop demonstrates how the Shoe (owned by the game) interacts with the Board (also owned by the game, but passed around like Erik's mom). The first overloaded function of Gambler : Player is also shown; note that unlike every other member function, it only relies on the player's state (i.e. bankroll).

The next two loops are short but pack a lot of punch. ShowCards with a single argument instructs the player to instruct his Strategy that everything on the board (except the dealer's down card!) should be taken into account and added to whatever internal state the Strategy carries. ProcessSubHand is a nifty function that serves as the real internal gameplay: it matches available choices (from the Rules) to chosen choices (from the player's Strategy), then updates the game state appropriately. Hopefully the recursive call isn't so "nifty" as to be unmaintainable.

The other loop once again allows the Strategies to update their game state. This time they only look at the hands more recently played than the ones they've seen (in short, the >2 cards of themselves and everyone after them, including the dealer but not his former upcard). UpdateBankroll updates the player's bet using a multiplier from Rules, which knows how to compare a person's card to the dealer's. Both of these functions are simpler than you might imagine, because actual scoring of a hand is done within BJHand; to facilitate quick comparison, BJHand assigns 0 to a bust, 1-21 to a normal hand, 50 to a 5-card draw, and 100 to a Blackjack.


Extending

FirstProgrammerManual remains an excellent guide to the other classes in use. Needless to say there are a ton more files than are presented there, but they follow a relatively simple naming scheme and only pertain to the advanced strategies (see StrategiesManual). I'll add to my previous class layout the individual steps needed to add a new Rules or Strategy.

Rules:

Strategy:

Note that the play class hierarchy is designed to make putting together any strategy or mix of strategies as easy as possible. You can push back whatever you like into the vector of play pointers and then write getChoice accordingly. For example, with a very modest effort, you could combine the smart and counting strategies into a mix that obviously must count the cards but that sometimes ignores the count by simply using the corresponding basic play for any given situation. This could be done by creating a new strategy subclass and pushing back counting play pointers for some situations and basic play pointers for others. Over a very large number of trials, using a driver program as a script to force certain hands, you could then evaluate the advantage gained by counting the cards and modifying your plays accordingly for any number of specific situations.

When adding your new classes to the main program, the change is very easy: #include the new foo.h at the top of bjgame.cpp and push_back your object in the BJGame constructor.


Design Decisions

Countless decisions go into making a program of this size, but two come to mind as greatly impacting the rest of the program's development. First was the way I decided to "overload" the formerly simple BJHand class to allow split functionality. This required all manner of ugliness: storing extra sub-hands in unused sections of the vector, recalculating all the internal values to include possible split processing, etc. Nothing too terrible, but the mere idea of storing multiple hands in a Hand struck me as ugly. Luckily, it's all encapsulated: calling classes have to specify which hand they're talking about at every turn, but that's no less annoying than an internal implementation of, say, a deque of Hands would've been. (And actually, the scoring of potentially split hands is completely encapsulated thanks to the fact we return just a multiplier).

The second decision was the way I created a Rules inheritance hierarchy. My comments in the rules.h file are apropos: The plus is that any given set of rules can be encapsulated into a single class. The minus is that implementing every combination requires a # of classes that's exponential in the #of rules, whereas, say, a single ugly class with 10 arguments in the constructor and tons of logic in the methods would avoid this. Given this setup, it's clear we're best off implementing things like "Basic Rules" and "Typical Vegas Rules" rather than trying to explicitly show off several rule combinations.

I muse in processSubHand() that perhaps Choice should be a class instead of a typedef, but I really don't think changing boolean tests to method calls would be any clearer. Nor would it clean up the if/else in that function, without passing it a crapload of parameters each time. For the record, I still think it's clearer to say Choice ch = CHOICE_FOO | CHOICE_BAR | CHOICE_BAZ | CHOICE_QUUX; than Choice ch = addChoice( addChoice(CHOICE_FOO, CHOICE_BAR), addChoice(CHOICE_BAZ, CHOICE_QUUX) );. Granted I showcased the inline function in the worst possible light, but the point stands: how can an 'or' operator be unclear when operating on Choices?


Testing

There are four cpp files that define auxilary main() functions and serve as test programs. These are: testsmart, testcount, testbjhand, and testdeck. They do simply as their name implies, but serve very important purposes in verifying that the correct choices are allowed and made, since the main program's control flow is entirely automated. The testsmart program verifies the basic strategy (herein called smart strategy), while testcount verifies the card-counting strategy and several other functionalities. Use the alternate compile options in the Makefile to create and run these test cases; see TestprogramManual for more details.


Future development

I've outlayed the steps necessary to add additional Rules or Strategies as expliticly as I can without knowing the desired goal. Extra functionality of the program as a whole, however, is easy to suggest. On the minor side, having Insurance be a part of gameplay would put a big twist in the current control flow. It's possible, but would require either an ugly hack or a change in the overall structure. Even more significant, one could imagine a "Manual" strategy wherein the user gets to play interactively. The actual Strategy interface would be relatively straightforward -- I think -- but adding in-game output would be harder than it seems. The logical place to put it would be in processSubHands, but lots of implementation details crop up. How do you know where on the screen player X should go, when you don't know the # of players? How do you know you're in a split, much less reprint the board as necessary? I won't hurt my brain thinking of more.


Back to BlackjackDesign

There are no comments on this page. [Add comment]

Valid XHTML 1.0 Transitional :: Valid CSS :: Powered by Wikka Wakka Wiki 1.1.6.4
Page was generated in 0.0704 seconds