aboutsummaryrefslogtreecommitdiffstats
path: root/play.c
diff options
context:
space:
mode:
authorH. Peter Anvin <hpa@zytor.com>2003-03-24 16:31:19 +0000
committerH. Peter Anvin <hpa@zytor.com>2003-03-24 16:31:19 +0000
commit1128ad360b5cccb1b82de092505e5ca1c4dbed8d (patch)
tree89c7ebe649b47fa1b901872d646a2183df023c8d /play.c
downloadgrv-1128ad360b5cccb1b82de092505e5ca1c4dbed8d.tar.gz
grv-1128ad360b5cccb1b82de092505e5ca1c4dbed8d.tar.xz
grv-1128ad360b5cccb1b82de092505e5ca1c4dbed8d.zip
Port of "grvning" to C/SDL, started 2003-03-22
Diffstat (limited to 'play.c')
-rw-r--r--play.c708
1 files changed, 708 insertions, 0 deletions
diff --git a/play.c b/play.c
new file mode 100644
index 0000000..f951749
--- /dev/null
+++ b/play.c
@@ -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);
+ }
+}