You may use the following sample C code as a starting point to create custom CRIBALT.DLL to handle decision-making chores for the program. The code should compile/link as is for a workable demo.
[In the HTML source, symbols ", <, >, and & replace ", <, >, and &, respectively. Use the copy text (rather than view source) function of your browser to extract the source code.]
//---------------------------------------------------------------------------- // CRIBALT.C contains stubs for alternate decision routines for Cribbage 97. // // Compile (MS 8.00c) and link (MS 5.60): // CL /c /W4 /G3s /Fc /Zp /O /GD /AL CRIBALT.C // LINK @CRIBALT.LNK // // CRIBALT.LNK: // CRIBALT.OBJ // CRIBALT.DLL /align:16 /far /nod /noe /w // CRIBALT.MAP // LDLLCEW.LIB LIBW.LIB // CRIBALT.DEF // // CRIBALT.DEF: // LIBRARY CRIBALT // DESCRIPTION 'Alternate decision routines for Cribbage 97' // EXETYPE WINDOWS 3.1 // CODE PRELOAD MOVEABLE DISCARDABLE // DATA PRELOAD MOVEABLE // HEAPSIZE 0000 // EXPORTS // Discard @1 // MakePegPlay @2 // // CRIBCORE.DLL has several other exports. Your alternate decision routines // are called only if Global/UseCribAltDLL entry of CRIBBAGE.INI specifies: // // UseCribAltDLL=0 -- Ignore CRIBALT.DLL (default) // =1 -- Use CRIBALT for player decisions only (hints/autoplay) // =2 -- Use CRIBALT for computer decisions only // =3 -- Use CRIBALT for all decisions // // These combinations of settings allow you to compare your routines with the // default routines by running duplicate autoplay matches. Two additional // Global section entries may be helpful for testing/evaluation: // // AllowMultipleInstances=1 -- Allows multiple instances of program // AllowCtrlF10=1 -- Enables Ctrl-F10 to toggle continuous play // // Continuous play halts after 1000 games, if not toggled off sooner. The // Autoplay/Autoscore buttons must both be enabled for Ctrl-F10 to work. //---------------------------------------------------------------------------- #include <windows.h> #define SuitOf(Card) ((Card) & 0xF0) #define RankOf(Card) ((Card) & 0x0F) #define CountOf(Rank) ((Rank) > 10 ? 10 : (Rank)) typedef struct tagHandStruct {LPSTR Hand; int Len; int Ndx; } HANDSTRUCT; typedef HANDSTRUCT FAR* LPHANDSTRUCT; //---------------------------------------------------------------------------- // You can ignore args here and omit WEP as long as, e.g., no global memory is // allocated for DLL instances. Also avoid global variables, since another // instance may overwrite your data. Local data is stored on the caller's // stack, so is specific to each instance. Use of large memory model is // suggested to avoid heartburn over segment references, since DS is not the // same as SS in a DLL. See Q106553 in VB Knowledge Base. //---------------------------------------------------------------------------- int FAR PASCAL LibMain() {return 1; } //---------------------------------------------------------------------------- // Call this or similar function if argument error detected in any of exported // functions. CRIBCORE does the same for its exports. The VB calling program // does not check for error return. It will continue sending good arguments // so long as you continue returning good data. This display is mainly for // debugging. // // You can write your routines without further calls to Windows functions. // This is helpful to know if your C experience is mainly in the warm and // fuzzy DOS environment (as it was for me). The CRIBCORE code does not call // any other external functions at all. //---------------------------------------------------------------------------- int ArgErrMsg(LPSTR FuncName) {char Buf[48]; wsprintf(Buf, "CRIBALT.DLL: %s() argument error", FuncName); MessageBox(GetActiveWindow(), Buf, "Call Terminated", MB_ICONSTOP); return -1; } //---------------------------------------------------------------------------- // Copy cards to your own buffer (do not tamper with VB input strings), with // preliminary error check. You do not have to convert the card format, as is // done here, but it was convenient in CRIBCORE. //---------------------------------------------------------------------------- int CopyHand(LPSTR Tgt, LPSTR Src, int Length) {int n, m, Card, Rank, Suit; for (n = 0; n < Length; n++) {Card = Src[n]; if (Card < 0 || Card > 51) return 1; for (m = 0; m < n; m++) if (Card == Src[m]) return 1; Rank = Card % 13 + 1; // 1-13 Suit = 16 << (Card / 13); // 16, 32, 64, 128 Tgt[n] = (char)(Rank | Suit); } return 0; } //---------------------------------------------------------------------------- // Return 1-based index of first legal peg play, or 0 if none. //---------------------------------------------------------------------------- int GetFirstPlay(LPSTR Hand, int Length, int Start, int PegCnt) {int n; for (n = Start; n < Length; n++) if (PegCnt + CountOf(RankOf(Hand[n])) <= 31) return n + 1; return 0; } //---------------------------------------------------------------------------- // Check for legal pegging reconstruction and return peg count (-1 if error). //---------------------------------------------------------------------------- int ChkPeg(LPSTR Cards, int Length, int CardsLeft, int OwnTurn, int IsRestart) {int n, PegCnt = 0, GoCnt = 0; HANDSTRUCT Own, Opp; LPHANDSTRUCT Cur; Own.Hand = Cards; Own.Len = 4-CardsLeft; Own.Ndx = 0; Opp.Hand = Cards+7; Opp.Len = Length-7; Opp.Ndx = 0; // Replay pegging to own turn, aborting on any inconsistency. while (Own.Ndx < Own.Len || Opp.Ndx < Opp.Len || !OwnTurn) {Cur = OwnTurn ? &Own : &Opp; if (Cur->Ndx >= Cur->Len) n = 32; else n = PegCnt + CountOf(RankOf(Cur->Hand[Cur->Ndx])); if (n > 31) // Insure 'go' not mistake, i.e., check if later card is // playable -- unknown opponent cards are at worst face cards. {n = OwnTurn ? 4 : Cur->Len; if (GetFirstPlay(Cur->Hand, n, Cur->Ndx, PegCnt)) return -1; if (!OwnTurn && Cur->Len < 4 && PegCnt <= 21) return -1; ++GoCnt; } else {PegCnt = n; GoCnt = 0; Cur->Ndx++; } if (PegCnt == 31 || GoCnt > 1) PegCnt = GoCnt = 0; OwnTurn = !OwnTurn; } // If restarting and peg count non-zero, insure legal restart. if (IsRestart && PegCnt) {if (GoCnt && Opp.Ndx < Opp.Len) return -1; if (GetFirstPlay(Own.Hand, 4, Own.Ndx, PegCnt)) return -1; } return IsRestart ? 0 : PegCnt; } //--------------------- EXPORTS: MODIFY THESE FUNCTIONS ---------------------- //---------------------------------------------------------------------------- // Discard decision function. // // Input: // Hand -- Exactly 6 cards (0-51 each). // DlScr -- Dealer game score (0-120). // PnScr -- Pone game score (0-120). // // Output: // On argument error, return -1. Otherwise return discard indexes as if // dealer in high byte nibbles and discard indexes as if pone in low byte // nibbles. Indexes are 0-5, with nibble order irrelevant. // // Game scores here and in next function let you include endgame strategy, // positional adjustments, etc., in your decision. Match score and opponent // tendencies from prior hands are not available, however. The corresponding // CRIBCORE exports have same names and arguments. // // Both exports by CRIBCORE are deterministic. That is, the same input always // produces the same output. This is necessary for duplicate play. Your // routines should behave similarly. // // If you need some randomness to break decision ties, rely on the order of // the input cards rather than, e.g., a clock function call. The VB calling // program passes along the input hand here and the unplayed input cards in // the next function in the same "random" order as the cards were dealt (even // if the VB display of those cards is sorted). //---------------------------------------------------------------------------- int __export FAR PASCAL Discard(LPSTR Hand, WORD DlScr, WORD PnScr) {char Hnd[6]; int DlPicks, PnPicks; // Convert hand to useful format, with preliminary error check. if (DlScr > 120 || PnScr > 120 || CopyHand(Hnd, Hand, 6)) return ArgErrMsg("Discard"); // SUBSTITUTE YOUR CODE HERE -- Sample code shows pone discard as first two // cards and dealer discard as last two cards. Reference Hnd[] from now // on, not Hand[]. PnPicks = 0x01; DlPicks = 0x45; return DlPicks << 8 | PnPicks; } //---------------------------------------------------------------------------- // Peg play decision function. // // Input: // Cards -- Own played cards in order, then own cards left, then own // discards, then cut, then opponent played cards in order. // Length -- Number of cards (7-11), implying opponent cards left. // OwnLeft -- Number of own cards left to play (0-4). // IsDealer -- True (non-zero) if own cards (leftmost) are dealer cards. // IsRestart -- True (non-zero) if current peg count is zero. // OwnScr -- Own game score (0-120). // OppScr -- Opponent game score (0-120). // // Output: // On argument error, return -1. Otherwise, return 0 for go or index 1-4 of // of card play. Index is 1-based from start of Cards. // // The IsRestart argument is needed to distinguish go play from card play that // follows count reset. The VB program currently pre-checks for go, so does // not bother to call function in this case. But include go check anyway. // // You will probably want to create a function like ChkPeg() to reconstruct // the played card sequence. The card sequence and peg count are implied. //---------------------------------------------------------------------------- int __export FAR PASCAL MakePegPlay(LPSTR Cards, WORD Length, WORD OwnLeft, int IsDealer, int IsRestart, WORD OwnScr, WORD OppScr) {char Cds[11]; int PegCnt; // Convert cards to useful format, with preliminary error check. if (OwnScr > 120 || OppScr > 120 || Length < 7 || Length > 11 || OwnLeft > 4 || CopyHand(Cds, Cards, Length) || (PegCnt = ChkPeg(Cds, Length, OwnLeft, !IsDealer, IsRestart)) == -1) return ArgErrMsg("MakePegPlay"); // SUBSTITUTE YOUR CODE HERE -- Sample code finds first legal play. // Reference Cds[] from now on, not Cards[]. return GetFirstPlay(Cds, 4, 4 - OwnLeft, PegCnt); } // End CRIBALT.C