/*
 * This is a more robust "pong" than mmpong.c ... it handles more than
 *  two people and puts paddles on all four sides of the screen, assuming
 *  you have that many mice plugged in.
 *
 * Player 1 is on the left, Player 2 on the right, 3 on the top, 4 on the
 *  bottom, and then 5 to n continue this pattern.
 *
 * This was written to show a robust use of the library with arbitrary
 *  numbers of players. mmpong.c is better if you want a small example
 *  to get you started on your own program.
 *
 * You need Simple Directmedia Layer (http://libsdl.org/) to use this code.
 *
 * Controls:
 *  Plus to add more balls to the game, minus to remove some. Escape to quit.
 *  Whatever mice you have will control the paddles.
 *
 * Written by Ryan C. Gordon (icculus@icculus.org).
 */

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

#include "SDL.h"
#include "manymouse.h"

#ifdef _MSC_VER
#define inline __inline
#endif

/*
 * paddles and balls. Things. They're all basically rectangles that move.
 */
typedef struct
{
    Uint32 color;
    float x;  /* location is top left pixel. */
    float y;
    int w;  /* size is in pixels. */
    int h;
    float xvelocity;  /* velocity is in pixels per second. */
    float yvelocity;
    float delayUpdate;  /* seconds to delay before updating. */
    int exists;
    int blanked; /* already blanked this frame. */
} PongThing;

typedef struct
{
    Uint8 r;
    Uint8 g;
    Uint8 b;
} RGB;

#define MAX_PADDLES 32
#define MAX_BALLS   32
#define MAX_UPDATERECTS 100

/* Paddle speed for keyboard input (in seconds to cross the screen). */
#define PADDLE_SPEED  1.0

/* default ball speeds (in seconds to cross the screen). */
#define BALL_VSPEED   5.0
#define BALL_HSPEED   1.7

/* time to wait between setting video mode and balls start moving. */
#define VIDEOINIT_DELAY 3.0f

typedef enum
{
    PADDLESIDE_LEFT=0,
    PADDLESIDE_RIGHT,
    PADDLESIDE_TOP,
    PADDLESIDE_BOTTOM,
    PADDLESIDE_MAX
} PaddleSide;

static PongThing paddles[MAX_PADDLES];
static PongThing balls[MAX_BALLS];
static int sideExists[PADDLESIDE_MAX];
static int sideScore[PADDLESIDE_MAX];
static SDL_Rect rects[MAX_UPDATERECTS];
static int rectCount = 0;
static SDL_Surface *screen = NULL;
static int available_mice = 0;
static Uint32 sdlvideoflags = SDL_FULLSCREEN;
static int sdlgrab = 1;
static int showfps = 0;
static int screenwidth = 640;
static int screenheight = 480;
static float screenWidthScale = 1.0f;
static float screenHeightScale = 1.0f;

static void renderOneThing(PongThing *thing, int blanking);

static int initVideo(void)
{
    int i;

    if (!SDL_WasInit(SDL_INIT_VIDEO))
    {
        if (SDL_Init(SDL_INIT_VIDEO) == -1)
            return 0;
    } /* if */

    SDL_WM_GrabInput(sdlgrab ? SDL_GRAB_ON : SDL_GRAB_OFF);
    SDL_ShowCursor(0);
    SDL_WM_SetCaption("ManyMouse PONG!", "ManyMousePong");

    screen = SDL_SetVideoMode(screenwidth, screenheight, 0, sdlvideoflags);
    if (screen == NULL)
    {
        SDL_Quit();
        return 0;
    } /* if */

    /*
     * We consider 640x480 to be the "native" resolution of the game.
     *  Everything here scales to any arbitrary resolution, but relative
     *  mouse motion is presumably the same no matter what the resolution.
     *
     * Therefore, we modulate relative motion to make it match what it would
     *  we in 640x480...this way, mice don't move slower or faster at
     *  different resolutions...
     */

    screenWidthScale = ((float) screen->w) / 640.0f;
    screenHeightScale = ((float) screen->h) / 480.0f;

    SDL_FillRect(screen, NULL, 0);  /* blank surface */
    SDL_Flip(screen);  /* move it to the screen. */

    for (i = 0; i < MAX_PADDLES; i++)
        paddles[i].blanked = 1;

    for (i = 0; i < MAX_BALLS; i++)
        balls[i].blanked = 1;
    
    return 1;  /* we're good to go. */
} /* initVideo */


static inline PaddleSide getPaddleSide(int paddleidx)
{
    return (PaddleSide) (paddleidx % 4);
} /* getPaddleSide */


static void setSidesExists(void)
{
    int i;

    sideExists[PADDLESIDE_LEFT] = 0;
    sideExists[PADDLESIDE_RIGHT] = 0;
    sideExists[PADDLESIDE_TOP] = 0;
    sideExists[PADDLESIDE_BOTTOM] = 0;

    for (i = 0; i < available_mice; i++)
    {
        PaddleSide side = getPaddleSide(i);
        if (paddles[i].exists)
            sideExists[side] = 1;
    } /* for */
} /* setSidesExists */


static int initMice(void)
{
    int i;

    available_mice = ManyMouse_Init();

    if (available_mice < 0)
    {
        printf("Error initializing ManyMouse!\n");
        return 0;
    }

    printf("ManyMouse driver: %s\n", ManyMouse_DriverName());

    if (available_mice == 0)
        printf("No mice detected!\n");
    else
    {
        for (i = 0; i < available_mice; i++)
        {
            const char *name = ManyMouse_DeviceName(i);
            printf("#%d: %s\n", i, name);
        }

        if (available_mice > MAX_PADDLES)
        {
            printf("Clamping to first %d mice.\n", available_mice);
            available_mice = MAX_PADDLES;
        }

        for (i = 0; i < available_mice; i++)
            paddles[i].exists = 1;
    }

    setSidesExists();
    return 1;
} /* initMice */


static Uint32 standardizedColor(void)
{
    /*
     * How I got these numbers:
     *   These color choices are ripped directly from QuickBasic's
     *   COLOR command in text mode. So, (1) is dark blue, (2) is
     *   dark green, etc...Then I used the GIMP color tool
     *   (http://www.gimp.org/), to find the equivalents in RGB format.
     */
    static RGB colorArray[] =
    {
        #if 0  /* these don't stand out well, just use the bright ones. */
        { 0, 0, 0 },       /* black */
        { 0, 26, 196 },    /* dark blue */
        { 0, 153, 45 },    /* dark green */
        { 0, 144, 138 },   /* dark cyan */
        { 160, 0, 0 },     /* dark red */
        { 197, 0, 202 },   /* dark magenta */
        { 129, 90, 16 },   /* brown */
        { 200, 200, 200 }, /* gray */
        { 90, 90, 90 },    /* dark gray */
        #endif
        { 0, 200, 255 },   /* bright blue */
        { 0, 90, 38 },     /* bright green */
        { 91, 110, 255 },  /* bright cyan */
        { 255, 0, 0 },     /* bright red */
        { 255, 0, 255 },   /* bright pink */
        { 255, 255, 0 },   /* bright yellow */
        { 255, 255, 255 }, /* bright white */
    };

    static int nextcolor = 0;
    const RGB *color = &colorArray[nextcolor++];
    nextcolor %= (sizeof (colorArray) / sizeof (colorArray[0]));
    return SDL_MapRGB(screen->format, color->r, color->g, color->b);
} /* standardizedColor */


#if 0
static inline Uint32 randomizedColor(void)
{
    Uint8 r = (Uint8) (255.0f*rand()/(RAND_MAX+1.0f));
    Uint8 g = (Uint8) (255.0f*rand()/(RAND_MAX+1.0f));
    Uint8 b = (Uint8) (255.0f*rand()/(RAND_MAX+1.0f));
    return SDL_MapRGB(screen->format, r, g, b);
} /* randomizedColor */
#endif


static inline void resetBallVelocity(PongThing *ball)
{
    /* divided by 3.0 would == 3 seconds from one side of screen to other. */
    float origx = ball->xvelocity < 0.0f ? -1.0f : 1.0f;
    float origy = ball->yvelocity < 0.0f ? -1.0f : 1.0f;
    ball->xvelocity = (((float) screen->w) / BALL_HSPEED) * origx;
    ball->yvelocity = (((float) screen->h) / BALL_VSPEED) * origy;
} /* resetBallVelocity */


static void initThings(void)
{
    int i = 0;
    int ball_width = (int) (screen->w * 0.017);
    int ball_height = ball_width; /* eh. */
    int vert_paddle_width = (int) (screen->w * 0.02);
    int vert_paddle_height = (int) (screen->h * 0.10);
    int centerx = ((screen->w - ball_width) / 2);
    int centery = ((screen->h - ball_height) / 2);

    srand(time(NULL));

    memset(sideExists, '\0', sizeof (sideExists));
    memset(sideScore, '\0', sizeof (sideScore));
    memset(paddles, '\0', sizeof (paddles));
    memset(balls, '\0', sizeof (balls));

    for (i = 0; i < MAX_PADDLES; i++)
    {
        PongThing *thing = &paddles[i];
        PaddleSide side = getPaddleSide(i);
        thing->color = standardizedColor();
        thing->blanked = 1;

        if ((side == PADDLESIDE_LEFT) || (side == PADDLESIDE_RIGHT))
        {
            thing->w = vert_paddle_width;
            thing->h = vert_paddle_height;
            thing->x = ((i % 2) ? screen->w - vert_paddle_width : 0);
            thing->y = (screen->h - vert_paddle_height) / 2;
        } /* if */

        else  /* top or bottom */
        {
            thing->h = vert_paddle_width;
            thing->w = vert_paddle_height;
            thing->x = (screen->w - vert_paddle_height) / 2;
            thing->y = ((i % 2) ? screen->h - vert_paddle_width : 0);
        } /* else */
    } /* for */

    for (i = 0; i < MAX_BALLS; i++)
    {
        PongThing *thing = &balls[i];
        thing->color = standardizedColor();
        thing->w = ball_width;
        thing->h = ball_height;
        thing->x = centerx;
        thing->y = centery;
        thing->blanked = 1;
        thing->xvelocity = 1.0f;
        thing->yvelocity = 1.0f;
        resetBallVelocity(thing);
    } /* for */

    /*
     * Just put one ball into play to start, and don't kick it off
     *  for three seconds, so going to fullscreen mode doesn't
     *  cause a score before the monitor catches up.
     */
    balls[0].exists = 1;
    balls[0].delayUpdate = VIDEOINIT_DELAY;
} /* initThings */



static void updateMice(int screen_w, int screen_h)
{
    ManyMouseEvent event;
    while (ManyMouse_PollEvent(&event))
    {
        PongThing *paddle = NULL;
        PaddleSide side = 0;

        if (event.device >= (unsigned int) available_mice)
            continue;

        paddle = &paddles[event.device];
        side = getPaddleSide(event.device);

        if (event.type == MANYMOUSE_EVENT_RELMOTION)
        {
            if (event.item == 0)
            {
                if ((side == PADDLESIDE_TOP) || (side == PADDLESIDE_BOTTOM))
                {
                    renderOneThing(paddle, 1); /* blank in framebuffer. */
                    paddle->x += ((float) event.value) * screenWidthScale;
                } /* if */
            } /* if */

            else if (event.item == 1)
            {
                if ((side == PADDLESIDE_LEFT) || (side == PADDLESIDE_RIGHT))
                {
                    renderOneThing(paddle, 1); /* blank in framebuffer. */
                    paddle->y += ((float) event.value) * screenHeightScale;
                } /* if */
            } /* else if */
        } /* if */

        else if (event.type == MANYMOUSE_EVENT_ABSMOTION)
        {
            if (event.item == 0)
            {
                if ((side == PADDLESIDE_TOP) || (side == PADDLESIDE_BOTTOM))
                {
                    float val = (float) (event.value - event.minval);
                    float maxval = (float) (event.maxval - event.minval);
                    renderOneThing(paddle, 1); /* blank in framebuffer. */
                    paddle->x = (float) ((val / maxval) * screen_w);
                } /* if */
            } /* if */

            else if (event.item == 1)
            {
                if ((side == PADDLESIDE_LEFT) || (side == PADDLESIDE_RIGHT))
                {
                    float val = (float) (event.value - event.minval);
                    float maxval = (float) (event.maxval - event.minval);
                    renderOneThing(paddle, 1); /* blank in framebuffer. */
                    paddle->y = (float) ((val / maxval) * screen_h);
                } /* if */
            } /* else if */
        } /* else if */

        else if (event.type == MANYMOUSE_EVENT_BUTTON)
        {
            if (event.value == 1)  /* pressed */
                paddle->color = standardizedColor();
        } /* else if */

        else if (event.type == MANYMOUSE_EVENT_DISCONNECT)
        {
            renderOneThing(paddle, 1); /* blank in framebuffer. */
            paddle->exists = 0;
            setSidesExists();
        } /* else if */
    } /* while */
} /* updateMice */


static int updateInput(void)
{
    SDL_Event event;

    updateMice(screen->w, screen->h);

    while (SDL_PollEvent(&event))
    {
        switch (event.type)
        {
            case SDL_QUIT:  /* window was closed, etc. */
                return 0;

            case SDL_KEYDOWN:
                if (event.key.keysym.sym == SDLK_ESCAPE)
                    return 0;  /* quit. */


                else if (event.key.keysym.sym == SDLK_KP_PLUS)
                {
                    /* put a new ball into play */
                    int i;
                    for (i = 0; i < MAX_BALLS; i++)
                    {
                        PongThing *ball = &balls[i];
                        if (ball->exists)
                            continue;

                        /* !!! FIXME: subroutine this. */
                        ball->x = ((screen->w - ball->w) / 2);
                        ball->y = ((screen->h - ball->h) / 2);
                        ball->color = standardizedColor();
                        ball->delayUpdate = 0.25f;
                        ball->exists = 1;
                        ball->blanked = 1;
                        ball->xvelocity = ball->yvelocity = 1.0f;
                        resetBallVelocity(ball);
                        break;
                    } /* for */
                } /* else if */

                else if (event.key.keysym.sym == SDLK_KP_MINUS)
                {
                    /* remove a ball from play */
                    int i;
                    for (i = MAX_BALLS - 1; i >= 0; i--)
                    {
                        PongThing *ball = &balls[i];
                        if (!ball->exists)
                            continue;

                        renderOneThing(ball, 1);
                        ball->exists = 0;
                        break;
                    } /* for */
                } /* else if */

                else if (event.key.keysym.sym == SDLK_g)
                {
                    if (event.key.keysym.mod & KMOD_CTRL)
                    {
                        sdlgrab = sdlgrab ? 0 : 1;
                        SDL_WM_GrabInput(sdlgrab ? SDL_GRAB_ON : SDL_GRAB_OFF);
                    } /* if */
                } /* else if */

                else if (event.key.keysym.sym == SDLK_RETURN)
                {
                    if (event.key.keysym.mod & KMOD_ALT)
                    {
                        int i;

                        if (sdlvideoflags & SDL_FULLSCREEN)
                            sdlvideoflags &= ~SDL_FULLSCREEN;
                        else
                            sdlvideoflags |= SDL_FULLSCREEN;

                        if (!initVideo())
                        {
                            printf("window/fullscreen toggle failed!\n");
                            return 0;  /* quit */
                        } /* if */

                        /*
                         * Render all balls then freeze them so the monitor
                         *  has time to catch up and players can get oriented.
                         */
                        for (i = 0; i < MAX_BALLS; i++)
                        {
                            PongThing *ball = &balls[i];
                            if (ball->exists)
                            {
                                resetBallVelocity(ball);
                                ball->delayUpdate = VIDEOINIT_DELAY;
                            } /* if */
                        } /* for */
                    } /* if */
                } /* else if */
                break;
        } /* switch */
    } /* while */

    return 1;  /* no quit events this time. */
} /* updateInput */


static void renderOneThing(PongThing *thing, int blanking)
{
    SDL_Rect *rect;

    if ((!thing->exists) || (blanking && thing->blanked))
        return;

    if (rectCount < MAX_UPDATERECTS)
        rect = &rects[rectCount++];
    else
        rect = &rects[0];  /* oh well. */

    /* !!! FIXME: need clipping. */

    rect->x = thing->x;
    rect->y = thing->y;
    rect->w = thing->w;
    rect->h = thing->h;

    SDL_FillRect(screen, rect, blanking ? 0x00000000 : thing->color);
    thing->blanked = blanking;

    /* note that this isn't on the screen until we call SDL_UpdateRects(). */

} /* renderOneThing */


static void renderThings(void)
{
    int i;

    for (i = 0; i < available_mice; i++)
        renderOneThing(&paddles[i], 0);

    for (i = 0; i < MAX_BALLS; i++)
        renderOneThing(&balls[i], 0);

    if (rectCount < MAX_UPDATERECTS)
        SDL_UpdateRects(screen, rectCount, rects);
    else
    {
        printf("Developer warning! overflowed updaterects!\n");
        SDL_Flip(screen);  /* just do the whole screen. */
    } /* else */

    rectCount = 0;
} /* renderThings */


static void triggerScore(PongThing *ball, PaddleSide scoreAgainst)
{
    #define INCR_SCORE(side) if (scoreAgainst != side) { sideScore[side]++; }
    INCR_SCORE(PADDLESIDE_LEFT);
    INCR_SCORE(PADDLESIDE_RIGHT);
    INCR_SCORE(PADDLESIDE_TOP);
    INCR_SCORE(PADDLESIDE_BOTTOM);
    #undef INCR_SCORE

    #define PRINT_SCORE(side) { \
        if (sideExists[PADDLESIDE_##side]) \
            printf("  %s side: %d.\n", #side, sideScore[PADDLESIDE_##side]); \
    }
    printf("SCORE!\n");
    PRINT_SCORE(LEFT);
    PRINT_SCORE(RIGHT);
    PRINT_SCORE(TOP);
    PRINT_SCORE(BOTTOM);
    printf("\n");
    #undef PRINT_SCORE

    /* center it again. */
    ball->x = ((screen->w - ball->w) / 2);
    ball->y = ((screen->h - ball->h) / 2);

    /* pick a new color for the next ball. */
    ball->color = standardizedColor();

    /* wait two seconds before ball is back in play. */
    ball->delayUpdate = 2.0f;

    /* point next ball at opposing side. */
    if ((scoreAgainst==PADDLESIDE_LEFT) || (scoreAgainst==PADDLESIDE_RIGHT))
        ball->xvelocity *= -1.0f;
    else
        ball->yvelocity *= -1.0f;

    resetBallVelocity(ball);
} /* triggerScore */


static void updatePaddle(PongThing *thing, float fractionalTime)
{
    if (!thing->exists)
        return;

    renderOneThing(thing, 1); /* blank existing thing in framebuffer. */

    /* this is for keyboard input, mostly...mice don't move here... */
    thing->x += thing->xvelocity * fractionalTime;
    thing->y += thing->yvelocity * fractionalTime;

    /* collided with left wall. */
    if (thing->x < 0.0f)
    {
        thing->x = 0.0f;
    } /* if */

    /* collided with right wall. */
    else if (thing->x > ((float) screen->w) - ((float) thing->w))
    {
        thing->x = ((float) screen->w) - ((float) thing->w);
    } /* if */

    /* collided with top wall. */
    if (thing->y < 0.0f)
    {
        thing->y = 0.0f;
    } /* if */

    /* collided with bottom wall. */
    else if (thing->y > ((float) screen->h) - ((float) thing->h))
    {
        thing->y = ((float) screen->h) - ((float) thing->h);
    } /* if */
} /* updatePaddle */


static inline int inRect(int x, int y, int bx1, int by1, int bx2, int by2)
{
    /* bounding box must be normalized. */

    if ((x < bx1) || (x > bx2))
        return 0;

    if ((y < by1) || (y > by2))
        return 0;

    return 1;
} /* inRect */


static inline int intersection(int px1, int py1, int px2, int py2,
                               int bx1, int by1, int bx2, int by2)
{
    /* if any corner of a rect is in the other, it's a collision. */
    return ( (inRect(px1, py1, bx1, by1, bx2, by2)) ||
             (inRect(px2, py1, bx1, by1, bx2, by2)) ||
             (inRect(px1, py2, bx1, by1, bx2, by2)) ||
             (inRect(px2, py2, bx1, by1, bx2, by2)) ||
             (inRect(bx1, by1, px1, py1, px2, py2)) ||
             (inRect(bx2, by1, px1, py1, px2, py2)) ||
             (inRect(bx1, by2, px1, py1, px2, py2)) ||
             (inRect(bx2, by2, px1, py1, px2, py2)) );
} /* intersection */


/* returns non-zero if bounced, zero if score against existing team. */
static inline int scoreOrBounce(PongThing *ball, PaddleSide side)
{
    if (sideExists[side])
    {
        triggerScore(ball, side);
        return 0;
    } /* if */

    return 1;
} /* scoreOrBounce */


static void updateBall(PongThing *ball, float fractionalTime)
{
    /* !!! FIXME: be nice if bounces randomized direction */

    int i;
    float xorig, yorig;
    int bx1, bx2, by1, by2;
    int bounced = 0;

    if (!ball->exists)
        return;

    ball->delayUpdate -= fractionalTime;
    if (ball->delayUpdate > 0.0f)
        return;

    renderOneThing(ball, 1); /* blank existing ball in framebuffer. */

    xorig = ball->x;
    yorig = ball->y;

    ball->x += ball->xvelocity * fractionalTime;
    ball->y += ball->yvelocity * fractionalTime;

    if (xorig < ball->x)
    {
        bx1 = xorig;
        bx2 = ball->x + ball->w;
    } /* if */
    else
    {
        bx1 = ball->x;
        bx2 = xorig + ball->w;
    } /* else */

    if (yorig < ball->y)
    {
        by1 = yorig;
        by2 = ball->y + ball->h;
    } /* if */
    else
    {
        by1 = ball->y;
        by2 = yorig + ball->h;
    } /* else */

    /* check paddle collisions. */
    for (i = 0; i < available_mice; i++)
    {
        int px1, px2, py1, py2;
        PongThing *paddle = &paddles[i];
        if (!paddle->exists)
            continue;

        /* !!! FIXME: should round to nearest, not truncate! */
        /* cast to int only once. */
        px1 = (int) paddle->x;
        px2 = px1 + paddle->w;
        py1 = (int) paddle->y;
        py2 = py1 + paddle->h;

        /*
         * The trick here is to not care where the ball is currently, but
         *  rather whether it would overlap or pass through a given paddle
         *  on this frame.
         *
         * We go for cheap, not accurate, erring on the side of the player
         *  (the bigger the lag spike, the more likely they are to trigger
         *  a collision, even when there shouldn't be one, but in Pong,
         *  there'd have to be a BIG lag spike to screw this up).
         *
         * We construct a bounding rectangle of the entire surface area
         *  the ball could have touched when going from point A to point B
         *  on this frame, and then see if the paddle's rectangle touches
         *  it. If so, it's a collision, even though the paddle might have
         *  scraped a corner of the bounding box where the ball never
         *  actually touched. When you're getting hundreds of frames per
         *  second however, the likelihood of this ever failing, let alone
         *  failing noticably, is basically null.
         *
         * My devbox currently renders this game at over 15,000 fps.
         */
        if (intersection(px1, py1, px2, py2, bx1, by1, bx2, by2))
        {
            /* It's a hit, bounce the ball. */
            /* Push it to the edge of the paddle and reverse velocity. */
            PaddleSide side = getPaddleSide(i);
            if (side == PADDLESIDE_LEFT)
            {
                ball->x = (px2 + ball->w) + 1;
                ball->xvelocity *= -1.0f;
            } /* else if */
            else if (side == PADDLESIDE_RIGHT)
            {
                ball->x = (px1 - ball->w) - 1;
                ball->xvelocity *= -1.0f;
            } /* if */
            else if (side == PADDLESIDE_TOP)
            {
                ball->y = (py2 + ball->h) + 1;
                ball->yvelocity *= -1.0f;
            } /* else if */
            else if (side == PADDLESIDE_BOTTOM)
            {
                ball->y = (py1 - ball->h) - 1;
                ball->yvelocity *= -1.0f;
            } /* else if */

            bounced = 1;
            break; /* detected a collision; stop checking paddles. */
        } /* if */
    } /* for */

    /* no collisions with paddles this frame? See about wall collisions... */
    /* Multiply velocity by -1.0 so it reverses. */
    if (i == available_mice)
    {
        /*
         * If we hit a wall, we need to see if there's at least one paddle
         *  guarding that side. If so, it's a score. If not, just bounce off
         *  the wall.
         */

        /* collided with left wall. */
        if (ball->x < 0.0f)
        {
            if ((bounced = scoreOrBounce(ball, PADDLESIDE_LEFT)) != 0)
            {
                ball->x = 0.0f;
                ball->xvelocity *= -1.0f;
            } /* else */
        } /* if */

        /* collided with right wall. */
        else if (ball->x > ((float) screen->w) - ((float) ball->w))
        {
            if ((bounced = scoreOrBounce(ball, PADDLESIDE_RIGHT)) != 0)
            {
                ball->x = ((float) screen->w) - ((float) ball->w);
                ball->xvelocity *= -1.0f;
            } /* else */
        } /* if */

        /* collided with top wall. */
        if (ball->y < 0.0f)
        {
            if ((bounced = scoreOrBounce(ball, PADDLESIDE_TOP)) != 0)
            {
                ball->y = 0.0f;
                ball->yvelocity *= -1.0f;
            } /* else */
        } /* if */

        /* collided with bottom wall. */
        else if (ball->y > ((float) screen->h) - ((float) ball->h))
        {
            if ((bounced = scoreOrBounce(ball, PADDLESIDE_BOTTOM)) != 0)
            {
                ball->y = ((float) screen->h) - ((float) ball->h);
                ball->yvelocity *= -1.0f;
            } /* else */
        } /* if */
    } /* if */

    if (bounced)
    {
        /* increase velocities from 0% to 5% if we bounced off something. */
        ball->xvelocity *= 1.0f + ((10.0f*rand()/(RAND_MAX+1.0f)) / 200.0f);
        ball->yvelocity *= 1.0f + ((10.0f*rand()/(RAND_MAX+1.0f)) / 200.0f);
    } /* if */
} /* updateBall */


static void updateThings(void)
{
    int i;
    static int firstRun = 1;
    static Uint32 lastTicks = 0;
    Uint32 ticks = SDL_GetTicks();
    float fractionalTime;

    if (firstRun)  /* since setting fullscreen can take several seconds... */
    {
        lastTicks = ticks;
        firstRun = 0;
    } /* if */

    fractionalTime = ((ticks - lastTicks) / 1000.0f);

    for (i = 0; i < available_mice; i++)
        updatePaddle(&paddles[i], fractionalTime);

    for (i = 0; i < MAX_BALLS; i++)
        updateBall(&balls[i], fractionalTime);

    lastTicks = ticks;
} /* updateThings */


static int processCmdLines(int argc, char **argv)
{
    int i;
    for (i = 1; i < argc; i++)
    {
        char *arg = argv[i];
        if (strcmp(arg, "--windowed") == 0)
            sdlvideoflags &= ~SDL_FULLSCREEN;
        else if (strcmp(arg, "--fullscreen") == 0)
            sdlvideoflags |= SDL_FULLSCREEN;
        else if (strcmp(arg, "--grabinput") == 0)
            sdlgrab = 1;
        else if (strcmp(arg, "--nograbinput") == 0)
            sdlgrab = 0;
        else if (strcmp(arg, "--showfps") == 0)
            showfps = 1;
        else if (strcmp(arg, "--noshowfps") == 0)
            showfps = 0;

        #define SCREENRESOPT(w,h) \
        else if (strcmp(arg, "--" #w "x" #h) == 0) { \
            screenwidth = w; \
            screenheight = h; \
        }
        SCREENRESOPT(320, 240)
        SCREENRESOPT(512, 384)
        SCREENRESOPT(640, 480)
        SCREENRESOPT(800, 500)
        SCREENRESOPT(800, 600)
        SCREENRESOPT(1024, 640)
        SCREENRESOPT(1024, 768)
        SCREENRESOPT(1152, 768)
        SCREENRESOPT(1152, 864)
        SCREENRESOPT(1280, 800)
        SCREENRESOPT(1280, 854)
        SCREENRESOPT(1280, 960)
        SCREENRESOPT(1280, 1024)
        SCREENRESOPT(1600, 1024)
        SCREENRESOPT(1600, 1200)
        SCREENRESOPT(1680, 1050)
        SCREENRESOPT(1920, 1200)
        #undef SCREENRESOPT

        else
        {
            printf("Unknown command line '%s'\n", arg);
            return 0;
        } /* else */
    } /* for */

    return 1;
} /* processCmdLines */


static void updateFPS(int reinit)
{
    /* quick and dirty frame counter. */
    static Uint32 fpsticks = 0;
    static Uint32 frames = 0;

    if (reinit)
    {
        frames = 0;
        fpsticks = SDL_GetTicks() + 5000;
    } /* if */
    else
    {
        frames++;
        if (SDL_GetTicks() >= fpsticks)
        {
            if (showfps)
                printf("fps == %d\n", (int) (frames / 5));
            fpsticks += 5000;
            frames = 0;
        } /* if */
    } /* else */
} /* updateFPS */


int main(int argc, char **argv)
{
    if (!processCmdLines(argc, argv))
        return 42;

    if (!initVideo())
        return 42;

    initThings();

    if (!initMice())
    {
        SDL_Quit();
        return 42;
    } /* if */

    updateFPS(1);
    while (updateInput())  /* go until quit event of some sort. */
    {
        updateThings();  /* move things to new locations. */
        renderThings();  /* draw moved things to screen. */
        updateFPS(0);
    } /* while */

    ManyMouse_Quit();
    SDL_Quit();  /* clean up video mode, etc. */
    return 0;
} /* main */

/* end of manymousepong.c ... */

