1 #include <stdlib.h>
2 #include <stdbool.h>
3 #include <stdio.h>
4 #include <string.h>
5 #include <arch/sms/SMSlib.h>
6 #include <arch/sms/PSGlib.h>
7 #include "gfx.h"
8 
9 // Header information
10 
11 const unsigned char ds_name[] = "Data Storm";
12 const unsigned char ds_author[] = "Haroldo-OK\\2016";
13 const unsigned char ds_description[] = "A fast paced shoot-em-up.\n"
14   "Built using devkitSMS & SMSlib - https://github.com/sverx/devkitSMS";
15 
16 #define ACTOR_MIN_X 8
17 #define ACTOR_MAX_X (256 - 8)
18 #define ACTOR_FRAME_TILE 4
19 #define ACTOR_DIR_TILE 8
20 #define ACTOR_TILE_SHIFT 4
21 
22 #define LANE_COUNT 7
23 #define LANE_CHAR_HEIGHT 3
24 #define LANE_TOP 1
25 #define LANE_BOTTOM (LANE_TOP + LANE_COUNT * LANE_CHAR_HEIGHT)
26 #define LANE_PIXEL_TOP_LIMIT (LANE_TOP * 8 + 8)
27 #define LANE_PIXEL_HEIGHT (LANE_CHAR_HEIGHT * 8)
28 #define LANE_BASE_TILE 0xB0
29 
30 #define PLAYER_MIN_Y LANE_PIXEL_TOP_LIMIT
31 #define PLAYER_CENTER_X 128
32 #define PLAYER_WIDTH 16
33 #define PLAYER_HEIGHT 16
34 #define PLAYER_BASE_TILE 16
35 #define PLAYER_DEATH_BASE_TILE 4
36 #define PLAYER_DEATH_FRAMES 2
37 #define PLAYER_VERTICAL_SPEED 12
38 
39 #define SHOT_FLAG_LEFT 1
40 #define SHOT_FLAG_RIGHT 2
41 #define SHOT_BASE_TILE 32
42 
43 #define ENEMY_BASE_TILE 48
44 #define ENEMY_TYPE_MASK 7
45 #define ENEMY_TYPE_SLOW 0
46 #define ENEMY_TYPE_MEDIUM 1
47 #define ENEMY_TYPE_FAST 2
48 #define ENEMY_TYPE_PELLET 3
49 #define ENEMY_TYPE_BALL 4
50 #define ENEMY_TYPE_ARROW 5
51 #define ENEMY_TYPE_TANK 6
52 #define ENEMY_TYPE_PHANTOM 7
53 
54 #define SCORE_BASE_TILE 0xB6
55 #define SCORE_TOP 22
56 #define SCORE_LEFT 13
57 #define SCORE_DIGITS 6
58 
59 #define LIFE_BASE_TILE 0xCA
60 
61 #define STATE_DEMO 1
62 #define STATE_GAME_START 2
63 #define STATE_NEXT_STAGE 3
64 #define STATE_PLAY 4
65 #define STATE_GAME_OVER 5
66 
67 typedef struct _shot {
68   unsigned int x;
69   unsigned char flag;
70 } shot;
71 
72 typedef struct _enemy {
73   unsigned int x;
74   unsigned char type;
75   char spd, timer;
76   unsigned char spawn_delay;
77 } enemy;
78 
79 typedef struct _enemy_spec {
80   unsigned char base_speed, additional_speed_mask, score_value;
81 } enemy_spec;
82 
83 typedef struct _level_spec {
84   unsigned char base_spawn_delay, spawn_chance_mask;
85 } level_spec;
86 
87 const unsigned char lane_coords[] = { 0, LANE_PIXEL_HEIGHT, 2 * LANE_PIXEL_HEIGHT, 3 * LANE_PIXEL_HEIGHT, 4 * LANE_PIXEL_HEIGHT, 5 * LANE_PIXEL_HEIGHT, 6 * LANE_PIXEL_HEIGHT };
88 
89 int joy;
90 unsigned int frame_timer;
91 
92 unsigned char player_x, player_y, player_frame;
93 unsigned char player_fire_delay;
94 unsigned char player_current_lane, player_target_lane;
95 unsigned char player_target_y;
96 unsigned char player_looking_left;
97 unsigned char player_move_x_sfx_counter;
98 bool player_dead;
99 bool player_invincible;
100 
101 unsigned char score_digits[SCORE_DIGITS];
102 unsigned int score_tiles[2][SCORE_DIGITS];
103 bool score_needs_update;
104 bool score_enabled;
105 
106 unsigned char lives;
107 bool lives_need_update;
108 
109 unsigned char current_game_state;
110 unsigned char next_game_state;
111 
112 bool sound_enabled;
113 
114 unsigned char level_number;
115 int remaining_enemies;
116 
117 shot shots[LANE_COUNT];
118 enemy enemies[LANE_COUNT];
119 
120 // Former local variables converted to globals to see if it speeds up things
121 unsigned char g_i, g_x, g_y, g_lane;
122 unsigned char *sc_p;
123 unsigned int *st_p, *st_p2;
124 shot *shot_p;
125 enemy *enm_p;
126 level_spec *lvl_p;
127 
128 const unsigned int bkg_data_storm[] = { 0xD6, 0xD5, 0xD4, 0xD5, 0x00, 0xD3, 0xD4, 0xD7, 0xD1, 0xD8 };
129 const unsigned int bkg_press_start[] = { 0xD0, 0xD1, 0xD2, 0xD3, 0xD3, 0x00, 0x00, 0xD3, 0xD4, 0xD5, 0xD1, 0xD4 };
130 
131 const unsigned char spawnable_enemies[] = {
132   ENEMY_TYPE_SLOW, ENEMY_TYPE_MEDIUM, ENEMY_TYPE_FAST,
133   ENEMY_TYPE_PELLET, ENEMY_TYPE_ARROW
134 };
135 
136 const enemy_spec enemy_specs[] = {
137   // ENEMY_TYPE_SLOW
138   {8, 0x03, 1},
139   // ENEMY_TYPE_MEDIUM
140   {12, 0x07, 1},
141   // ENEMY_TYPE_FAST
142   {10, 0x0F, 2},
143   // ENEMY_TYPE_PELLET
144   {32, 0, 8},
145   // ENEMY_TYPE_BALL
146   {24, 0, 9},
147   // ENEMY_TYPE_ARROW
148   {32, 0x03, 4},
149   // ENEMY_TYPE_TANK
150   {12, 0x07, 4},
151   // ENEMY_TYPE_PHANTOM
152   {14, 0, 0},
153 };
154 
155 const level_spec level_specs[] = {
156   // 1
157   {64, 0x3F},
158   // 2
159   {32, 0x3F},
160   // 3
161   {24, 0x3F},
162   // 4
163   {24, 0x1F},
164   // 5
165   {16, 0x1F},
166   // 6
167   {8, 0x0F},
168   // 7
169   {8, 0x0F},
170   // 8
171   {8, 0x07},
172   // 9
173   {0, 0x07},
174 };
175 
add_double_sprite(unsigned char x,unsigned char y,unsigned char tile)176 void add_double_sprite(unsigned char x, unsigned char y, unsigned char tile) {
177   SMS_addSprite(x - 8, y, tile);
178   SMS_addSprite(x, y, tile + 2);
179 }
180 
draw_ship(unsigned char x,unsigned char y,unsigned char base_tile,unsigned char facing_right)181 void draw_ship(unsigned char x, unsigned char y, unsigned char base_tile, unsigned char facing_right) {
182   if (frame_timer & 0x10) {
183     base_tile += ACTOR_FRAME_TILE;
184   }
185 
186   if (facing_right) {
187     base_tile += ACTOR_DIR_TILE;
188   }
189 
190   add_double_sprite(x, y, base_tile);
191 }
192 
draw_player_ship()193 void draw_player_ship() {
194   draw_ship(player_x, player_y, PLAYER_BASE_TILE, !player_looking_left);
195 }
196 
load_ingame_tiles()197 void load_ingame_tiles() {
198   SMS_loadTiles(ship_til, 0, ship_til_size);
199 }
200 
generate_lane(unsigned int * buffer)201 void generate_lane(unsigned int *buffer) {
202   unsigned char i;
203   unsigned int *p, *p2;
204 
205   p = buffer;
206   p2 = p + 31;
207 
208   // Creates the gradient for the lanes (yes, I'm STILL too lazy to make a map.)
209 
210   for (i = 0; i != 5; i++) {
211     *p = LANE_BASE_TILE;
212     *p2 = LANE_BASE_TILE;
213     p++; p2--;
214   }
215 
216   for (i = 4; i != 9; i++) {
217     *p = LANE_BASE_TILE + 1;
218     *p2 = LANE_BASE_TILE + 1;
219     p++; p2--;
220   }
221 
222   for (i = 9; i != 16; i++) {
223     *p = LANE_BASE_TILE + 2;
224     *p2 = LANE_BASE_TILE + 2;
225     p++; p2--;
226   }
227 }
228 
draw_lanes()229 void draw_lanes() {
230   unsigned char i;
231   unsigned int y;
232   unsigned int buffer[32];
233 
234   generate_lane(buffer);
235 
236   // Draws the topmost/bottommost dividers
237 
238   SMS_loadTileMap(0, LANE_TOP, buffer, 64);
239   SMS_loadTileMap(0, LANE_BOTTOM, buffer, 64);
240 
241   // Opens a hole in the middle
242 
243   buffer[14] = LANE_BASE_TILE + 3;
244   buffer[15] = 0;
245   buffer[16] = 0;
246   buffer[17] = LANE_BASE_TILE + 4;
247 
248   // Draws remaining dividers
249 
250   y = LANE_TOP + LANE_CHAR_HEIGHT;
251   for (i = 2; i != (LANE_COUNT + 1); i++) {
252     SMS_loadTileMap(0, y, buffer, 64);
253     y += LANE_CHAR_HEIGHT;
254   }
255 }
256 
change_lane()257 void change_lane() {
258   player_target_y = lane_coords[player_target_lane] + LANE_PIXEL_TOP_LIMIT;
259 }
260 
init_shots()261 void init_shots() {
262   unsigned char i;
263   shot *p;
264 
265   for (i = 0, p = shots; i != LANE_COUNT; i++, p++) {
266     p->flag = 0;
267   }
268 }
269 
draw_shot()270 void draw_shot() {
271   if (shot_p->flag) {
272     draw_ship(shot_p->x, g_y, SHOT_BASE_TILE, shot_p->flag == SHOT_FLAG_RIGHT);
273   }
274 }
275 
draw_shots()276 void draw_shots() {
277   for (g_i = 0, shot_p = shots, g_y = LANE_PIXEL_TOP_LIMIT; g_i != LANE_COUNT; g_i++, shot_p++, g_y += LANE_PIXEL_HEIGHT) {
278     draw_shot();
279   }
280 }
281 
move_shot()282 void move_shot() {
283   if (shot_p->flag) {
284     if (shot_p->flag == SHOT_FLAG_LEFT) {
285       shot_p->x -= 6;
286     } else {
287       shot_p->x += 6;
288     }
289 
290     if (shot_p->x <= ACTOR_MIN_X || shot_p->x >= ACTOR_MAX_X) {
291       shot_p->flag = 0;
292     }
293   }
294 }
295 
move_shots()296 void move_shots() {
297   for (g_i = 0, shot_p = shots; g_i != LANE_COUNT; g_i++, shot_p++) {
298     move_shot();
299   }
300 }
301 
fire()302 void fire() {
303   shot *p = shots + player_current_lane;
304   if (!p->flag && player_x == PLAYER_CENTER_X) {
305     p->x = player_x;
306     p->flag = player_looking_left ? SHOT_FLAG_LEFT : SHOT_FLAG_RIGHT;
307 
308      PSGPlayNoRepeat(player_shot_psg);
309   }
310 }
311 
init_enemies()312 void init_enemies() {
313   unsigned int i;
314   enemy *p;
315 
316   for (i = 0, p = enemies; i != LANE_COUNT; i++, p++) {
317     p->spd = 0;
318     p->spawn_delay = 0;
319   }
320 }
321 
draw_enemy()322 void draw_enemy() {
323   if (enm_p->spd) {
324     draw_ship(enm_p->x, g_y, ENEMY_BASE_TILE + (enm_p->type << ACTOR_TILE_SHIFT), enm_p->spd > 0);
325   }
326 }
327 
draw_enemies()328 void draw_enemies() {
329   for (g_i = 0, enm_p = enemies, g_y = LANE_PIXEL_TOP_LIMIT; g_i != LANE_COUNT; g_i++, enm_p++, g_y += LANE_PIXEL_HEIGHT) {
330     draw_enemy();
331   }
332 }
333 
stop_sound()334 void stop_sound() {
335   PSGStop();
336   PSGSFXStop();
337 }
338 
sound_frame()339 void sound_frame() {
340   if (!sound_enabled) {
341     stop_sound();
342     return;
343   }
344 
345   PSGFrame();
346   PSGSFXFrame();
347 }
348 
wait_frame()349 void wait_frame() {
350   SMS_waitForVBlank();
351   sound_frame();
352 }
353 
wait_frames(int count)354 void wait_frames(int count) {
355     for (; count; count--) {
356       wait_frame();
357     }
358 }
359 
draw_player_death_frame(unsigned char frame)360 void draw_player_death_frame(unsigned char frame) {
361   SMS_initSprites();
362 
363   draw_ship(player_x, player_y, PLAYER_DEATH_BASE_TILE + (frame << 2), 0);
364 
365   SMS_finalizeSprites();
366   SMS_waitForVBlank();
367   SMS_copySpritestoSAT();
368 
369   sound_frame();
370 
371   wait_frames(18);
372 }
373 
change_life_counter(unsigned char next_value)374 void change_life_counter(unsigned char next_value) {
375   lives = next_value;
376   lives_need_update = true;
377 }
378 
kill_player()379 void kill_player() {
380   unsigned char i;
381 
382   if (player_invincible) {
383     return;
384   }
385 
386   stop_sound();
387   PSGPlayNoRepeat(player_death_psg);
388 
389   init_enemies();
390   init_shots();
391   player_looking_left = true;
392 
393   for (i = 0; i != PLAYER_DEATH_FRAMES; i++) {
394     draw_player_death_frame(i);
395   }
396 
397   wait_frames(30);
398 
399   for (i = PLAYER_DEATH_FRAMES; i != 0; i--) {
400     draw_player_death_frame(i - 1);
401   }
402 
403   player_x = PLAYER_CENTER_X;
404   player_dead = false;
405 
406   change_life_counter(lives - 1);
407 
408   if (!lives) {
409     next_game_state = STATE_GAME_OVER;
410   }
411 }
412 
prepare_score()413 void prepare_score() {
414   if (!score_needs_update) {
415     return;
416   }
417 
418   st_p = score_tiles[0];
419   st_p2 = score_tiles[1];
420   for (g_i = 0, sc_p = score_digits; g_i != SCORE_DIGITS; g_i++, sc_p++, st_p++, st_p2++) {
421     *st_p = (*sc_p << 1) + SCORE_BASE_TILE;
422     *st_p2 = *st_p + 1;
423   }
424 }
425 
increase_score(unsigned int how_much)426 void increase_score(unsigned int how_much) {
427   sc_p = score_digits + SCORE_DIGITS - 2;
428 
429   if (!score_enabled) {
430     return;
431   }
432 
433   while (how_much) {
434     *sc_p += how_much;
435     how_much = 0;
436 
437     while (*sc_p > 9) {
438       *sc_p -= 10;
439       how_much++;
440     }
441 
442     sc_p--;
443   }
444 
445   score_needs_update = true;
446 }
447 
init_score()448 void init_score() {
449   unsigned char i;
450   unsigned char *sc_p;
451 
452   for (i = 0, sc_p = score_digits; i != SCORE_DIGITS; i++, sc_p++) {
453     *sc_p = 0;
454   }
455 
456   score_needs_update = true;
457   prepare_score();
458 }
459 
draw_score()460 void draw_score() {
461   if (!score_needs_update) {
462     return;
463   }
464 
465   SMS_loadTileMapArea(SCORE_LEFT, SCORE_TOP, *score_tiles, SCORE_DIGITS, 2);
466   score_needs_update = false;
467 }
468 
kill_enemy()469 void kill_enemy() {
470   enm_p->spd = 0;
471   increase_score(enemy_specs[enm_p->type].score_value);
472   PSGSFXPlay(enemy_death_psg, SFX_CHANNEL2 | SFX_CHANNEL3);
473   remaining_enemies--;
474 }
475 
spawn_enemy(unsigned char type,bool from_left)476 void spawn_enemy(unsigned char type, bool from_left) {
477   const enemy_spec *sp = enemy_specs + type;
478   unsigned int speed = sp->base_speed + (rand() & sp->additional_speed_mask);
479 
480   if (type != ENEMY_TYPE_PHANTOM) {
481     speed = (speed * (level_number + 1)) >> 2;
482   }
483 
484   if (from_left) {
485     enm_p->x = ACTOR_MIN_X + 1;
486     enm_p->spd = speed;
487   } else {
488     enm_p->x = ACTOR_MAX_X - 1;
489     enm_p->spd = -speed;
490   }
491   enm_p->type = type;
492 
493   if (type == ENEMY_TYPE_ARROW) {
494     PSGSFXPlay(enemy_arrow_psg, SFX_CHANNEL2);
495   }
496 }
497 
spawn_random_enemy()498 void spawn_random_enemy() {
499   unsigned char type;
500 
501   g_x = rand() % ENEMY_TYPE_MASK;
502   if (g_x >= 5) {
503     return;
504   }
505 
506   type = spawnable_enemies[g_x];
507   if ((type == ENEMY_TYPE_PELLET) && (rand() & 1)) {
508     // Reduce the probability of spawning a pellet
509     return;
510   }
511 
512   spawn_enemy(type, rand() & 1);
513 }
514 
collide_enemy()515 void collide_enemy() {
516   shot_p = shots + g_lane;
517 
518   if (g_lane == player_current_lane && enm_p->x > player_x - 8 && enm_p->x < player_x + 8) {
519     if (enm_p->type == ENEMY_TYPE_PELLET) {
520       // Pellets act as bonuses
521       kill_enemy();
522       PSGPlayNoRepeat(bonus_psg);
523       spawn_enemy(ENEMY_TYPE_PHANTOM, enm_p->x > PLAYER_CENTER_X);
524       enm_p->spawn_delay = 100;
525     } else {
526       // Other enemies are lethal.
527       player_dead = true;
528     }
529     return;
530   }
531 
532   if (enm_p->type == ENEMY_TYPE_PHANTOM || enm_p->type == ENEMY_TYPE_PELLET) {
533     // Phantom ships and pellets are invulnerable
534     return;
535   }
536 
537   if (shot_p->flag) {
538     if (shot_p->x - enm_p->x <= 16 || enm_p->x - shot_p->x <= 16) {
539       if (enm_p->type == ENEMY_TYPE_TANK) {
540         // Tanks can only be hit from the back
541         if (enm_p->spd < 0) {
542           if (shot_p->flag == SHOT_FLAG_LEFT) {
543             // Both facing left? Blam!
544             kill_enemy();
545           } else {
546             // Shot the front? Push back.
547             if (enm_p->x < (ACTOR_MAX_X - 8)) {
548               enm_p->x += 6;
549             }
550           }
551         } else {
552           if (shot_p->flag == SHOT_FLAG_RIGHT) {
553             // Both facing right? Blam!
554             kill_enemy();
555           } else {
556             // Shot the front? Push back.
557             if (enm_p->x > (ACTOR_MIN_X + 8)) {
558               enm_p->x -= 6;
559             }
560           }
561         }
562       } else {
563         // Other enemies will simply be dead.
564         kill_enemy();
565       }
566 
567       // Removes the shot
568       shot_p->flag = 0;
569     }
570   }
571 }
572 
move_enemies()573 void move_enemies() {
574   for (g_lane = 0, enm_p = enemies; g_lane != LANE_COUNT; g_lane++, enm_p++) {
575     if (!enm_p->spd) {
576       if (enm_p->spawn_delay) {
577         if (enm_p->spawn_delay == 1) {
578           spawn_random_enemy();
579           enm_p->spawn_delay = 0;
580         } else {
581           enm_p->spawn_delay--;
582         }
583       } else {
584         enm_p->spawn_delay = lvl_p->base_spawn_delay + rand() & lvl_p->spawn_chance_mask;
585       }
586     } else {
587       collide_enemy();
588 
589       if (enm_p->type == ENEMY_TYPE_PELLET) {
590         if ((frame_timer & 0x03) == 1) {
591           enm_p->timer++;
592         }
593         if (enm_p->timer > 120) {
594           enm_p->timer = 0;
595           enm_p->type = ENEMY_TYPE_BALL;
596           enm_p->spd = 32;
597         }
598       } else {
599         enm_p->timer += enm_p->spd;
600         enm_p->x += enm_p->timer >> 4;
601         enm_p->timer &= 0x0F;
602       }
603 
604       // Enemy moved out?
605       if (enm_p->x <= ACTOR_MIN_X || enm_p->x >= ACTOR_MAX_X) {
606         if (enm_p->type == ENEMY_TYPE_BALL || enm_p->type == ENEMY_TYPE_TANK || enm_p->type == ENEMY_TYPE_ARROW) {
607           // Turn around
608           enm_p->x = enm_p->spd < 0 ? ACTOR_MIN_X + 1 : ACTOR_MAX_X - 1;
609           enm_p->spd = -enm_p->spd;
610 
611           if (enm_p->type == ENEMY_TYPE_ARROW) {
612             // Become a tank
613             enm_p->type = ENEMY_TYPE_TANK;
614             enm_p->spd = enm_p->spd >> 1;
615             PSGSFXPlay(enemy_tank_psg, SFX_CHANNEL2);
616           }
617         } else {
618           // Disappear
619           enm_p->spd = 0;
620         }
621       }
622 
623       collide_enemy();
624     }
625   }
626 }
627 
draw_lives()628 void draw_lives() {
629   unsigned int buffer[32];
630 
631   if (!lives_need_update) {
632     return;
633   }
634 
635   // Redraws the topmost lane dividers
636   generate_lane(buffer);
637   SMS_loadTileMap(0, LANE_TOP, buffer, 64);
638   memset(buffer, 0, sizeof buffer);
639   SMS_loadTileMap(0, 0, buffer, 64);
640 
641   if (!lives) {
642     // No lives left.
643     return;
644   }
645 
646   buffer[0] = LIFE_BASE_TILE;
647   buffer[1] = LIFE_BASE_TILE + 2;
648   buffer[2] = LIFE_BASE_TILE + 1;
649   buffer[3] = LIFE_BASE_TILE + 3;
650 
651   for (g_i = 0, g_x = 16 - lives; g_i != lives; g_i++, g_x += 2) {
652     SMS_loadTileMapArea(g_x, 0, buffer, 2, 2);
653   }
654 
655   lives_need_update = false;
656 }
657 
init_player()658 void init_player() {
659   player_x = PLAYER_CENTER_X;
660   player_y = PLAYER_MIN_Y;
661   player_current_lane = 0;
662   player_target_lane = 0;
663   player_dead = false;
664 }
665 
move_player_target_lane()666 void move_player_target_lane() {
667   // Move towards the targeted lane
668   if (player_y != player_target_y) {
669     if (player_y < player_target_y) {
670       player_y += PLAYER_VERTICAL_SPEED;
671     } else {
672       player_y -= PLAYER_VERTICAL_SPEED;
673     }
674   } else {
675     player_current_lane = player_target_lane;
676   }
677 }
678 
play_horizontal_movement_sfx()679 void play_horizontal_movement_sfx() {
680   if (!player_move_x_sfx_counter) {
681     PSGSFXPlay(player_move_psg, SFX_CHANNEL2);
682     player_move_x_sfx_counter = 32;
683   } else {
684     player_move_x_sfx_counter--;
685   }
686 }
687 
handle_player_movement()688 void handle_player_movement() {
689   joy = SMS_getKeysStatus();
690 
691   if (joy & PORT_A_KEY_LEFT) {
692     player_looking_left = true;
693   } else if (joy & PORT_A_KEY_RIGHT) {
694     player_looking_left = false;
695   }
696 
697   if (player_current_lane == player_target_lane) {
698     // Allow to move left or right if there's a pellet on the current lane
699     enm_p = enemies + player_current_lane;
700     if (enm_p->spd && enm_p->type == ENEMY_TYPE_PELLET || player_x != PLAYER_CENTER_X) {
701       if ((joy & PORT_A_KEY_LEFT) && (player_x > ACTOR_MIN_X)) {
702         player_x -= 4;
703         play_horizontal_movement_sfx();
704       } else if ((joy & PORT_A_KEY_RIGHT) && (player_x < ACTOR_MAX_X)) {
705         player_x += 4;
706         play_horizontal_movement_sfx();
707       } else {
708         player_move_x_sfx_counter = 0;
709       }
710     } else {
711       player_move_x_sfx_counter = 0;
712     }
713 
714     if (player_x == PLAYER_CENTER_X) {
715       // Move up or down the lanes according to joypad command
716       if ((joy & PORT_A_KEY_UP) && (player_current_lane > 0)) {
717         player_target_lane--;
718         change_lane();
719       } else if ((joy & PORT_A_KEY_DOWN) && (player_current_lane < LANE_COUNT - 1)) {
720         player_target_lane++;
721         change_lane();
722       }
723     }
724   } else {
725     move_player_target_lane();
726   }
727 
728   if (remaining_enemies <= 0) {
729     next_game_state = STATE_NEXT_STAGE;
730     if (lives < 6) {
731       change_life_counter(lives + 1);
732     }
733   }
734 }
735 
auto_player_movement()736 void auto_player_movement() {
737   if (player_current_lane == player_target_lane) {
738     if (player_looking_left) {
739       // Move up, if possible
740       if (player_current_lane > 0) {
741         player_target_lane--;
742         change_lane();
743       } else {
744         player_looking_left = false;
745       }
746     } else {
747       // Move down, if possible
748       if (player_current_lane < LANE_COUNT - 1) {
749         player_target_lane++;
750         change_lane();
751       } else {
752         player_looking_left = true;
753       }
754     }
755   } else {
756     move_player_target_lane();
757   }
758 
759   joy = SMS_getKeysStatus();
760   if (joy & (PORT_A_KEY_1 | PORT_A_KEY_2)) {
761     next_game_state = STATE_GAME_START;
762 
763     do {
764       wait_frame();
765     } while (SMS_getKeysStatus());
766   }
767 }
768 
intermission()769 void intermission() {
770   unsigned int timeleft;
771   unsigned int buffer[4][32], *p;
772   unsigned char pal_buffer[16];
773 
774   SMS_displayOff();
775   stop_sound();
776 
777   SMS_loadTiles(ship_til, 0, ship_til_size);
778   SMS_loadTiles(intermission_bkg_til, 0, intermission_bkg_til_size);
779   SMS_loadBGPalette(ship_pal);
780   SMS_loadSpritePalette(ship_pal);
781 
782   // Draw the background
783 
784   for (g_y = 0; g_y != 4; g_y++) {
785     for (g_x = 0; g_x != 32; g_x++) {
786       buffer[g_y][g_x] = g_y | (g_x & 0x04 ? 0x400 : 0);
787     }
788   }
789 
790   for (g_y = 0; g_y < 24; g_y += 4) {
791     SMS_loadTileMapArea(0, g_y, *buffer, 32, 4);
792   }
793 
794   // Draw the black rectangle in the center
795 
796   p = *buffer;
797   for (g_y = 0; g_y != 4; g_y++) {
798     for (g_x = 0; g_x != 6; g_x++) {
799       *p = 0x8FF;
800       p++;
801     }
802   }
803   SMS_loadTileMapArea(13, 10, *buffer, 6, 4);
804 
805   // Draw the level number
806   SMS_initSprites();
807   SMS_addSprite(126, 88, SCORE_BASE_TILE + (level_number << 1));
808   SMS_finalizeSprites();
809   SMS_copySpritestoSAT();
810 
811   SMS_displayOn();
812 
813   PSGPlayNoRepeat(intermission_psg);
814 
815   for (timeleft = 90; timeleft; timeleft--) {
816     for (g_i = 0; g_i != 16; g_i++) {
817       pal_buffer[g_i] = ship_pal[(g_i + timeleft) & 0x0F];
818     }
819 
820     SMS_waitForVBlank();
821     SMS_loadBGPalette(pal_buffer);
822     sound_frame();
823 
824     wait_frames(2);
825   }
826 }
827 
gameplay_loop(void (* player_handler)())828 void gameplay_loop(void (*player_handler)()) {
829   SMS_displayOff();
830 
831   init_player();
832 
833   load_ingame_tiles();
834 
835   SMS_loadBGPalette(ship_pal);
836   SMS_loadSpritePalette(ship_pal);
837 
838   draw_lanes();
839   init_enemies();
840   init_shots();
841 
842   lives_need_update = true;
843   score_needs_update = true;
844   prepare_score();
845   draw_score();
846   draw_lives();
847 
848   if (player_invincible) {
849     SMS_loadTileMap(11, 12, bkg_data_storm, sizeof bkg_data_storm);
850     SMS_loadTileMap(10, 14, bkg_press_start, sizeof bkg_press_start);
851   }
852 
853   SMS_displayOn();
854 
855   remaining_enemies = 40 + (level_number << 2);
856 
857   while (!next_game_state) {
858     // Player
859 
860     (*player_handler)();
861     fire();
862 
863     // Shots
864 
865     move_shots();
866     move_enemies();
867     prepare_score();
868 
869     // Draw
870 
871     SMS_initSprites();
872 
873     draw_player_ship();
874     draw_shots();
875     draw_enemies();
876 
877     SMS_finalizeSprites();
878 
879     SMS_waitForVBlank();
880     SMS_copySpritestoSAT();
881     draw_score();
882     draw_lives();
883 
884     sound_frame();
885 
886     frame_timer++;
887 
888     if (player_dead) {
889       kill_player();
890     }
891   }
892 }
893 
main(void)894 void main(void) {
895   SMS_VDPturnOnFeature(VDPFEATURE_USETALLSPRITES);
896   SMS_VDPturnOffFeature(VDPFEATURE_HIDEFIRSTCOL);
897   SMS_useFirstHalfTilesforSprites(true);
898 
899   next_game_state = STATE_DEMO;
900   while (true) {
901     if (next_game_state) {
902       current_game_state = next_game_state;
903       next_game_state = 0;
904     }
905 
906     switch (current_game_state) {
907       case STATE_DEMO:
908         change_life_counter(0);
909         player_invincible = true;
910         sound_enabled = false;
911         score_enabled = false;
912         gameplay_loop(auto_player_movement);
913         break;
914 
915       case STATE_GAME_START:
916         change_life_counter(5);
917         player_invincible = false;
918         sound_enabled = true;
919         score_enabled = true;
920         next_game_state = STATE_NEXT_STAGE;
921         level_number = 0;
922         init_score();
923         break;
924 
925       case STATE_NEXT_STAGE:
926         if (level_number < 9) {
927           level_number++;
928         }
929         lvl_p = level_specs + level_number - 1;
930         intermission();
931         next_game_state = STATE_PLAY;
932         break;
933 
934       case STATE_PLAY:
935         gameplay_loop(handle_player_movement);
936         break;
937 
938       case STATE_GAME_OVER:
939         stop_sound();
940         draw_lives();
941         PSGPlayNoRepeat(game_over_psg);
942         wait_frames(285);
943         stop_sound();
944         next_game_state = STATE_DEMO;
945         break;
946     }
947   }
948 
949 }
950