Appendix: Custom DLL

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 &quot;, &lt;, &gt;, and &amp; 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


Home | Contents | Top | Back | Next

1