/****************************************************************/
/*  bricks.c - loosely based on a version of the game for CGA   */
/*             adapters that never seemed to have gotten ported */
/*             to EGA/VGA.                                      */
/*             The original was character based and ran fast on */
/*             the lowly IBM PC.  This version, well you get    */
/*             the source code and it's free.                   */
/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/
/* The makefile.tc is probably worthless if you don't use the   */
/* same make utility ndmake45 that I use. So, look for bkmak.bat*/
/* There should be 6 files:                                     */
/*      bricks.c                                                */
/*      local.h                                                 */
/*      bkmak.bat                                               */
/*      bricks.ln                                               */
/*      makefile.bk                                             */
/*      readme                                                  */
/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/
/*  Uploaded version 5/08/92  bricks.c   -bles                  */
/****************************************************************/

#include    <stdio.h>
#include    <dos.h>
#include    <math.h>
#include    <conio.h>
#include    <io.h>
#include    <stdlib.h>
#include    <stdarg.h>
#include    "local.h"

#include <graphics.h>

#define GETOPT              /* undef this if you don't have getopt.c */

#define LEFT    0x4b        /* cursor left key          */
#define RIGHT   0x4d        /* cursor left key          */
#define QUIT    'q'         /* quit game                */

#define BALL_UP     -1
#define BALL_DOWN   1
#define BALL_LEFT   -1
#define BALL_RIGHT  1
#define REVERSE_DIR -1

#define B_RADIUS        8   /* radius of the ball   */
#define PADDLE_WIDTH    60  /* size of paddle       */

#define BALL_DELAY  18      /* delay counter for move/display loop  */
#define MAGIC_START 2000    /* start magic brick moment             */
#define MAGIC_STOP  6000    /* stop magic brick moment              */
#define MAGIC_OFF   99      /* just a goofey number to sense magic  */



#define MOVE_PADDLE \
        if (kbhit()) { key = move_paddle(&StartX, &StartY, Paddle); }

/* keeps paddle from getting stuck at set locations on the screen   */
/* graphics is too slow to use small steps                          */
int     paddle_incr[] = {17, 23, 25, 27};

int     GraphDriver;    /* The Graphics device driver       */
int     GraphMode;      /* The Graphics mode value      */
int     x_max, y_max;   /* The maximum resolution of the screen */
int     x_min, y_min;
int     MaxColors;      /* The maximum # of colors available    */
int     ErrorCode;      /* Reports any graphics errors      */

typedef struct  {
                int     face;       /* largest Y value for row of bricks    */
                int     count;      /* indicates row is empty               */
                BOOLEAN brick[20];  /* left edge of each brick              */
                int     brickX[20]; /* upper left corner of brick           */
                int     brickY[20];
                void    *bptr;  /* pointer to brick image in this layer */
                } LAYER;

LAYER   layers[5];              /* 4 layers of bricks and one magic brick   */
int magic_counter = 0;          /* turns on/off magic brick display         */
int magic_index = MAGIC_OFF;    /* brick selected to be magic               */

short backgnd_color[] = {BLACK, BROWN, LIGHTBLUE, LIGHTRED, LIGHTGRAY};

/* string used to display score, level, ball information    */
char stat_msg[80] = {0};
char *stat_fmt =
    "BALL  %2d               LEVEL  %2d               SCORE  %d";

/*  Function prototypes                     */
void Initialize();
void DrawBricks(int *, int *);
void PutImageDemo(int *, int *, int, int);
void StatusLine(int, char *msg);
void boink_on(int);
void boink_off();
void bump_score(int *, int, int);
BOOLEAN cmds(int, char **, BOOLEAN *soundFlag, int *speedDelay, int *numBalls);

/* this function came out of Borland's demo software, as is.    */
/* The rest of this mess is my fault.                           */
void changetextstyle(int font, int direction, int charsize);

#ifdef  GETOPT
/* command line processing function */
extern  int     getopt(int, char **, char *);
extern  char    *optarg;    /* defined in getopt.c  */
extern  short   optind;
#endif




BOOLEAN soundFlag = TRUE;
int     speedDelay = BALL_DELAY;
int     numBalls = 20;

int
main(int argc, char **argv)
{
    int top;
    int bwidth;

#ifdef  GETOPT
    /* check and process and command line options   */
    if (!cmds(argc, argv, &soundFlag, &speedDelay, &numBalls))
        {
        exit(1);
        }
#endif

    /* Set system into Graphics mode    */
    Initialize();

    DrawBricks(&top, &bwidth);
    PutImageDemo(&top, &bwidth, speedDelay, numBalls);

    /* Return the system to text mode   */
    (void) getchar();
    closegraph();
    boink_off();
    return (0);
}

/*  INITIALIZE: Initializes the graphics system and reports     */
/*  any errors which occurred.                  */

void Initialize(void)
{
     /* Request autodetection   */
     GraphDriver = DETECT;

    if ((ErrorCode = registerbgidriver(EGAVGA_driver)) < 0)
        {
        fprintf(stderr, "graphics error: %s\n", grapherrormsg(ErrorCode));
        exit(1);
        }

    if ((ErrorCode = registerbgidriver(CGA_driver)) < 0)
        {
        fprintf(stderr, "graphics error: %s\n", grapherrormsg(ErrorCode));
        exit(1);
        }

    if ((ErrorCode = registerbgifont(small_font)) < 0)
        {
        fprintf(stderr, "graphics error: %s\n", grapherrormsg(ErrorCode));
        exit(1);
        }

     initgraph(&GraphDriver, &GraphMode, "");
     if ((ErrorCode = graphresult()) != grOk)
        {
        /* Error occured during init    */
        fprintf(stderr, "graphics error: %s\n", grapherrormsg(ErrorCode));
        exit(1);
        }

     MaxColors = getmaxcolor() + 1; /* Read maximum number of colors*/

     x_max = getmaxx();
     y_max = getmaxy(); /* Read size of screen      */
     x_min = 0;
     y_min = 0;
}



void DrawBricks(top, bwidth)
int     *top;
int     *bwidth;
{
    struct viewporttype vp;
    int color, height;
    int x, y, i, j;
    unsigned size;
    char cnum[5];

    color = 1;
    getviewsettings(&vp);       /* Get the current window size  */
    *bwidth  =  ((vp.right+1) / 16) - 1;  /* Get box dimensions    */
    height =  ((vp.bottom+1) / 16) - 1;

    /* # bytes required to store add 2 for box */
    size = imagesize(0, 0, *bwidth+2, height+2);

    layers[4].face = height;
    layers[4].count = 1;
    layers[4].bptr = malloc(size);
    layers[4].brickX[0] = 0;
    layers[4].brickY[0] = 0;
    layers[4].brick[0] = TRUE;

    setfillstyle(SOLID_FILL, WHITE);/* Set to solid fill in color   */
    setcolor(WHITE);                /* Set the same border color    */

    /* Draw the rectangle       */
    bar3d(0, 0, *bwidth, height, 0, 0);

    /* get one copy of magic brick  */
    getimage(0, 0, *bwidth, height, layers[4].bptr);

    for (j = 0, y = 0 ; j < 4 ; ++j, y += (height + 1))     /* Row loop     */
        {
        layers[j].face = y + height;
        layers[j].count = 16;
        layers[j].bptr = malloc(size);

        for (i = 0, x = 0 ; i < 16 ; ++i, x += (*bwidth + 1))/* Column loop */
            {
            layers[j].brickX[i] = x;
            layers[j].brickY[i] = y;
            layers[j].brick[i] = TRUE;

            setfillstyle(SOLID_FILL, color);/* Set to solid fill in color   */
            setcolor(WHITE);                /* Set the same border color    */

            /* Draw the rectangle       */
            bar3d(x, y, x+*bwidth, y+height, 0, 0);

            if (i == 0)
                {
                /* get one copy of each brick   */
                getimage(x, y, x+*bwidth, y+height, layers[j].bptr);
                }
            }               /* End of row loop      */
        color = ++color % MaxColors;
        }                   /* End of Column loop       */

    *top = 0;
}


/*   PUTIMAGEDEMO                           */

void PutImageDemo(int *top, int *bwidth, int speedDelay, int numBalls)
{

    static int StartX = 100;
    static int StartY = 140;
    int x_dir = BALL_RIGHT;
    int y_dir = BALL_UP;
    int x = StartX;
    int y = y_max - 80;
    int key = 0;

    int i;
    int PauseTime = speedDelay;
    int balls = numBalls;
    int score = 0;
    int level = 0;
    int level_mult = 10;
    int x_step = 4;
    int y_step = 4;

    unsigned    size;
    void    *Ball;
    void    *Paddle;

    /* Draw Ball */
    setcolor(WHITE);
    circle(StartX, StartY, B_RADIUS);
    setfillstyle(SOLID_FILL, WHITE);
    floodfill(StartX, StartY, WHITE);

    /* create ball image */
    size = imagesize(StartX - (B_RADIUS + 1), StartY - (B_RADIUS + 1),
                        StartX + (B_RADIUS + 1), StartY + (B_RADIUS + 1));

    Ball = malloc(size);
    getimage(StartX - (B_RADIUS + 1), StartY - (B_RADIUS + 1),
                StartX + (B_RADIUS + 1), StartY + (B_RADIUS + 1), Ball);
    putimage(StartX - (B_RADIUS + 1), StartY - (B_RADIUS + 1), Ball, XOR_PUT);

    /* create paddle image */
    line(StartX, StartY+20, StartX+PADDLE_WIDTH, StartY+20);
    size = imagesize(StartX, StartY+19, StartX+PADDLE_WIDTH, StartY+21);
    Paddle = malloc(size);
    getimage(StartX, StartY+19, StartX+PADDLE_WIDTH, StartY+21, Paddle);
    putimage(StartX, StartY+19, Paddle, XOR_PUT);
    StartY = y_max - PADDLE_WIDTH;
    putimage(StartX, StartY, Paddle, XOR_PUT);


    /* Put msg at bottom of screen  */
    sprintf(stat_msg, stat_fmt, balls, 1, 0);
    StatusLine(x_max/2, stat_msg);


    /* wait until a key is hit */
    (void) getch();

    while (key != QUIT)
        {
        MOVE_PADDLE
        /* are all the bricks gone ??   */
        if (!(layers[0].count | layers[1].count
            | layers[2].count | layers[3].count))
            {
            /* update level number */
            level++;

            /* erase paddle while changing background color */
            putimage(StartX, StartY, Paddle, XOR_PUT);
            setbkcolor(backgnd_color[level]);
            cleardevice();

            /* redraw paddle */
            putimage(StartX, StartY, Paddle, XOR_PUT);

            /* update status line */
            sprintf(stat_msg, stat_fmt, balls, level+1, score);
            StatusLine(x_max/2, stat_msg);

            /* add a few balls for good work */
            balls += 2;

            /* make bricks worth a bit more             */
            /* if level five, quit! and get some rest   */
            if ((level_mult += 5) > 30)
                break;

            /* set'em up again */
            DrawBricks(top, bwidth);

            /* speed up the game a little */
            PauseTime -= 1;

            /* reset the magic brick information */
            magic_counter = 0;
            magic_index = MAGIC_OFF;
            }

        MOVE_PADDLE

        /* draw ball */
        putimage(x, (y - B_RADIUS / 2), Ball, XOR_PUT);
        delay(PauseTime/2);
        MOVE_PADDLE
        delay(PauseTime/2);
        MOVE_PADDLE
        putimage(x, (y - B_RADIUS / 2), Ball, XOR_PUT);

        /* Move Ball */
        x += (x_step * x_dir);
        y += (y_step * y_dir);

        if (magic_counter == MAGIC_START)
            {
            randomize();
            /* pick a brick  */
            while (!layers[0].brick[(magic_index = random(16))])
                ;
            /* display magic brick */
            putimage(layers[0].brickX[magic_index],
                    layers[0].brickY[magic_index], layers[4].bptr, COPY_PUT);
            magic_counter++;
            }
        else if (magic_counter == MAGIC_STOP)
            {
            if (magic_index != MAGIC_OFF)
                putimage(layers[0].brickX[magic_index],
                    layers[0].brickY[magic_index], layers[0].bptr, COPY_PUT);
            magic_index = MAGIC_OFF;
            }
        else
            {
            magic_counter++;
            }

        /* check each layer of bricks for a hit */
        {
        register short i;
        for (i = 3; i >= 0; --i)
            {
            MOVE_PADDLE
            /* check if it is past the face of the row and if there are */
            /* any bricks left in the row                               */
            if ((y <= layers[i].face) && (layers[i].count))
                {
                MOVE_PADDLE
                {
                register short j = (x / *bwidth);

                /* now check to see if it is touching a brick face  */
                if (layers[i].brick[j])
                    {
                    if (i == 0 && j == magic_index)
                        {
                        putimage(layers[i].brickX[j], layers[i].brickY[j],
                                                     layers[i].bptr, COPY_PUT);
                        putimage(layers[i].brickX[j], layers[i].brickY[j],
                                                     layers[i].bptr, XOR_PUT);
                        layers[i].brick[j] = FALSE;
                        --layers[i].count;
                        y_dir *= REVERSE_DIR;
                        bump_score(&score, level, balls);
                        magic_index = MAGIC_OFF;
                        magic_counter = 6000;
                        }
                    else
                        {
                        boink_on(440);
                        putimage(layers[i].brickX[j], layers[i].brickY[j],
                                                 layers[i].bptr, XOR_PUT);
                        layers[i].brick[j] = FALSE;
                        --layers[i].count;
                        y_dir *= REVERSE_DIR;
                        boink_off();
                        score += (4 - i) * level_mult;
                        sprintf(stat_msg, stat_fmt, balls, level+1, score);
                        StatusLine(x_max/2, stat_msg);
                        }
                    break;
                    }
                }

                MOVE_PADDLE
                {
                register short j = ((x + B_RADIUS) / *bwidth);

                /* now check too see if it is touching a brick right side   */
                if (layers[i].brick[j])
                    {
                    if (i == 0 && j == magic_index)
                        {
                        putimage(layers[i].brickX[j], layers[i].brickY[j],
                                                     layers[i].bptr, COPY_PUT);
                        putimage(layers[i].brickX[j], layers[i].brickY[j],
                                                     layers[i].bptr, XOR_PUT);
                        layers[i].brick[j] = FALSE;
                        --layers[i].count;
                        y_dir *= REVERSE_DIR;
                        x_dir *= REVERSE_DIR;
                        bump_score(&score, level, balls);
                        magic_index = MAGIC_OFF;
                        magic_counter = 6000;
                        break;
                        }
                    else
                        {
                        boink_on(440);
                        putimage(layers[i].brickX[j], layers[i].brickY[j],
                                                     layers[i].bptr, XOR_PUT);
                        layers[i].brick[j] = FALSE;
                        --layers[i].count;
                        y_dir *= REVERSE_DIR;
                        x_dir *= REVERSE_DIR;
                        boink_off();
                        score += (4 - i) * level_mult;
                        sprintf(stat_msg, stat_fmt, balls, level+1, score);
                        StatusLine(x_max/2, stat_msg);
                        }
                    break;
                    }
                }

                MOVE_PADDLE
                {
                register short j = ((x - B_RADIUS) / *bwidth);

                /* now check too see if it is touching a brick left side    */
                if (layers[i].brick[j])
                    {
                    if (i == 0 && j == magic_index)
                        {
                        putimage(layers[i].brickX[j], layers[i].brickY[j],
                                                     layers[i].bptr, COPY_PUT);
                        putimage(layers[i].brickX[j], layers[i].brickY[j],
                                                     layers[i].bptr, XOR_PUT);
                        layers[i].brick[j] = FALSE;
                        --layers[i].count;
                        y_dir *= REVERSE_DIR;
                        x_dir *= REVERSE_DIR;
                        bump_score(&score, level, balls);
                        magic_index = MAGIC_OFF;
                        magic_counter = 6000;
                        break;
                        }
                    else
                        {
                        boink_on(440);
                        putimage(layers[i].brickX[j], layers[i].brickY[j],
                                                     layers[i].bptr, XOR_PUT);
                        layers[i].brick[j] = FALSE;
                        --layers[i].count;
                        y_dir *= REVERSE_DIR;
                        x_dir *= REVERSE_DIR;
                        boink_off();
                        score += (4 - i) * level_mult;
                        sprintf(stat_msg, stat_fmt, balls, level+1, score);
                        StatusLine(x_max/2, stat_msg);
                        }
                    break;
                    }
                }
                }
            }
        }
        MOVE_PADDLE

/* just speed things up below by making sure these are constants */
#define B_RADIUS1  ((B_RADIUS * 7) / 4)
#define B_RADIUS2  (B_RADIUS / 4)

        /* check if ball is near the paddle */
        if ((y >= StartY - B_RADIUS) && (y <= StartY + B_RADIUS))
            {
            /* the fractions are what appeared to be    */
            /* correct for the ball movement I wanted   */
            if ((x > (StartX - B_RADIUS1))
                && (x < ((StartX + PADDLE_WIDTH) + B_RADIUS2)))
                {
                boink_on(880);
                boink_off();
                /* move ball to the left and up */
                if (x < StartX + (PADDLE_WIDTH / 2))
                    {
                    x_dir = BALL_LEFT;
                    x_step = ((StartX + (PADDLE_WIDTH / 4) - x) / 3);
                    if (x_step < 0)
                        x_step = 0;
                    if (x_step > 5)
                        x_step = 5;
                    y_step = 5 - (x_step / 3);
                    MOVE_PADDLE
                    }
                /* move ball to the right   */
                else if (x > StartX + (PADDLE_WIDTH / 2))
                    {
                    x_dir = BALL_RIGHT;
                    x_step = ((x - StartX - (PADDLE_WIDTH / 2)) / 3);
                    if (x_step < 0)
                        x_step = 0;
                    if (x_step > 5)
                        x_step = 5;
                    y_step = 5 - (x_step / 3);
                    MOVE_PADDLE
                    }
                y_dir = BALL_UP;
                continue;
                }
            }

        MOVE_PADDLE

        /* keep ball within screen left and right sides */
        if ((x + (3 * B_RADIUS)) >= x_max)
            {
            boink_on(1320);
            /* subtract 4 times because 3 times makes the   */
            /* ball stick to the wall                       */
            x = x_max - (4 * B_RADIUS);
            x_dir = BALL_LEFT;
            boink_off();
            }
        else if (x <= x_min)
            {
            boink_on(1320);
            x = x_min;
            x_dir = BALL_RIGHT;
            boink_off();
            }

        MOVE_PADDLE

        /* keep ball within top and bottom of screen    */
        if (y + B_RADIUS - 1 > StartY)
            {
            boink_on(120);
            boink_off();
            /* ball has gone past paddle so stop and wait   */
            /* to restart the game                          */
            if (!--balls)
                {
                break;
                }
            sprintf(stat_msg, stat_fmt, balls, level+1, score);
            StatusLine(x_max/2, stat_msg);
            x = StartX + B_RADIUS;
            y = StartY - B_RADIUS;
            y_dir = BALL_UP;
            while (!kbhit())
                ;
            MOVE_PADDLE
            }
        else if (y < (*top + B_RADIUS))
            {
            boink_on(1320);
            y = *top + B_RADIUS;
            y_dir = BALL_DOWN;
            boink_off();
            MOVE_PADDLE
            }
        }
    boink_off();
    free(Ball);
    sprintf(stat_msg, stat_fmt, balls, level, score);
    StatusLine(x_max/2, stat_msg);
    while (kbhit())
        (void) getch();
}



int
move_paddle(StartX, StartY, Paddle)
int     *StartX;
int     *StartY;
void    *Paddle;
{
    int key;
    int i;
    static int p_index = 0;
    int ptemp = paddle_incr[p_index];


    /* check for cursor key ie. <- or ->, they have leading nulls   */
    if ((key = getch()) == 0)
        {
        key = getch();
        /* paddle movement  */
        switch (key)
            {
            /* <- move to the left  */
            case LEFT:  if (*StartX < (x_min + ptemp))
                            {
                            putimage(*StartX, *StartY, Paddle, XOR_PUT);
                            *StartX = 0;
                            putimage(*StartX, *StartY, Paddle, XOR_PUT);
                            }
                         else
                            {
                            putimage(*StartX, *StartY, Paddle, XOR_PUT);
                            *StartX -= ptemp;
                            putimage(*StartX, *StartY, Paddle, XOR_PUT);
                            }
                        break;
            /* -> move to the right */
            case RIGHT: if (*StartX >= (x_max - (PADDLE_WIDTH + ptemp)))
                            {
                            putimage(*StartX, *StartY, Paddle, XOR_PUT);
                            *StartX = x_max - (PADDLE_WIDTH + 1);
                            putimage(*StartX, *StartY, Paddle, XOR_PUT);
                            }
                         else
                            {
                            putimage(*StartX, *StartY, Paddle, XOR_PUT);
                            *StartX += ptemp;
                            putimage(*StartX, *StartY, Paddle, XOR_PUT);
                            }
                            break;
            }
        }
    p_index = (p_index + 1) & 0x03;
    return (key);
}



/*  Boink() makes sound when the ball hits something            */
void
boink_on(int freq)
{
    if (soundFlag)
        sound(freq);
    delay(100);
}

void
boink_off()
{
    nosound();
}

void
bump_score(int *score, int level, int balls)
{
    int k;
    int freq = 220;

    level++;
    for (k = 0; k < (level * 25); k++, freq += 20, *score += 40)
        {
        if (soundFlag)
            sound(freq);
        delay(100);
        sprintf(stat_msg, stat_fmt, balls, level, *score);
        StatusLine(x_max/2, stat_msg);
        nosound();
        }
}


/*  STATUSLINE: Display a status line at the bottom of the screen.  */

void StatusLine(int xloc, char *msg)
{
    int height;

    setcolor(MaxColors - 1);        /* Set current color to white   */

    changetextstyle(DEFAULT_FONT, HORIZ_DIR, 1);
    settextjustify(CENTER_TEXT, TOP_TEXT);
    setlinestyle(SOLID_LINE, 0, NORM_WIDTH);
    setfillstyle(EMPTY_FILL, 0);

    height = textheight("H");       /* Detemine current height  */
    bar(0, y_max - (height + 4), x_max, y_max);
    rectangle(0, y_max - (height + 4), x_max, y_max);
    outtextxy(xloc, y_max - (height + 2), msg);

}


/*  CHANGETEXTSTYLE: similar to settextstyle, but checks for    */
/*  errors that might occur whil loading the font file.     */

void changetextstyle(int font, int direction, int charsize)
{
    int ErrorCode;

    graphresult();          /* clear error code     */
    settextstyle(font, direction, charsize);
    if((ErrorCode = graphresult()) != grOk)
        {
        /* if error occured     */
        closegraph();
        fprintf(stderr, " graphics error: %s\n", grapherrormsg( ErrorCode ) );
        exit(1);
        }
}


#ifdef  GETOPT
/*
 *  cmds()  -   process command line args
 */
BOOLEAN
cmds(int argc, char **argv, BOOLEAN *soundFlag, int *speedDelay, int *numBalls)
{
    int         optchar;
    short       errflg = 0;

    if ((argc > 1) && (argv[1][0] != '/'))
        {
        errflg++;
        }
    else
        {
        while((optchar = getopt(argc, argv, "sb:d:")) != EOF)
            {
            switch(optchar)
                {
                case 'b':   /* number of balls to start with */
                            sscanf(optarg, "%d", numBalls);
                            break;

                case 'd':   /* delay of main display loop */
                            sscanf(optarg, "%d", speedDelay);
                            break;

                case 's':   /* sound on/off */
                            *soundFlag = FALSE;
                            break;

                case '?':   /* unknown option   */
                default:
                            errflg++;
                            break;
                }
            }
        }

    if (errflg)
        {
        fprintf(stderr, "Usage: bricks [/b #] [/d #] [/s]\n");
        fprintf(stderr, "  /b   Number of balls to start with.\n");
        fprintf(stderr, "       Default is 20.\n");
        fprintf(stderr, "  /d   Speed of game.\n");
        fprintf(stderr, "       Default is 18.\n");
        fprintf(stderr, "  /s   Sound Off.\n");
        fprintf(stderr, "  Example:\n");
        fprintf(stderr, "    bricks /b10 /s\n");
        fprintf(stderr, "    10 - Balls, No Sound.\n");
        return(FALSE);
        }
    return(TRUE);
}
#endif
/*the*end*the*end*the*end*the*end*the*end*the*end*the*end*the*end*the*end*/
