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