 // lazyNES catwalk demo, 'Lazycow 2020
// (graphics from Lazycow, music from Cyborgjeff, for use in this demo only)

#include "lazynes.h"
#include "famitone2.h"

 // data
//
extern const ubyte backPal[];     // background color palettes
extern const ubyte backCol[];    // background block color info (palette select)
extern const ubyte roboPal[];   // sprite color palettes
extern const ubyte roboTab[];  // metadata of all sprites
extern const uword roboOff[]; // offsets for each sprite into the metadata
extern const ubyte musicData[];  // music data, generated by text2data
extern const ubyte soundData[]; // sound effect data, generated by nsf2data

enum {
	NULL=0,
	F=3, // pseudo floating point shift amount
	maxDrawObjects=15,
	tileMapWF=5, tileMapW=(1<<tileMapWF)/*=32*/, tileMapH=15 // size of tileMap
};

const ubyte aTab[]={ // animation start shapes of all robos
	0,8,16,24
};

typedef struct {
	ubyte
		s,    // sprite image number
		c,   // animation counter
		t,  // type
		f; // flags: dfAlive, dfHit
	byte
		yo, // (vertical) offset
		d, // direction (-1==left, 1==right)
		vx, vy; // velocity
	uword
		x, y; // shifted coordinats ( <<F / >>F )
} DrawObject;
enum { dfAlive=1, dfHit=2 };

static const ubyte

	tTab[]={ // table which defines the 4 blocks of each 2x2 tile
		0,0,0,0, 8,9,24,25, 10,11,26,27, 12,13,28,29, 14,15,30,31, // 0..4
		2,3,18,19, 3,4,17,20, 5,2,21,16, 5,4,21,20, 6,7,22,23}, // 5..9

	map[]={ // tilemap
		5,0,1,2,3,4,0,0,1,2,3,4,0,0,1,2, 3,4,0,0,1,2,3,4,0,0,1,2,3,4,0,5,
		5,0,1,2,3,4,0,0,1,2,3,4,0,0,1,2, 3,4,0,0,1,2,3,4,0,0,1,2,3,4,0,5,
		5,5,5,5,5,4,5,6,8,7,5,6,9,7,5,5, 5,6,8,7,5,6,9,7,5,5,7,5,3,5,5,5,
		5,0,1,2,3,4,0,0,1,2,3,4,0,0,1,2, 3,4,0,0,1,2,3,4,0,0,1,2,3,4,0,5,
		5,0,1,2,3,4,0,0,1,2,3,4,0,0,1,2, 3,4,0,0,1,2,3,4,0,0,1,2,3,4,0,5,
		5,6,8,7,5,6,8,7,5,6,9,7,5,0,5,5, 5,4,5,6,8,7,5,6,9,8,7,5,6,8,7,5,
		5,0,1,2,3,4,0,0,1,2,3,4,0,0,1,2, 3,4,0,0,1,2,3,4,0,0,1,2,3,4,0,5,
		5,0,1,2,3,4,0,0,1,2,3,4,0,0,1,2, 3,4,0,0,1,2,3,4,0,0,1,2,3,4,0,5,
		5,6,9,7,5,4,5,6,8,8,8,7,5,6,8,7, 5,6,9,7,5,6,8,7,5,0,5,6,8,7,5,5,
		5,0,1,2,3,4,0,0,1,2,3,4,0,0,1,2, 3,4,0,0,1,2,3,4,0,0,1,2,3,4,0,5,
		5,0,1,2,3,4,0,0,1,2,3,4,0,0,1,2, 3,4,0,0,1,2,3,4,0,0,1,2,3,4,0,5,
		5,6,8,8,7,5,6,9,7,5,3,5,6,8,7,5, 5,5,0,5,6,8,8,8,7,5,5,6,9,7,5,5,
		0,0,1,2,3,4,0,0,1,2,3,4,0,0,1,2, 3,4,0,0,1,2,3,4,0,0,1,2,3,4,0,0,
		0,0,1,2,3,4,0,0,1,2,3,4,0,0,1,2, 3,4,0,0,1,2,3,4,0,0,1,2,3,4,0,0,
		5,5,5,6,8,7,5,5,6,8,7,5,5,5,5,5, 5,6,8,7,5,5,6,8,7,5,5,6,8,7,5,5};


DrawObject
	dTab[maxDrawObjects]; // table of all robos
ubyte
	moveTics=8;


 // browse table of DrawObjects to look for an unused entry
//
DrawObject* GetDO() {
	DrawObject* d;  ubyte i;
	for (d=dTab,i=0; i<maxDrawObjects ;++i,++d) if ( ! d->f ) return d;
	return NULL;
}


 // add new robo
//
void Spawn(DrawObject* d, ubyte type) {
	d->f=dfAlive;
	d->t=type&15;
	d->c=0;  d->vy=0;  d->yo=0;
	d->y=0;  d->x=(tileMapW>>1)<<4+F;
	if (type&16) {  d->d=-1;  d->s=aTab[d->t];  d->vx=-moveTics; } // left?
		else {  d->d=1;   d->s=aTab[d->t]+4;  d->vx=moveTics; } // facing right?
	if (2==d->t)  d->yo=-5<<F; // robo type #2 is oversized, adjust
		else if (3==d->t) {
		d->yo=-16<<F; // robo type #3 is super-oversized, adjust
		d->y=160<<F;  d->x=0<<F+3;  d->vx=moveTics<<1;
	}
}


 // check for collision
//
ubyte Collide(DrawObject* d, DrawObject* v) {
	enum { vw=16<<F, vh=16<<F, dw=24<<F, dh=16<<F }; // object sizes
	word delta;
	if ( (delta=d->y-v->y-vh)<0 && delta+vh+dh>0
		&& (delta=d->x-v->x-vw)<0 && delta+vw+dw>0) return 1;
	return 0;
}


 //
//
int main() {
	enum { scrollVel=4 }; // maximum scrolling velocity
	word
		xOff=0<<F, // current scrolling offset
		xVel=1;   // current scrolling velocity
	uword
		tics;
	ubyte
		ntsc=0,
		drawObjects=maxDrawObjects,
		seed=0,
		respawnCount;

	// To avoid glitches, always write color palettes immediately after lnSync()!
	ntsc=lnSync(lfBlank)&128; // blank screen to enable lnPush() usage, NTSC-check
	lnPush(lnBackCol,12,backPal);     // set background colors
	lnPush(lnSprPal0,3,&roboPal[1]); // set sprite colors
	lnPush(lnSprPal1,3,&roboPal[5]);
	lnPush(lnSprPal2,3,&roboPal[9]);
	lnPush(lnSprPal3,3,&roboPal[13]);
	if (!ntsc) moveTics=9; // adjust moving velocity in PAL

	   // draw background while lfBlank-mode is on
	{ //
		ubyte
			x, y,
			* mPtr=(void*)map;
		uword
			t0=lnNameTab0,
			t1=lnNameTab1;

		// set blocks in name tables / set color info in attrubut tables
		for (y=0; y<tileMapH ;++y,t0+=32,t1+=32) for (x=0; x<32 ;++x,++mPtr) {

			uword nt;  ubyte tileOff=*mPtr<<2; // tile tab offset of current tile
			if (x<16) { nt=t0;  t0+=2; } else { nt=t1;  t1+=2; } // left or right?
			lnPush(nt,   2,&tTab[tileOff]);    // draw upper part of tile
			lnPush(nt+32,2,&tTab[tileOff+2]); // draw lower part of tile

			if ( 0==(y&1) && 0==(x&1) ) { // inspect 4 tiles to build attribute entry
				uword at;  ubyte v, h=x;  if (h>=16) h-=16; // left or right?
				if (x<16) { at=lnAttrTab0; } else { at=lnAttrTab1; } // left or right?
				v=y>>1;  v<<=3;  v+=h>>1;  at+=v; // calc pos in attribute table
				tileOff=mPtr[0]<<2;  v=  backCol[tTab[tileOff]]&3;
				tileOff=mPtr[1]<<2;  v|=(backCol[tTab[tileOff]]&3)<<2;
				if (y<tileMapH-2) { // last row: Only 2 entries, skip 2nd half here
					tileOff=mPtr[tileMapW]<<2;    v|=(backCol[tTab[tileOff]]&3)<<4;
					tileOff=mPtr[tileMapW+1]<<2;  v|=(backCol[tTab[tileOff]]&3)<<6;
				}
				lnPush(at,1,&v);
			}

		}
	}
	
	// init famitone2
	ftInit(musicData,ntsc);
	ftSfxInit(soundData);
	ftMusicPlay(0);

	 // main loop
	//
	memset(dTab,0,sizeof dTab); // init objects
	tics=200;
	respawnCount=0; // time until next respawn check
	for (;;) {
		DrawObject* d;  ubyte i;

		 // spawn new robo?
		//
		++tics;  if (200<tics //&& tics<1750
			&& ++respawnCount>=64) {
			respawnCount=0;
			if ( (d=GetDO()) ) {
				ubyte type=seed&3;  if (type==3) type=0; // not type #3 here, please
				if (0==seed) type=3; // 1st robo is the big one
					else if (seed&4) type|=16; // start dir: left (instead of right?)
				Spawn(d,type);
				seed+=1;  if (seed>=252) seed=4;
			}
		}

		 // DrawObject loop
		//
		for (d=dTab,i=0; i<drawObjects ;++i,++d) if (d->f&dfAlive) {
			ubyte tx=d->x>>F+4,  ty=d->y>>F+4, // calculate current tile coordinates
				offset, onScreen=0;
			const ubyte* collPtr=&map[tx+(ty<<tileMapWF)]; // pointer to current tile

			// affected by gravity?
			offset=1<<tileMapWF;  if (d->d<0) ++offset; // check tile below
			if (d->t!=3 && (d->f&dfHit || collPtr[offset]<5)) // fall?
				{ ++d->vy;  d->y+=d->vy;  d->vx=0; }
				else if (d->vy && 0==(d->f&dfHit)) { // touchdown?
				d->vy=0;  d->y=ty<<F+4; // reset falling velocity, align to tile raster
				if (d->d>0) d->vx=moveTics; else d->vx=-moveTics; // continue moving
			}

			// animate
			if (0==(d->f&dfHit) && ++d->c>=4) {
				ubyte m=aTab[d->t];  if (d->d>0) m+=4;
				d->c=0;  ++d->s;  if (d->s>=m+4) d->s=m;
			}

			// move
			d->x+=d->vx;
			if (d->x>=(tileMapW-1)<<F+4 || d->y>=224<<F) d->f=0; // leaving this world
				else { // still walking
				uword x;
				offset=0;  if (d->d>0) ++offset; // check tile left or right
				if (collPtr[offset]>=5) { d->d=-d->d;  d->vx=-d->vx;  d->c=4; } // turn?

				// display?
				if ((x=d->x-xOff)<240<<F) {
					lnAddSpr(roboTab+roboOff[d->s],x>>F,(d->y+d->yo)>>F);
					onScreen=1;
				}
			}
			// collided with the "big one"?
			if (d!=dTab && 0==(d->f&dfHit) && Collide(dTab,d)) {
				d->f|=dfHit;  d->vy=-2<<F;
				if (onScreen) ftSfxPlay(0,FT_SFX_CH0); // play effect when hit onscreen
			}
		}

		 // scrolling
		//
		if (tics>=200) xOff+=xVel;
		if ( 0==(tics&15) ) {
			if (xVel>0) {
				if (xOff<224<<F) { if (xVel<scrollVel) ++xVel; } else if
					(xVel>1) --xVel; else xVel=-1;
			} else {
				if (xOff>33<<F) { if (xVel>-scrollVel) --xVel; } else if
					(xVel<-1) ++xVel; else xVel=1;
			}
		}
		lnScroll(xOff>>F,0);

		lnSync(0); // wait for vertical blank
	}

	return 0; // never
}
