
char title [] = "Zoing! 1.0 (c) 1986 Alonzo Gariepy, Public Domain";

/*  Simulates elastic collision of multiple balls in rectangular area. */
/*      See the doc file accompanying this source code.                */
/*                                                                     */
/*  31 August 1986      Finished and released version 1.0, AMG         */
/*                                                                     */
/*   1 Sept   1986      Added check for 1.2 graphics lib 33, AMG       */
/*                                                                     */

#include <exec/types.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <graphics/sprite.h>

#define LIBVER        libver
long    LIBVER        = 33;          /* need this for ActivateWindow  */
#define ONE_POINT_TWO one_point_two
long    ONE_POINT_TWO = 0;

#define MAXBALLS      8
#define BALLS         balls_used    /* defined on command line.    */
#define FRACBITS      6             /* 0<x<7 fixed point integers. */
#define GRAVITY       gravity       /* defined by user. 2 is good. */
#define LEVITY        levity        /* defined by user. 1 is best. */
#define IS_HIRES      1             /* workbench always high res.  */
#define IS_INTERLACE  is_interlace  /* variable due to interlace.  */
#define STEPSPERFRAME 2             /* 0<x<? simulation resolution */
#define SHIFTX        (FRACBITS-IS_HIRES)
#define SHIFTY        shift_y       /* variable due to interlace.  */
#define MINX          0
#define MINY          0
#define MAXX          (SIMX (304))
#define MAXY          max_y         /* variable due to interlace.  */

/* defined in arguments() and openwindow() */
long    BALLS, IS_INTERLACE, SHIFTY, MAXY, GRAVITY, LEVITY;


#define BALL_SPRITE     happyr
#define CURSOR_SPRITE   charlie
#define HEAD_SPRITE     harryr
#define WEIRD_SPRITE    sunnyw
#define SIZEOF_SPRITE   36    /* total words including posctl and term */

#define CURSORDX        7     /* The offset from pointer to cursor sprite */
#define CURSORDY        7
#define D               (SIMX (16))

#define CONX(x)         ((x) << SHIFTX)
#define CONY(y)         ((y) << SHIFTY)
#define SCREENX(x)      ((x) >> SHIFTX)
#define SCREENY(y)      ((y) >> SHIFTY)
#define SIMX(x)         ((x) << FRACBITS)
#define SIMY(y)         ((y) << FRACBITS)

struct NewWindow windef = {
        0, 0, 84 + 8*sizeof(title) - 7, 20,
        -1, -1,
        CLOSEWINDOW,
        WINDOWCLOSE | WINDOWDEPTH | WINDOWDRAG | WINDOWSIZING,
        NULL, NULL,
        title,
        NULL, NULL, 84 + 8*sizeof("Zoing!") - 9, 0, 0, 0,
        WBENCHSCREEN
};

extern  UWORD charlie [SIZEOF_SPRITE];
extern  UWORD amused  [SIZEOF_SPRITE];
/* and you thought Castro was a yogurt for Unix programmers */
extern  UWORD happyw  [SIZEOF_SPRITE];
extern  UWORD angryr  [SIZEOF_SPRITE];
extern  UWORD happyr  [SIZEOF_SPRITE];
extern  UWORD sunnyw  [SIZEOF_SPRITE];
extern  UWORD harryr  [SIZEOF_SPRITE];

long    framecount  = 0;
long    cursor      = 0;  /* boolean flag: window and cursor ball active */
long    angry [MAXBALLS];
long    alt_on [MAXBALLS];

struct  SimpleSprite spr[MAXBALLS];
struct  Window       *win;
struct  ViewPort     *vp;

UWORD   *sprbuf;

long    x [MAXBALLS]  = {0, SIMX(300),
                                SIMX(128),
                          SIMX(114), SIMX(114),
                     SIMX(100), SIMX(100), SIMX(100), };

long    y [MAXBALLS]  = {0, SIMY(10),
                                SIMY(92),
                          SIMY(84), SIMY(100),
                     SIMY(76), SIMY(92), SIMY(108), };

long    vx [MAXBALLS] = {0, -180,  0,  0, 0,  0, 0, 0,};
long    vy [MAXBALLS] = {0,  70,   0,  0, 0,  0, 0, 0,};

UWORD   *sprites [MAXBALLS] = {charlie, sunnyw, harryr, happyr,
                               happyr,  happyr, happyr, happyr, };

UWORD   *sprites_alt [MAXBALLS] = {amused, sunnyw, harryr, angryr,
                                   angryr,  angryr, angryr, angryr, };

long    GfxBase, IntuitionBase;

main (argc, argv)
    long argc;
    char *argv[];
{
    int i;

    openwindow ();
    arguments (argc, argv);
    copysprites ();
    defineballs ();

    for (;;)
        {
        trackcursor ();
        doIDCMP ();
        WaitTOF (vp);
        for (i = 0; i < STEPSPERFRAME; ++i)
            {
            collisions ();
            moveballs ();
            }
        showballs ();
        }
}

arguments (argc, argv)
    long    argc;
    char    *argv [];
{
    char    *name = (char *)0, usage [sizeof (title)];
    long    i, string = 0;

    for (i = 0; i < sizeof (usage) - 1;)
        if (name && (usage [i] = *name))
            ++name, ++i;
        else
            {
            switch (string)
                {
                case 0:  name = " Usage: ";
                         string = 1;
                         continue;
                case 1:  name = argv [0];
                         string = 2;
                         continue;
                case 2:  name = " balls(1..10) gravity(0..8) ";
                         string = 3;
                         continue;
                case 3:  break;
                }
            break;
            }
    usage [sizeof(usage) - 1] = (char)0;

    BALLS = MAXBALLS;
    GRAVITY = 0;
    LEVITY  = 0;
    switch (argc)
        {
        case 4:
            LEVITY = atoi (argv [3]);
            if (LEVITY < 0 || LEVITY > 5)
                seppuku (usage);
            if (LEVITY && ONE_POINT_TWO)
                ActivateWindow (win);
        case 3:
            GRAVITY = atoi (argv [2]);
            if (GRAVITY < 0 || GRAVITY > 10)
                seppuku (usage);
        case 2:
            BALLS = atoi (argv [1]) + 1;
            if (*argv [1] == '?' || BALLS < 2 || BALLS > MAXBALLS)
                seppuku (usage);
        default:
            break;
        }
}

trackcursor ()
{
    static   long   oldx, oldy;
    register long   xx, yy;

    if (win->Flags & WINDOWACTIVE)
        {
        x [0] = xx = CONX (win->WScreen->MouseX) - SIMX (CURSORDX);
        y [0] = yy = CONY (win->WScreen->MouseY) - SIMX (CURSORDX);

        if (cursor)
            {
            vx [0] = (xx - oldx) / STEPSPERFRAME;
            vy [0] = (yy - oldy) / STEPSPERFRAME;
            }
        else
            {
            vx [0] = vy [0] = 0;
            cursor = 1;
            }
        oldx = xx;
        oldy = yy;
        }
    else
        {
        cursor = 0;
        }
}


moveballs ()         /* move the balls and reflect from walls */
{
    register int     i;

    x [0] += vx [0];
    y [0] += vy [0];

    for (i = 1; i < BALLS; i++)
        {
        x [i] += vx [i];
        y [i] += vy [i];

        for (;;)    /* we might require several bounces */
            {
            if (x [i] <= MINX)
                x [i] = MINX + MINX - x [i], vx [i] = -vx [i];
            if (x [i] >= MAXX)
                x [i] = MAXX + MAXX - x [i], vx [i] = -vx [i];
            if (x [i] >= MINX)
                break;
            }

        vy [i] += LEVITY;
        for (;;)
            {
            if (y [i] <= MINY)
                y [i] = MINY + MINY - y [i], vy [i] = -vy [i];
            if (y [i] >= MAXY)
                y [i] = MAXY + MAXY - y [i], vy [i] = -vy [i];
            if (y [i] >= MINY)
                break;
            }
        vy [i] += GRAVITY;
        }
}

showballs ()
{
    int        i;

    ++framecount;

    for (i = 1; i < BALLS; ++i)
        {
        if (alt_on [i] != angry [i])
            {
            ChangeSprite (vp, spr+i, angry[i] ? sprites_alt[i] : sprites[i]);
            alt_on [i] = angry [i];
            }
        MoveSprite (vp, spr + i, SCREENX (x [i]), SCREENY (y [i]));
        }

/* I used to make the cursor gloat here, but it was too distracting! */
}

openwindow ()
{
    long    height;

    if (GfxBase = OpenLibrary ("graphics.library", LIBVER))
        ONE_POINT_TWO = 1;
    else if (GfxBase = OpenLibrary ("graphics.library", 0))
        ONE_POINT_TWO = 0;
    else
        seppuku ("Zoing! Can't open graphics.");

    if (!(IntuitionBase = OpenLibrary ("intuition.library", 0)))
        seppuku ("Zoing! Can't open Intuition.");

    if (!(win = (struct Window *) OpenWindow (&windef)))
        seppuku ("Zoing! Can't open window.");
    vp = (struct ViewPort *) ViewPortAddress (win);

    height = win->WScreen->Height;

    IS_INTERLACE = (height > 300) ? 1 : 0;
    SHIFTY = FRACBITS - IS_INTERLACE;
    MAXY = CONY(height) - SIMY(16);
}

defineballs ()
{
    long    i, j, color, red, green, blue;

    for (i = 16; i <= 19; ++i)
        {
        color = GetRGB4 (vp->ColorMap, i);
        red   = color >> 8 & 0xF;
        green = color >> 4 & 0xF;
        blue  = color      & 0xF;

        for (j = 4; j <= 12; j += 4)
            SetRGB4 (vp, i + j, red, green, blue);
        }

    for (i = 1; i < BALLS; i++)
        {
        if (GetSprite (spr + i, -1) < 0)
            seppuku ("Zoing! Sprite allocation failed. ");

        spr [i].height = 16;
        spr [i].x = SCREENX (x [i]);
        spr [i].y = SCREENY (y [i]);

        ChangeSprite (vp, spr + i, sprites [i]);
        }

    SetPointer (win, sprites [0], 16, 16, -CURSORDX, -CURSORDY);
    SetWindowTitles (win, title, title);
}

free_resources ()
{
        register int i;

        for (i = 1; i < BALLS; i++)
                if (spr [i].num)
                        FreeSprite ((long) spr [i].num);

        if (sprbuf)
                FreeMem (sprbuf, 2*2*SIZEOF_SPRITE * BALLS);
        if (win)
                CloseWindow (win);
        if (GfxBase)
                CloseLibrary (GfxBase);
        if (IntuitionBase)
                CloseLibrary (IntuitionBase);
}

doIDCMP ()
{
    long    msg;

    if (msg = GetMsg (win -> UserPort))
        {
        ReplyMsg (msg);
        free_resources ();
        printf ("framecount = %ld\n", framecount);
        exit (0);
        }
}

seppuku (str)
    char *str;
{
    if (win)
        {
        SetWindowTitles (win, str, -1);
        if (ONE_POINT_TWO)
            ActivateWindow (win);               /* don't die quietly */
        WaitPort (win -> UserPort);
        }
    free_resources ();
    exit (100);
}

copysprites ()
{
    UWORD   *cw;       /* current position in buffer of sprite images */
    UWORD   *sprite;   /* current position in sprite                  */
    int     i, j;

    if (!(sprbuf = (UWORD *)
                AllocMem (2 * 2*SIZEOF_SPRITE * BALLS, MEMF_CHIP)))
        seppuku ("Zoing! Can't allocate sprite buffer. ");

    cw = sprbuf;    /* current position at top of buffer */


    for (i = 0; i < BALLS; ++i)
        {
        sprite = sprites [i];
        if (!sprite)
            seppuku ("Zoing! Missing sprite definition. ");
        sprites [i] = cw;
        for (j = 0; j < SIZEOF_SPRITE; ++j)
            *cw++ = *sprite++;
        }

    for (i = 0; i < BALLS; ++i)
        {
        sprite = sprites_alt [i];
        if (!sprite)
            seppuku ("Zoing! Missing sprite definition. ");
        sprites_alt [i] = cw;
        for (j = 0; j < SIZEOF_SPRITE; ++j)
            *cw++ = *sprite++;
        }
}

collisions ()
{
    /* These are statics so we don't have to worry about stack size. */
    long   i, j;
    long   dx, dy, dh, halfdh, dvx, dvy, evx, evy;

    for (i = cursor ? 0 : 1;  i < BALLS; ++i)
        for (j = i+1; j < BALLS; ++j)
            {
            dx = x [i] - x [j];
            dy = y [i] - y [j];

            if (dx > D || dx < -D || dy > D || dy < -D)
                continue;

            dh = dx*dx + dy*dy;
                           /* big chunk of fudge */
            if (dh > D*D + (2 << 2*FRACBITS))
                continue;

            dvx = vx [i] - vx [j];
            dvy = vy [i] - vy [j];

            if (i == 0)
                {
                dvx = dvx*2 - dvx/2;  /* first term:   infinite mass */
                dvy = dvy*2 - dvy/2;  /* second term:  bit inelastic */
                }

            evx = dx * dvx + dy * dvy;      /* sign of relative speed */

            if (evx >= 0)  /* same velocity or position, or departing */
                continue;

/* Since energy is proportional to the square of the velocity, simple   */
/* rounding does not work (errors won't necessarily balance over time). */
/* We'll try to make sure that the middle round value always is rounded */
/* down by subtracting one from halfdh.                                 */

            halfdh = dh/2 - 1;   /* if anything we lose energy */

            evx = evx * dx;
            if (evx > 0)
                evx = (evx + halfdh) / dh;
            else
                evx = (evx - halfdh) / dh;
            evy = (dx * dvy - dy * dvx) * dx;
            if (evy > 0)
                evy = dvy - (evy + halfdh) / dh;
            else
                evy = dvy - (evy - halfdh) / dh;

            if (i)    /* cursor doesn't change speed */
                vx [i] -= evx, vy [i] -= evy;
            vx [j] += evx;
            vy [j] += evy;

            /* some cute display stuff */
            switch (i)      /* personalities */
                {
                case 0:
                    if (j > 2)
                        angry [j] = 1;          /* yuck! */
                    break;
                case 1: case 2:             /* the fun guys cheer */
                    angry [j] = 0;          /* everybody up       */
                    break;
                default:
                    angry [i] |= angry [j]; /* infectious grief */
                    angry [j] |= angry [i];
                    break;
                }
            }
}

UWORD charlie [] =   { 0x0000, 0x0000, 0x0000, 0x07e0, 0x0000, 0x1ff8,
                       0x0020, 0x3ffc, 0x00f0, 0x7ffe, 0x0ff8, 0x7ffe,
                       0x33cc, 0xffff, 0xf3cf, 0xffff, 0xfeff, 0xffff,
                       0xfdbf, 0xffff, 0xfe7f, 0xffff, 0xfe7f, 0xffff,
                       0x7ffe, 0x7ffe, 0x781e, 0x7ffe, 0x3ffc, 0x3ffc,
                       0x1ff8, 0x1ff8, 0x07e0, 0x07e0, 0x0000, 0x0000
                     };

UWORD amused [] =    { 0x0000, 0x0000, 0x0000, 0x07e0, 0x0000, 0x1ff8,
                       0x0020, 0x3ffc, 0x00f0, 0x7ffe, 0x0ff8, 0x7ffe,
                       0x33cc, 0xffff, 0xf3cf, 0xffff, 0xfeff, 0xffff,
                       0xfdbf, 0xffff, 0xfe7f, 0xffff, 0xfe7f, 0xffff,
                       0x77ee, 0x7ffe, 0x781e, 0x7ffe, 0x3ffc, 0x3ffc,
                       0x1ff8, 0x1ff8, 0x07e0, 0x07e0, 0x0000, 0x0000
                     };

UWORD happyw [] =    { 0x0000, 0x0000, 0x0000, 0x07e0, 0x0020, 0x1ff8,
                       0x0070, 0x3ffc, 0x0ff8, 0x7ffe, 0x3ffc, 0x7ffe,
                       0xf3cf, 0xffff, 0xf3cf, 0xffff, 0xfeff, 0xffff,
                       0xfdbf, 0xffff, 0xfe7f, 0xffff, 0xeff7, 0xffff,
                       0x77ee, 0x7ffe, 0x781e, 0x7ffe, 0x3ffc, 0x3ffc,
                       0x1ff8, 0x1ff8, 0x07e0, 0x07e0, 0x0000, 0x0000
                     };

UWORD angryr [] =    { 0x0000, 0x0000, 0x07e0, 0x0000, 0x1ff8, 0x0000,
                       0x3ffc, 0x0000, 0x73ce, 0x0c30, 0x7dbe, 0x0240,
                       0xf3cf, 0x0c30, 0xf3cf, 0x0c30, 0xfeff, 0x0100,
                       0xfdbf, 0x0240, 0xfe7f, 0x0180, 0xffff, 0x0000,
                       0x7ffe, 0x0000, 0x781e, 0x07e0, 0x3ffc, 0x0000,
                       0x1ff8, 0x0000, 0x07e0, 0x0000, 0x0000, 0x0000
                     };

UWORD happyr [] =    { 0x0000, 0x0000, 0x07e0, 0x0000, 0x1ff8, 0x0000,
                       0x3ffc, 0x0000, 0x7ffe, 0x0000, 0x7ffe, 0x0000,
                       0xf3cf, 0x0c30, 0xf3cf, 0x0c30, 0xffff, 0x0000,
                       0xffff, 0x0000, 0xffff, 0x0000, 0xeff7, 0x1008,
                       0x77ee, 0x0810, 0x781e, 0x07e0, 0x3ffc, 0x0000,
                       0x1ff8, 0x0000, 0x07e0, 0x0000, 0x0000, 0x0000
                     };

UWORD sunnyw [] =    { 0x0000, 0x0000, 0x07e0, 0x07e0, 0x1ff8, 0x1ff8,
                       0x3ffc, 0x3ffc, 0x7ffe, 0x2184, 0x73ce, 0x4c32,
                       0xf3cf, 0xedb7, 0xffff, 0xe187, 0xfeff, 0xffff,
                       0xfdbf, 0xffff, 0xfe7f, 0xffff, 0xeff7, 0xffff,
                       0x77ee, 0x7ffe, 0x781e, 0x7ffe, 0x3ffc, 0x3ffc,
                       0x1ff8, 0x1ff8, 0x07e0, 0x07e0, 0x0000, 0x0000
                     };

UWORD harryr [] =    { 0x0000, 0x0000, 0x07e0, 0x07e0, 0x1ff8, 0x1fd8,
                       0x3ffc, 0x3f8c, 0x7ffe, 0x7006, 0x7ffe, 0x4002,
                       0xf3cf, 0x0c30, 0xf3cf, 0x0c30, 0xfeff, 0x0100,
                       0xfdbf, 0x0240, 0xfe7f, 0x0180, 0xeff7, 0x1008,
                       0x77ee, 0x0810, 0x781e, 0x07e0, 0x3ffc, 0x0000,
                       0x1ff8, 0x0000, 0x07e0, 0x0000, 0x0000, 0x0000
                     };

/*  This program was created with Vladimir Schwartz's editor, PTE, the  */
/*  best likely ever to be available for the Amiga.  The sprite images  */
/*  were designed using Ray R. Larson's SpriteMaker.  Funny how Charlie */
/*  Chaplin and his look alike symbolize similar organizations...       */
