Programming Notes

The original DOS version of the program (a 15K COM file, with TXT file for documentation) was written in assembly language in 1989. Most of the effort then went into the discarding and pegging decision routines.

For this 1997 Windows version, the core routines were rewritten in C and compiled/linked as CRIBCORE.DLL (20K). The display interface was rewritten in Visual Basic 3.0 (VB 4.0 seemed like overkill) and compiled as CRIBBAGE.EXE (140K). This in turn requires VBRUN300.DLL (400K), the Microsoft Visual Basic 3.0 runtime library. The help file CRIBBAGE.HLP (60K) was created using the Microsoft Help Compiler. You may already have the runtime DLL in your Windows or System directory from some other installed application.

If you are a C programmer with some idle time, you may also create a custom CRIBALT.DLL to handle discarding/pegging decisions (see Appendix).

Startup:

The main EXE file contains about 15K in bitmaps/icons. The 52 card images are constructed at program startup from a 16-color bitmap consisting of the card numbers, suit pips, and face card images. The board and default card back are drawn rather than loaded from bitmap. Image controls are used instead of PictureBox controls for the most part. The program's use of GDI resources is a little heavy (particularly under Windows 3.1). The About box shows free GDI space.

The program manually redraws its own bitmap images as needed to simulate transparent regions. You may, however, load a picture with true transparent regions as a custom card back. E.g., try loading an Icon file or a Windows metafile as a card back.

On startup, the program also scales the client area (including most controls, font sizes, etc.) to a fixed pixel size, independent of the Windows large/small/custom font size. This seemed to be the best way to preserve program appearance, but required undoing the scaling that VB does automatically. Font appearance can vary, since font increments are not continuous and since font availability depends on your machine environment. I've run into and worked around some other quirks, e.g., with high-color resolution under a Mach64 video driver. If you have display problems and still have any patience left, try small fonts and 256 colors. I've tried my best to avoid the need for this.

API Calls:

Direct Windows API calls are used for card construction, form print, common dialog interface, INI file read/write, program instance check, Windows version check, About info, and sound playing. The form print is adapted from article Q118938 (revision date: 20-Oct-1995 ) of the Microsoft VB Knowledge Base.

INI File:

The CRIBBAGE.INI file contains a Global section and one section for each player. The file is read at program start and written to at program end, with additional read/writes on player changes. If option settings are locked (see Duplicate Match Play), state updates are written to file more frequently to help guard against power loss problems (inadvertent or intentional).

The player sections may each have Options, State, Statistics1, Statistics2, DogEars, CardBack, DataFile, PatterFile, WinSound and LossSound entries. These preserve Options Menu settings, match/game state, match statistics, dog-eared card list, and most recent file names. The Options entry begins with the Seed or File Pointer. The Options Menu settings then follow in order. The State entry is preceded by a check value, so that an edit of the State entry or a change in the reconstructed hand (from the Seed or Pointer/DataFile entry, as applicable) will be detected. If an edit is detected, the State entry is ignored. This protects the program from the possibility of hanging when restoring the match/game state.

The Global section contains at least Version and Players entries. The Players entry lists up to a dozen players, with the names (most recent is leftmost) serving as section headers. Beyond a dozen, the least recent entry (rightmost) is removed from the list and its section is cleared. The Version entry allows this and future program versions to recognize and handle wrong-version INI files. Currently, a wrong version is handled by clearing known INI entries to guard against possible conflicts and then by stamping in the current version. Three additional Global section entries that you may manually adjust in the INI file are described in the Appendix sample code.

Card Generation:

The default random number stream is generated by Seed = (Seed + 1) * 0x711CB3A5. The constant must end with ...101 binary for the generator to cycle through all possible 4-byte values. The high 6 bits of Seed determine the next card, with the top 2 bits determining the suit and the next 4 bits determining the rank. If the rank is not 0-12, the next value of Seed is fetched. Hands are dealt by generating 13 distinct cards, throwing out duplicates. The 13th card is the cut and the pone/dealer hands are the odd/even cards, respectively, from the first 12.

A data file stream is handled similarly, except that the low 6 bits of each byte are used rather than the high 6 bits of each 4-byte Seed. The data file is scanned sequentially. If end of file is exceeded (or on any open/read error), the program reverts to the default random seed method. The last file pointer becomes the initial seed at that point. If the file is not very random, it may be necessary to scan quite a few bytes to complete a deal. True random file data should average about 18.2 bytes per deal and 17 times that per game (including the internal dead hands dealt out to allow for duplicate match play/replay).

Decision Routines:

The discard decision routine is fairly straight-forward. The program cuts the 46 outstanding cards to each possible 4-card holding to assess its average score and maximum/minimum possible scores. A precomputed table (one for pone and one for dealer) with 91 entries holds the average expected crib score for the 91 possible discards by rank. The expected crib score for the discard is fetched from the table. A separate routine computes a risk value based on the peg positions. The expected crib score is weighed by the risk value, and then added to (dealer) or subtracted from (pone) the average score for the 4-card holding. The maximum (if reckless risk) or minimum (if cautious risk) score is risk-weighed and added in also. A small adjustment is made for pegging potential. The discard with the best result is chosen. Near game end, if any choice guarantees enough points to go out, only those choices are assessed, with the assessment based only on safe pegging considerations.

The pegging decision routine is recursive, with consideration of own play, opponent next play, then own next play at a minimum. If opponent play scores, lookahead is extended to own play following non-scoring opponent play. Choice is determined by summing own play scores and subtracting risk-weighed opponent scores, with opponent plays further weighed by estimated likelihood that opponent holds card of that rank. The probability of each rank for opponent is estimated from 1820-entry table (one for pone and one for dealer), with adjustments for inferences made during pegging. Table holds precomputed frequencies for the 1820 possible 4-card opponent holdings by rank.

Tables:

The tables mentioned above were constructed independently of the random card routine. I avoided Monte Carlo techniques so that the decision strategy would not be biased toward the very random number generator used in actual play of the game.

A simplified discard decision routine (assuming neutral risk and ignoring flushes, pegging potential, and endplay) was used to discard as pone and as dealer from all possible 6-card hands by rank. This determined the 1820-entry frequency tables (for held cards) and new 91-entry average crib score tables (for discards). The process was repeated iteratively until the average crib score tables and the hand frequency tables stabilized. Some horsepower was needed for this -- several hours on a Pentium 166 MHz. For the earlier DOS version on a much slower machine, some further simplifications had yielded decent, but cruder tables. The current discard tables from the Pentium calculations are shown here. The program copy of the table data is rounded to 1/32nd of a point and stored with a -2 point delta..

Computed average crib score when pone discards AA-KK (flushes ignored):

A6.07
25.076.43
35.177.346.78
45.745.446.106.59 P O N E
D I S C A R D

56.066.176.857.469.39 
64.935.134.925.477.66 7.17
74.955.125.164.917.08 6.647.25
84.925.035.085.026.36 6.057.886.76
94.664.824.824.756.22 6.315.465.976.44
104.464.644.704.557.46 4.414.445.025.526.11
J4.724.914.974.807.75 4.614.734.654.985.606.56
Q4.414.604.664.497.42 4.294.444.384.144.655.55 5.89
K4.344.534.594.437.31 4.254.384.314.133.994.89 4.565.72
 A234567 8910JQK

Computed average crib score when dealer discards AA-KK (flushes ignored):

A5.26
24.185.67
34.476.975.90
45.454.514.885.65 D E A L E R
D I S C A R D

55.485.446.016.548.95 
63.803.873.723.876.65 5.74
73.733.813.673.746.04 4.945.98
83.703.583.843.845.49 4.706.585.42
93.333.633.663.695.47 5.114.064.745.09
103.373.513.613.626.68 3.153.103.864.274.73
J3.653.793.883.897.04 3.403.433.393.984.645.37
Q3.393.523.623.636.71 3.083.173.162.973.364.90 4.66
K3.423.553.663.676.70 3.133.213.203.052.864.07 3.504.62
 A23456 78910JQK

The discard routine is pretty good. The pegging routine needs work, since it still makes some bonehead plays, is too predictable, and pushes too often. But overall, I think the program will challenge even the experienced player. This was my original goal.

Credits:

Special thanks here go to my dad, who taught me the game long before computers were around to amuse and entertain us. Belated thanks go to Ted Blackney, Eddie Green, and Joe Wergin from the Madison, WI, area. About 20 years ago they dragged me off to the National Open in Raleigh, NC, where I duly served as cannon fodder. But this gave me a chance to watch the action in later rounds. That opened my eyes to the importance of positional play for the first time. Eddie finished second in the tournament. Ted and Joe didn't fare as well, but both were already renown Skat players. Joe later became president of the American Cribbage Congress. Thanks also go to Scott Numbers for indispensable advice on Windows API and DLL construction. And finally thanks to my brother Mark and co-workers Bern Knutsen and Steve Ducharme for helpful suggestions during beta testing. Mark endured a barrage of ZIP files e-mailed across country as I kept tinkering with things.

Program and documentation copyright, 1989 and 1997, by Craig R. Hessel. All rights reserved. You may freely copy/distribute the program and documentation provided no fee is charged and provided neither is modified.

As of: 24 Dec 99
Home: [email protected]
Web Site: http://www.lan2wan.com/hessel


Home | Contents | Top | Back | Next

1