/*------------------------------------------------------------------------*/
/*             Double-Buffered Infinite BitMap Scroll Demo                */
/*           */
/*                   by Mark E. Whitehead   23 SEP 89                   */
/*            */
/*  THIS IS FREELY DISTRIBUTABLE PUBLIC DOMAIN SOFTWARE!              */
/* No charge should be made except for a copying fee.                */
/* If you find a use for this program please give me some            */
/*  credit; otherwise next time all the variable names will           */
/* read something like 'xAcdW3q2'.   Doomo Arigatoo!                 */
/*           */
/*           */
/*      This program demonstrates a double-buffered 'infinite' smooth     */
/* scrolling display using Standard Amiga graphics routines.   It also    */
/* features Simple Sprite animation.  The program creates a randomly      */
/* generated terrain which scrolls from top to bottom.  The user controls */
/* a Spaceship with the joystick while alien 'flying saucers' zig-zag down*/
/* the screen.  The joystick button also launches a missle.  NO collision */
/* detection is performed - that is beyond the scope of this demo. Other  */
/* than that almost everything is here to get you started on your first   */
/* Space Shoot-em-up game!  But first, a word about what we've got to     */
/* work with...                                                           */
/*  The Amiga has built-in hardware scrolling capability but is is not*/
/* practical for large bitmaps since the memory requirements are very     */
/* intensive.  A better method is to scroll the screen using the BLITTER  */
/* and to "update" the graphics in the opposite direction with new data.  */
/* Fortunately, we don't have to mess around with the blitter chip        */
/* directly since SCROLLRASTER() has been provided in ROM. (Thanks!)      */
/* When the screen is scrolled without double-buffering there tends  */
/* to be some separation of the bit planes causing psychodelic colors to  */
/* leak through, as well as a kind of ~~~wave~~~.  Double-buffering stops */
/* this by making sure that all the bit planes have been moved into place */
/* before being displayed.  So there ya go..                              */
/*  In pictorial form here is how the program solves the problem:     */
/*                                                                        */
/*      +-----+-----+-----+-----+ ---------------------                   */
/*      |  a  |  b  |  c  |  d  |  new data buffer    |                   */
/*      |-----+-----+-----+-----| ------------------  |                   */
/*      |  e  |  f  |  g  |  h  |    |                This is one of the  */
/*      |-----+-----+-----+-----|    |                two BitMaps which   */
/*      |     |     |     |     |    |                are alternately     */
/*      |                       |    ViewPort         attached to the     */
/*      ~                       ~    shows this       RastPort            */
/*      ~                       ~    much of Raster   |                   */
/*      |-----------------------|    |                |                   */
/*      |     |     |     |     |    |                |                   */
/*      +-----------------------+  ---------------------                  */
/*                                                                        */
/*  What area of the BitMap (Raster) is defined by the Position of    */
/*  the ViewPort.  In this case it is adjusted down the height of the */
/* terrain blocks.  The letters 'a','b','c' etc. above are block     */
/* shapes which in this program are randomly laid out but can be fed */
/*  in from a pre-defined array!                                      */
/*                                                                        */
/*------------------------------------------------------------------------*/

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

#include "images.c" /* The user defines block shapes in this file */ 
   /* Note ye well that this data is not copied  */
   /* into CHIP memory and thus must be LINKED   */
   /* apropriately such as 'ln +cd scroll' (MANX)*/

#include "sprimages.c" /* The user defines sprites in this file */

#define ever  (;;)
#define REV  0L
#define abs(x)  ( ((x) < 0) ? -(x) : (x) )

#define GROUND  0 /* These are the block shapes which */
#define VOLCANO  1 /* define the terrain piecemeal     */
#define PLANT  2

#define NSPRITES 7
#define SPRSIZE  (2L*32L+4L) /* Space for LARGEST Sprite */

#define SHIPL  0
#define SHIPR  1 
#define ALIEN0  2
#define ALIEN1  3
#define ALIEN2  4
#define ALIEN3  5
#define MISSILE  6

#define TOP  0
#define BOTTOM  200
#define LEFT  10
#define RIGHT  290

UWORD colortable[] = {
 0x0000, 0x0ea6, 0x0e93, 0x0e81, /* Background */  
 0x0d70, 0x03a0, 0x0a20, 0x0380, /* Background */
 0x0000, 0x0000, 0x0000, 0x0000, /* Unused     */ 
 0x0000, 0x0000, 0x0000, 0x0000, /* Unused     */
 0x0000, 0x0000, 0x0ccc, 0x0f00, /* Missile    */
 0x0000, 0x0000, 0x088f, 0x052f, /* Ship l & r */
 0x0000, 0x0000, 0x0ccc, 0x0888, /* Aliens     */
 0x0000, 0x0000, 0x0ccc, 0x0888, /* Aliens     */
};

#define BLK_W  32 /* Each terrain block is 32 pix wide   */
#define BLK_H  20 /* Each terrain block is 20 pix high   */
#define BLK_D  3 /* Each terrain block is 3 planes deep */
#define N_HBLK  10 /* There are 10 blocks across the scrn */
#define N_VBLK  11 /* There are 10 blocks down the scrn   */
    /* plus one buffer row */ 

#define DEPTH   3L   /* 'L' added so I don't */
#define WIDTH   320L   /*  have to cast later..*/
#define HEIGHT   200L   /* Height of Viewport */
#define RHEIGHT  (HEIGHT + (long) BLK_H) /* Height of Raster   */ 

struct Screen  *scrn;
struct Window  *win;
struct ViewPort     *vp;
struct RastPort     *rp;
struct  BitMap *bitmap_ptrs[2] = { NULL, NULL }; /* U need this! */  

WORD toggle_page = 0;

struct Image block = { 0, 0, BLK_W, BLK_H, BLK_D, NULL, 0x7, 0x0, NULL};

struct IntuiMessage *msg = NULL;

void *GfxBase, *IntuitionBase;

struct NewWindow windef = {
 0, 0, WIDTH, 10,
 1, 0,
 CLOSEWINDOW | VANILLAKEY,
 WINDOWCLOSE | BORDERLESS | ACTIVATE,
 NULL, NULL, NULL, NULL, NULL,
 0, 0, 0, 0,
 CUSTOMSCREEN
};

struct NewScreen scrndef = {
 0, 0, WIDTH, HEIGHT, DEPTH,
 0, 1,
 SPRITES, 
 CUSTOMSCREEN | CUSTOMBITMAP,
 NULL, NULL, NULL, NULL
};

short rowdata[N_HBLK]; /* Store terrain data here for reference */

UWORD *sprbuffer = NULL; /* Pointer to CHIP memory buffer we make */

struct SimpleSprite shiplspr, shiprspr, mslspr, alienspr[4];

short dx[4] = { 2, -1, -2, 1 }, cx[4] = { 40, 50, 20, 100 }, 
 x[4] = { 100, 40, 200, 280 }, y[4] = { 170, 10, 160, 80 },
 sx = 140, sy = 160, mx = 0, my = 200;

short fire_cmd = FALSE, misl_on = FALSE;


/*--------------------- M A I N    R O U T I N E ------------------------*/

main()
{
 short i, 
  count = 1, /* Initialize count to 1 for first time    */ 
  pause = TRUE,   /* Let the user read the options first...  */
  tracer = TRUE, /* Toggle Page number text on screen       */
  newrow = FALSE; /* This flag indicates a new row is needed */ 

 UBYTE code;
 ULONG class;

 openstuff(); /* Open intuition, graphics, screen, window */
 setupsprites();

 /* For first pass plaster GROUND terrain across page 0 */

 paint_pf();
 pageflip();

 /* For first pass plaster GROUND terrain across page 1 */

 paint_pf();
 ScrollRaster( rp, 0L, -1L, 0L, 0L, 320L, 219L);  
 pageflip();

 /* NOTE: now we have two bitmaps with the same picture; with one */
 /* bitmap exactly ONE row lower than the other.  When we go to   */
 /* scrolling them we will move each exactly TWO rows!  This will */
 /* have the effect of the screen moving only one line at a time  */
 /* when we toggle back and forth.  Think about it for awhile...  */  

 for ever {
  
  if (msg = (struct IntuiMessage *) GetMsg(win->UserPort) ) {
   class = msg->Class;
   code  = msg->Code;
   ReplyMsg(msg);
   switch (class) {
   case CLOSEWINDOW: closestuff();
       return;
   case VANILLAKEY:  switch (code) {
       case 'Q': case 'q': 
          closestuff();
          return;
       case 'T': case 't':
          tracer = !tracer;
          break;
       case ' ': pause = !pause;
          break;
       }
   }
  }

  if (!pause) {

   ScrollRaster( rp, 0L, -2L, 0L, 0L, 320L, 219L);  

   if (newrow) {
    draw_new_row();
    newrow = FALSE;
   }

   if (++count >= BLK_H) {
    /* This data can come from a pre-defined */
    /*       array just as easily!           */
    for (i=0; i<N_HBLK; i++) 
     rowdata[i] = RangeRand(5L);
    draw_new_row();
    newrow = TRUE;
    count = 0;
   }

   for (i=0; i<4; i++) move_aliens( i);

   move_ship();

   move_missle();

   if (tracer)
    switch (toggle_page) {
    case 0: jamit( 10,  218, 1, "Page 0"); break;
    case 1: jamit( 260, 218, 1, "Page 1"); break;
    }
    
   pageflip(); /* Now - FLIP TO THE HIDDEN PAGE! */
 
  }
 }
}


/*------------------------------------------------------------------------*/
/* Move missle on screen */

move_missle()
{
 if (misl_on) {       /* If missle on screen... */
  if ( (my -= 6) <= TOP) { 
   misl_on = FALSE;    /* If at top move offscreen */
   MoveSprite( vp, &mslspr, (long) 0, (long) 200);
  } else        /* Else keep going up */
   MoveSprite( vp, &mslspr, (long) mx, (long) my);
 } else {       /* NOT on screen */ 
  if (fire_cmd) {
   misl_on = TRUE;     /* Put onscreen if commanded */ 
   mx = sx + 14;
   my = sy - 16;
   MoveSprite( vp, &mslspr, (long) mx, (long) my);
  }
 }
}


/*------------------------------------------------------------------------*/
/* Move Ship under joystick control */

move_ship()
{
 short dx = 0, dy = 0;

 joystick( &dx, &dy, &fire_cmd);

 sx += dx<<1; sy += dy<<1;

 if (sx < 10) sx = 10; else if (sx > 280) sx = 280;

 if (sy > 160) sy = 160; else if (sy < 10) sy = 10;

 MoveSprite( vp, &shiplspr, (long) sx, (long) sy);
 MoveSprite( vp, &shiprspr, (long) sx+16, (long) sy);
}


/*------------------------------------------------------------------------*/
/* Move Aliens in a zig-zag diving pattern */

move_aliens(i)
short i;
{
 if (--(cx[i]) < 0) {
  cx[i] = 50 + RangeRand(100L); /* New charge duration */ 
  dx[i] = -dx[i];   /* Go the other way... */
 }

 if ( cx[i] < 15)
  y[i] += 3;
 else
  if ( cx[i] < 40)
   y[i] += 2;
  else
   ++(y[i]);

 if ( y[i] > BOTTOM) {
  y[i] = TOP;   /* Re-position at top */
  x[i]  = 50 + RangeRand(200L); /* New starting x pos */
 }

 x[i] += dx[i]; 

 if ( x[i] < LEFT || x[i] > RIGHT) dx[i] = -dx[i];

 MoveSprite( vp, &alienspr[i], (long) x[i], (long) y[i]);
}


/*-------------------------------------------------------------------------*/
/* Do Page Flipping - From ROM Kernal Manual BOB example */

pageflip()
{
 scrn->ViewPort.RasInfo->BitMap = bitmap_ptrs[toggle_page];
 WaitTOF();
 MakeScreen(scrn);
 RethinkDisplay();
 toggle_page ^=1;
 scrn->RastPort.BitMap = bitmap_ptrs[toggle_page];
}


/*-------------------------------------------------------------------------*/
/* New Terrain */

draw_new_row()
{
 short i, x;
 UWORD *image;

 for (i=0, x=0; i<N_HBLK; i++, x+=BLK_W) {
  switch ( rowdata[i] ) {
   case GROUND:  image = ground;  break;
   case VOLCANO: image = volcano; break;
   case PLANT:   image = plant;   break;
   default:      image = ground; break;
  }
  drawblock( image, x, toggle_page);
 }
}



/*-------------------------------------------------------------------------*/
/* Paint playfield */

paint_pf()
{
 short i, j, x, y;

 for (j=0, y=0; j<N_VBLK; j++, y+=BLK_H)
  for (i=0, x=0; i<N_HBLK; i++, x+=BLK_W)
   drawblock( &ground, x, y);

 shadow( 35, 70, 6, "Double-Buffered Infinite BitMap");
 shadow( 35, 80, 6, " Scroll Demonstration Program");
 shadow( 35, 90, 6, "by Mark Edward Whitehead  1989");

 shadow( 50, 130, 7, "Joystick Controls SpaceShip");
 shadow( 50, 140, 7, "Spacebar will STOP/START Scroll");
 shadow( 50, 150, 7, "T toggles Page Flip display");
 shadow( 50, 160, 7, "Q quits Demonstration");
}


/*-------------------------------------------------------------------------*/
/* Draw a block on screen */

drawblock( p, x, y)
UWORD *p;
short x, y;
{
 block.LeftEdge = x;
 block.TopEdge = y;
 block.ImageData = p;
 DrawImage( rp, &block, 0L, 0L);
}


/*-------------------------------------------------------------------------*/
/* Set up the sprites.  This routine and it's support routines take the    */
/* hassle out of using Sprites.  What I do is create a buffer which is     */
/* partitioned into areas the size of the largest Sprite and has big as    */
/* the number of Sprite shapes I'm going to need.  Then the data is copied */
/* into this CHIP buffer.  Now I can quickly calculate the offset into the */
/* buffer to change my sprite shapes.  MEW                                 */ 

setupsprites()
{
 if (!(sprbuffer = AllocMem( (long) 2*SPRSIZE*NSPRITES, MEMF_CHIP) ))
  die("No memory for sprite data.");

 add_spr( SHIPL, shipl);
 add_spr( SHIPR, shipr);

 add_spr( ALIEN0, alien);
 add_spr( ALIEN1, alien);

 add_spr( ALIEN2, alien);
 add_spr( ALIEN3, alien);

 add_spr( MISSILE, missile);

 init_sprite( &shiplspr, SHIPL, 2, sx, sy, 32);
 init_sprite( &shiprspr, SHIPR, 3, sx+16, sy, 32);

 init_sprite( &alienspr[0], ALIEN0, 6, x[0], y[0], 15);
 init_sprite( &alienspr[1], ALIEN1, 7, x[1], y[1], 15);
 init_sprite( &alienspr[2], ALIEN2, 4, x[2], y[2], 15);
 init_sprite( &alienspr[3], ALIEN3, 5, x[3], y[3], 15);

 init_sprite( &mslspr, MISSILE, 1, mx, my, 15);
}


/*-------------------------------------------------------------------------*/
/* Initialize a sprite */

init_sprite( s, shpnum, n, x, y, h)
struct SimpleSprite *s;
short shpnum, n, x, y, h;
{
 if ( GetSprite( s, (long) n) < 0 ) die("Sprite not available.");

 s->x = x;
 s->y = y;
 s->height = h;

 ChangeSprite( vp, s, sprbuffer + shpnum * SPRSIZE);
}


/*-------------------------------------------------------------------------*/
/* Add a sprite data to the CHIP ram buffer. SPRSIZE is the largest spr    */

add_spr( n, src)
UWORD n, *src;
{
 short i;
 UWORD *buffer;

 buffer = sprbuffer +  n * SPRSIZE;

 for (i=0; i<SPRSIZE; i++) *buffer++ = *src++;
}


/*-------------------------------------------------------------------------*/
/* Get memory for a double buffered bit map */

get_bitmaps()
{
 short i, j;

 for(j=0; j<2; j++) {
  if ((bitmap_ptrs[j] = AllocMem( (long) sizeof(struct BitMap), MEMF_CHIP)) == 0) 
    die("Can't get bitmap memory");

  InitBitMap(bitmap_ptrs[j], DEPTH, WIDTH, RHEIGHT);

  for(i=0; i<DEPTH; i++) {

   if ((bitmap_ptrs[j]->Planes[i] = (PLANEPTR)
    AllocRaster(WIDTH, RHEIGHT)) == 0)
     die("Can't get bitmap planes");

   BltClear(bitmap_ptrs[j]->Planes[i],(long) (WIDTH/8)*RHEIGHT, 1L);
  }
 }
}


/*-------------------------------------------------------------------------*/
/* Free memory for a double buffered bit map */

free_bitmaps()
{
 short i, j;

 for (j=0; j<2; j++) {
  if (bitmap_ptrs[j]) {
   for (i=0; i<DEPTH; i++) {
   if (bitmap_ptrs[j]->Planes[i] != 0)
    FreeRaster(bitmap_ptrs[j]->Planes[i], WIDTH, RHEIGHT);
   }
  FreeMem(bitmap_ptrs[j], (long) sizeof(struct BitMap));
  } 
 }
}


/*-------------------------------------------------------------------------*/
/* Print drop-shadow text to rastport */

shadow( x, y, color, txt)
short x, y, color;
char *txt;
{
 SetDrMd( rp, (long) JAM1);

 SetAPen( rp, (long) 0L);
 Move( rp, (long) x-1, (long) y+1);
 Text( rp, (long) txt, (long) strlen(txt) );

 SetAPen( rp, (long) color);
 Move( rp, (long) x, (long) y);
 Text( rp, (long) txt, (long) strlen(txt) );
}


/*-------------------------------------------------------------------------*/
/* Jam those colors into the display */

jamit( x, y, color, txt)
short x, y, color;
char *txt;
{
 SetDrMd( rp, (long) JAM2);
 SetBPen( rp, (long) 0);
 SetAPen( rp, (long) color);
 Move( rp, (long) x, (long) y);
 Text( rp, (long) txt, (long) strlen(txt) );
}


/*-------------------------------------------------------------------------*/
/* Read joystick hardware directly and return delta x, delta y and fire    */
/* button discrete.  Note that this routine bypasses normal i/o functions  */
/* so that any screen blanker program, for example, won't hear about it... */

joystick( dx, dy, fire)
USHORT *dx, *dy, *fire;
{
 USHORT *joy = (USHORT *) 0xdff00c;
 USHORT *button = (USHORT *) 0xbfe0fe;
 USHORT sample;

 sample = *joy & 771; /* Mask off Non-Joystick bits */

 switch (sample) {
  case 0:  *dx = 0; *dy = 0; break; /* Center      */
  case 1:  ++(*dy); break;   /* Down        */
  case 2:  ++(*dy); ++(*dx); break; /* Down, Right */
  case 3:  ++(*dx); break;   /* Right       */
  case 256: --(*dy); break;   /* Up          */
  case 259: --(*dy); ++(*dx); break; /* Up, Right   */
  case 512: --(*dy); --(*dx); break; /* Up, Left    */
  case 768: --(*dx); break;     /* Left        */
  case 769: ++(*dy); --(*dx); break; /* Down, Left  */
  default: *dx = 0; *dy = 0;
 }

 if ( !(*button & 0x0080) )  *fire = TRUE; else *fire = FALSE;
}


/*-------------------------------------------------------------------------*/
/* Open up system libraries and stuff */

openstuff()
{
 if (!(IntuitionBase = OpenLibrary ("intuition.library", REV)))
  die ("Intuition doesn't 'open up' to me.");

 if (!(GfxBase = OpenLibrary ("graphics.library", REV)))
  die ("Art shop closed.");

 get_bitmaps();  /* Allocate custom bitmaps for dbuf */

 scrndef.CustomBitMap = bitmap_ptrs[0];
 scrn->RastPort.Flags = DBUFFER;

 if (!(scrn = OpenScreen (&scrndef)))
  die ("Screen painted shut.");

 windef.Screen = scrn;

 if (!(win = OpenWindow( &windef)))
  die ("Window stuck tight.");

 vp  = &(scrn -> ViewPort);
 rp  = &(scrn -> RastPort);

 LoadRGB4( vp, colortable, 32L);

 /* Make the screen using pointer to bitmap 0 */

 scrn->ViewPort.RasInfo->BitMap = bitmap_ptrs[0];
 scrn->ViewPort.RasInfo->RyOffset = BLK_H;
 MakeScreen(scrn);
 RethinkDisplay();
}


/*-------------------------------------------------------------------------*/
/* Close what we opened, deallocate what we allocated */

closestuff()
{
 if (sprbuffer)
  FreeMem( sprbuffer, (long) 2*SPRSIZE*NSPRITES);

 if (shiplspr.num) 
  FreeSprite( (long) shiplspr.num );
 if (shiprspr.num) 
  FreeSprite( (long) shiprspr.num );
 if (alienspr[0].num) 
  FreeSprite( (long) alienspr[0].num );
 if (alienspr[1].num) 
  FreeSprite( (long) alienspr[1].num );
 if (alienspr[2].num) 
  FreeSprite( (long) alienspr[2].num );
 if (alienspr[3].num) 
  FreeSprite( (long) alienspr[3].num );
 if (mslspr.num) 
  FreeSprite( (long) mslspr.num );

 if (win)
  CloseWindow(win);
 if (scrn)
  CloseScreen(scrn);

 free_bitmaps();  /* This must be done AFTER screen closed */

 if (GfxBase)
  CloseLibrary (GfxBase);
 if (IntuitionBase)
  CloseLibrary (IntuitionBase);
}


/*-------------------------------------------------------------------------*/
/* Post a complaint on the way out */

die(str)
char *str;
{
 puts(str);
 closestuff();
 exit (-1);
}

