//
//
//  Invaders must die! was written in about a day.
//  It's a quick and dirty NES game that can be used as a basis
//  for similar projects.
//
//  If you have any questions regarding the code, contact me
//  via the method specified at  http://www.sebastianmihai.com
//  (operational in 2012), and I will try my best to help.
//
//                             Sebastian Mihai, 2012
//

#include <rNES.h>
#include "graphics\tiles.h"

byte bkg_pal[] = { 
	0x0E, 0x19, 0x10, 0x01,
	0x0E, 0x28, 0x18, 0x06,
	0x0E, 0x14, 0x04, 0x00 };

byte spr_pal[] = { 
	0x0E, 0x16, 0x25, 0x12,
	0x0E, 0x01, 0x03, 0x01,
	0x0E, 0x14, 0x15, 0x01,
	0x0E, 0x09, 0x0B, 0x01
};

#define MAX_INVADERS 10

// pointer to sprites
rNES_sprite *sprites = (rNES_sprite *)0x0200;;

byte joy; // used to read joystick
word i,j; // dumb counters
byte playerX,playerY;
byte PPU_status;
unsigned long currentFrame = 0;
word score,kills;
byte level; // determines speed of invaders
word highscore=0;

// I've had some bad experiences with arrays of structs when developing for the
// Gameboy, so I'll split it up for this example game
byte invaderX[MAX_INVADERS];
byte invaderY[MAX_INVADERS];
byte invaderAlive[MAX_INVADERS];

#define NO_ROCKET 0
#define ROCKET_FIRED 1
#define ROCKET_EXPLODING 2
byte rocketX, rocketY;
byte rocketState;
#define MAX_EXPLOSION_FRAMES 12
byte rocketExplosionFrames; // counts down every time a rocket explodes

void destroy_player() {

	(sprites+1)->tile = 0;
	(sprites+1)->x = 0;
	(sprites+1)->y = 0;
	(sprites+2)->tile = 0;
	(sprites+2)->x = 0;
	(sprites+2)->y = 0;
	(sprites+3)->tile = 0;
	(sprites+3)->x = 0;
	(sprites+3)->y = 0;
	(sprites+15)->tile = 0;
	(sprites+15)->x = 0;
	(sprites+15)->y = 0;
}

void destroy_invader( byte which ) {

	// move rocket on top (just a little bit lower), so its 
	// explosion looks like the invader is exploding
	rocketX = invaderX[which];
	rocketY = invaderY[which] + 2;

	(sprites+5+which)->attr = 0;
	(sprites+5+which)->x = 0;
	(sprites+5+which)->y = 0;
	(sprites+5+which)->tile = 0;
	
	invaderAlive[which] = 0;
	invaderX[which] = 0;
	invaderY[which] = 0;
}

int rocket_hit() {
	byte k;
	int d1,d2;
	
	for( k=0; k<MAX_INVADERS; k++ ) {
		if( invaderAlive[k] && rocketY <= invaderY[k] + 8 ) {
			d1 = rocketX - invaderX[k];
			d2 = invaderX[k] - rocketX;
		
			if( (rocketX > invaderX[k] && d1 < 8) ||
				(rocketX <= invaderX[k] && d2 < 8)
			) {
				return k;
			}
		}
	}
	
	return -1;
}

void despawn_rocket() {
	(sprites+4)->attr = 0;
	(sprites+4)->x = 0;
	(sprites+4)->y = 0;
	(sprites+4)->tile = 0;
}

byte need_to_spawn() {
	byte k;
	
	for( k=0; k<MAX_INVADERS; k++ ) {
		if( invaderAlive[k] == 0 ) {
			return 1;
		}
	}
	return 0;
}

void spawn_enemy() {
	// find first empty spot in invader array
	byte spot = -1, k;
	
	byte y = 32;
	byte x = ((currentFrame * 14577) % 190) + 25; // homemade random number generator
	
	for( k=0; k<MAX_INVADERS; k++ ) {
		if( invaderAlive[k] == 0 ) {
			spot = k;
			break;
		}
	}
	
	if( spot == -1 ) return;
	
	(sprites+5+spot)->attr = 0;
	(sprites+5+spot)->x = x;
	(sprites+5+spot)->y = y;
	(sprites+5+spot)->tile = 9;
	
	invaderAlive[spot] = 1;
	invaderX[spot] = x;
	invaderY[spot] = y;
}

void animate_invaders( word frame ) {
	byte k;
	
	for( k=0; k<MAX_INVADERS; k++ ) {
		if( invaderAlive[k] ) {
			(sprites+5+k)->attr = 0;
			(sprites+5+k)->y = invaderY[k];
		
			if( frame % 20 < 10 ) {
				(sprites+5+k)->tile = 9;
			} else {
				(sprites+5+k)->tile = 10;
			}
		}
	}
}

// Quick and dirty itoa - converts an integer to a string
// Warning - make sure that you have allocated enough memory for *s ! :)
void Itoa( char* s, word number ) {

	char temp[] = "Temporary storage!";
	signed char digits = 0;

	word x = number;
	while( x >= 10 ) {
		temp[digits] = '0' + x % 10;
		x = x / 10;
		digits++;
	}

	temp[digits] = '0' + x;

	// copy digits to return string, reversing order
	while(digits >= 0) {
		*s++ = temp[digits];
		digits--;
	}

	// add terminator
	*s = 0;

	// hasta la vista baby!
}

void animate_player( word frame ) {

	if( frame % 10 < 5 ) {
		// sprite 1 left side of player tank
		(sprites+1)->tile = 1; //indexed in sprite_tiles
		
		// sprite 2 middle side of player tank
		(sprites+2)->tile = 2; //indexed in sprite_tiles
		
		// sprite 3 right side of player tank
		(sprites+3)->tile = 3; //indexed in sprite_tiles
	} else {
		(sprites+1)->tile = 4; //indexed in sprite_tiles
		(sprites+2)->tile = 5; //indexed in sprite_tiles
		(sprites+3)->tile = 6; //indexed in sprite_tiles
	}
	(sprites+15)->tile = 13;
}

// <which> is the sprite number
void animate_rocket( word frame ) {

	if( rocketState == ROCKET_EXPLODING ) {
		// an exploding rocket
		if( rocketExplosionFrames > 2 * MAX_EXPLOSION_FRAMES / 3 ) {
			// frame 2
			(sprites+4)->tile = 11;
		} else if( rocketExplosionFrames > MAX_EXPLOSION_FRAMES / 3 ) {
			// frame 1
			(sprites+4)->tile = 12;
		} else {
			// frame 0
			(sprites+4)->tile = 11;
		}
		
	} else {
		// just a normal flying rocket
		if( frame % 10 < 5 ) {
			(sprites+4)->tile = 7;
		} else {
			(sprites+4)->tile = 8;
		}
	}
}

// <which> is the sprite number
void move_rocket( byte x, byte y ) {
	(sprites+4)->attr = 0;
	(sprites+4)->x = x;
	(sprites+4)->y = y;
}

// <which> is the sprite number
void move_invader( byte x, byte y, byte which ) {
	(sprites+which)->attr = 0;
	(sprites+which)->x = x;
	(sprites+which)->y = y;
}

void move_player ( byte x, byte y ) {
	(sprites+1)->x = x - 8;
	(sprites+1)->y = y;
	(sprites+1)->attr = 0;
	(sprites+2)->x = x;
	(sprites+2)->y = y;
	(sprites+2)->attr = 0;
	(sprites+3)->x = x + 8;
	(sprites+3)->y = y;
	(sprites+3)->attr = 0;
	(sprites+15)->x = x;
	(sprites+15)->y = y - 7; // cannon is 1 px INSIDE tank
	(sprites+15)->attr = 0;
}

void initialize_sprites() {
	byte k;
	
	for( k = 0; k<MAX_INVADERS; k++ ) {
		(sprites+5+k)->attr = 0;
		(sprites+5+k)->x = 0;
		(sprites+5+k)->y = 0;
		(sprites+5+k)->tile = 0;
		invaderAlive[k] = 0;
	}
	
	// sprite 0 black empty sprite
	(sprites+0)->tile = 0;
	(sprites+0)->attr = rNES_sprite_behind_bg;
	(sprites+0)->x = 0;
	(sprites+0)->y = 30;
	
	// sprites 1-3 player tank
	animate_player( 0 );
		
	// sprite 4 rocket OR rocket explosion
	
	// sprites 5 - 14 enemies (up to 10 on screen)
	
	// sprite 15  player tank cannon
}

void initializeSystem() {
	const char* author = "Written by Sebastian Mihai, 2012";
	rNES_init();
	rNES_sound_init( rNES_snd_chn_1 | rNES_snd_chn_2 );	
	rNES_disable_graphics();	
	
	rNES_copy_vram( bkg_pal, 12, rNES_back_palette );
	rNES_copy_vram( spr_pal, 16, rNES_sprite_palette );
	
	// background 0 - 63 holds font tiles
	rNES_copy_tiles( font, 0, rNES_back_tiles, 64 );
	// background 64 -   holds scenery tiles
	rNES_copy_tiles( scenery_tiles, 0, rNES_back_tiles+64, 5 );
	
	// tiles 0 -    hold objects
	rNES_copy_tiles( sprite_tiles, 0, rNES_sprite_tiles, 14 );
}

void drawBuilding( byte x, byte y, byte height, byte width ) {

	word r; //helps us determine if each window is lit or not

	for( j=y; j!=y+height; ++j )
		for( i=x; i!=x+width; ++i ) {
			// first tile of background is at 64 (since 0 -> 63 is the font)
			//rNES_put_vram( rNES_name_table_0 + (j+10)*32 + i+19, 64 );
			r = j * i + height + width;
			
			if( r % 2 == 0 ) {
				rNES_put_vram( rNES_name_table_0 + (j+4)*32 + i, 64 );
			} else {
				rNES_put_vram( rNES_name_table_0 + (j+4)*32 + i, 65 );
			}
		}
	// the door
	rNES_put_vram( rNES_name_table_0 + (y+height+4-1)*32 + x+(width/2), 66 );
}


void starrySky( byte x, byte y, byte tile ) {
	rNES_put_vram( rNES_name_table_0 + (y+4)*32 + x, tile );
}

void drawBackground() {
	
	// y + height must equal 20
	drawBuilding( 0, 16, 4, 3 );
	drawBuilding( 4, 13, 7, 5 );
	drawBuilding( 10, 18, 2, 9 );
	drawBuilding( 20, 11, 9, 3 );
	drawBuilding( 24, 14, 6, 7 );
	starrySky( 26, 2, 68 );
	starrySky( 16, 15, 67 );
	starrySky( 0, 9, 67 );
	starrySky( 7, 7, 67 );
	starrySky( 20, 10, 67 );
	starrySky( 29, 13, 67 );
	starrySky( 12, 1, 67 );
	starrySky( 15, 2, 67 );
	starrySky( 1, 5, 67 );
	starrySky( 18, 9, 67 );
	starrySky( 13, 13, 68 );
}

void game() {

	byte gameover = 0;

	initializeSystem();
	drawBackground();
	rNES_fill_vram( rNES_attr_table_0, 8, bin(0,1,0,1,0,1,0,1) );
	rNES_fill_vram( rNES_name_table_1-16, 16, bin(1,0,1,0,1,0,1,0) );
	
	rNES_print( "BY SEBASTIAN MIHAI, 2012",	4, 25, rNES_name_table_0, charset );
	rNES_print( "                                ", 0, 2, rNES_name_table_0, charset );
	rNES_print( "INVADERS MUST DIE!", 7, 2, rNES_name_table_0, charset );
	rNES_print( "           ", 9, 3, rNES_name_table_0, charset );
	
	score = 0;
	level = 0;
	kills = 0;
	rocketState = NO_ROCKET;
	
	// clear sprite area
	rNES_fill_ram( 0x0200, 0x0100, 0 );
	
	playerX=128;
	playerY=185;
	move_player( playerX, playerY );
	
	initialize_sprites();
	
	rNES_enable_graphics();
	rNES_waitvblank();
	rNES_sprite_dma( 2, 0 );
	
	while(1) {
	
		//level varies from 0 to 9
		level = kills / 5;
		if( level > 9 ) level = 9;
		
		currentFrame++;
		joy = rNES_get_joy( rNES_JOYPAD_1 );
		
		// PLAYER TANK
		if( rNES_RIGHT_Down( joy ) ) {
			playerX+=1 + level / 3 ;
			if( playerX > 240 ) {
				playerX = 240;
			}
		}
		else if( rNES_LEFT_Down( joy ) ) {
			playerX-=1 + level / 3 ;
			if( playerX < 8 ) {
				playerX = 8;
			}
		}
		
		if( rNES_RIGHT_Down( joy ) || rNES_LEFT_Down( joy ) ) {
			rNES_sound( rNES_snd_chn_1, rNES_sndlen_10, 3, 1700 );
			move_player( playerX, playerY );
			animate_player( currentFrame );
		} else {
			// when not moving, tank doesn't animate
			animate_player( 0 );
		}
		
		// ROCKET
		if( rocketState == ROCKET_FIRED ) {
			
			if ( rocket_hit() != -1 ) {
				// Rocket hit an invader
				kills++;
				score = score + level + 1;
				rNES_sound( rNES_snd_chn_2, rNES_sndlen_10, 10, 2000 );
				destroy_invader( (byte)rocket_hit() );
				rocketState = ROCKET_EXPLODING;
			} else if( rocketY <= 40 ) { 
				// Rocket flew out of bounds
				rocketState = NO_ROCKET;
				despawn_rocket();
			} else {
				// Rocket flies upwards
				rocketY-=7;
				move_rocket( rocketX, rocketY );
				animate_rocket( currentFrame );
			}
		} else if( rocketState == NO_ROCKET ) {
			if( rNES_A_Down( joy ) ) {
				// Fire a rocket!!
				//rNES_sound( rNES_snd_chn_2, rNES_sndlen_2, 10, 200 );
				rocketState = ROCKET_FIRED;
				rocketX = playerX;
				rocketY = playerY-8;
				rocketExplosionFrames = MAX_EXPLOSION_FRAMES;
			}
		} else if( rocketState == ROCKET_EXPLODING ) {
			// count down each explosion frame... when we reach 0
			// the explosion is over
			rocketExplosionFrames--;
			if ( rocketExplosionFrames == 0 ) {
				rocketState = NO_ROCKET;
				despawn_rocket();
			} else {
				// we're still calling move because the rocket moves on top
				// of the invader, in order for the explosion to happen at
				// the right spot
				move_rocket( rocketX, rocketY );
				animate_rocket( currentFrame );
			}
		}
		
		// INVADERS
		if( currentFrame % (10-level) == 0 ) {
			// move all invaders down
			for( i = 0; i<MAX_INVADERS; i++ ) {
				if( invaderAlive[i] ) {
					invaderY[i]++;
					//invader landed!!!
					if( invaderY[i] > 180 ) {
						// game over!
						gameover = 1;
					}
				}
			}
			animate_invaders( currentFrame );
			if( need_to_spawn() ) {
				spawn_enemy();
			}
		}
		
		// destroy all invaders if game is over
		if( gameover ) {
			for( j=0; j<MAX_INVADERS; j++ ) {
				destroy_invader(j);
			}
			despawn_rocket();
			destroy_player();
		}
		
		while( true ) {
			PPU_status = rNES_PPU_status();
			
			//if( rNES_sprite0hit( PPU_status ) )
				//rNES_scroll(playerX,0);
			
			if( rNES_VBlank( PPU_status ) )
				break;
		}
		rNES_sprite_dma( 2, 0 );
		
		if( gameover ) {
			rNES_sound( rNES_snd_chn_1, rNES_sndlen_96, 8, 1000 );
			for( i = 0; i < 20000; i++) {
				j = i;
			}
			return;
		}
		
	}
}

void intro() {

	char scoreOutput[] = "           "; // allocate some space for the string

	initializeSystem();
	drawBackground();
	
	rNES_fill_vram( rNES_attr_table_0, 8, bin(0,1,0,1,0,1,0,1) );
	rNES_fill_vram( rNES_name_table_1-16, 16, bin(1,0,1,0,1,0,1,0) );
	
	rNES_print( "BY SEBASTIAN MIHAI, 2012",	4, 25, rNES_name_table_0, charset );
	
	rNES_print( "                                ", 0, 2, rNES_name_table_0, charset );
	Itoa( scoreOutput, score );
	rNES_print( "SCORE", 0, 2, rNES_name_table_0, charset );
	rNES_print( scoreOutput, 6, 2, rNES_name_table_0, charset );
	Itoa( scoreOutput, highscore );
	rNES_print( "HIGHSCORE", 13, 2, rNES_name_table_0, charset );
	rNES_print( scoreOutput, 23, 2, rNES_name_table_0, charset );
	
	rNES_print( "PRESS START", 9, 3, rNES_name_table_0, charset );
	
	rNES_enable_graphics();
	rNES_waitvblank();
	
	while(1) {
		
		currentFrame++;
		joy = rNES_get_joy( rNES_JOYPAD_1 );
		
		if( rNES_START_Down( joy ) ) {
			while( rNES_START_Down( joy ) ) {
				joy = rNES_get_joy( rNES_JOYPAD_1 );
			}
			return;
		}
		
		while( true ) {
			PPU_status = rNES_PPU_status();
			
			if( rNES_VBlank( PPU_status ) )
				break;
		}
		
	}
}

//
// Main program - show intro screen, then play game
//
void main() {
	while(1) {
		intro();
		game();
		if( score > highscore ) {
			highscore = score;
		}
	}
}
