// // invaders.c // // Simple space invaders type game for Net Yaroze. // // Sprite library by Robert Swan. // // http://www.netyaroze-europe.com/~rcutting // richard.cutting@virgin.net // #include "graphics.h" #include "starfld.h" #include "sprite.h" #include "pad.h" //#include "gamelib.h" // Number of invaders. #define NUM_INVADERS 50 // Max number of missiles that can be on screen. #define NUM_MISSILES 20 // States that an invader can be in. #define DEAD 0 #define ALIVE 1 #define DYING 2 // States that the game can be in. #define STOP 0 // stop the game #define TITLE 1 // display title screen #define RUNNING 2 // game in progress #define LANDED 3 // invaders have landed #define NEW_LEVEL 4 // start new level #define GAME_OVER 5 // game over player lost all ships // Maximum number of animation frames. #define MAX_FRAMES 2 // Screen Size #define FRAME_X 340 #define FRAME_Y 256 // Other bits and pieces. #define GAP 4 // gap between invaders #define SIZE 16 // 16 * 16 sprites #define MISSILE_SPEED 4 // players missile speed #define INVADER_MISSILE_SPEED 3 // invaders missile speed #define INVADER_SPEED 1 // speed invaders move at #define PLAYER_SPEED 2 // speed player moves at // Memory addresses for TIM's #define PLAYER 0x80090000 #define MISSILE 0x80091c20 #define I_T1_F1 0x80090320 #define I_T1_F2 0x80090640 #define I_T2_F1 0x80090960 #define I_T2_F2 0x80090c80 #define I_T3_F1 0x80090fa0 #define I_T3_F2 0x800912c0 #define I_T4_F1 0x800915e0 #define I_T4_F2 0x80091900 #define I_T5_F1 0x80090fa0 #define I_T5_F2 0x800912c0 #define BONUS_SHIP 0x80090320 #define EXPLODE 0x80091f40 #define OVER 0x80092260 #define TITLE_SCREEN 0x80092bd0 #define LASER_VB 0x8009d1e8 #define LASER_VH 0x800B17e8 #define EXPLODE_VB 0x800B2408 #define EXPLODE_VH 0x800C6A08 // Structure for player data. typedef struct tagPlayer { int lives; u_long score; u_long pad; int fired; int missile_in_flight; GsSPRITE sprite; } PLAYER_type; // Structure for Invader data. typedef struct tagInvader { GsSPRITE sprite; int x; int y; int num_frames; int anim_clock; int motion_clock; int frame; u_long frames[MAX_FRAMES]; int state; } INVADER_type; // Structure for game control variables. typedef struct tagGameControl { u_long game_clock; int level; int state; int ps; } GAME_CONTROL_type; // Structure for missiles. typedef struct tagMissile { int x; int y; int dy; } MISSILE_type; // Globals INVADER_type G_aliens[NUM_INVADERS]; PLAYER_type G_player; GAME_CONTROL_type G_game; MISSILE_type G_missiles[NUM_MISSILES]; GsSPRITE G_s_missile, G_s_message, G_title; // This array defines the way that the invaders are displayed. Each number // corresponds to a different type of invader. int G_invaders[50] = { 1,1,1,1,1,1,1,1,1,1, 2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4,4,4, 5,5,5,5,5,5,5,5,5,5 }; int G_dx = INVADER_SPEED, G_r = -1; short G_vab_laser, G_vab_explode; // Code starts here // Initialise sounds static void InitSound( void ) { G_vab_laser = SsVabTransfer( ( u_char * ) LASER_VH, ( u_char * ) LASER_VB, -1, 1 ); G_vab_explode = SsVabTransfer( ( u_char * ) EXPLODE_VH, ( u_char * ) EXPLODE_VB, -1, 1 ); } // Load all TIM's into memory. static void LoadAllTims( void ) { LoadTIMData( PLAYER ); LoadTIMData( MISSILE ); LoadTIMData( I_T1_F1 ); LoadTIMData( I_T1_F2 ); LoadTIMData( I_T2_F1 ); LoadTIMData( I_T2_F2 ); LoadTIMData( I_T3_F1 ); LoadTIMData( I_T3_F2 ); LoadTIMData( I_T4_F1 ); LoadTIMData( I_T4_F2 ); LoadTIMData( I_T5_F1 ); LoadTIMData( I_T5_F2 ); LoadTIMData( EXPLODE ); LoadTIMData( BONUS_SHIP ); LoadTIMData( OVER ); LoadTIMData( TITLE_SCREEN ); } // Initialise player structure. static void InitialisePlayer( void ) { G_player.lives = 5; G_player.score = 0; G_player.missile_in_flight = 0; G_player.pad = 0; G_player.fired = 0; SetSpriteInfo( &( G_player.sprite ), PLAYER, 190, 220 ); } // Initialise game structure. static void InitialiseGame( void ) { G_game.game_clock = 0; G_game.level = 1; G_game.state = RUNNING; SetSpriteInfo( &G_s_message, OVER, 170, 128 ); // game over G_s_message.scalex = G_s_message.scaley = 512; } // Initialise all invaders. static void InitialiseInvaders( void ) { register int i, *j, x, y; register INVADER_type *k; G_dx = INVADER_SPEED; k = &( G_aliens[0] ); j = &( G_invaders[0] ); x = 60; for ( i = 0; i < NUM_INVADERS; i++, j++, x += 20, k++ ) { // reset x for each new column if ( i % 10 == 0) { x = 60; } k->num_frames = 2; k->anim_clock = 0; k->motion_clock = 0; k->frame = 0; k->state = ALIVE; // set TIM's that will be used for animation switch( *j ) { case 1: k->frames[0] = I_T1_F1; k->frames[1] = I_T1_F2; y = 40; break; case 2: k->frames[0] = I_T2_F1; k->frames[1] = I_T2_F2; y = 60; break; case 3: k->frames[0] = I_T3_F1; k->frames[1] = I_T3_F2; y = 80; break; case 4: k->frames[0] = I_T4_F1; k->frames[1] = I_T4_F2; y = 100; break; case 5: k->frames[0] = I_T5_F1; k->frames[1] = I_T5_F2; y = 120; break; } // set starting x and y pos k->x = x; k->y = y + G_game.level; } } // Initialise missiles. static void InitialiseMissiles( void ) { register int i; register MISSILE_type *m; for ( i = 0, m = &( G_missiles[0] ); i < NUM_MISSILES; i++, m++ ) { m->x = 0; m->y = 0; m->dy = 0; } // we only use one sprite for all missiles, set it up here. SetSpriteInfo( &G_s_missile, MISSILE, 0, 0 ); } // Initialise title screen graphics. static void InitialiseTitleScreen( void ) { // I use a sprite to display the title screen text as I can't figure // out how to get a TIM displayed directly into the frame buffer yet ! SetSpriteInfo( &G_title, TITLE_SCREEN, 60, 30 ); // We frig with the rgb values on the title screen text to create // an effect, this value controls what we add to the rgb values, // we start by decreasing them. G_r = -1; } // Spawn a new missile. static void SpawnMissile( int x, int y, int dy ) { register int i; register MISSILE_type *m; for ( i = 0, m = &( G_missiles[0] ); i < NUM_MISSILES; i++, m++ ) { // dy == 0 indicates a free slot if ( m->dy == 0 ) { m->x = x; m->y = y; m->dy = dy; return; } } } // Move all the invaders. int MoveInvaders( void ) { register int i, drop = 0; register INVADER_type *p; for ( i = 0, p = &( G_aliens[0] ); i < NUM_INVADERS; i++, p++ ) { if ( p->state != ALIVE ) { if ( p->state == DYING ) p->state = DEAD; continue; } p->x += G_dx; // hit edge of screen so drop if ( ! drop && ( p->x >= 304 || p->x <= 16 ) ) { drop = 1; } // Animate on every third frame if ( ++( p->anim_clock ) == 3 ) { p->anim_clock = 0; // is there a better way to do this ? SetSpriteInfo( &( p->sprite ), p->frames[p->frame], p->x, p->y ); p->frame++; if ( p->frame == 2 ) { p->frame = 0; } } // shall this invader drop a missile ? if ( rand()%1000 == 0 ) { SpawnMissile( p->x, p->y, INVADER_MISSILE_SPEED ); } } // if we hit the edge of a screen then drop the invaders and // reverse direction if ( drop ) { for ( i = 0, p = &( G_aliens[0] ); i < NUM_INVADERS; i++, p++ ) { if ( p->state != ALIVE ) { continue; } p->y += 4; // check for landing if ( p->y >= 230 ) { return( LANDED ); } p->sprite.y = p->y; } G_dx = -G_dx; } return( RUNNING ); } // Move the player. static void MovePlayer( void ) { PLAYER_type *player; player = &( G_player ); if ( player->pad & PADLleft ) { if ( player->sprite.x >= 16 ) { player->sprite.x -= PLAYER_SPEED; } } if ( player->pad & PADLright ) { if ( player->sprite.x <= 304 ) { player->sprite.x += PLAYER_SPEED; } } if ( player->pad & PADcross && player->missile_in_flight == 0 ) { player->fired = 1; } } // Move missiles. static void MoveMissiles( void ) { register int i; register MISSILE_type *m; for ( i = 0, m = &( G_missiles[0] ); i < NUM_MISSILES; i++, m++ ) { if ( m->dy != 0 ) { m->y += m->dy; // check if reached top/bottom of screen if ( m->y <= 16 || m->y >= 240 ) { // if dy < 0 then its the players missile if ( m->dy < 0 ) { G_player.missile_in_flight = 0; } m->dy = 0; } } } } // Render missiles. static void RenderMissiles( void ) { register int i; register MISSILE_type *m; register GsSPRITE *s; s = &( G_s_missile ); for ( i = 0, m = &( G_missiles[0] ); i < NUM_MISSILES; i++, m++ ) { if ( m->dy == 0 ) { continue; } s->x = m->x; s->y = m->y; RenderFastSprite( s, 1 ); } } // Render the player. static void RenderPlayer( void ) { RenderFastSprite( &( G_player.sprite ), 1 ); } // Render the aliens. static void RenderAliens( void ) { register INVADER_type *j; register int i, k; for ( i = 0, k = 0, j = &( G_aliens[0] ); i < NUM_INVADERS; i++, j++ ) { if ( j->state != DEAD ) { RenderFastSprite( &( j->sprite ), 1 ); k++; // keep count of live aliens } } // speed up if only 10 left if ( k == 10 && abs( G_dx ) <= 1 ) { G_dx += ( G_dx > 0 ? 1 : -1 ); } // start new level if all invaders shot if ( k == 0 ) { G_game.state = NEW_LEVEL; } } // Display number of lives. static void DisplayLives( void ) { short x,y; register int i; // don't bother if the players got more than 10 lives if ( G_player.lives >= 10 ) return; // we're going to hijack the player sprite so we need to remember // where it is at the moment x = G_player.sprite.x; y = G_player.sprite.y; for ( i = 0; i < G_player.lives; i++ ) { G_player.sprite.x = 10 + ( 20 * i ); G_player.sprite.y = 10; RenderFastSprite( &( G_player.sprite ), 1 ); } // restore players sprite G_player.sprite.x = x; G_player.sprite.y = y; } // Check for collisions. static void DetectCollisions( void ) { register int i,j; register MISSILE_type *m; register INVADER_type *p; register GsSPRITE *s; GsSPRITE dummy; short voice; // the detection routine uses two sprites, so we need a dummy // sprite for the missiles s = &dummy; s->w = 16; s->h = 16; // go through all live missiles, check if each one hit anything for ( i = 0, m = &( G_missiles[0] ); i < NUM_MISSILES; i++, m++ ) { if ( m->dy == 0 ) { continue; } s->x = m->x; s->y = m->y; // dy < 0, players missile if ( m->dy < 0 ) { for ( j = 0, p = &( G_aliens[0] ); j < NUM_INVADERS; j++, p++ ) { if ( p->state != ALIVE ) { continue; } if ( SpriteCollide( s, &( p->sprite ) ) ) { if ( m->dy < 0 ) { G_player.missile_in_flight = 0; } m->dy = 0; // cull missile p->state = DYING; // live for 1 more frame G_player.score += 10; if ( G_player.score % 10000 == 0 ) G_player.lives++; SetSpriteInfo( &( p->sprite ), EXPLODE, p->x, p->y ); voice = SsUtKeyOn( G_vab_explode, 0, 0, 60, 0, 127,127); break; } } } else // alien missile { // see if it hit player if ( SpriteCollide( s, &( G_player.sprite ) ) ) { m->dy = 0; G_player.lives--; voice = SsUtKeyOn( G_vab_explode, 0, 0, 60, 0, 127,127); if ( G_player.lives == 0 ) { G_game.state = GAME_OVER; return; } } } } } // Display end game animation sequence. static int EndGameAnimation( void ) { int x; // scale up game over message. if ( G_s_message.scalex < 10000 ) { G_s_message.scalex += 128; G_s_message.scaley = G_s_message.scalex; } RenderSprite( &G_s_message, 1 ); // keep the starfield going scroll_starfield(); render_starfield(); return( ( G_s_message.scalex < 10000 ) ); } // Display the title screen. static void DisplayTitleScreen( void ) { G_title.r += G_r; G_title.g = G_title.b = G_title.r; if ( G_title.r == 10 ) { G_r = 1; } else if ( G_title.r == 200 ) { G_r = -1; } scroll_starfield(); RenderFastSprite( &G_title, 0 ); render_starfield(); } // Start a new game. static void NewGame( void ) { InitialisePlayer(); InitialiseGame(); InitialiseInvaders(); InitialiseMissiles(); } // Main entry point. main() { short voice; InitialiseScreen( FRAME_X, FRAME_Y, 0 ); InitialiseOT(); LoadAllTims(); InitSound(); PadInit(); InitialiseTitleScreen(); initialise_starfield( USE_BOX, 0, 150, 3, STAR_SCROLL_DOWN, 0, 1, 3, 0 ); G_game.ps = FntOpen( 200, 10, 120, 18, 0, 40 ); G_game.state = TITLE; while( G_game.state != STOP ) { StartRender(); G_player.pad = PadRead(); if ( G_player.pad & PADselect && G_player.pad & PADstart ) { G_game.state = STOP; } else { // on title screen if ( G_game.state == TITLE ) { if ( G_player.pad & PADstart ) { NewGame(); G_game.state = RUNNING; } else { DisplayTitleScreen(); } } else if ( G_game.state == GAME_OVER ) { SsUtAllKeyOff( 0 ); if ( ! EndGameAnimation() ) { G_game.state = TITLE; InitialiseTitleScreen(); } } else if ( G_game.state == NEW_LEVEL ) { if ( ++G_game.level == 5 ) G_game.level = 0; InitialiseInvaders(); InitialiseMissiles(); G_game.state = RUNNING; } else if ( G_game.state == RUNNING ) { MovePlayer(); if ( G_player.fired ) { G_player.fired = 0; G_player.missile_in_flight = 1; voice = SsUtKeyOn( G_vab_laser, 0, 0, 60, 0, 127,127); SpawnMissile( G_player.sprite.x, G_player.sprite.y + 4, -MISSILE_SPEED ); } MoveInvaders(); MoveMissiles(); DetectCollisions(); scroll_starfield(); render_starfield(); RenderPlayer(); RenderAliens(); RenderMissiles(); DisplayLives(); FntPrint( G_game.ps, "Score: %d", G_player.score ); FntFlush( G_game.ps ); } FinishRender(); } } free_starfield(); SsUtAllKeyOff( 0 ); SsVabClose( G_vab_laser ); SsVabClose( G_vab_explode ); ResetGraph( 3 ); }