/* Fuzzy Battle Sheep!
 *
 * written by John Corigliano with SAS/C 6.51
 *
 * ©1994 John Corigliano
 *
 * internet: j.corigliano@genie.geis.com
 *      bix: mopp
 *
 */

#define __USE_SYSBASE          /* SAS/C - use Absolute Exec Base = 4    */
long    __oslibversion = 0;    /* SAS/C - auotinit, any library will do */

#include <exec/types.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <intuition/gadgetclass.h>
#include <graphics/gfx.h>
#include <graphics/gfxbase.h>
#include <devices/audio.h>

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

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

#define MakeID(a,b,c,d)	  ((LONG)(a)<<24 | (LONG)(b)<<16 | (c)<<8 | (d))

#define ROW_MASK 0x80000000          /* Mask = row 1       */
#define COL_MASK 0x00800000          /* Mask = column 1    */
#define ALL_ROWS 0xFF000000          /* Mask = all rows    */
#define ALL_COLS 0x00FFFFFF          /* Mask = all columns */

/* Macros to save some typing! */
#define GET_ROW(n)   ((n / 24) + 1)
#define GET_COL(n)   (n - (((n / 24) * 24) - 1))
#define MAKE_SHIP(n) (ROW_MASK>>(GET_ROW(n)-1))|(COL_MASK>>(GET_COL(n)-1))

struct TextAttr Topaz80 = {"topaz.font", 8, 0, 0,};

#include "placegads.h"  /* Contains gadget and menu definitions */

#define NTSC_CLOCK    3579545L     /* For sounds */
#define PAL_CLOCK     3546895L

#define OK      0xFF
#define SQUARES 0xC0

/* Each square on the 2 grids will either have
 * a number 0-192 or one of these values
 */
#define SUNK    0xFF   /* This square holds a destroyed ship            */
#define MISS    0xFE   /* This square holds nothing                     */
#define HIT     0xFD   /* This square has a ship currently under attack */

/* Colors to draw squares */
#define FRESH 0x0
#define KILL  0x1
#define CURR  0x2
#define SET   0x3
#define EMPTY 0x4

#define TOP    0x1
#define BOTTOM 0x2
#define PLAYER 5
#define COMP   6
#define SLEN   40

#define HORIZ 0x2

/* A struct Ship holds a ULONG that determines its position on the
 * grid (Ship.Pos) and a UBYTE that determines its status (Ship.Data)
 *
 * Ship Position Bits 00000000 00000000 00000000 00000000
 *                    -------- --------------------------
 *                    Rows 1-8       Columns 1 - 24
 *
 * Ship Data Bits 00000000
 *                ||||||||_ Sunk
 *                |||||||__ Orientation (0 = Vert, 1 = Horiz)
 *                ||||||___ Hits 1 - 6
 */

struct Ship { 
    ULONG Pos;
    UBYTE Data;
    };

struct Ship play_ships[] = {     /* These are the player's ships */
    {0x80FC0000, 0x02},
    {0x40F80000, 0x02}, 
    {0x20F00000, 0x02},
    {0x10E00000, 0x02},
    {0x08C00000, 0x02}
    };
struct Ship comp_ships[] = {     /* These are the computer's ships */
    {0,0},
    {0,0},
    {0,0},
    {0,0},
    {0,0}
    };

struct noiz {                /* Sound info structre */
    UBYTE *name;             /* file to open        */
    UBYTE *data;             /* sound sample data   */
    ULONG  size;             /* size                */
    UWORD  SPS;              /* samples per second  */
};
struct noiz sounds[] = {
    {"sound01", NULL, 0, 0},
    {"sound02", NULL, 0, 0},
    {"sound03", NULL, 0, 0},
    {"sound04", NULL, 0, 0},
    {"sound05", NULL, 0, 0},
};

UBYTE sizes[] = {6,5,4,3,2};
UBYTE *Cnames[] = {"Big Daddy","Mathilda","Lamikins","Lil' Sis","Baaa-by"};
UBYTE *Pnames[] = {"Mr. Big", "Sheba", "Wolfy", "Mutt", "Puppy"};
UBYTE *verbs[] = {"killed", "murdered", "destroyed", "annihilated",
                  "erased", "eradicated", "mutilated", "null and voided",
                  "got", "pummeled", "iced", "buried", "wiped out",
                  "shot down","disintegrated", "cut down",
                  "bludgeoned"};
UBYTE *tk[] = {"dumbest", "average", "smartest"};
UBYTE dead_masks[] = {0xFC, 0xF8, 0xF0 ,0xE0, 0xC0};
UBYTE comp_score = 0, play_score = 0, dir = 0;
UBYTE player[192], computer[192];               /* <--  The two grids !! */
ULONG Raud_dev = 1L, Laud_dev = 1L;
UBYTE think = 2;
BOOL in_progress = FALSE, play_one = FALSE;

/* My fuctoin prototypes */
VOID closer(UBYTE *);
VOID doIDCMP(VOID);
VOID setPlayer(VOID);
VOID dBox(ULONG, SHORT, UBYTE);
VOID moveUp(struct Ship *, WORD);
VOID moveDown(struct Ship *, WORD);
VOID moveLeft(struct Ship *, WORD);
VOID moveRight(struct Ship *, WORD);
BOOL safeDraw(ULONG, ULONG, WORD);
VOID shipRotate(struct Ship *, WORD);
VOID pickShips(struct Ship *);
LONG getRand(LONG);
UBYTE collided(ULONG, ULONG, WORD, struct Ship *);
UBYTE findHit(struct Ship *, ULONG, UBYTE);
BOOL findBlock(UWORD, UWORD);
VOID wText(UBYTE *);
VOID compsTurn(VOID);
BOOL findShort(UBYTE, WORD);
VOID endgame(VOID);
VOID updateDisplay(UBYTE);
VOID getXY(UBYTE *, UBYTE *, UBYTE *, ULONG);
VOID openAudio(VOID);
VOID openSounds(VOID);
LONG setSeek(FILE *, ULONG);
VOID playSound(struct noiz *, struct IOAudio *, struct MsgPort *);

struct Screen *scn = NULL;
struct Window *win = NULL;
struct TextFont *wFont = NULL;
struct MsgPort *RaudioMP = NULL, *LaudioMP = NULL;
struct IOAudio Raudio, Laudio;
BOOL playit = TRUE;

/* 
 * main() sets up the audio.device and opens the Window.
 * Note: No libraries are opened and the WBStartup is not handled.
 * I let SAS/C handle all that nonsense!
 */
main()
{
    struct NewWindow nw = {0,11,510,175,0,1,
        IDCMP_GADGETUP|IDCMP_MOUSEBUTTONS|IDCMP_CLOSEWINDOW|
        IDCMP_MENUPICK, WFLG_CLOSEGADGET|WFLG_DEPTHGADGET|
        WFLG_DRAGBAR|WFLG_SMART_REFRESH|WFLG_ACTIVATE,
        &gad, NULL, NULL, NULL, NULL, 100, 11, 510, 175,
        WBENCHSCREEN,
    };
    SHORT d;
    UWORD box[6][10] = {
        {9,82, 9,15, 498,15, 10,15, 10,81},
        {9,153, 9,86, 498,86, 10,86, 10,152},
        {475,156, 475,168, 141,168, 474,168, 474,157},
        {499,15, 499,82, 10,82, 498,82, 498,16},
        {499,87, 499,153, 10,153, 498,153, 498,88},
        {140,168, 140,156, 474,156, 141,156, 141,167},
    };

    openAudio();
    openSounds();

    srand(VBeamPos());

    if (!(scn = LockPubScreen(NULL))) closer("Could not lock screen!!");
    if (win = OpenWindow(&nw)) {
        if (scn->Font->ta_YSize > 8) {
            wFont = OpenFont(&Topaz80);
            SetFont(win->RPort, wFont);
        }
        strip.Width = TextLength(&win->WScreen->RastPort, "Game", 4) + 8;
        SetMenuStrip(win, &strip);
        SetWindowTitles(win,"©1994 John Corigliano","Fuzzy Battle Sheep!");
        SetAPen(win->RPort, 2);
        for (d = 0; d < 6; d++) {
            Move(win->RPort, box[d][0], box[d][1]);
            Draw(win->RPort, box[d][2], box[d][3]);
            Draw(win->RPort, box[d][4], box[d][5]);
            Move(win->RPort, box[d][6], box[d][7]);
            Draw(win->RPort, box[d][8], box[d][9]);
            if (d == 2) SetAPen(win->RPort, 1);
        }
    }

    UnlockPubScreen(NULL, scn);
    if (!win) closer("Window problem...");

    wText("Press 'Set Up' to begin...");
    updateDisplay(TOP);
    updateDisplay(BOTTOM);

    doIDCMP();

    closer(NULL);
}

/* 
 * This clean-up function can be called from just about anywhere in the
 * program.
 */
VOID closer(UBYTE *s)
{
    BYTE i;

    if (!Raud_dev) CloseDevice((struct IORequest *)&Raudio);
    if (RaudioMP)  DeletePort(RaudioMP);
    if (!Laud_dev) CloseDevice((struct IORequest *)&Laudio);
    if (LaudioMP)  DeletePort(LaudioMP);
    for (i = 0; i < 5; i++)
        if (sounds[i].data) FreeMem(sounds[i].data, sounds[i].size);
    if (win) {
        ClearMenuStrip(win);
        CloseWindow(win);
    }
    if (wFont) CloseFont(wFont);
    if (s) puts(s);
    exit(0);
}

/*
 * Main message loop
 */
VOID doIDCMP(VOID)
{
    BOOL done = FALSE, my_turn = FALSE;
    struct IntuiMessage *imsg;
    ULONG class, ans;
    UWORD code, mnum, inum, snum;
    UBYTE s[SLEN];
    struct EasyStruct es = {
        sizeof(struct EasyStruct),
        0,
        "Battle Sheep Note:",
        "End this game?",
        "Okay | Cancel",
    };

    while (!done) {
        if ((my_turn) && (in_progress)) {
            compsTurn();
            my_turn = FALSE;
        }
        else {
            Wait(1L << win->UserPort->mp_SigBit);
            while (imsg = (struct IntuiMessage *)GetMsg(win->UserPort)) {
                class = imsg->Class;
                code = imsg->Code;
                ReplyMsg((struct Message *)imsg);
                if (class == IDCMP_GADGETUP) {
                    if (in_progress) ans = EasyRequest(win, &es, NULL);
                    else ans = 1;
                    if (ans) {
                        OffGadget(&gad, win, NULL);
                        comp_score = 6;
                        endgame();
                        setPlayer();
                        SetAPen(win->RPort, 0);
                        RectFill(win->RPort, 15, 157, 135, 169);
                        OnGadget(&gad, win, NULL);
                    }
                }
                if (class == IDCMP_MOUSEBUTTONS) {
                    if ((code == SELECTDOWN) && in_progress && !my_turn)
                        my_turn = findBlock(win->MouseX, win->MouseY);
                }
                if (class == IDCMP_MENUPICK) {
                    mnum = MENUNUM(code);
                    inum = ITEMNUM(code);
                    snum = SUBNUM(code);
                    if (mnum == 0) {
                        if (inum == 0) {
                            think = snum;
                            sprintf(s,"Thinking is now %s.",tk[think]);
                            wText(s);
                        }
                        if (inum == 1) {
                            if (snum == 0) playit = FALSE;
                            else if (snum == 1) playit = TRUE;
                        }
                        if (inum == 3) done = TRUE;
                    }
                }
                if (class == IDCMP_CLOSEWINDOW) done = TRUE;
            if (done == TRUE)
                done = (EasyRequest(win, &es, NULL)) ? TRUE : FALSE;
            }
        }
    }
    while (imsg = (struct IntuiMessage *)GetMsg(win->UserPort))
        ReplyMsg((struct Message *)imsg);
}

/*
 * This is where we get the player set-up. The lower grid is replaced
 * with gadgets (defined in "placegads.h"). At the end, the gadgets are
 * re-organized so that if the function is called again, the gadgets
 * will be properly linked.
 */
VOID setPlayer(VOID)
{
    BOOL done = FALSE;
    struct IntuiMessage *imsg;
    struct Gadget *work=NULL, *active;
    UWORD cur = 0;
    ULONG class;

    active = &g7;

    if (play_one) updateDisplay(TOP);       /* First time? */
    else play_one = TRUE;

    SetAPen(win->RPort, 0);
    RectFill(win->RPort, 15, 88, 495, 152);
    AddGList(win, &g12 , (~0), 12, NULL);  /* Gadget g12 is the first in */
    RefreshGList(&g12, win, NULL, 12);     /* the linked list of gads    */

    dBox(play_ships[0].Pos, CURR, PLAYER);
    dBox(play_ships[1].Pos, SET, PLAYER);
    dBox(play_ships[2].Pos, SET, PLAYER);
    dBox(play_ships[3].Pos, SET, PLAYER);
    dBox(play_ships[4].Pos, SET, PLAYER);

    wText("Place your sheep...");

    OffMenu(win, NOITEM);
    while (!done) {
        Wait(1L << win->UserPort->mp_SigBit);
        while (imsg = (struct IntuiMessage *)GetMsg(win->UserPort)) {
            class = imsg->Class;
            if (class == IDCMP_GADGETUP)
                work = (struct Gadget *)imsg->IAddress;
            ReplyMsg((struct Message *)imsg);
            if (class == IDCMP_GADGETUP) {
                switch (work->GadgetID) {
                    case UP_GAD:
                        moveUp(&(play_ships[cur]), cur);
                        break;
                    case DN_GAD:
                        moveDown(&(play_ships[cur]), cur);
                        break;
                    case LT_GAD:
                        moveLeft(&(play_ships[cur]), cur);
                        break;
                    case RT_GAD:
                        moveRight(&(play_ships[cur]), cur);
                        break;
                    case RO_GAD:
                        shipRotate(&(play_ships[cur]), cur);
                        break;
                    case RN_GAD:
                        dBox(play_ships[0].Pos, FRESH, PLAYER);
                        dBox(play_ships[1].Pos, FRESH, PLAYER);
                        dBox(play_ships[2].Pos, FRESH, PLAYER);
                        dBox(play_ships[3].Pos, FRESH, PLAYER);
                        dBox(play_ships[4].Pos, FRESH, PLAYER);
                        pickShips(play_ships);
                        dBox(play_ships[0].Pos, SET, PLAYER);
                        dBox(play_ships[1].Pos, SET, PLAYER);
                        dBox(play_ships[2].Pos, SET, PLAYER);
                        dBox(play_ships[3].Pos, SET, PLAYER);
                        dBox(play_ships[4].Pos, SET, PLAYER);
                        dBox(play_ships[cur].Pos, CURR, PLAYER);
                        break;
                    case QT_GAD:
                        done = TRUE;
                        break;
                    default:
                        if (work != active) {
                            /* Mutual exclusion nonsense! */
                            RemoveGList(win, active, 1);
                            RemoveGList(win, work, 1);
                            active->Flags &= ~GFLG_SELECTED;
                            work->Flags |= GFLG_SELECTED;
                            SetAPen(win->RPort, 0);
                            RectFill(win->RPort, active->LeftEdge,
                                                  active->TopEdge,
                                                  active->LeftEdge + 100,
                                                  active->TopEdge + 12);
                            RectFill(win->RPort, work->LeftEdge,
                                                  work->TopEdge,
                                                  work->LeftEdge + 100,
                                                  work->TopEdge + 12);
                            AddGList(win, active, (~0), 1, NULL);
                            AddGList(win, work, (~0), 1, NULL);
                            RefreshGList(work, win, NULL, 1);
                            RefreshGList(active, win, NULL, 1);
                            active = work;
                            dBox(play_ships[cur].Pos, SET, PLAYER);
                            cur = work->GadgetID - 206;
                            dBox(play_ships[cur].Pos, CURR, PLAYER);
                        }
                        break;
                }
            }
        }
    }

    SetPointer(win, pnt, 16, 16, -6, 0);
    wText("Please wait....");

    dBox(play_ships[0].Pos, SET, PLAYER);
    dBox(play_ships[1].Pos, SET, PLAYER);
    dBox(play_ships[2].Pos, SET, PLAYER);
    dBox(play_ships[3].Pos, SET, PLAYER);
    dBox(play_ships[4].Pos, SET, PLAYER);

    for (cur = 0; cur < 192; cur++)                /* These arrays are   */
        player[cur] = computer[cur] = (UBYTE)cur;  /* the 2 grids        */
    pickShips(comp_ships);
    play_score = comp_score = 0;
    in_progress = TRUE;

    RemoveGList(win, gad.NextGadget, 12);
    g1.NextGadget  = NULL;                 /* Re-link the gadgets */
    g2.NextGadget  = &g1;
    g3.NextGadget  = &g2;
    g4.NextGadget  = &g3;
    g5.NextGadget  = &g4;
    g6.NextGadget  = &g5;
    g7.NextGadget  = &g6;
    g8.NextGadget  = &g7;
    g9.NextGadget  = &g8;
    g10.NextGadget  = &g9;
    g11.NextGadget  = &g10;
    g12.NextGadget  = &g11;

    active->Flags &= ~GFLG_SELECTED;
    g7.Flags |= GFLG_SELECTED;

    updateDisplay(BOTTOM);

    ClearPointer(win);
    wText("It's your turn.");

    OnMenu(win, NOITEM);
    while (imsg = (struct IntuiMessage *)GetMsg(win->UserPort))
        ReplyMsg((struct Message *)imsg);

    return;
}

/*
 * Draws one or a series of boxes on either grid
 */
VOID dBox(ULONG ship, SHORT pen, UBYTE who)
{
    UWORD x, y = 0, z, a = 0;
    SHORT b_x[6], b_y[6], ix = 2, iy = 1, ixx = 17, iyy = 6;
    ULONG r_mask = ROW_MASK, c_mask = COL_MASK;

    x = 15;
    for (z = 0; z < 24; z++) {
        if (ship & c_mask) b_x[y++] = x;
        x += 20;
        c_mask >>= 1;
    }
    x = (who == PLAYER) ? 17 : 88;
    for (z = 0; z < 8; z++)
        {
        if (ship & r_mask) b_y[a++] = x;
        x += 8;
        r_mask >>= 1;
    }

    if (pen == EMPTY) {
        ix = iy = 0;
        ixx = 19;
        iyy = 7;
        pen = FRESH;
    }
    SetAPen(win->RPort, pen);
    if (y == 1)
        for (x = 0; x < a; x++) {
            RectFill(win->RPort,b_x[0]+ix,b_y[x]+iy,b_x[0]+ixx,b_y[x]+iyy);
        }
    else
        for (x = 0; x < y; x++) {
            RectFill(win->RPort,b_x[x]+ix,b_y[0]+iy,b_x[x]+ixx,b_y[0]+iyy);
        }
    return;
}

/*
 * Move a ship up a row
 */
VOID moveUp(struct Ship *ship, WORD cur)
{
    ULONG row = ALL_ROWS;

    if (ship->Pos & ROW_MASK) return;
    row &= ship->Pos;
    row <<= 1;

    safeDraw((ship->Pos & ALL_COLS), row, cur);
    return;
}

/*
 * Move a ship down a row
 */
VOID moveDown(struct Ship *ship, WORD cur)
{
    ULONG row = ALL_ROWS;

    if (ship->Pos & 0x01000000) return;
    row &= ship->Pos;
    row >>= 1;

    safeDraw((ship->Pos & ALL_COLS), row, cur);
    return;
}

/*
 * Move a ship left one column
 */
VOID moveLeft(struct Ship *ship, WORD cur)
{
    ULONG col = ALL_COLS;

    if (ship->Pos & COL_MASK) return;
    col &= ship->Pos;
    col <<= 1;

    safeDraw(col, (ship->Pos & ALL_ROWS), cur);
    return;
}

/*
 * Move a ship right one column
 */
VOID moveRight(struct Ship *ship, WORD cur)
{
    ULONG col = ALL_COLS;

    if (ship->Pos & 0x1) return;
    col &= ship->Pos;
    col >>= 1;

    safeDraw(col, (ship->Pos & ALL_ROWS), cur);
    return;
}

/*
 * Turn ship 90 degrees
 */
VOID shipRotate(struct Ship *ship, WORD cur)
{
    ULONG row=ALL_ROWS, col=ALL_COLS, h_check=ROW_MASK, v_check=COL_MASK;
    WORD x, y;

    for (x = 0; x < 24; x++) {
        if (ship->Pos & v_check) break;
        v_check >>= 1;
    }

    for (y = 0; y < 8; y++) {
        if (ship->Pos & h_check) break;
        h_check >>= 1;
    }

    if (ship->Data & HORIZ) {
        if ((8 - y) < sizes[cur]) return;
    }
    else {
        if ((24 - x) < sizes[cur]) return;
    }

    row = (ALL_COLS & ship->Pos) << (8 + x - y);
    col = (ALL_ROWS & ship->Pos) >> (8 + x - y);

    if (safeDraw(row, col, cur)) {
        if (ship->Data & HORIZ) ship->Data = 0x0;
        else ship->Data = 0x2;
    }
    return;
}

/*
 * Make sure we don't draw one ship on top of another!
 */
BOOL safeDraw(ULONG col, ULONG row, WORD cur)
{
    if (OK != collided(row, col, cur, play_ships)) return(FALSE);
   
    dBox(play_ships[cur].Pos, FRESH, PLAYER);
    play_ships[cur].Pos = row | col;
    dBox(play_ships[cur].Pos, CURR, PLAYER);

    return(TRUE);
}

/*
 * Is there a ship at this position?
 */
UBYTE collided(ULONG col, ULONG row, WORD cur, struct Ship *s)
{
    UBYTE x;

    for (x = 0; x < 5; x++) {
        if (x != cur) {
            if (((s[x]).Pos & row) && ((s[x]).Pos & col))
                return(x);
        }
    }

    return(OK);
}

/*
 * Randomly position five ship
 */
VOID pickShips(struct Ship *ships)
{
    UWORD ran, row, col, orien, touch = 1;
    UBYTE x, ct;
    ULONG r_mask, c_mask;
    BOOL fail;

    touch = (UWORD)getRand(250);
    touch = (touch <= 125) ? 0 : 1;   /* Will the ships touch? */

    for (ct = 0; ct < 5; ct++) {
        do {
            orien = (UWORD)getRand(250);
            orien = (orien <= 125) ? 0 : 1;
            fail = FALSE;
            ran = (UWORD)getRand(SQUARES);
            row = GET_ROW(ran);
            col = GET_COL(ran);
            if (orien == 1)
                while (row > (8-sizes[ct])) row -= 1;
            else
                while (col > (24 - sizes[ct])) col -= 1;
            r_mask = ROW_MASK >> (row-1);
            c_mask = COL_MASK >> (col-1);
            for (x = 0; x < (sizes[ct] - 1); x++) {
                if (orien == 1) r_mask |= r_mask >> 1;
                else c_mask |= c_mask >> 1;
            }
            if (OK != collided(r_mask, c_mask, ct, ships)) fail = TRUE;
            if (touch) {
                if (OK != collided(r_mask>>1,c_mask, ct, ships)) fail=TRUE;
                if (OK != collided(r_mask<<1,c_mask, ct, ships)) fail=TRUE;
                if (OK != collided(r_mask,c_mask>>1, ct, ships)) fail=TRUE;
                if (OK != collided(r_mask,c_mask<<1, ct, ships)) fail=TRUE;
            }
        } while (fail);
        ships[ct].Pos = r_mask | c_mask;
        if (orien == 1) ships[ct].Data = 0x0;
        else ships[ct].Data = 0x2;
    }

    return;
}

/*
 * My function that picks a pseudo-random number between zero and max
 */
LONG getRand(LONG max)
{
    LONG z;

    do {
        z = rand();
        while (z > max) z %= max;
    } while (z < 0);

    return(z + 1);
}

/*
 * Updates the grid and if there was a hit, it returns a number
 * between 0 and 4 that corresponds to an array of struct Ship.
 * i.e. play_ships[x]
 * If not a hit, returns OK
 */
UBYTE findHit(struct Ship *them, ULONG ship, UBYTE who)
{
    UBYTE x;

    if (OK != (x = collided(ALL_ROWS & ship, ALL_COLS & ship, OK, them))) {
        dBox(ship, CURR, who);
        return(x);
    }
    else {
        dBox(ship, EMPTY, who);
        return (OK);
    }
}

/*
 * Interprets mouseX and mouseY. Also determines if the player hit
 * or missed.
 */
BOOL findBlock(UWORD x, UWORD y)
{
    UWORD i, j, k = 1, pos = 0;
    UBYTE ct;
    UBYTE hit_mask = 0x80, s[SLEN];

    if ((x < 15) || (x > 495) || (y < 88) || (y > 152)) return(FALSE);

    for (i = 88; i <= 144; i += 8) {
        for (j = 15; j <= 475; j += 20, k++) {
            if (((x >= j) && (x <= (j+19))) && ((y >= i) && (y<= (i+7)))) {
                pos = k;
                break;
            }
        }
        if (pos) break;
    }
    pos -= 1;
    if (computer[pos] >= HIT) return(FALSE);

    if (OK != (ct = findHit(comp_ships, MAKE_SHIP(computer[pos]), COMP))) {
        for (j = 1; j <= sizes[ct]; j++) {
            if (!(comp_ships[ct].Data & hit_mask)) break;
            hit_mask >>= 1;
        }
        comp_ships[ct].Data |= hit_mask;
        if ((dead_masks[ct] & comp_ships[ct].Data) == dead_masks[ct]) {
            dBox(comp_ships[ct].Pos, KILL, COMP);
            comp_ships[ct].Data |= 0x1;
            i = ((UWORD)getRand(17)) - 1;
            sprintf(s, "You %s %s!!!", verbs[i], Pnames[ct]);
            wText(s);
            if (playit) playSound(&sounds[3], &Laudio, LaudioMP);
            play_score += 1;
        }
        else if (playit) playSound(&sounds[0], &Laudio, LaudioMP);
        computer[pos] = HIT;
        endgame();
    }
    else {
        computer[pos] = MISS;
        if (playit) playSound(&sounds[2], &Laudio, LaudioMP);
    }

    return (TRUE);
}

/*
 * Centers a string of text and prints it.
 */
VOID wText(UBYTE *s)
{
    UBYTE word[SLEN], len, lj, rj, j, k = 0;
    UWORD i;

    len = strlen(s);
    if (!len) return;
    len = (len > SLEN-1) ? SLEN-1 : len;
    lj = ((SLEN - 1) - len) / 2;
    rj = ((SLEN - 1) - len) - lj;

    for(i = 0; i < lj; i++) word[i] = ' ';
    for(j = i; j < lj + len; j++) word[j] = s[k++];
    for(i = j; i < SLEN-1; i++) word[i] = ' ';
    word[SLEN-1] = '\0';

    SetAPen(win->RPort, 1);
    Move(win->RPort, 155, 165);
    Text(win->RPort, word, SLEN - 1);

    return;
}

/*
 * My very own interpretation of Fuzzy Logic! This function simply
 * evaluates each square's membership in a (fictional) Fuzzy Set.
 * The stronger the membership, the more likely the computer will be
 * to select it. Membership is determined by:
 *  1) Empty?             = No Membership
 *  2) Neighbors          = Membership
 *  3) Neighbors are HITs = Strongest Membership
 * Note: a HIT is a ship UNDER ATTACK.
 */
VOID compsTurn(VOID)
{
#define COL_A(a) (a==0||a==24||a==48||a==72||a==96||a==120||a==144||a==168)
#define COL_B(a) (a==1||a==25||a==49||a==73||a==97||a==121||a==145||a==167)
#define COL_Y(a) (a==22||a==46||a==70||a==94||a==118||a==142||a==166||a==190)
#define COL_Z(a) (a==23||a==47||a==71||a==95||a==119||a==143||a==167||a==191)
#define ROW_A(a) (a < 24)
#define ROW_B(a) ((a < 48) && (a > 23))
#define ROW_Y(a) ((a < 168) && (a > 143))
#define ROW_Z(a) (a > 167)

    UBYTE r = 0, row, col, hit_mask = 0x80, s[SLEN], num, i;
    UBYTE sets[SQUARES], max;
    ULONG row_mask, col_mask;

    if (think == 0) {      /* No logic - random pick */
        while (!r) {
            r = (UBYTE)getRand(SQUARES) - 1;
            if (player[r] >= HIT) r = 0;
        }
    }
    else {
        if (!(play_ships[4].Data & 1))      max = 2;  /* Find shortest   */
        else if (!(play_ships[3].Data & 1)) max = 3;  /* ship. Squares   */
        else if (!(play_ships[2].Data & 1)) max = 4;  /* that have < max */
        else if (!(play_ships[1].Data & 1)) max = 5;  /* can't be members*/
        else max = 6;
        for (i = 0; i < SQUARES; i++) {
            sets[i] = 0;   /* Start with a membership of zero */
            if ((findShort(player[i], max)) && (player[i] < HIT)) {
                /* Increase membership for HITs */
                if ((!ROW_A(i)) && (player[i-24] == HIT)) {
                    sets[i] += 10;
                    if ((!ROW_B(i)) && (player[i-48] == HIT)) sets[i]+=20;
                }
                if ((!ROW_Z(i)) && (player[i+24] == HIT)) {
                    sets[i] += 10;
                    if ((!ROW_Y(i)) && (player[i+48] == HIT)) sets[i]+=20;
                }
                if ((!COL_A(i)) && (player[i-1] == HIT)) {
                    sets[i] += 10;
                    if ((!COL_B(i)) && (player[i-2] == HIT)) sets[i]+=20;
                }
                if ((!COL_Z(i)) && (player[i+1] == HIT)) {
                    sets[i] += 10;
                    if ((!COL_Y(i))&&(player[i+2] == HIT)) sets[i]+=20;
                }
                if ((think == 2) && (sets[i] == 0)) {
                    num = 0;
                    /* Increase membership for neighbors */
                    if ((!ROW_A(i)) && (player[i-24] < HIT)) num++;
                    if ((!ROW_Z(i)) && (player[i+24] < HIT)) num++;
                    if ((!COL_A(i)) && (player[i-1]  < HIT)) num++;
                    if ((!COL_Z(i)) && (player[i+1]  < HIT)) num++;
                    if (ROW_A(i) || ROW_Z(i) || COL_A(i) || COL_Z(i))
                        sets[i] += (num + 1);
                    else sets[i] += num;
                }
            }
        }
        max = r = 0;
        for (i = 0; i < SQUARES; i++) {     /* Find strongest members */
            if (sets[i] > max) {
                max = sets[i];
                r = i;
            }
        }
        if ((max > 0) && (max < 10)) {     /* No HITs, but neighbors  */
            do {                                 /* Randomly select   */
                i = (UBYTE)getRand(SQUARES) - 1; /* a member with max */
            } while (sets[i] != max);            /* neighbors         */
            r = i;
        }
        else if (max == 0) {              /* Should never get here!   */
            r = 0;                        /* No members! Ack!         */
            while (!r) {
                r = (UBYTE)getRand(SQUARES) - 1;
                if (player[r] >= HIT) r = 0;
            }
        }
    }

    row_mask = ROW_MASK >> (GET_ROW(player[r]) - 1);
    col_mask = COL_MASK >> (GET_COL(player[r]) - 1);

    if (OK != (max = findHit(play_ships, row_mask|col_mask, PLAYER))) {
        for (i = 1; i <= sizes[max]; i++) {
            if (!(play_ships[max].Data & hit_mask)) break;
            hit_mask >>= 1;
        }
        play_ships[max].Data |= hit_mask;
        if ((dead_masks[max] & play_ships[max].Data) == dead_masks[max]) {
            play_ships[max].Data |= 0x1;
            dBox(play_ships[max].Pos, KILL, PLAYER);
            if (play_ships[max].Data & HORIZ) {
                col_mask = COL_MASK;
                while (!(col_mask & play_ships[max].Pos)) col_mask >>= 1;
                for (i = 0; i < sizes[max]; i++) {
                    getXY(&row, &col, &num, row_mask|col_mask);
                    player[num] = SUNK;
                    col_mask >>= 1;
                }
            }
            else {
                row_mask = ROW_MASK;
                while (!(row_mask & play_ships[max].Pos)) row_mask >>= 1;
                for (i = 0; i < sizes[max]; i++) {
                    getXY(&row, &col, &num, row_mask|col_mask);
                    player[num] = SUNK;
                    row_mask >>= 1;
                }
            }
            i = ((UWORD)getRand(17)) - 1;
            sprintf(s, "I %s %s!!!", verbs[i], Cnames[max]);
            wText(s);
            if (playit) playSound(&sounds[4], &Raudio, RaudioMP);
            comp_score += 1;
        }
        else {
            player[r] = HIT;
            if (playit) playSound(&sounds[0], &Raudio, RaudioMP);
        }
        endgame();
    }
    else {
        player[r] = MISS;
        if (playit) playSound(&sounds[1], &Raudio, RaudioMP);
    }

    return;
}

/*
 * Checks horizontally and vertically for neighbors
 */
BOOL findShort(UBYTE number, WORD shortest)
{
    WORD i, j;
    UBYTE up_ct, dn_ct, lt_ct, rt_ct, row, col;

    up_ct = dn_ct = lt_ct = rt_ct = 0;

    row = GET_ROW(number);
    col = GET_COL(number);

    if (row != 1) {
        j = number - (24 * (row - 1));
        for (i = number - 24; i >= j; i -= 24) {
            if (player[i] >= MISS) break;
            up_ct += 1;
        }
    }
    if (row != 8) {
        j = number + (24 * (8 - row));
        for (i = number + 24; i <= j; i += 24) {
            if (player[i] >= MISS) break;
            dn_ct += 1;
        }
    }
    if (col != 1) {
        j = number - (col - 1);
        for (i = number - 1; i >= j; i--) {
            if (player[i] >= MISS) break;
            lt_ct += 1;
        }
    }
    if (col != 24) {
        j = (24 * row) - 1;
        for (i = number + 1; i <= j; i++) {
            if (player[i] >= MISS) break;
            rt_ct += 1;
        }
    }

    if (((lt_ct + rt_ct + 1)< shortest) && ((up_ct + dn_ct + 1)< shortest))
        return(FALSE);
    else
        return(TRUE);
}

/*
 * Checks to see if the game has ended
 */
VOID endgame(VOID)
{
    UBYTE x, y, ro, co, o;
    ULONG rm, cm;

    if (comp_score == 5) {
        wText("I win!!!!");
        for (x = 0; x < 5; x++) {
            if (!(comp_ships[x].Data & 1)) {
                if (comp_ships[x].Data & HORIZ) {
                    rm = comp_ships[x].Pos & ALL_ROWS;
                    cm = COL_MASK;
                    while (!(cm & comp_ships[x].Pos)) cm >>= 1;
                    for (y = 0; y < sizes[x]; y++) {
                        getXY(&ro, &co, &o, rm|cm);
                        if (computer[o] < HIT) dBox(rm|cm, SET, COMP);
                        cm >>= 1;
                    }
                }
                else {
                    rm = ROW_MASK;
                    cm = comp_ships[x].Pos & ALL_COLS;
                    while (!(rm & comp_ships[x].Pos)) rm >>= 1;
                    for (y = 0; y < sizes[x]; y++) {
                        getXY(&ro, &co, &o, rm|cm);
                        if (computer[o] < HIT) dBox(rm|cm, SET, COMP);
                        rm >>= 1;
                    }
                }
            }
        }
    }
                
    if (play_score == 5) wText("Arghh! You won!");

    if ((play_score >= 5) || (comp_score >= 5)) {
        for (x = 0; x < 5; x++) {
            play_ships[x].Data &= 0x2;
            comp_ships[x].Data = 0x0;
            comp_ships[x].Pos = 0x0;
        }
        comp_score = play_score = 0;
        in_progress = FALSE;
    }
    return;
}

/*
 * Redraws grid
 */
VOID updateDisplay(UBYTE side)
{
    UWORD x, y;
    UWORD y1 = 17, y2 = 81;

    SetDrMd(win->RPort, JAM2);
    SetAPen(win->RPort, 0);

    if (side == BOTTOM) {
        y1 = 88;
        y2 = 152;
    }

    RectFill(win->RPort, 15, y1, 495, y2);
    SetAPen(win->RPort, 2);
    for (x = 15; x <= 475; x += 20) {
        Move(win->RPort, x, y1);
        Draw(win->RPort, x, y2 - 1);
        Move(win->RPort, x+1, y1);
        Draw(win->RPort, x+1, y2 - 2);
    }
    for (y = y1; y <= (y2 - 8); y += 8) {
        Move(win->RPort, 15, y);
        Draw(win->RPort, 493,y);
    }
    SetAPen(win->RPort, 1);
    for (x = 34; x <= 494; x += 20) {
        for (y = y1; y <= (y2 - 8); y += 8) {
            Move(win->RPort, x, y);
            Draw(win->RPort, x, y+7);
            Draw(win->RPort, x-18, y+7);
            Move(win->RPort, x-1, y+1);
            Draw(win->RPort, x-1, y+6);
        }
    }

    return;
}

/*
 * Converts a ULONG (i.e. struct Ship.Pos) to row, column, and ordinal
 * number.
 * Example 0x80800000 = 10000000100000000000000000000000 binary
 * which corresponds to ^row 1  ^column 1
 * which would be player[0] or computer[0]
 */
VOID getXY(UBYTE *r, UBYTE *c, UBYTE *ord, ULONG comp)
{
    UBYTE i;
    ULONG row_mask = ROW_MASK, col_mask = COL_MASK;

    for (i = 1; i <= 8; i++) {
        if (comp & row_mask) break;
        row_mask >>= 1;
    }
    *r = i;
    for (i = 1; i <= 24; i++) {
        if (comp & col_mask) break;
        col_mask >>= 1;
    }
    *c = i;
    *ord = (((*r - 1) * 24) + *c) - 1;
    
    return;
}

/*
 * Open audio.device and allocate one left channel and one right channel
 */
VOID openAudio(VOID)
{
    UBYTE Rchan[] = { 0x1, 0x8 }, Lchan[] = { 0x2, 0x4};

    if (!(RaudioMP = CreatePort(0, 0))) return;
    Raudio.ioa_Request.io_Command                = ADCMD_ALLOCATE;
    Raudio.ioa_Request.io_Message.mn_ReplyPort   = RaudioMP;
    Raudio.ioa_Request.io_Message.mn_Node.ln_Pri = 120;
    Raudio.ioa_Request.io_Flags                  = ADIOF_NOWAIT;
    Raudio.ioa_AllocKey                          = 0;
    Raudio.ioa_Data                              = Rchan;
    Raudio.ioa_Length                            = sizeof(Rchan);
    Raud_dev = OpenDevice(AUDIONAME, 0L, (struct IORequest *)&Raudio, 0L);
    if (Raud_dev) {
        DeletePort(RaudioMP);
        RaudioMP = NULL;
    }

    Raudio.ioa_Request.io_Command = CMD_WRITE;
    Raudio.ioa_Request.io_Flags   |= ADIOF_PERVOL;
    Raudio.ioa_Volume             = 60;
    Raudio.ioa_Cycles             = 1;

    if (!(LaudioMP = CreatePort(0, 0))) return;
    Laudio.ioa_Request.io_Command                = ADCMD_ALLOCATE;
    Laudio.ioa_Request.io_Message.mn_ReplyPort   = LaudioMP;
    Laudio.ioa_Request.io_Message.mn_Node.ln_Pri = 120;
    Laudio.ioa_Request.io_Flags                  = ADIOF_NOWAIT;
    Laudio.ioa_AllocKey                          = 0;
    Laudio.ioa_Data                              = Lchan;
    Laudio.ioa_Length                            = sizeof(Lchan);
    Laud_dev = OpenDevice(AUDIONAME, 0L, (struct IORequest *)&Laudio, 0L);
    if (Laud_dev) {
        DeletePort(LaudioMP);
        LaudioMP = NULL;
    }
    Laudio.ioa_Request.io_Command = CMD_WRITE;
    Laudio.ioa_Request.io_Flags   |= ADIOF_PERVOL;
    Laudio.ioa_Volume             = 60;
    Laudio.ioa_Cycles             = 1;

    return;
}

/*
 * Read 8VSX sound samples into memory
 */
VOID openSounds(VOID)
{
#define ID_BODY  MakeID('B', 'O', 'D', 'Y')
#define ID_VHDR  MakeID('V', 'H', 'D', 'R')

    FILE *fi;
    LONG fi_seek;
    UBYTE i;
    struct Voice8Header {
        ULONG oneShotHiSamples;
        ULONG repeatHiSamples;
        ULONG samplesPerHiCycle;
        UWORD samplePerSec;
        UBYTE ctOctave;
        UBYTE sCompression;
        LONG  volume;
    } vhdr;

    for (i = 0; i < 5; i++) {
        if (fi = fopen(sounds[i].name, "rb")) {
            fi_seek = setSeek(fi, ID_VHDR);
            if (fi_seek != -1) {
                fseek(fi, fi_seek + 8, SEEK_SET);
                fread((VOID *)&vhdr, 1, sizeof(struct Voice8Header), fi);
                sounds[i].SPS = vhdr.samplePerSec;
                sounds[i].size = vhdr.oneShotHiSamples;
                if (!sounds[i].size) sounds[i].size = vhdr.repeatHiSamples;
                fi_seek = setSeek(fi, ID_BODY);
                if (fi_seek != -1) {
                    fseek(fi, fi_seek + 8, SEEK_SET);
                    sounds[i].data = AllocMem(sounds[i].size, MEMF_CHIP);
                    fread((VOID *)sounds[i].data, 1, sounds[i].size, fi);
                }
            }
            fclose(fi);
        }
    }

    return;
}

/*
 * Find a Chunk ID in an IFF file
 */
LONG setSeek(FILE *sf, ULONG ID)
{
    WORD ct, i;
    BOOL test = FALSE;
    LONG byts = 0, seek = 0;
    UBYTE buf[1024];

    rewind(sf);
    ct = fread((VOID *)buf, 1, 1024, sf);
    while (1) {
        for (i = 0; i < ct; i++) {
            if (MakeID(buf[i],buf[i+1],buf[i+2],buf[i+3]) == ID) {
                test = TRUE;
                break;
            }
        }
        if (test || feof(sf)) break;
        ct = fread(buf, 1, 1024, sf);
        byts += ct;
    }
    seek = byts + i;
    if (test) return(seek);
    else return(-1);
}

/*
 * Play an 8VSX sound sample
 */
VOID playSound(struct noiz *nz, struct IOAudio *io_p, struct MsgPort *port)
{
    struct Message *msg;

    if (!port) return;
    if (!nz->data) return;
    if (GfxBase->DisplayFlags & PAL)
        io_p->ioa_Period = PAL_CLOCK / nz->SPS;
    else
        io_p->ioa_Period = NTSC_CLOCK / nz->SPS;
    io_p->ioa_Data = (UBYTE *)nz->data;
    io_p->ioa_Length = nz->size;
    BeginIO((struct IORequest *)io_p);
    WaitPort(port);
    msg = GetMsg(port);

    return;
}
