/* * play.c * * Actual gameplaying loop */ #include #include #include #include #include #include "graphics.h" #include "grv.h" #include "highscore.h" /* ------------------------------------------------------------------------- */ /* For the finale level, take the next white cherry and make it black */ void white2black(void) { struct xy *nw = &gp.whitecherrylist[gp.nextwhite]; if ( gp.nwhite ) { color(0, bg(nw->x, nw->y)); lprint(nw->x, nw->y, "\xeb"); gp.nextwhite++; gp.nwhite--; } } /* * Functions called by keyboard events */ static void pause_game(void) { struct saved_screen *ss; SDL_KeyboardEvent *ke; int Tick; /* Pause the game; this requires adjusting the TZero for this game... */ if ( !(ss = save_screen()) ) return; /* Can't save, can't pause */ Tick = SDL_GetTicks(); color(0, gp.c2); lprint(10, 12, "\xc9\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xbb"); lprint(11, 12, "\xba GAME PAUSED \xba"); lprint(12, 12, "\xc7\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xb6"); lprintf(13, 12, "\xba TIME: %s \xba", format_time(gp.Tid)); lprint(14, 12, "\xc8\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xbc"); while ( (ke = get_key()) ) { if ( ke->keysym.sym == kbd_keys[10] ) break; } restore_screen(ss); free(ss); /* Adjust time basis */ gp.TZero += (SDL_GetTicks() - Tick); } static void shoot(int dx, int dy) { if ( !gp.KulSpr ) return; /* No ammo */ gp.KulSpr--; update_shots(); add_bullet(gp.x, gp.y, dx, dy, 14); run_bullets(); } static void move_player_to(int x, int y); static void escape(void) { int x, y; /* Move the player to a random empty spot */ do { x = irnd(22)+2; y = irnd(40)+1; } while ( screen(x,y) != ' ' ); gp.XWk = gp.YWk = 0; move_player_to(x,y); } /* * Handle pending keystroke */ static void handle_key(SDL_KeyboardEvent *ke) { SDLMod mod; SDLKey sym; mod = ke->keysym.mod; sym = ke->keysym.sym; if ( mod & (KMOD_RALT|KMOD_LALT) ) { /* These keystrokes require */ switch ( sym ) { case SDLK_q: /* Immediate death */ gp.Life = 0; /* Fall through */ case SDLK_w: /* Immediate death (one life) */ gp.EOLWait = 0; /* Fall through */ case SDLK_d: /* Normal death */ gp.Status = Status_Dead; break; case SDLK_l: /* Immediate end of level */ gp.EOLWait = 0; /* Fall through */ case SDLK_e: /* Normal end of level */ gp.Cheat = 1; gp.Status = Status_Done; break; case SDLK_p: /* +1000 power */ gp.Cheat = 1; gp.GkCh += 1000; update_power(); break; case SDLK_x: /* +1 life */ if ( gp.Life < 6 ) { gp.Cheat = 1; gp.Life++; color(14,gp.c); lprintf(1,16+2*gp.Life, "\x01"); } break; case SDLK_z: /* Infinite life */ { int e; gp.Cheat = 1; gp.Life = 6; gp.InfLife = 1; locate(1,18); color(14,gp.c); for ( e = 1 ; e <= gp.Life ; e++ ) { print("\x01 "); } } break; case SDLK_b: /* All white -> black */ gp.Cheat = 1; while ( gp.nwhite ) white2black(); break; case SDLK_s: /* +1 shot */ gp.Cheat = 1; if ( gp.KulSpr < 90 ) gp.KulSpr++; update_shots(); break; case SDLK_t: /* Treasure */ gp.Cheat = 1; mystery(); break; case SDLK_END: /* Immediate zap game */ exit(1); default: break; } } if ( sym == kbd_keys[0] ) { /* Up */ gp.XWk = (gp.XWk > 0) ? 0 : -1; gp.YWk = 0; } else if ( sym == kbd_keys[1] ) { /* Left */ gp.XWk = 0; gp.YWk = (gp.YWk > 0) ? 0 : -1; } else if ( sym == kbd_keys[2] ) { /* Right */ gp.XWk = 0; gp.YWk = (gp.YWk < 0) ? 0 : 1; } else if ( sym == kbd_keys[3] ) { /* Down */ gp.XWk = (gp.XWk < 0) ? 0 : 1; gp.YWk = 0; } else if ( sym == kbd_keys[4] ) { /* Shoot up */ if ( gp.x > 2 ) shoot(-1,0); } else if ( sym == kbd_keys[5] ) { /* Shoot left */ if ( gp.y > 1 ) shoot(0,-1); } else if ( sym == kbd_keys[6] ) { /* Shoot right */ if ( gp.y < 40 ) shoot(0,1); } else if ( sym == kbd_keys[7] ) { if ( gp.x < 23 ) shoot(1,0); } else if ( sym == kbd_keys[8] ) { gp.XWk = gp.YWk = 0; } else if ( sym == kbd_keys[9] ) { if ( gp.Level >= 15 ) escape(); } else if ( sym == kbd_keys[10] ) { pause_game(); } } /* Note: the caller must call update_score() */ void take_cherry(void) { gp.Bar--; gp.Sc += gp.Level+1; if ( gp.Bar == 0 && gp.Status == Status_Live ) gp.Status = Status_Done; if ( gp.nwhite ) /* Are there white cherries on screen? */ white2black(); } /* Note: the caller must call update_score() */ void take_diamond(void) { gp.Sc += (int64_t)(gp.Level * (80.0+exp(rnd()*6.0))) + 100; } static void taken_by_ghost(void) { int e; if ( gp.Level == ELev-1 ) { if ( gp.GkCh >= 3 ) { gp.GkCh -= 3; update_power(); for ( e = 0 ; e < MAXGHOST ; e++ ) { if ( !ghost[e].dead && ghost[e].x == gp.x && ghost[e].y == gp.y ) { ghost[e].dead = 1; } } color(14,0); lprint(gp.x, gp.y, "\x01"); } else { gp.GkCh = 0; update_power(); gp.Status = Status_Dead; } } else { gp.Status = Status_Dead; } } void kill_ghost(int x, int y) { int e; for ( e = 0 ; e < MAXGHOST ; e++ ) { if ( !ghost[e].dead && ghost[e].x == x && ghost[e].y == y ) { ghost[e].dead = 1; } } color(9,0); lprint(x,y," "); gp.GkCh += irnd(3)+2; update_power(); } void fall_apple(int x, int y) { int s, f; int cont; f = bg(x,y); if ( rnd() < 0.3 ) { color(30,f); lprint(x,y,"*"); } else { color(0,f); lprint(x,y," "); } cont = 1; while ( cont && x < 23 ) { cont = 0; x++; s = screen(x,y); f = bg(x,y); if ( s == SYM_PLAYER || s == SYM_GHOST || s == SYM_CLUSTER || s == ' ' ) { color((f == 4) ? 0 : 4, f); lprint(x,y,"\xfe"); } switch( s ) { case SYM_CLUSTER: { int dx, dy; for ( dx = -1 ; dx <= 1 ; dx++ ) { for ( dy = -1 ; dy <= 1 ; dy++ ) { if ( dx||dy ) add_bullet(x,y,dx,dy,12); } } mymssleep(10); lprint(x,y," "); run_bullets(); } return; case SYM_PLAYER: gp.Status = Status_Dead; break; case SYM_GHOST: kill_ghost(x,y); break; case ' ': cont = 1; break; default: break; } mymssleep(cont ? 10 : 50); if ( s == ' ' ) lprint(x,y," "); } } static void activate_apple(int x, int y) { int attr = screen1(x,y); /* if not flashing... */ if ( !(attr & 0x80) ) { color(16+(attr & 15), (attr >> 4) & 7); lprint(x,y,"\xfe"); addaction(x,y,gp.Tid+30.0/(gp.Level+1), act_apple); } } void fall_rock(int x, int y) { int s, f; int x0 = x, y0 = y; if ( screen(x,y) != SYM_ROCK ) return; color(8,0); while ( x < 23 ) { x++; s = screen(x,y); f = bg(x,y); if ( f ) break; if ( s == SYM_PLAYER ) { gp.Status = Status_Dead; return; } else if ( s == SYM_GHOST ) { kill_ghost(x,y); } else if ( s != ' ' ) break; color(8,0); lprint(x-1,y," "); lprint(x,y,"\x04"); mymssleep(10); } /* See if there is something above we need to release */ if ( x0 > 2 ) { s = screen(x0-1,y0); if ( s == SYM_ROCK ) fall_rock(x0-1,y0); else if ( s == SYM_APPLE ) activate_apple(x0-1,y0); } } static void push_rock(int x, int y) { int x2 = x + gp.XWk; int y2 = y + gp.YWk; if ( gp.XWk < 0 || x2 < 2 || x2 > 23 || y2 < 1 || y2 > 40 || screen(x2,y2) != ' ' || bg(x2,y2) != 0 ) { /* We can't push rocks up, or off the edge of the screen, or into another object */ gp.XWk = gp.YWk = 0; return; } color(8,0); lprint(x,y," "); lprint(x2,y2,"\x04"); fall_rock(x2,y2); /* Check to see if it falls */ } static void move_player_to(int x, int y) { int x1 = gp.x, y1 = gp.y; int s, m; /* Symbol, foreground */ int su; /* Symbol above */ int hpp = 0; char sbuf[2]; s = screen(x,y); m = fg(x,y); sbuf[0] = s; sbuf[1] = '\0'; if ( x > 2 ) su = screen(x-1,y); else su = ' '; if ( strchr(DOORS "\xfe\xb1\xe5\x0f", s) || (s == SYM_CHERRY && m == 15) ) { /* Can't go there */ gp.XWk = gp.YWk = 0; return; } if ( s == SYM_ROCK ) { push_rock(x,y); return; } /* Otherwise we did move... */ gp.x = x; gp.y = y; color(14,0); lprint(x1,y1," "); /* Erase old player */ lprint(x,y,"\x01"); /* Draw new player */ switch ( s ) { case SYM_DIAMOND: /* Diamond */ take_diamond(); update_score(); break; case SYM_GHOST: /* Ghost */ taken_by_ghost(); break; case SYM_CHERRY: /* Cherry */ take_cherry(); update_score(); break; case SYM_POROUS_WALL: /* Porous wall */ gp.Sc -= (gp.Level+1)*5; update_score(); break; case SYM_BONUS: /* Bonus dot */ { int64_t SSq = gp.Bon ? (int64_t)(gp.Level * exp(6.0*rnd()+3.0)) : 0; if ( SSq ) { message(0, "Bonus: %" PRId64 " points", SSq); } else { message(0, "Sorry, no bonus!!"); } gp.Sc += SSq; update_score(); } break; case SYM_SHOT: /* Ammo */ gp.KulSpr++; update_shots(); break; case SYM_MYSTERY: /* Mystery Treasure */ mystery(); break; case 'H': hpp = 0; goto hyper; case 'Y': hpp = 1; goto hyper; case 'P': hpp = 2; goto hyper; case 'E': hpp = 3; goto hyper; case 'R': hpp = 4; goto hyper; hyper: gp.Hyp++; color(15,gp.c); lprint(25, 31+hpp*2, sbuf); break; default: break; } if ( su == SYM_APPLE ) activate_apple(x-1,y); if ( x1 > 2 && screen(x1-1,y1) == 0x04 ) { fall_rock(x1-1,y1); } } static void move_player(void) { int x, y, x1, y1; /* Move player */ x1 = gp.x; y1 = gp.y; x = gp.x + gp.XWk; y = gp.y + gp.YWk; if ( x < 2 ) { x = 2; gp.XWk = 0; } if ( x > 23 ) { x = 23; gp.XWk = 0; } if ( y < 1 ) { y = 1; gp.YWk = 0; } if ( y > 40 ) { y = 40; gp.YWk = 0; } if ( x != x1 || y != y1 ) move_player_to(x,y); } /* * This returns a logic value depending on the sign of the argument * > 0 returns true, < 0 returns false, == 0 returns random. * * This can thus be used pick(sel) ? positive : negative */ static int pick(int sel) { return (sel == 0) ? irnd(2) : (sel > 0); } /* * This is the logic that handles moving around the ghosts */ static void move_ghosts(void) { int e; int x1, y1, x, y; int dx, dy; int r, i; /* Direction algorithm */ static const int rkt[8][4] = { {0,1,3,2},{1,0,2,3},{2,1,3,0},{3,0,2,1}, {0,3,1,2},{1,2,0,3},{2,3,1,0},{3,2,0,1} }; static const int rx[4] = {-1,0,1,0}; static const int ry[4] = {0,1,0,-1}; if ( !gp.FS ) return; /* Don't change color on frozen ghosts */ /* ghost[e].dead can be 0 (live), 1 (dead), or -1 (nonexistent) */ for ( e = 0 ; e < MAXGHOST ; e++ ) { if ( ghost[e].dead == 1 && gp.Level == ELev-1 ) { if ( rnd() < 0.012 && screen(23,20) == ' ' ) { ghost[e].x = 23; ghost[e].y = 20; ghost[e].dead = 0; } } if ( !ghost[e].dead ) { x1 = ghost[e].x; y1 = ghost[e].y; /* dx and dy compute the difference from the player's location. This is used to pick the direction the ghost should move in. For some dumb reason, this uses graph-paper (x,y) coordinates and neither text-screen nor graphics-screen ones... */ dx = y1 - gp.y; dy = gp.x - x1; /* Set the variable r to the index for the direction set to move in. */ r = (pick(dx) ? (pick(dy) ? (pick(dx-dy) ? 7 : 6) : (pick(dx+dy) ? 3 : 4)) : (pick(dy) ? (pick(-dx-dy) ? 5 : 2) : (pick(dx-dy) ? 0 : 1))); for ( i = 0 ; i < 4 ; i++ ) { int s, f; x = x1 + rx[rkt[r][i]] * gp.FS; y = y1 + ry[rkt[r][i]] * gp.FS; if ( x < 2 || x > 23 || y < 1 || y > 40 ) continue; f = bg(x,y); s = screen(x,y); if ( f > 0 || (s != ' ' && s != 0x01) ) continue; color(9,0); lprint(x1,y1," "); lprint(x,y,"\x02"); ghost[e].x = x; ghost[e].y = y; if ( s == 0x01 ) taken_by_ghost(); break; } } } } /* * End of game/super bonus code */ static void super_bonus(void) { char buf[40]; int64_t SB; color(15,4); /* Ick */ cls(); lprint(2,5,"C O N G R A T U L A T I O N S !"); lprint(3,5,"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); lprint(6,2,"You have reached the end of the game!"); SB = 3*(gp.Sc - gp.SBs)/2; gp.Sc += SB; gp.SBs += SB; lprintf(8,15,"SUPER BONUS"); sprintf(buf, "%" PRId64 " POINTS", SB); color(14,4); lprint(10,21-(strlen(buf)+1)/2,buf); color(15,4); lprint(15,2,"You now get to keep trying, starting"); lprintf(16,2,"on level "); color(14,4); gprintf("%d",gp.ZLevel+1); color(15,4); print("..."); gp.Level = gp.ZLevel; mymssleep(5000); } /* * End of level code */ static void end_level(int done) { char textbuf[40]; int e; int64_t sbon; gp.have_id = 1; /* We now need the game_id */ color(0,gp.c2); lprint(3, 6, "\xc9\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xbb"); for ( e = 4 ; e <= 21 ; e++ ) { lprint(e, 6, "\xba \xba"); } lprint(22, 6, "\xc8\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xbc"); lprint(4, 15, "END OF LEVEL"); lprintf(8, 8, "TIME: %s ", format_time(gp.Tid)); if ( gp.Bar ) { if ( gp.Bar == gp.OrigBar ) { sprintf(textbuf, "NO CHERRIES TAKEN"); } else { sprintf(textbuf, "%d OF %d CHERRIES TAKEN", gp.OrigBar-gp.Bar, gp.OrigBar); } lprint(9,20-strlen(textbuf)/2,textbuf); } sbon = (int64_t)( (30000.0*(gp.Level+1))/gp.Tid * (double)(gp.OrigBar-gp.Bar)/gp.OrigBar + 0.5 ); lprintf(10, 8, "PERFORMANCE BONUS: %" PRId64 , sbon); gp.Sc += sbon; lprint(11, 7, "\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4"); lprintf(12, 8, "TOP PERFORMANCES, LEVEL %d", gp.Level+1); lprint(20, 8, "YOUR TOTAL SCORE AT THIS"); lprintf(21, 8, "LEVEL WAS %" PRId64, gp.Sc-gp.StScore); /* Show level high scores */ /* Best time */ lprint(14,8,"TIME"); if ( !done ) { lprint(16,8, "Sorry, not"); lprint(17,8, "eligible"); } else { int i, rank; if ( gp.Cheat ) { rank = 0; } else { rank = highscore_add_level_time(gp.gameid, gp.Level, (int32_t)(gp.Tid * 1000.0), 1); } for ( i = 0 ; i < 3 ; i++ ) { int32_t t; color(0,(rank == i+1) ? gp.c : gp.c2); t = bests.level[gp.Level].time_ms[i].time_ms; if ( t != NO_TIME ) lprintf(16+i,8, " %d %s ", i+1, format_time(t/1000.0)); } } /* Best score */ color(0,gp.c2); lprint(14,21,"SCORE"); { int i, rank; if ( gp.Cheat ) { rank = 0; } else { rank = highscore_add_level_score(gp.gameid, gp.Level, gp.Sc-gp.StScore, 1); } for ( i = 0 ; i < 3 ; i++ ) { int64_t s = bests.level[gp.Level].score[i].score; color(0,(rank == i+1) ? gp.c : gp.c2); if ( s ) lprintf(16+i,21, " %d %" PRId64 " ", i+1, s); } } /* Wait 4 seconds */ if ( gp.EOLWait ) { mymssleep(4000); } gp.Level++; if ( gp.Level >= ELev ) { super_bonus(); gp.Hyp = 0; gp.Lvf = 0; } else { /* Level Hyperspace */ while ( gp.Hyp > 0 ) { if ( gp.Level >= ELev-1 ) { gp.Hyp = 0; break; } levelscreen(); lprintf(5, 17, "LEVEL %d", gp.Level+1); lprint(12, 13, "LEVEL HYPERSPACE"); /* Wait 1 second */ mymssleep(1000); gp.Hyp--; gp.Level++; } } } /* * Gameplay main loop function * This is called after all level initialization is done */ void play(void) { int Tick; /* Start of current game round (ms) */ SDL_TimerID round_timer; SDL_Event event; SDL_KeyboardEvent *ke; gp.Status = Status_Live; round_timer = SDL_AddTimer(gp.Speed, post_periodic, (void *)event_next_round); gp.OrigBar = gp.Bar; gp.StScore = gp.Sc; gp.XWk = gp.YWk = 0; gp.TZero = SDL_GetTicks(); gp.Tid = 0.0; while ( gp.Status == Status_Live ) { update_screen(); /* Wait for an event */ while ( SDL_WaitEvent(&event) ) { if ( event.type == SDL_USEREVENT ) { if ( event.user.code == event_next_round ) break; else if ( event.user.code == event_blink ) update_blink(); } else if ( event.type == SDL_KEYDOWN ) { push_key(&event.key); } } /* Get time for this round and convert to fractional seconds */ gp.Tid = ((Tick = SDL_GetTicks()) - gp.TZero)/1000.0; if ( (ke = poll_key()) ) handle_key(ke); move_player(); if ( gp.Status != Status_Live ) break; move_ghosts(); if ( gp.Status != Status_Live ) break; handle_action(); } SDL_RemoveTimer(round_timer); if ( gp.Status == Status_Dead ) { /* Dead stuff */ gp.Life--; gp.GkCh = max(gp.GkCh, gp.ChBd); gp.Lvf = 0; /* Sleep 4 seconds */ if ( gp.EOLWait ) mymssleep(4000); } else { /* End of level stuff */ gp.Lvf++; end_level(gp.Status == Status_Done); } }