/*
 * Jack v2.1
 * $VER: jack.c 2.1 (08.20.94)
 *
 * written by John Corigliano with SAS/C 6.51
 *
 * Copyright 1994 John Corigliano
 *
 * internet: j.corigliano@genie.geis.com
 *           mopp@bix.com
 *           j_mopp@delphi.com
 *
 *    GEnie: J.CORIGLIANO
 *      bix: mopp
 *   Delphi: j_mopp
 *
 */


#define __USE_SYSBASE          /* SAS/C - use Absolute Exec Base = 4    */
long    __oslibversion = 37L;  /* SAS/C - auotinit, WB 2.04 please      */

#include <intuition/intuition.h>
#include <intuition/gadgetclass.h>
#include <exec/memory.h>
#include <exec/lists.h>
#include <exec/nodes.h>
#include <exec/ports.h>
#include <graphics/gfx.h>
#include <graphics/gfxbase.h>
#include <graphics/gfxmacros.h>
#include <libraries/gadtools.h>
#include <dos/dos.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <float.h>

#include <proto/intuition.h>
#include <proto/gadtools.h>
#include <proto/exec.h>
#include <proto/graphics.h>
#include <proto/dos.h>

#include "jack.h"        /* Holds card image structures        */

#ifdef LATTICE
int CXBRK(void)    { return(0); }
int chkabort(void) { return(0); }
#endif

/* Macros */
#define GAD_BUF(X) ((struct StringInfo *)X->SpecialInfo)->Buffer
#define GAD_OFF(X) if (!(X->Flags & GFLG_DISABLED)) \
                     GT_SetGadgetAttrs(X,win,NULL,GA_Disabled,TRUE,TAG_END)
#define GAD_ON(X)  if (X->Flags & GFLG_DISABLED) \
                    GT_SetGadgetAttrs(X,win,NULL,GA_Disabled,FALSE,TAG_END)

#define MASK       0x0F

#define REQ_OK      0
#define REQ_ASK     1

/* Gadget IDs */
#define HIT_GAD     0
#define STICK_GAD   1
#define DD_GAD      2
#define SUR_GAD     3
#define DEAL_GAD    4
#define BET_GAD     5

#define MAX_CARDS 416        /* 8 decks                    */
#define A_DECK     52        /* 52 cards per deck          */
#define BOX_WIDTH 146        /* decks gauge x-size         */
#define BOX_HEIGHT  9        /* decks gauge y-size         */
#define BOX_LEFT  179        /* decks gauge left edge      */
#define BOX_TOP   (114 + win_offset)

#define P_ROW       (4 + win_offset)
#define D_ROW       (66 + win_offset)
#define COL_1       13       /* Col to put first card      */
#define COL_2       74       /* Col to put second cards    */
#define CARD_WIDTH  60       /* Pixels                     */
#define CARD_HEIGHT 40       /* Pixels                     */

#define PLAYER     0x0       /* An ID                      */
#define DEALER     0x1       /* An ID                      */
#define FIRST      0x2       /* Playing dealer's hole card */
#define BACK       0x3       /* Playing split card         */

/* My function prototypes */
VOID openWindow(VOID);
BOOL checkBet(VOID);
VOID doIDCMP(VOID);
VOID closer(VOID);
VOID printBank(VOID);
VOID gauge(VOID);
VOID drawCard(struct Card *, UWORD, UWORD);
VOID allocDeck(VOID);
VOID shuffle(VOID);
BOOL initDeal(VOID);
VOID addScore(UBYTE, UBYTE *);
VOID addCard(VOID);
VOID printScore(VOID);
VOID printWins(VOID);
VOID dealsTurn(VOID);
VOID wText(UBYTE *);
VOID playBack(VOID);
VOID openStatWin(VOID);
VOID safeCloseWin(struct Window *);
VOID refreshStatWin(VOID);
VOID showStats(VOID);
BOOL putReq(UBYTE *, UBYTE *, BYTE);

UBYTE ver[] = {"$VER: Jack 2.1 (08.20.94"};

struct Card **deck = NULL, *hole_card, *backcard;
UWORD dealt = 0, p_cord;
UBYTE num_decks = 1, old_decks = 1, t_decks = 0;
UBYTE p_score, p_score2, d_score;
UBYTE p_crd[13], d_crd[13], p_hand[16], d_hand[16];
FLOAT bank = 100, betA = 10, betB = 0, ins = 0, bet_main = 10, win_p = 0;
UBYTE win_offset;
UWORD ySize, hands = 0, winners = 0;
BOOL start_stats = FALSE, use_delay = TRUE;

struct Window *win = NULL, *stat_win = NULL;
struct Gadget *glist = NULL, *gads[6];
struct TextAttr Topaz80 = {"topaz.font", 8, 0, 0,};
struct TextFont *wFont = NULL;
VOID *vi = NULL;
struct Menu *strip;
struct MsgPort *mport = NULL;

/* 
 *main() proccesses the args, opens the window,
 *  and calls the IDCMP loop.
 */
main(int argc, char **argv)
{
    /* If run from WB, SAS/C handles startup and sets these values */
    if (!argc) {
        argc = _WBArgc;
        argv = _WBArgv;
    }

    /* Parse args */
    while (--argc) {
        if (!strcmp("STATS", argv[argc]))
            start_stats = TRUE;
        else if (!strncmp("BANK=", argv[argc], 5)) {
            bank = atof(&argv[argc][5]);
            if (bank < 0.10) bank = 100.00;
            if (bank > (FLT_MAX / 2)) bank = 100;
        }
        else if (!strncmp("DECKS=", argv[argc], 6))
            num_decks = old_decks = atoi(&argv[argc][6]);
        else if (!strcmp("NODELAY", argv[argc]))
            use_delay = FALSE;
    }

    openWindow();
    doIDCMP();
    closer();
}

/*
 * Clean up routine that can be called at any point in the program. It
 *  frees memory, closes the window, frees menus and gadgets, removes
 *  the message port.
 */
VOID closer(VOID)
{
    if (deck) FreeMem(deck, sizeof(struct Card *) * A_DECK * num_decks);
    if (strip) {
        ClearMenuStrip(win);
        FreeMenus(strip);
    }
    if (stat_win) safeCloseWin(stat_win);
    if (win) safeCloseWin(win);
    if (wFont) CloseFont(wFont);
    if (vi) FreeVisualInfo(vi);
    if (mport) DeletePort(mport);
    if (glist) FreeGadgets(glist);
    exit(0);
}

/*
 * Open the window with gadgets and menus. Note: the window has no IDCMP
 *  flags set. This is done so that Intuition will not create a message
 *  port for this window. doIDCMP() creates the port for this win.
 */
VOID openWindow(VOID)
{
    struct NewMenu nm[] = {
        { NM_TITLE, "Game",      0,0,0,0,},
        {  NM_ITEM, "Decks",     0,0,0,0,},
        {   NM_SUB, "1",         0,CHECKIT,~1,0,},
        {   NM_SUB, "2",         0,CHECKIT,~2,0,},
        {   NM_SUB, "4",         0,CHECKIT,~4,0,},
        {   NM_SUB, "8",         0,CHECKIT,~8,0,},
        {  NM_ITEM, "Stats",     0,0,0,0,},
        {   NM_SUB, "On",        0,CHECKIT,~1,0,},
        {   NM_SUB, "Off",       0,CHECKIT,~2,0,},
        {  NM_ITEM, NM_BARLABEL, 0,0,0,0,},
        {  NM_ITEM, "Quit",      0,0,0,0,},
        {   NM_END, NULL,        0,0,0,0,},
    };
    struct Screen *scn;
    struct Gadget *gad;
    struct NewGadget ng;
    UWORD win_left;

    if (!(scn = LockPubScreen(NULL))) closer();
    vi = GetVisualInfo(scn, TAG_END);
    win_offset = 1 + scn->WBorTop + scn->Font->ta_YSize;
    
    switch (num_decks ) {
        case 1:
            nm[2].nm_Flags |= CHECKED;
            break;
        case 2:
            nm[3].nm_Flags |= CHECKED;
            break;
        case 4:
            nm[4].nm_Flags |= CHECKED;
            break;
        case 8:
            nm[5].nm_Flags |= CHECKED;
            break;
        default:
            num_decks = old_decks = 1;
            nm[2].nm_Flags |= CHECKED;
            break;
    }

    if (start_stats) nm[7].nm_Flags |= CHECKED;
    else nm[8].nm_Flags |= CHECKED;
    
    gad = CreateContext(&glist);

    ng.ng_LeftEdge   = 127;
    ng.ng_TopEdge    = 113 + win_offset;
    ng.ng_Width      = 49;
    ng.ng_Height     = 13;
    ng.ng_GadgetText = "Hit";
    ng.ng_TextAttr   = &Topaz80;
    ng.ng_GadgetID   = HIT_GAD;
    ng.ng_Flags      = NULL;
    ng.ng_VisualInfo = vi;
    ng.ng_UserData   = NULL;
    gads[HIT_GAD] = gad = CreateGadget(BUTTON_KIND, gad, &ng, 
                       GA_Disabled, TRUE, TAG_END);

    ng.ng_LeftEdge   = 78;
    ng.ng_TopEdge    = 126 + win_offset;
    ng.ng_GadgetText = "DD";
    ng.ng_GadgetID   = DD_GAD;
    gads[DD_GAD] = gad = CreateGadget(BUTTON_KIND, gad, &ng,
                       GA_Disabled, TRUE, TAG_END);

    ng.ng_LeftEdge   = 127;
    ng.ng_TopEdge    = 126 + win_offset;
    ng.ng_GadgetText = "Stick";
    ng.ng_GadgetID   = STICK_GAD;
    gads[STICK_GAD] = gad = CreateGadget(BUTTON_KIND, gad, &ng,
                       GA_Disabled, TRUE, TAG_END);

    ng.ng_LeftEdge   = 78;
    ng.ng_TopEdge    = 113 + win_offset;
    ng.ng_GadgetText = "Srndr";
    ng.ng_GadgetID   = SUR_GAD;
    gads[SUR_GAD] = gad = CreateGadget(BUTTON_KIND, gad, &ng,
                       GA_Disabled, TRUE, TAG_END);

    ng.ng_TopEdge    = 126 + win_offset;
    ng.ng_LeftEdge   = 177;
    ng.ng_Width      = 150;
    ng.ng_GadgetText = "Deal";
    ng.ng_GadgetID   = DEAL_GAD;
    gads[DEAL_GAD] = gad = CreateGadget(BUTTON_KIND, gad, &ng, TAG_END);

    ng.ng_LeftEdge   = 227;
    ng.ng_TopEdge    = 141 + win_offset;
    ng.ng_Width      = 100;
    ng.ng_GadgetText = "Wager:";
    ng.ng_Flags      = PLACETEXT_LEFT;
    ng.ng_GadgetID   = BET_GAD;
    gads[BET_GAD] = gad = CreateGadget(STRING_KIND, gad, &ng,
                       STRINGA_Justification, GACT_STRINGRIGHT,
                       GTST_String, "10.00",
                       TAG_END);

    if (!start_stats) win_left = (scn->Width - 332) / 2;
    else win_left = (scn->Width - 462) / 2;

    if (win = OpenWindowTags(NULL,
                WA_Left,        win_left,
                WA_Top,         (scn->Height - (166 + win_offset)) / 2,
                WA_Width,       332,
                WA_Height,      166 + win_offset,
                WA_Activate,    TRUE,
                WA_CloseGadget, TRUE,
                WA_DepthGadget, TRUE,
                WA_DragBar,     TRUE,
                WA_Gadgets,     glist,
                WA_IDCMP,       NULL,
                WA_PubScreen,   scn,
                WA_Title,       "©1994 John Corigliano",
                WA_ScreenTitle, "Black Jack!",
                TAG_END)) {
        GT_RefreshWindow(win, NULL);

        /* Set the window font to "topaz.font", 8 */
        wFont = OpenFont(&Topaz80);
        SetFont(win->RPort, wFont);
        ySize = win->IFont->tf_YSize;

        /* Attach menu strip */
        strip = CreateMenus(nm, TAG_END);
        LayoutMenus(strip, vi, TAG_END);
        SetMenuStrip(win, strip);

        /* Use GadTools for beveled boxes */
        DrawBevelBox(win->RPort, 5, 2 + win_offset, 322, 46,
                     GT_VisualInfo, vi,
                     GTBB_Recessed, TRUE, TAG_END);
        DrawBevelBox(win->RPort, 5, 50 + win_offset, 322, 12,
                     GT_VisualInfo, vi,
                     GTBB_Recessed, TRUE, TAG_END);
        DrawBevelBox(win->RPort, 5, 64 + win_offset, 322, 46,
                     GT_VisualInfo, vi,
                     GTBB_Recessed, TRUE, TAG_END);
        DrawBevelBox(win->RPort, 5, 113 + win_offset, 70, 26,
                     GT_VisualInfo, vi,
                     GTBB_Recessed, TRUE, TAG_END);
        DrawBevelBox(win->RPort, 177, 113 + win_offset, 150, 12,
                     GT_VisualInfo, vi,
                     GTBB_Recessed, TRUE, TAG_END);
        SetAPen(win->RPort, 1);

        Move(win->RPort, 10, 123 + win_offset);
        Text(win->RPort, "You:", 4);
        Move(win->RPort, 10, 133 + win_offset);
        Text(win->RPort, "Deal:", 5);
        printBank();
        printWins();
    }

    UnlockPubScreen(NULL, scn);

    if (!win) closer();
    return;
}

/*
 * Checks to make sure bet is in the proper format (i.e 1.00)
 * Also makes sure that the player can cover the bet
 */
BOOL checkBet(VOID)
{
    UBYTE bet_str[32], str[32], i;
    BOOL format = FALSE;

    strcpy(bet_str, GAD_BUF(gads[BET_GAD]));
    if (strlen(bet_str) < 3) format = TRUE;
    if (!format) {
        for (i = 0; i < (strlen(bet_str) - 3); i++)
            if (!(isdigit(bet_str[i]))) format = TRUE;
        if (bet_str[i++] != '.') format = TRUE;
        if (!(isdigit(bet_str[i++]))) format = TRUE;
        if (!(isdigit(bet_str[i]))) format = TRUE;
    }
    if (format) {
        DisplayBeep(win->WScreen);
        sprintf(str, "Bad format: %s", bet_str);
        wText(str);
        bet_main = betA = 0;
        ActivateGadget(gads[BET_GAD], win, NULL);
        return(FALSE);
    }

    bet_main = betA = atof(bet_str);
    return(TRUE);
}

/*
 * Main message processing loop. Note: it creates a message port for the
 *  two windows to share. This way the program can have two windows open
 *  but only checks for messages at one message port!
 */
VOID doIDCMP(VOID)
{
    struct IntuiMessage *imsg;
    struct Gadget *work = NULL;
    struct Window *temp_win;
    struct MenuItem *item;
    BOOL done = FALSE, inp = FALSE;
    UWORD mnum, inum, snum, code;
    ULONG class;

    allocDeck();

    /* Create a message port and attatch it to the window */
    if (!(mport = CreatePort(0,0))) closer();
    win->UserPort = mport;
    ModifyIDCMP(win,IDCMP_GADGETUP|IDCMP_MENUPICK|IDCMP_CLOSEWINDOW);

    if (start_stats) openStatWin();

    while (!done) {
        Wait(1L << mport->mp_SigBit);
        while (imsg = GT_GetIMsg(win->UserPort)) {
            class = imsg->Class;
            code = imsg->Code;
            temp_win = imsg->IDCMPWindow;   /* Which window? */
            if (class == IDCMP_GADGETUP)
                work = (struct Gadget *)imsg->IAddress;
            GT_ReplyIMsg(imsg);
            if (class == IDCMP_REFRESHWINDOW) 
                if (stat_win) {
                    refreshStatWin();
                    showStats();
                }
            if (class == IDCMP_CLOSEWINDOW) {
                if (temp_win == win) done = TRUE;
                else {
                    safeCloseWin(stat_win);
                    stat_win = NULL;
                    Forbid();
                    ClearMenuStrip(win);
                    item = strip->FirstItem->NextItem->SubItem;
                    item->Flags &= ~CHECKED;
                    item->NextItem->Flags |= CHECKED;
                    ResetMenuStrip(win, strip);
                    Permit();
                }
            }
            else if (class == IDCMP_GADGETUP) {
                wText("Okay...");
                if ((work->GadgetID == DEAL_GAD) && checkBet()){
                    if ((bank == 0) &&
                     (putReq("Your broke!","Want a $100 loan?",REQ_ASK))) {
                        bank = 100;
                        printBank();
                    }
                    else if ((betA > 0) && (betA <= bank)) {
                        bank -= betA;
                        printBank();
                        if (dealt > ((num_decks * A_DECK) - 15))
                            shuffle();
                        if (initDeal()) {
                            if (p_score < 21) {
                                GAD_OFF(gads[DEAL_GAD]);
                                GAD_OFF(gads[BET_GAD]);
                                GAD_ON(gads[HIT_GAD]);
                                GAD_ON(gads[STICK_GAD]);
                                if ((bank >= (betA / 2)) && !backcard)
                                    GAD_ON(gads[SUR_GAD]);
                                if (bank >= betA)
                                    GAD_ON(gads[DD_GAD]);
                                inp = TRUE;
                            }
                            else dealsTurn();
                        }
                    }
                    else if (betA <= 0) {
                        DisplayBeep(win->WScreen);
                        wText("You must make a bet first!!");
                    }
                    else {
                        DisplayBeep(win->WScreen);
                        wText("You don't have enough money.");
                    }
                }
                else if (work->GadgetID == BET_GAD)
                    checkBet();
                else if (work->GadgetID == HIT_GAD) {
                    GAD_OFF(gads[DD_GAD]);
                    GAD_OFF(gads[SUR_GAD]);
                    addCard();
                    if (p_score >= 21) {
                        if (backcard) {
                            putReq("Ready for", "second hand...", REQ_OK);
                            playBack();
                        }
                        else {
                            GAD_OFF(gads[HIT_GAD]);
                            GAD_OFF(gads[STICK_GAD]);
                            dealsTurn();
                            inp = FALSE;
                        }
                    }
                }
                else if (work->GadgetID == STICK_GAD) {
                    if (backcard) playBack();
                    else {
                        GAD_OFF(gads[DD_GAD]);
                        GAD_OFF(gads[SUR_GAD]);
                        GAD_OFF(gads[HIT_GAD]);
                        GAD_OFF(gads[STICK_GAD]);
                        dealsTurn();
                        inp = FALSE;
                    }
                }
                else if (work->GadgetID == SUR_GAD) {
                    bank -= betA / 2;
                    printBank();
                    p_hand[0] = p_hand[1] = p_score = 0;
                    p_cord = COL_2 + CARD_WIDTH + 1;
                    drawCard(deck[dealt], COL_1, P_ROW);
                    addScore(PLAYER, p_hand);
                    drawCard(deck[dealt], COL_2, P_ROW);
                    addScore(PLAYER, p_hand);
                    if (p_score == 21) {
                        p_score = 0xFE;
                        GAD_OFF(gads[DD_GAD]);
                        GAD_OFF(gads[SUR_GAD]);
                        GAD_OFF(gads[HIT_GAD]);
                        GAD_OFF(gads[STICK_GAD]);
                        dealsTurn();
                    }
                    else {
                        if (bank < (betA / 2)) GAD_OFF(gads[SUR_GAD]);
                        if (bank < betA) GAD_OFF(gads[DD_GAD]);
                    }
                }
                else if(work->GadgetID == DD_GAD) {
                    GAD_OFF(gads[DD_GAD]);
                    GAD_OFF(gads[SUR_GAD]);
                    bank -= betA;
                    betA += betA;
                    printBank();
                    addCard();
                    if (backcard) {
                        putReq("Ready for", "second hand...", REQ_OK);
                        playBack();
                    }
                    else {
                        GAD_OFF(gads[HIT_GAD]);
                        GAD_OFF(gads[STICK_GAD]);
                        dealsTurn();
                        inp = FALSE;
                    }
                }
                if (stat_win) showStats();                
            }
            else if (class == IDCMP_MENUPICK) {
                mnum = MENUNUM(code);
                inum = ITEMNUM(code);
                snum =  SUBNUM(code);
                if (mnum == 0) {
                    if (!inum) {
                        t_decks = 1 << snum;
                        if (t_decks == num_decks) t_decks = 0;
                        else if (!inp) {
                            num_decks = t_decks;
                            t_decks = 0;
                            allocDeck();
                        }
                    }
                    else if (inum == 1) {
                        if (!snum) {
                            if (!stat_win) openStatWin();
                        }
                        else {
                            if (stat_win) {
                                safeCloseWin(stat_win);
                                stat_win = NULL;
                            }
                        }
                    }
                    else if (inum == 3) done = TRUE;
                }
            }
        }
    }

    return;
}

/*
 * Simply prints the players bank
 */
VOID printBank(VOID)
{
    UBYTE st[] = {"                    "};

    /* Use floating point notation? */
    if (bank > 9999999999999.99) sprintf(st, "Bank: $%-11.2e", bank);
    else sprintf(st, "Bank: $%-13.2f", bank);
    st[strlen(st)] = 32;

    SetAPen(win->RPort, 1);
    Move(win->RPort, 10, 150 + win_offset);
    Text(win->RPort, st, 19);
    return;
}

/*
 * Prints players win percentage
 */
VOID printWins(VOID)
{
    UBYTE str[] = {"                              "};
    UBYTE perc = 0;

    if (hands) perc = 100 * ((FLOAT)winners / (FLOAT)hands);
    
    sprintf(str, "Wins: %d/%d = %d%%", winners, hands, perc);
    str[strlen(str)] = 32;
    Move(win->RPort, 10, 160 + win_offset);
    Text(win->RPort, str, 29);
}

/*
 * Updates the decks gauge to refect the amount of cards dealt.
 */
VOID gauge(VOID)
{
    LONG offset;
    FLOAT per;

    per = ((FLOAT)dealt + 1) / ((FLOAT)num_decks * A_DECK);

    if (dealt == 0) {
        SetAPen(win->RPort, 3);
        RectFill(win->RPort, BOX_LEFT, BOX_TOP,
                      BOX_LEFT + BOX_WIDTH, BOX_TOP + BOX_HEIGHT);
        DrawBevelBox(win->RPort, BOX_LEFT, BOX_TOP,
                      BOX_WIDTH + 1, BOX_HEIGHT + 1,
                      GT_VisualInfo, vi, TAG_DONE);
        return;
    }

    offset = BOX_WIDTH - ((LONG)(per * BOX_WIDTH));

    SetAPen(win->RPort, 0);
    RectFill(win->RPort, BOX_LEFT + offset + 1, BOX_TOP,
                      BOX_LEFT + BOX_WIDTH, BOX_TOP + BOX_HEIGHT);
    SetAPen(win->RPort, 1);
    Move(win->RPort, BOX_LEFT + offset - 1, BOX_TOP + 1);
    Draw(win->RPort, BOX_LEFT + offset - 1, BOX_TOP + BOX_HEIGHT);
    Move(win->RPort, BOX_LEFT + offset, BOX_TOP);
    Draw(win->RPort, BOX_LEFT + offset, BOX_TOP + BOX_HEIGHT);

    return;
}

/*
 * Draws a card at x, y (see "jack.h" for the declaration of the Card
 * structure and the Image structures).
 */
VOID drawCard(struct Card *c, UWORD x, UWORD y)
{
    UBYTE ct;
    struct Image *suit;

    if (c->Flags & HEARTS) suit = &heart;
    else if (c->Flags & DIAMONDS) suit = &diamond;
    else if (c->Flags & SPADES) suit = &spade;
    else suit = &club;

    SetAPen(win->RPort, 2);
    RectFill(win->RPort, x, y, x+CARD_WIDTH, y+CARD_HEIGHT);

    if ((c->Flags & SPADES) || (c->Flags & CLUBS)) SetAPen(win->RPort, 1);
    else SetAPen(win->RPort, 3);
    SetBPen(win->RPort, 2);

    /* Draw the card ID - "2", "3", "4", etc. */
    Move(win->RPort, x+1, y+ySize-2);
    if (strlen(c->Name) != 2) {
        Text(win->RPort, c->Name, 1);
        Move(win->RPort, x+52, y+(CARD_HEIGHT-ySize)+7);
        Text(win->RPort, c->Name, 1);
    }
    else {
        Text(win->RPort, c->Name, 2);
        Move(win->RPort, x+44, y+(CARD_HEIGHT-ySize)+7);
        Text(win->RPort, c->Name, 2);
    }

    /* Draw the suits and face cards (if needed) */
    if (c->Face) {
        DrawImage(win->RPort, suit, x+11, y+1);
        DrawImage(win->RPort, c->Face, x+c->Cords[0],y+c->Cords[1]);
        DrawImage(win->RPort, suit, x+40, y+32);
    }
    else {
        for (ct = 0; ct < ((c->Flags & 0xF) * 2); ct += 2)
            DrawImage(win->RPort, suit, x+c->Cords[ct],y+c->Cords[ct+1]);
    }

    SetAPen(win->RPort, 1);
    Move(win->RPort, x, y);
    Draw(win->RPort, x+CARD_WIDTH, y);
    Draw(win->RPort, x+CARD_WIDTH, y+CARD_HEIGHT);
    Draw(win->RPort, x, y+CARD_HEIGHT);
    Draw(win->RPort, x, y);

    gauge();
    SetBPen(win->RPort, 0);
    if (use_delay) Delay(5);

    return;
}

/*
 * Allocates memory for the deck. Also, frees memory if the deck already
 * existed.
 */
VOID allocDeck(VOID)
{
    SetAPen(win->RPort, 0);
    RectFill(win->RPort, COL_1, P_ROW, 324, P_ROW + CARD_HEIGHT);
    RectFill(win->RPort, COL_1, D_ROW, 324, D_ROW + CARD_HEIGHT);

    if (deck) FreeMem(deck, sizeof(struct Card *) * A_DECK * old_decks);
    deck = AllocMem(sizeof(struct Card *) * A_DECK * num_decks,
                     MEMF_ANY | MEMF_CLEAR);
    old_decks = num_decks;
    shuffle();

    return;
}

/* 
 * This routine is called when a fresh deal is needed. It deals the first
 *  four cards, checks for Black Jacks and possible splits and insurance
 *  bets.
 */
BOOL initDeal(VOID)
{
    UBYTE s[32], t1;

    /* Initialize data */
    betB = ins = p_score = d_score = p_score2 = 0;
    p_cord = COL_2 + CARD_WIDTH + 1;
    backcard = NULL;
    for (t1 = 0; t1 < 16; t1++) p_hand[t1] = d_hand[t1] = 0;

    /* Erase old cards */
    SetAPen(win->RPort, 0);
    RectFill(win->RPort, COL_1, P_ROW, 324, P_ROW + CARD_HEIGHT);
    RectFill(win->RPort, COL_1, D_ROW, 324, D_ROW + CARD_HEIGHT);

    drawCard(deck[dealt], COL_1, P_ROW);
    addScore(PLAYER, p_hand);

    drawCard(deck[dealt], COL_1, D_ROW);
    addScore(DEALER, d_hand);

    drawCard(deck[dealt], COL_2, P_ROW);
    addScore(PLAYER, p_hand);

    if (p_score == 21) p_score = 0xFE;

    /* Dealer has an Ace */
    if ((d_hand[0] == 1) && (p_score != 0xFE) && (bank >= (betA / 2))) {
        if (putReq("Would you like", "an Insurance bet?", REQ_ASK)) {
            ins = betA / 2;
            bank -= ins;
            printBank();
            if (ins > 9999999999999.99)
                sprintf(s, "Insurance bet = $%11.2e", ins);
            else
                sprintf(s, "Insurance bet = $%.2f", ins);
            wText(s);
        }
    }

    DrawImage(win->RPort, &hole, COL_2, D_ROW);
    hole_card = deck[dealt++];

    /* Dealer has Black Jack */
    if ((((hole_card->Flags & MASK) == 0x1) && (d_score == 10)) ||
         (((hole_card->Flags & MASK) > 0x9) && (d_score == 11))) {
        drawCard(hole_card, COL_2, D_ROW);
        addScore(FIRST, d_hand);
        d_score = 0xFE;
        printScore();
        if (ins) bank += ins * 3;
        if (p_score == 0xFE) {
            wText("We both have Black Jack!");
            bank += betA;
        }
        else {
            if (ins) wText("At least you won the insurance!");
            else wText("Black Jack! You lose!!");
        }
        printBank();
        hands++;
        printWins();
        return(FALSE);
    }

    /* Player can split */
    if ((deck[dealt-2]->Flags & MASK) == (deck[dealt-4]->Flags & MASK)) {
        if (bank >= betA) {
            if (putReq("Do you", "want to Split?", REQ_ASK)) {
                backcard = deck[dealt-2];
                if (p_hand[1] == 1) p_score -= 11;
                else p_score -= p_hand[1];
                p_hand[1] = 0;
                printScore();
                drawCard(deck[dealt], COL_2, P_ROW);
                addScore(PLAYER, p_hand);
                bank -= betA;
                printBank();
                GAD_OFF(gads[SUR_GAD]);
                wText("Playing first hand...");
                if (p_score == 21) {
                    p_score = 0xFE;
                    putReq("First hand is", "a Black Jack!", REQ_OK);
                    playBack();
                    if (p_score == 0xFE) return(FALSE);    /* 2 BJacks! */
                }
            }
        }
    }
            
    return(TRUE);
}

/*
 * Deciphers the point value of a card from its Flags.
 */
VOID addScore(UBYTE who, UBYTE *array)
{
    UBYTE score, i, hard = 0, aces = 0;

    if (who == FIRST) score = hole_card->Flags & MASK;
    else if (who == BACK) score = backcard->Flags & MASK;
    else score = deck[dealt]->Flags & MASK;

    if (score == 0x1) {
        aces++;
        if (who == PLAYER) p_crd[12]++;
        else if (who != BACK) d_crd[12]++;
    }
    else {
        if (who == PLAYER) p_crd[score - 2]++;
        else if (who != BACK) d_crd[score - 2]++;
    }

    if (score > 0x9) score = 0xA;
    for (i = 0; i < 16; i++) {
        if (array[i] == 0) break;
        if (array[i] != 1) hard += array[i];
        else {
            hard += 11;
            aces++;
        }
    }
    array[i] = score;
    if (score == 1) hard += 11;
    else hard += score;
    for (i = 0; i < aces; i++) {
        if (hard <= 21) break;
        else hard -= 10;
    }

    if (who == PLAYER) p_score = hard;
    else if (who != BACK) d_score = hard;

    if ((who != FIRST) && (who != BACK)) dealt++;

    printScore();
    return;
}

/*
 * The player wants another card
 */
VOID addCard(VOID)
{
    drawCard(deck[dealt], p_cord, P_ROW);
    addScore(PLAYER, p_hand);
    
    if (p_cord >= 254) p_cord -= 225;
    else p_cord += 61;

    return;
}

/*
 * Prints both scores. If score == 0xFE its a Black Jack
 */
VOID printScore(VOID)
{
    UBYTE s1[3], s2[3];

    if (p_score == 0xFE) strcpy(s1, "BJ");
    else sprintf(s1, "%2d", p_score);
    if (d_score == 0xFE) strcpy(s2, "BJ");
    else sprintf(s2, "%2d", d_score);

    SetAPen(win->RPort, 2);
    Move(win->RPort, 50, 123 + win_offset);
    Text(win->RPort, s1, 2);
    Move(win->RPort, 50, 133 + win_offset);
    Text(win->RPort, s2, 2);

    return;
}

/*
 * The dealer takes his cards and determines the winner and payoffs
 */
VOID dealsTurn(VOID)
{
    UWORD d_cord;
    BOOL okay = TRUE;
    BYTE h1 = 0, h2 = 0, say[32], *res[] = {"Won", "Lost", "Push"};

    d_cord = COL_2 + CARD_WIDTH + 1;

    drawCard(hole_card, COL_2, D_ROW);
    addScore(FIRST, d_hand);
    if (d_score >= 17) okay = FALSE;
    while (okay) {
        drawCard(deck[dealt], d_cord, D_ROW);
        addScore(DEALER, d_hand);
        if (d_cord >= 254) d_cord -= 225;
        else d_cord += 61;
        if (d_score >= 17) okay = FALSE;
    }

    if (p_score == 0xFE) {               /* Black Jack            */
        bank += betA * 2.5;
        p_score = 21;
        wText("You win with Black Jack!!");
        winners++;
    }
    else if (p_score > 21) {             /* Player tap            */
        wText("You've tapped out.");
        h1 = 1;
    }
    else if (d_score > 21) {             /* Dealer Tap            */
        bank += betA * 2;
        wText("Dealer tapped - You win!");
        winners++;
    }
    else if (p_score == d_score) {       /* Push                  */
        bank += betA;
        wText("It's a Push - No winners");
        h1 = 2;
    }
    else if (p_score > d_score) {        /* Winner                */
        bank += betA * 2;
        wText("You win!!");
        winners++;
    }
    else {                               /* d_score > p_score     */
        wText("The Dealer wins.");
        h1 = 1;
    }

    if (p_score2) {                      /* Player split          */
        hands++;
        if (p_score2 == 0xFE) {          /* Black Jack            */
            bank += betB * 2.5;
            p_score2 = 21;
            winners++;
        }
        else if (p_score2 > 21)          /* Player tap            */
            h2 = 1;
        else if (d_score > 21) {         /* Dealer tap            */
            bank += betB * 2;
            winners++;
        }
        else if (p_score2 == d_score) {  /* Push                  */
            h2 = 2;
            bank += betB;
        }
        else if (p_score2 > d_score) {   /* Player wins           */
            bank += betB * 2;
            winners++;
        }
        else                             /* Player tap and        */
            h2 = 1;                      /* d_score > p_score2    */

        sprintf(say, "%s: %d <> %s: %d", res[h2],p_score2,res[h1],p_score);
        wText(say);
    }

    printBank();
    hands++;
    printWins();

    if (t_decks) {               /* Check to see if player changed the */
        num_decks = t_decks;     /* number od decks while the hand was */
        t_decks = 0;             /* in progress.                       */
        allocDeck();
    }
    GAD_ON(gads[BET_GAD]);
    GAD_ON(gads[DEAL_GAD]);

    return;
}

/*
 * Centers a string and prints it
 */
VOID wText(UBYTE *s)
{
    UBYTE len;
    UWORD offs;
    struct TextExtent texex;

    len = strlen(s);
    
    SetAPen(win->RPort,0);
    RectFill(win->RPort, 7, 51 + win_offset, 323, 60 + win_offset);

    SetAPen(win->RPort, 1);
    Move(win->RPort, 0, 0);

    TextExtent(win->RPort, s, len, &texex);
    offs = 5 + ((322 - texex.te_Extent.MaxX) / 2);
    Move(win->RPort, offs, 58 + win_offset);
    Text(win->RPort, s, len);

    return;
}

/*
 * The player has 'Split'
 */
VOID playBack(VOID)
{
    UBYTE i;

    p_cord = COL_2 + CARD_WIDTH;

    SetAPen(win->RPort, 0);

    RectFill(win->RPort, COL_1, P_ROW, 324, P_ROW + CARD_HEIGHT);

    GAD_ON(gads[DD_GAD]);
    GAD_OFF(gads[SUR_GAD]);

    p_score2 = p_score;
    p_score = 0;
    betB = betA;
    betA = bet_main;

    for (i = 0; i < 16; i++) p_hand[i] = 0;

    wText("Playing second hand, now...");

    drawCard(backcard, COL_1, P_ROW);
    addScore(BACK, p_hand);

    drawCard(deck[dealt], COL_2, P_ROW);
    addScore(PLAYER, p_hand);
    backcard = NULL;

    if (p_score == 21) {
        p_score = 0xFE;
        GAD_OFF(gads[DD_GAD]);
        GAD_OFF(gads[HIT_GAD]);
        GAD_OFF(gads[STICK_GAD]);
        dealsTurn();
    }
    return;
}

/*
 * Open the Statistics window
 */
VOID openStatWin(VOID)
{
    UWORD xwin;
    WORD wzoom[] = {0,0,130,0};

    if ((win->LeftEdge + 332) > (win->WScreen->Width - 130))
        xwin = win->LeftEdge - 130;
    else xwin = win->LeftEdge + 332;

    wzoom[0] = xwin;
    wzoom[1] = win->TopEdge;
    wzoom[3] = win_offset;

    if (!(stat_win = OpenWindowTags(NULL,
            WA_Left,        xwin,
            WA_Top,         win->TopEdge,
            WA_Width,       130,
            WA_Height,      166 + win_offset,
            WA_CloseGadget, TRUE,
            WA_DepthGadget, TRUE,
            WA_Zoom,        wzoom,
            WA_DragBar,     TRUE,
            WA_IDCMP,       NULL,
            WA_SmartRefresh,TRUE,
            WA_RMBTrap,     TRUE,
            WA_PubScreen,   win->WScreen,
            WA_ScreenTitle, win->ScreenTitle,
            WA_Title,       "Stats",
            TAG_END)))
        return;

    if (wFont) SetFont(stat_win->RPort, wFont);
    stat_win->UserPort = mport;
    ModifyIDCMP(stat_win, IDCMP_CLOSEWINDOW|IDCMP_REFRESHWINDOW);
    refreshStatWin();
    showStats();

    return;
}

/*
 * Since the two windows share an IDCMP port, must make sure that there
 *  are no more messages for that window before trying to close it.
 */
VOID safeCloseWin(struct Window *w)
{
    struct IntuiMessage *msg;
    struct Node         *succ;

    Forbid();
    msg = (struct IntuiMessage *)mport->mp_MsgList.lh_Head;
    while(succ = msg->ExecMessage.mn_Node.ln_Succ) {
        if (msg->IDCMPWindow == w) {
            Remove((struct Node *)msg);
            ReplyMsg((struct Message *)msg);
        }
        msg = (struct IntuiMessage *)succ;
    }

    w->UserPort = NULL;
    ModifyIDCMP(w, NULL);
    Permit();

    CloseWindow(w);

    return;
}

/*
 * Redraw the Statistics window graphics
 */
VOID refreshStatWin(VOID)
{
    static UBYTE *sc[] = {
        " 2", " 3", " 4", " 5", " 6", " 7", " 8", " 9",
        "10", " J", " Q", " K", " A"
    };
    static UBYTE title[] = {"    P   D   %"};
    UBYTE row = 22, col = 7, x;

    if (!stat_win) return;

    SetAPen(stat_win->RPort, 3);
    RectFill(stat_win->RPort, 4, win_offset, 125, 162 + win_offset);
    SetAPen(stat_win->RPort, 0);
    RectFill(stat_win->RPort, 28, 13 + win_offset, 117, 146 + win_offset);
    DrawBevelBox(stat_win->RPort, 28, 13 + win_offset, 89 + 1, 133 + 1,
                            GT_VisualInfo, vi, TAG_END);
    
    SetAPen(stat_win->RPort, 1);
    SetDrMd(stat_win->RPort, JAM1);
    Move(stat_win->RPort, 6, 9 + win_offset);
    Text(stat_win->RPort, title, 13);
    SetAPen(stat_win->RPort, 2);
    Move(stat_win->RPort, 4, 8 + win_offset);
    Text(stat_win->RPort, title, 13);

    SetAPen(stat_win->RPort, 1);
    for (x = 0; x < 13; x++) {
        Move(stat_win->RPort, col, row + win_offset);
        Text(stat_win->RPort, sc[x], 2);
        row += 10;
    }
    row = 21; col = 5;
    SetAPen(stat_win->RPort, 2);
    for (x = 0; x < 13; x++) {
        Move(stat_win->RPort, col, row + win_offset);
        Text(stat_win->RPort, sc[x], 2);
        row += 10;
    }

    SetDrMd(stat_win->RPort, JAM2);
    return;
}


/*
 * My own version of EasyRequest(). I use Nico François' RT_Patch - it
 *  puts all system requesters at the pointer. I am emulating that
 *  here for people who don't have RT_Patch
 */
BOOL putReq(UBYTE *stra, UBYTE *strb, BYTE flag)
{
    struct IntuiMessage *im;
    struct Window *req_win;
    BOOL done = FALSE, ans;
    WORD le, te, gl, offa, offb, lena, lenb, left_off = 100;
    struct Gadget *rgad, *rlist;
    struct NewGadget rng;
    struct Requester req;
    struct TextExtent texe;

    gl = (flag) ? 10 : 51;
    rgad = CreateContext(&rlist);
    rng.ng_LeftEdge   = gl;
    rng.ng_TopEdge    = 28 + win_offset;
    rng.ng_Width      = 80;
    rng.ng_Height     = 14;
    rng.ng_GadgetText = "Okay";
    rng.ng_TextAttr   = &Topaz80;
    rng.ng_GadgetID   = 1;
    rng.ng_Flags      = NULL;
    rng.ng_VisualInfo = vi;
    rng.ng_UserData   = NULL;
    rgad = CreateGadget(BUTTON_KIND, rgad, &rng, TAG_END);

    if (flag) {
        left_off = 137;
        rng.ng_LeftEdge   = 95;
        rng.ng_GadgetText = "Cancel";
        rng.ng_GadgetID   = 0;
        rgad = CreateGadget(BUTTON_KIND, rgad, &rng, TAG_END);
    }

    InitRequester(&req);
    Request(&req, win);
    
    le = ((win->MouseX + win->LeftEdge) < left_off) ? 0 :
                (win->MouseX + win->LeftEdge - left_off);
    te = ((win->MouseY + win->TopEdge) < 47)   ? 0 :
                (win->MouseY + win->TopEdge - 47);

    if (req_win = OpenWindowTags(NULL,
                WA_Left,        le,
                WA_Top,         te,
                WA_Width,       182,
                WA_Height,      45 + win_offset,
                WA_Gadgets,     rlist,
                WA_IDCMP,       IDCMP_GADGETUP,
                WA_DepthGadget, TRUE,
                WA_DragBar,     TRUE,
                WA_Activate,    TRUE,
                WA_RMBTrap,     TRUE,
                WA_Title,       "Jack Note:",
                WA_ScreenTitle, win->ScreenTitle,
                WA_PubScreen,   win->WScreen,
                TAG_END)) {
        if (wFont) SetFont(req_win->RPort, wFont);
        DrawBevelBox(req_win->RPort, 9, 2 + win_offset, 166, 
                     10 + win_offset, GT_VisualInfo, vi,
                     GTBB_Recessed, TRUE, TAG_END);
        lena = strlen(stra);
        lenb = strlen(strb);
        SetAPen(req_win->RPort, 1);
        Move(req_win->RPort, 0, 0);
        TextExtent(req_win->RPort, stra, lena, &texe);
        offa = (182 - texe.te_Extent.MaxX) / 2;
        Move(req_win->RPort, offa, 11 + win_offset);
        Text(req_win->RPort, stra, lena);
        Move(req_win->RPort, 0, 0);
        TextExtent(req_win->RPort, strb, lenb, &texe);
        offb = (182 - texe.te_Extent.MaxX) / 2;
        Move(req_win->RPort, offb, 20 + win_offset);
        Text(req_win->RPort, strb, lenb);

        while (!done) {
            Wait(1L << req_win->UserPort->mp_SigBit);
            while (im = (struct IntuiMessage *)GetMsg(req_win->UserPort)) {
                if (im->Class == IDCMP_GADGETUP) {
                    ans = ((struct Gadget *)im->IAddress)->GadgetID;
                    done = TRUE;
                }
                ReplyMsg((struct Message *)im);
            }
        }
        CloseWindow(req_win);
    }
    FreeGadgets(rlist);
    EndRequest(&req, win);
    return(ans);
}
