/*

  Rubout
  by Mark K. Lottor
  July 1988

  Known to run on:  
     Sun-3/50, Sun-3/60 with monochrome video

*/


/* include some stuff i didn't write */

#include <stdio.h>
#include <math.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/fcntl.h>

/* just to ring the bell */
#include <sys/ioctl.h>
#include <sundev/kbio.h>
#include <sundev/kbd.h>


/* constants */

#define DELAY 8000

#define BALLYV 9
#define BALLXV_LIMIT 8

/* the screen size in bits */
#define WIDTH 1152
#define HEIGHT 900

#define WALL_WIDTH 10

#define STATUSH 20    /* room for game status at bottom of screen */

#define NBRICKS 25    /* number of bricks per row */

/* size of a brick */
#define BRICKW ((WIDTH-(WALL_WIDTH*2))/NBRICKS)
#define BRICKH 30

/* size of playing area */
#define SIZEW ((WALL_WIDTH*2)+(NBRICKS*BRICKW))
#define SIZEH HEIGHT    /* -WALL_WIDTH-STATUSH */

/* wall boundaries */
#define LEFT_WALL (WALL_WIDTH-1)
#define RIGHT_WALL (SIZEW-WALL_WIDTH)
#define TOP_WALL (SIZEH-WALL_WIDTH-1)
#define BOTTOM_WALL (STATUSH-1)

/* size of paddle */
#define PADDLEW 50
#define PADDLEH 10
#define PADDLEROW STATUSH

#define BALLS 10            /* balls per game */


/*
digit bitmaps for score
*/

unsigned char digit[10][8] =
{
  0xE7, 0xDB, 0xBD, 0xBD, 0xBD, 0xBD, 0xDB, 0xE7,
  0xC1, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xD7, 0xE7,
  0x81, 0xBF, 0xC7, 0xFB, 0xFD, 0xFD, 0xBD, 0xC3,
  0xC3, 0xBD, 0xFD, 0xF3, 0xF3, 0xFD, 0xBD, 0xC3,
  0xF7, 0xF7, 0xF7, 0x81, 0xB7, 0xB7, 0xB7, 0xBF,
  0xC3, 0xBD, 0xFD, 0xFD, 0x83, 0xBF, 0xBF, 0x81,
  0xC3, 0xBD, 0xBD, 0xBD, 0x83, 0xBF, 0xBF, 0xC3,
  0xDF, 0xDF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, 0x81,
  0xC3, 0xBD, 0xBD, 0xC3, 0xC3, 0xBD, 0xBD, 0xC3,
  0xC3, 0xFD, 0xFD, 0xC1, 0xBD, 0xBD, 0xBD, 0xC3
};


/*
 files
*/

/* mouse device */
#define MOUSEDEV "/dev/mouse"

/* frame buffer */
#define SCREENDEV "/dev/fb"

/* high score file */
#define HIGHFILE "/usr/games/rubout.highscores"


/* global stuff */

  char *fb;		   /* address of frame buffer */
  int delay = DELAY;
  int paddlew = PADDLEW;
  int beepp = -1;           /* non-zero to beep */
  int paddlex, paddley;
  int balls;		   /* balls left */
  int bricks;              /* bricks left */
  int score = 0;
  int board;		   /* current board number */
  int ballx, bally, ballxv, ballyv;

  char button;
  int mx, my;

  int mouse;
  int mousebuf[3];
  int mousep = 0;


main(argc, argv)
  int argc;
  char *argv[];
{
  int lostball, lastbrick;

  parseoptions(argc,argv);

  mapscreen();
  openmouse();

  printf("\033[q"); fflush(stdout);
  clearscreen();
  instructions();
  getclickstart();

  srandom(time());
  balls = BALLS;

  for (board = 0 ;; board++)
  {
    setupboard();
    for (;;)
    {
      paddlex = (SIZEW/2)-(paddlew/2);
      paddley = PADDLEROW;
      setpaddle();
      setballs();
      getclickstart();
      for (;;)
      {
	slowdown();
	button='\0'; mx=0; my=0;
  	getinput(&button,&mx,&my);
  	doclick(button);
	updatepaddle(mx);
  	updateballs(&lostball,&lastbrick);
	if (lostball || lastbrick) break;
      }
      if (lastbrick) balls++;
      if ((balls == 0) || lastbrick) break;
    }
    if (balls == 0) break;
    getclickstart();
  }
  gameover();
  closemouse();
}


clearscreen()
{
  int i;
  char *fbb = fb;

/* clear frame buffer */
  for(i=0; i<WIDTH*HEIGHT/8; i++) *(fbb++) = 0xFF;
}


instructions()
{
  printat(5,37);
  puts("Rubout");
  printat(7,33);
  puts("by Mark K. Lottor");
  printat(15,31);
  puts("Use the mouse to play");
}

printat(y,x)
  int y,x;
{
  printf("\033[%d;%dH",y,x);
  fflush(stdout);
}

char getclick()
{
  char button;
  int mx,my;

  do
  {
    button = '\0';
    getinput(&button,&mx,&my);
  }
  while ((button != 'l') && (button != 'm') && (button != 'r'));
  return(button);
}

getclickstart()
{
  printat(30,34);
  puts("Click to continue");
  getclick();
  printat(30,34);
  puts("                 ");
}


/* mouse stuff */
openmouse()
{
  mouse = open(MOUSEDEV,O_RDONLY);
  if (mouse < 0) fatal("Can't open mouse");
  fcntl(mouse, F_SETFL, FNDELAY);   /* set non-blocking i/o */
}

closemouse()
{
  close(mouse);
}

getinput(button,mx,my)
  char *button;
  int *mx, *my;
{
  char c;
  unsigned char ci;
  int x;

  while (read(mouse, &c, 1) == 1)
  {
    ci = (unsigned char) c;
    if ((ci >= 0x80) && (ci <= 0x87)) 
    {
      mousep = 0;
      mousebuf[mousep++] = ci;
    }
    else
    {
      mousebuf[mousep++] = ci;
      if (mousep == 3)
      {
	if (!(mousebuf[0] & 01)) *button='r';
	 else if (!(mousebuf[0] & 02)) *button='m';
	  else if (!(mousebuf[0] & 04)) *button='l';
        *mx = mousebuf[1];
	if (*mx > 127) *mx = -(256 - *mx);
        *my = mousebuf[2];
	if (*my > 127) *my = -(256 - *my);
	mousep = 0;
	break;
      }
    }
  }  
}

doclick(button)
{
}


updatepaddle(mx)
  int mx;
{
  int i, amx;
  static int lastmx = 0;
  
  if (mx == 0) 
  {
    for (i=0; i<2000; i++);
    return;
  }

  amx = abs(mx);
  if (amx > 30) amx = 120;
  else if (amx > 20) amx = 70;
  else if (amx > 10) amx = 20;

/* if moving far, just go there */
  if ((amx >= 70) && (lastmx > 20))
  {
    for (i=0; i<paddlew; i++) paddleoff(paddlex+i);
    if (mx < 0) paddlex = paddlex - amx;
    if (mx > 0) paddlex = paddlex + amx;
    if (paddlex <= LEFT_WALL) paddlex = LEFT_WALL+1;
    if (paddlex+paddlew > RIGHT_WALL) paddlex = RIGHT_WALL-paddlew;
    for (i=0; i<paddlew; i++) paddleon(paddlex+i);
    lastmx = abs(mx);
    return;
  }

  lastmx = abs(mx);
  for (i=0; i<amx; i++)
  {
    if ((mx < 0) && (paddlex > LEFT_WALL+1))
    {
      paddlex--;
      paddleon(paddlex);
      paddleoff(paddlex+paddlew);
    }
    else if ((mx > 0) && (paddlex < RIGHT_WALL-paddlew))
    {
      paddleon(paddlex+paddlew);
      paddleoff(paddlex);
      paddlex++;
    }
  }
}

paddleon(x)
  int x;
{
  int y;

  for (y=0; y<PADDLEH; y++) biton(x,paddley+y);
}

paddleoff(x)
  int x;
{
  int y;

  for (y=0; y<PADDLEH; y++) bitoff(x,paddley+y);
}



beep()
{
  int i;

  if (beepp == 0) return;
  turn_on_bell();
  for (i=0; i<22000; i++);
  turn_off_bell();
}

cluck()
{
  int i;

  if (beepp == 0) return;
  turn_on_bell();
  for (i=0; i<7000; i++);
  turn_off_bell();
}

slowdown()
{
  int i;

  for (i=0; i<delay; i++);
}

updateballs(lostball,lastbrick)
/* sets lostball or lastbrick non-zero if true*/
  int *lostball, *lastbrick;
{
  int newx, newy;
  int c;
  int brickx, bricky;
  int bx, by;
  int hitx, hity;
  int bby, i;

  *lostball = 0;
  *lastbrick = 0;

  newx = ballx+ballxv;
  newy = bally+ballyv;
  if ((newx == ballx) && (newy == bally))
  {
    ballx = newx;
    bally = newy;
    return;
  }

  balloff(ballx,bally);
  c = balltest(newx,newy);

  if (newy <= BOTTOM_WALL)
  {
    balls--;
    *lostball = 1;
    for (i=0; i<paddlew; i++) paddleoff(paddlex+i);
    return;
  }

  if (c == 0)
  {
    ballon(newx,newy);
    ballx = newx;
    bally = newy;
    return;
  }

  if ((newx <= LEFT_WALL) || (newx+7 >= RIGHT_WALL))
  {
    ballon(ballx,bally);
    cluck();
    ballxv = -ballxv;
    return;
  }

  if (newy+7 >= TOP_WALL)
  {
    ballon(ballx,bally);
    cluck();
    ballyv = -ballyv;
    return;
  }

  if (newy < PADDLEROW+PADDLEH)
  {
    ballon(ballx,bally);
    cluck();
    ballyv = -ballyv;
    ballxv = ballxv + (newx+3-paddlex-paddlew/2)/4;
    if (ballxv == 0) ballxv = 1;   /* don't get stuck */
    if (ballxv > BALLXV_LIMIT) ballxv = BALLXV_LIMIT; /* limit it */
    if (ballxv < -BALLXV_LIMIT) ballxv = -BALLXV_LIMIT; /* limit it */
    return;
  }

/* otherwise must've hit a brick */
  for (hitx=0; hitx<8; hitx++)
    for (hity=7; hity>=0; hity--)
    {
      if (bittest(newx+hitx,newy+hity))
      { 
        beep();
        bricks--;
        if (bricks == 0) *lastbrick = 1;
        brickx = WALL_WIDTH + (BRICKW * ((newx+hitx-WALL_WIDTH) / BRICKW));
        bricky = BRICKH * ((newy+hity) / BRICKH);
        for (bx=0; bx<BRICKW; bx++)
          for (by=0; by<BRICKH; by++)
            bitoff(brickx+bx,bricky+by);
	score += bricky/10;
	displayscore();
	goto nomore;
      }
    }
nomore:
  ballon(ballx,bally);
/* if hit side, just let ball pass thru hole */
  bby = bally+3;
  if ((bally > bricky) && (bally < bricky+BRICKH-1)) return;
  ballyv = -ballyv;
  return;
}


setballs()
{
/* take a ball away */
  balloff(SIZEW-WALL_WIDTH-balls*10,STATUSH/2-4);

/* set initial ball position */
  ballx = (SIZEW/2)-4;
  bally = PADDLEROW+PADDLEH*3;
  ballon(ballx,bally);

/* set initial random velocity */
  ballxv = (int) (random() % 10) - 5;
  ballyv = BALLYV;
}

setupboard()
{
  int x,y,i,j,bx,by;

  clearscreen();

/* setup top wall */
  rectangle(0,SIZEH-WALL_WIDTH,SIZEW,WALL_WIDTH);

/* setup left wall */
  rectangle(0,STATUSH,WALL_WIDTH,SIZEH-STATUSH);

/* setup right wall */
  rectangle(SIZEW-WALL_WIDTH,STATUSH,WALL_WIDTH,SIZEH-STATUSH);

/* setup bricks */
#define S1ROWS 5
#define S1ROWH (BRICKH*19)

if (board < 2)
{
  bricks = 0;
  y = S1ROWH;
  for (i=0; i<S1ROWS; i++)
  {
    x = WALL_WIDTH;
    for (j=0; j<NBRICKS; j++)
    {
      bricks++;
      for (bx=0; bx<BRICKW; bx++)
        for (by=0; by<BRICKH; by++)
        {
	  if ((bx == 0) || (by == 0)) continue;
	  if ((bx == BRICKW-1) && (j == NBRICKS-1)) continue;
	  if (((x+bx) % 2) == ((y+by) % 2)) biton(x+bx,y+by);
        }
      x += BRICKW;
    }
    y += BRICKH;
  }
}

#define S2ROWS 4
#define S2ROWH (BRICKH*11)

if (board == 1)
{
  y = S2ROWH;
  for (i=0; i<S2ROWS; i++)
  {
    x = WALL_WIDTH;
    for (j=0; j<NBRICKS; j++)
    {
      bricks++;
      for (bx=0; bx<BRICKW; bx++)
        for (by=0; by<BRICKH; by++)
        {
	  if ((bx == 0) || (by == 0)) continue;
	  if ((bx == BRICKW-1) && (j == NBRICKS-1)) continue;
	  if (((x+bx) % 2) == ((y+by) % 2)) biton(x+bx,y+by);
        }
      x += BRICKW;
    }
    y += BRICKH;
  }
}

#define S3ROWS 3
#define S3ROWH (BRICKH*10)

if (board == 2)
{
 int k;
 
 y = S3ROWH;
 for (k=0; k<3; k++)
 {
  for (i=0; i<S3ROWS; i++)
  {
    x = WALL_WIDTH;
    for (j=0; j<NBRICKS; j++)
    {
      bricks++;
      for (bx=0; bx<BRICKW; bx++)
        for (by=0; by<BRICKH; by++)
        {
	  if ((bx == 0) || (by == 0)) continue;
	  if ((bx == BRICKW-1) && (j == NBRICKS-1)) continue;
	  if (((x+bx) % 2) == ((y+by) % 2)) biton(x+bx,y+by);
        }
      x += BRICKW;
    }
    y += BRICKH;
  }
  y += BRICKH * 3; 
 }
}

if (board > 2)
{
#define S4ROWS 18
#define S4ROWH (BRICKH*10)

  for (i=0; i<S4ROWS; i++)
  {
    if ((random() % 2) == 0) continue;

    y = S4ROWH+i*BRICKH;

    x = WALL_WIDTH;
    for (j=0; j<NBRICKS; j++)
    {
      bricks++;
      for (bx=0; bx<BRICKW; bx++)
        for (by=0; by<BRICKH; by++)
        {
	  if ((bx == 0) || (by == 0)) continue;
	  if ((bx == BRICKW-1) && (j == NBRICKS-1)) continue;
	  if (((x+bx) % 2) == ((y+by) % 2)) biton(x+bx,y+by);
        }
      x += BRICKW;
    }
  }
}

/* display row of balls left */
  for (i=1; i<=balls; i++) ballon(SIZEW-WALL_WIDTH-i*10,STATUSH/2-4);
}

setpaddle()
{
  int x,y;

  rectangle(paddlex,paddley,paddlew,PADDLEH);
}


ballon(x,y)
  int x,y;
{
  int i;

  for (i=2; i<6; i++)
  {
    biton(x+i,y);
    biton(x+i,y+7);
  }
  for (i=1; i<7; i++)
  {
    biton(x+i,y+1);
    biton(x+i,y+6);
  }
  for (i=0; i<8; i++)
  {
    biton(x+i,y+2);
    biton(x+i,y+3);
    biton(x+i,y+4);
    biton(x+i,y+5);
  }
}

balloff(x,y)
  int x,y;
{
  int i;

  for (i=2; i<6; i++)
  {
    bitoff(x+i,y);
    bitoff(x+i,y+7);
  }
  for (i=1; i<7; i++)
  {
    bitoff(x+i,y+1);
    bitoff(x+i,y+6);
  }
  for (i=0; i<8; i++)
  {
    bitoff(x+i,y+2);
    bitoff(x+i,y+3);
    bitoff(x+i,y+4);
    bitoff(x+i,y+5);
  }
}

int balltest(x,y)
  int x,y;
{
  int i;

  return (bittest(x,y)+bittest(x,y+7)+bittest(x+7,y)+bittest(x+7,y+7));
}


rectangle(x,y,sx,sy)
  int x,y,sx,sy;
{
  int ix,iy,isx,isy;

  isx = x + sx;
  isy = y + sy;

  for (ix=x; ix<isx; ix++)
    for (iy=y; iy<isy; iy++)
      biton(ix,iy);
}


#define K1 (HEIGHT-1)
#define K2 (WIDTH/8)

biton(x,y)
  int x,y;
{
  int fbo;
  char *fbp;

  fbo = (K1-y)*K2+x/8;
  fbp = fb + fbo;
  *fbp &= ~(0x80 >> (x % 8));
}

bitoff(x,y)
  int x,y;
{
  int fbo;
  char *fbp;

  fbo = (K1-y)*K2+x/8;
  fbp = fb + fbo;
  *fbp |= (0x80 >> (x % 8));
}

int bittest(x,y)
  int x,y;
{
  int fbo;
  char *fbp;
 
  fbo = (K1-y)*K2+x/8;
  fbp = fb + fbo;
  return (~(*fbp) & (0x80 >> (x % 8)));
}


gameover()
{
/* exit normal video mode */
  printat(20,35);
  printf("game over\n");
}


mapscreen()
{
  int fd, prot, share, offset, pagesize;
  unsigned len;
  char *valloc(), color;

  if ((fd = open(SCREENDEV,O_RDWR)) < 0)
    fatal("open");

  pagesize = getpagesize();

  len = ((WIDTH*HEIGHT)/8);

  len = pagesize+len-(len%pagesize);

  if ((fb = valloc(len)) == 0)
    fatal("fb valloc failed");

  prot = PROT_READ|PROT_WRITE;
  share = MAP_SHARED;

  offset = 0;

  if (mmap(fb, len, prot, share, fd, offset) != 0)
    fatal("fb mmap failed");
}

turn_on_bell() {
        int bell, bell_on = KBD_CMD_BELL;
        bell = open("/dev/kbd",1);
        ioctl(bell, KIOCCMD, &bell_on);
        close(bell);
}

turn_off_bell() {
        int bell, bell_off = KBD_CMD_NOBELL;
        bell = open("/dev/kbd",1);
        ioctl(bell, KIOCCMD, &bell_off);
        close(bell);
}


fatal(msg)
  char *msg;
{
  fprintf(stderr,"Error: %s\n",msg);
  exit(1);
}


parseoptions(argc, argv)
  int argc;
  char *argv[];
{
  char c;

  argv++; /* skip program name */
  argc--;

  while (argc-- > 0)
  {
    c = *argv[0];
    if (c == '-') c = *++argv[0];
    argv++;
      switch (c)
      {
      case 'q':
	beepp = 0;
        break;
      case 's':
	delay = atoi(*argv);
	argv++; argc--;
        break;
      case 'p':
        paddlew = atoi(*argv);
	argv++; argc--;
        break;
      default:
        printf("unknown option: %c\n", c);
	printf("Usage: rubout [options]\n");
	printf("  -q              (quiet -- no sound)\n");
	printf("  -s speed        (default is 8000)\n");
	printf("  -p paddlesize   (default is 50)\n");
	exit(1);
      }
  }
}


displayscore()
{
  char *a, *b;
  int i;
  int d;
  int c = 0;
  char scores[10];

  a = fb + 5 + (WIDTH/8 * (HEIGHT-(STATUSH/2)-4));
  sprintf(scores,"%d.",score);
  while ((d=scores[c++]) != '.')
  {
    b = a + c;
    for (i=7; i>=0; i--)
    {
      *b = digit[d-48][i];
      b += WIDTH/8;
    }
  }
}

