diff options
author | H. Peter Anvin <hpa@zytor.com> | 2003-03-24 16:31:19 +0000 |
---|---|---|
committer | H. Peter Anvin <hpa@zytor.com> | 2003-03-24 16:31:19 +0000 |
commit | 1128ad360b5cccb1b82de092505e5ca1c4dbed8d (patch) | |
tree | 89c7ebe649b47fa1b901872d646a2183df023c8d /play.c | |
download | grv-1128ad360b5cccb1b82de092505e5ca1c4dbed8d.tar.gz grv-1128ad360b5cccb1b82de092505e5ca1c4dbed8d.tar.xz grv-1128ad360b5cccb1b82de092505e5ca1c4dbed8d.zip |
Port of "grävning" to C/SDL, started 2003-03-22
Diffstat (limited to 'play.c')
-rw-r--r-- | play.c | 708 |
1 files changed, 708 insertions, 0 deletions
@@ -0,0 +1,708 @@ +/* + * play.c + * + * Actual gameplaying loop + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <math.h> +#include "graphics.h" +#include "grv.h" + +/* + * Keyboard map + */ + +SDLKey kbd_keys[NKEYS] = { + SDLK_w, /* Up */ + SDLK_a, /* Left */ + SDLK_s, /* Right */ + SDLK_z, /* Down */ + SDLK_u, /* Shoot up */ + SDLK_h, /* Shoot left */ + SDLK_j, /* Shoot right */ + SDLK_n, /* Shoot down */ + SDLK_SPACE, /* Stop */ + SDLK_ESCAPE, /* Escape */ + SDLK_F1, /* Pause */ +}; + +/* ------------------------------------------------------------------------- */ + +/* 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, "ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ»"); + lprint(11, 12, "º GAME PAUSED º"); + lprint(12, 12, "ÇÄÄÄÄÄÄÄÄÄÄÄÄÄÄĶ"); + lprintf(13, 12, "º TIME: %s º", format_time(gp.Tid)); + lprint(14, 12, "ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ"); + + 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 <Alt> */ + if ( sym == SDLK_F5 ) { + gp.Status = Status_Dead; + } else if ( sym == SDLK_END ) { + /* Zap game */ + exit(1); + } else if ( sym == SDLK_l ) { + gp.Cheat = 1; + gp.EOLWait = 0; + gp.Status = Status_Done; /* End of level */ + } + } + + 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," "); + } +} + +void fall_rock(int x, int y) +{ + int s, f; + + 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); + } +} + +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, f; /* Why these names? */ + int su, mu, fu; /* Same thing for the space above */ + int hpp = 0; + char sbuf[2]; + + s = screen(x,y); + m = fg(x,y); + f = bg(x,y); + sbuf[0] = s; sbuf[1] = '\0'; + + if ( x > 2 ) { + su = screen(x-1,y); + mu = fg(x-1,y); + fu = bg(x-1,y); + } else { + su = ' '; mu = 0; fu = gp.c; + } + + 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: %lld 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; + } + + /* Released apple */ + if ( su == 0xfe && f != 0 ) { + /* Set flashing */ + color(fu == 4 ? 16 : 20, fu); + lprint(x-1,y,"\xfe"); + + addaction(x-1,y,gp.Tid+30.0/(gp.Level+1), act_apple); + } + + 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 level code + */ +static void end_level(int done) +{ + char textbuf[40]; + int e; + int64_t sbon; + + color(0,gp.c2); + lprint(3, 6, "ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ»"); + for ( e = 4 ; e <= 21 ; e++ ) { + lprint(e, 6, "º º"); + } + lprint(22, 6, "ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ"); + + 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: %lld", sbon); + gp.Sc += sbon; + + lprint(11, 7, "ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ"); + lprintf(12, 8, "TOP PERFORMANCES, LEVEL %d", gp.Level+1); + + lprint(20, 8, "YOUR TOTAL SCORE AT THIS"); + lprintf(21, 8, "LEVEL WAS %lld", gp.Sc-gp.StScore); + + /* Wait 4 seconds */ + mymssleep(4000); + + gp.Level++; + + /* 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 ) { + /* 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; + } else { + /* End of level stuff */ + gp.Lvf++; + end_level(gp.Status == Status_Done); + } +} |