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