#include #include #include #include "keyb.h" #include "control.h" #include "vga.h" #include "sound.h" #include "text.h" #include "map.h" #include "data.h" #include "timer.h" #include "entities.h" #include "tmonster.h" #include "player.h" #include "game.h" #define GAME_LIVES_START 3 #define GAME_LIVES_MAX 9 #define GAME_TIME_MAX 60 #define GAME_MAX_PICKAXE 3 #define GAME_EXTRA_LIFE ((uint16_t)10000) #define HUD_CLEAN 0 #define HUD_LIVES 1 #define HUD_SCORE 2 #define HUD_STAGE 4 #define HUD_TIME 8 #define HUD_PICKAXE 16 #define HUD_ALL 255 #define GAMEOVER_DELAY 96 #define STAGECLEAR_DELAY 64 static uint32_t hiscore = 15000; static uint8_t hud; static volatile uint8_t clock_updated; /* game variables */ static uint8_t lives; static uint32_t score; static uint16_t extra_life; static uint8_t stage; static uint8_t time; static uint8_t pickaxe; static uint8_t stageclear; static uint8_t gameover; static uint8_t pause; static Entity *tmonster; static uint8_t continuegame; /* 0-index */ #define START_STAGE 0 #define STAGES_LEN 30 static const uint8_t *stages[STAGES_LEN] = { binary_stage01_start, binary_stage02_start, binary_stage03_start, binary_stage04_start, binary_stage05_start, binary_stage06_start, binary_stage07_start, binary_stage08_start, binary_stage09_start, binary_stage10_start, binary_stage11_start, binary_stage12_start, binary_stage13_start, binary_stage14_start, binary_stage15_start, binary_stage16_start, binary_stage17_start, binary_stage18_start, binary_stage19_start, binary_stage20_start, binary_stage21_start, binary_stage22_start, binary_stage23_start, binary_stage24_start, binary_stage25_start, binary_stage26_start, binary_stage27_start, binary_stage28_start, binary_stage29_start, binary_stage30_start, }; static void hud_render() { char b[32]; if (hud & HUD_ALL) { Rect src = { 128, 32, 144, 112 }; Rect dst = { 4, 0, 16, 16 }; /* lives */ blitrc(binary_sprites_start, &src, &dst); /* pickaxe */ src.x = 112; dst.x = 90; blitrc(binary_sprites_start, &src, &dst); put_text(132, 4, "TIME", 1); put_text(249, 4, "STAGE", 1); } if (hud & HUD_LIVES) { sprintf(b, "%d", lives); put_text(18, 4, b, 1); } if (hud & HUD_SCORE) { sprintf(b, "%06li", score); put_text(34, 4, b, 5); } if (hud & HUD_PICKAXE) { sprintf(b, "%d", pickaxe); put_text(106, 4, b, 1); } if (hud & HUD_TIME) { sprintf(b, "%02d", time); put_text(172, 4, b, time > 10 || stageclear ? 1 : 15); if (!gameover && !stageclear && time <= 10) sound_play_efx(EFX_TIME); } if (hud & HUD_STAGE) { sprintf(b, "%02d", stage < STAGES_LEN ? stage + 1 : stage); put_text(297, 4, b, 1); } hud = HUD_CLEAN; } #define WALK_DELAY 8 #define WALK_CYCLE_FRAMES 4 static const Rect intro_frames[WALK_CYCLE_FRAMES] = { /* walk cycle */ { 0, 0, 144, 112 }, { 16, 0, 144, 112 }, { 0, 0, 144, 112 }, { 32, 0, 144, 112 }, }; static void run_intro() { /* for the HUD */ pickaxe = 0; time = GAME_TIME_MAX; hud = HUD_ALL; blit_target(TARGET_BUFFER); blit_erase(0); map_init(binary_intro_start); map_render(); hud_render(); put_text(88, 65, "GET ALL THAT GOLD!", 1); wait_vsync(); blit_copy_all(); Rect dst = { 80, 88 + MAP_OFFS_Y, 16, 16 }; uint8_t frame = 0, delay = 0; wait_frames(32); sound_music_pattern(PAT_INTRO); /* so the music starts */ wait_vsync(); blit_target(TARGET_SCREEN); while (dst.x < 224) { wait_vsync(); blit_copy16(&dst); dst.x++; if (map_update_gold(dst.x + 8, dst.y + 15 - MAP_OFFS_Y)) sound_play_efx(EFX_GOLD); blitrc(binary_sprites_start, &intro_frames[frame], &dst); if (delay++ == WALK_DELAY) { delay = 0; frame++; if (frame == WALK_CYCLE_FRAMES) frame = 0; } } wait_vsync(); blit_copy16(&dst); wait_frames(32); sound_music_pattern(PAT_SILENCE); wait_frames(64); } static uint8_t run_gameover() { wait_vsync(); blit_erase(0); /* restore the HUD after erasing the screen */ hud = HUD_ALL; hud_render(); put_text(124, 90, "GAME OVER", 1); sound_music_pattern(PAT_GAMEOVER); /* wait some time, and check if the player wants to continue */ for (uint16_t i = 0; i < 524; i++) { if (stage < STAGES_LEN) { if (i == 200 || i == 400) put_text(68, 105, "PRESS SPACE TO CONTINUE", i == 200 ? 5 : 0); if (i > 200 && i < 400 && keys[KEY_SPACE]) { sound_music_pattern(PAT_SILENCE); return 1; } } wait_vsync(); } sound_music_pattern(PAT_SILENCE); return 0; } static const Rect pop_in_frames[] = { { 128, 16, 144, 112 }, { 112, 16, 144, 112 } }; static const Rect pop_in_letters[] = { { 32, 32, 160, 48 }, { 48, 32, 160, 48 }, { 64, 32, 160, 48 }, { 64, 32, 160, 48 }, { 80, 32, 160, 48 }, { 96, 32, 160, 48 }, { 112, 32, 160, 48 }, { 48, 32, 160, 48 }, }; static void pop_in_letter(uint16_t x, uint16_t y, uint8_t letter) { Rect dst = { x, y, 16, 16 }; const Rect *src; sound_play_efx(EFX_WARP); for (uint8_t i = 0; i < 2; i++) { src = &pop_in_frames[i]; wait_vsync(); blitrc(binary_sprites_start, src, &dst); wait_frames(7); } src = &pop_in_letters[letter]; wait_vsync(); blitrc(binary_tiles_start, src, &dst); } static void run_endgame() { blit_target(TARGET_BUFFER); blit_erase(0); /* for the HUD */ pickaxe = 0; time = GAME_TIME_MAX; hud = HUD_ALL; hud_render(); wait_vsync(); blit_copy_all(); blit_target(TARGET_SCREEN); for (uint8_t i = 0; i < 4; i++) pop_in_letter(88 + i * 16, 60, i); for (uint8_t i = 0; i < 4; i++) pop_in_letter(168 + i * 16, 60, 4 + i); wait_vsync(); put_text(48, 100, "YOU HAVE COMPLETED THE GAME!", 1); put_text(108, 115, "THANK YOU FOR", 6); put_text(72, 125, "PLAYING GOLD MINE RUN!", 6); sound_music_pattern(PAT_STAGE); wait_frames(512); run_gameover(); } static void run_stageclear() { blit_target(TARGET_BUFFER); blit_erase(0); hud = HUD_ALL; hud_render(); put_text(116, 90, "STAGE", 1); put_text(164, 90, "CLEAR", 1); wait_vsync(); blit_copy_all(); blit_target(TARGET_SCREEN); sound_music_pattern(PAT_STAGE); /* wait some time */ wait_frames(164); while (time) { add_score(20); time--; hud |= HUD_TIME; wait_frames(4); wait_vsync(); hud_render(); sound_play_efx(EFX_GOLD); } /* wait some time */ wait_frames(64); } static uint8_t run_confirm_quit() { timer_stop(); blit_target(TARGET_BUFFER); blit_erase(0); hud = HUD_ALL; hud_render(); put_text(108, 90, "ABANDON GAME?", 1); wait_vsync(); blit_copy_all(); blit_target(TARGET_SCREEN); uint8_t sel = 0; uint8_t ctl = CTL_NONE; uint8_t cooldown = 0; update_menu: wait_vsync(); if (sel) { put_text(144, 105, " YES", 4); put_text(144, 115, ">NO", 5); } else { put_text(144, 105, ">YES", 5); put_text(144, 115, " NO", 4); } while (1) { if (cooldown) cooldown--; else { ctl = control_read(); if ((ctl & CTL_UP) || (ctl & CTL_DOWN)) { cooldown = 10; sel ^= 1; goto update_menu; } if (keys[KEY_ENTER]) break; } wait_vsync(); } /* restore the game screen only when not abandoned */ if (sel) { blit_target(TARGET_BUFFER); map_render(); wait_vsync(); blit_copy_all(); blit_target(TARGET_SCREEN); wait_vsync(); entities_draw(); } timer_resume(); return sel == 0; } void run_game() { continuegame = 0; continue_game: lives = GAME_LIVES_START; score = 0; extra_life = 0; if (!continuegame) { stage = START_STAGE; run_intro(); } next_stage: pause = 0; gameover = 0; stageclear = 0; pickaxe = 0; time = GAME_TIME_MAX; hud = HUD_ALL; tmonster = NULL; blit_target(TARGET_BUFFER); blit_erase(0); hud_render(); put_text(136, 90, "READY?", 1); wait_vsync(); blit_copy_all(); sound_music_pattern(PAT_READY); /* wait some time */ wait_frames(164); map_init(stages[stage]); map_render(); entities_sort(); wait_vsync(); blit_copy_all(); blit_target(TARGET_SCREEN); wait_vsync(); entities_draw(); timer_start(GAME_TIME_MAX, &clock_updated); sound_music_pattern(PAT_PLAY); while (1) { if (clock_updated) { time = timer_value(); hud |= HUD_TIME; } if (hud) hud_render(); if (keys[KEY_ESC]) { if (run_confirm_quit()) break; } if (keys[KEY_P]) { /* pause / resume */ pause ^= 1; /* wait for the key to be released */ while (keys[KEY_P]) wait_vsync(); if (pause) { timer_stop(); sound_mute(); } else { timer_resume(); sound_unmute(); } } if (pause) continue; if (map_is_complete()) { if (!stageclear) { timer_stop(); tmonster = NULL; entities_warp_out_all(); sound_play_efx(EFX_WARP); player_stageclear(); stageclear = STAGECLEAR_DELAY; } else { if (stageclear == STAGECLEAR_DELAY) sound_music_pattern(PAT_SILENCE); stageclear--; if (stageclear == 1) { sound_music_pattern(PAT_SILENCE); run_stageclear(); stage++; if (stage == STAGES_LEN) { run_endgame(); break; } goto next_stage; } } } else { player_update(); /* time monster */ if (!time && !tmonster) { tmonster = entities_new(); if (tmonster) tmonster_init(tmonster); } if (time && tmonster) { tmonster->used = USED_FREE; tmonster = NULL; } } entities_update(); entities_sort(); wait_vsync(); /* prevent interrupts so updating the audio doesn't result in sprite * flickering on the less powered machines */ disable(); entities_draw(); enable(); if (gameover) { if (gameover == GAMEOVER_DELAY) sound_music_pattern(PAT_SILENCE); gameover--; if (gameover == 1) { if (run_gameover()) { continuegame = 1; goto continue_game; } break; } } } sound_music_pattern(PAT_SILENCE); } void add_score(uint8_t v) { score += v; extra_life += v; if (extra_life >= GAME_EXTRA_LIFE) { extra_life -= GAME_EXTRA_LIFE; if (lives < GAME_LIVES_MAX) { sound_play_efx(EFX_ONEUP); lives++; hud |= HUD_LIVES; } } hud |= HUD_SCORE; } uint32_t get_hiscore() { return hiscore; } uint8_t dec_lives() { lives--; if (lives == 0) { gameover = GAMEOVER_DELAY; timer_stop(); } hud |= HUD_LIVES; return lives; } void reset_time() { time = GAME_TIME_MAX; hud |= HUD_TIME; timer_start(GAME_TIME_MAX, &clock_updated); } void add_pickaxe() { if (pickaxe < GAME_MAX_PICKAXE) { pickaxe++; hud |= HUD_PICKAXE; } } uint8_t use_pickaxe() { if (pickaxe) { pickaxe--; hud |= HUD_PICKAXE; return 1; } return 0; } uint8_t is_stageclear() { return stageclear; }