aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--action.c327
-rw-r--r--bullets.c147
-rw-r--r--drawlevel.c968
-rw-r--r--graphics.c409
-rw-r--r--graphics.h32
-rw-r--r--grv.c105
-rw-r--r--grv.h166
-rw-r--r--grvfont.c258
-rw-r--r--grvfont.psfbin0 -> 3588 bytes
-rw-r--r--keyboard.c75
-rw-r--r--mystery.c108
-rw-r--r--play.c708
-rwxr-xr-xpsftoc.pl24
-rw-r--r--random.c171
-rw-r--r--test.c25
-rw-r--r--utils.c86
16 files changed, 3609 insertions, 0 deletions
diff --git a/action.c b/action.c
new file mode 100644
index 0000000..1e27fe2
--- /dev/null
+++ b/action.c
@@ -0,0 +1,327 @@
+/*
+ * action.c
+ *
+ * Handle action queue events
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <math.h>
+#include "graphics.h"
+#include "grv.h"
+
+/* ------------------------------------------------------------------------ *
+ * Primary action event queue machinery
+ * ------------------------------------------------------------------------ */
+
+struct action {
+ int x, y;
+ enum actions what;
+ double when;
+};
+
+#define MAXACT 80
+static struct action actions[MAXACT];
+static int action_count;
+
+void addaction(int x, int y, double when, enum actions what)
+{
+ int i = 0;
+ if ( action_count >= MAXACT )
+ return;
+
+ while ( i < action_count && actions[i].when <= when && actions[i].what )
+ i++;
+
+ memmove(&actions[i+1], &actions[i],
+ (action_count-i)*sizeof(struct action));
+ actions[i].x = x;
+ actions[i].y = y;
+ actions[i].when = when;
+ actions[i].what = what;
+ action_count++;
+}
+
+/* Remove any action which matches the given (x,y,what) */
+void removeaction(int x, int y, enum actions what)
+{
+ int i = 0;
+
+ while ( i < action_count ) {
+ if ( actions[i].x == x && actions[i].y == y && actions[i].what == what ) {
+ memmove(&actions[i], &actions[i+1],
+ (action_count-i-1)*sizeof(struct action));
+ action_count--;
+ } else {
+ i++;
+ }
+ }
+}
+
+void reset_actions(void)
+{
+ action_count = 0;
+}
+
+static int sort_by_time(const void *a, const void *b)
+{
+ const struct action *aa = (const struct action *)a;
+ const struct action *bb = (const struct action *)b;
+
+ /* This *must* be a stable sort! */
+ if ( aa->when == bb->when )
+ return (aa < bb) ? -1 : 1;
+ else
+ return (aa->when < bb->when) ? -1 : 1;
+}
+
+/* This changes the time of *all* actions of a specifc type to "when" */
+void retime_all(double when, enum actions what)
+{
+ int i;
+
+ for ( i = 0 ; i < action_count ; i++ ) {
+ if ( actions[i].what == what )
+ actions[i].when = when;
+ }
+
+ qsort(actions, action_count, sizeof(struct action), sort_by_time);
+}
+
+/* ------------------------------------------------------------------------ *
+ * Stuff that gets triggered by action events...
+ * ------------------------------------------------------------------------ */
+
+/* Blow bomb or smash */
+static void blow_bomb(int x, int y, int smash)
+{
+ /* The first parameter is how far above and below (x,y) does this happen? */
+ static const int bomb_pattern[] = {
+ 5, 0, 0, 2, 2, 3, 3, 3, 2, 2, 0, 0
+ };
+ static const int smash_pattern[] = {
+ 6, 0, 2, 3, 4, 4, 5, 5, 5, 4, 4, 3, 2, 0
+ };
+ static const int big_smash_pattern[] = {
+ 9, 0, 2, 4, 5, 6, 6, 7, 7, 7, 7, 7, 7, 7, 6, 6, 5, 4, 2, 0
+ };
+ const int *pattern;
+ int path, patw;
+ int x2, y2;
+ int ch, m, f, fl;
+ int erase;
+
+ pattern = smash
+ ? (rnd() < 0.1 ? big_smash_pattern : smash_pattern)
+ : bomb_pattern;
+
+ patw = *pattern++;
+
+ /* Note the order in which this happens. We escavate from the bottom up
+ in each column; this means that when we run into rocks we can just let
+ them fall. We can also activate rocks and apples above the hole
+ at the end of each x2 loop.
+
+ This was a bug in the original BASIC version... it didn't do this. */
+
+ for ( y2 = y-patw ; y2 <= y+patw ; y2++ ) {
+ path = *pattern++;
+
+ if ( y2 < 1 || y2 > 40 )
+ continue;
+
+ for ( x2 = x+path ; x2 >= x-path ; x2-- ) {
+ if ( x2 < 2 || x2 > 23 )
+ continue;
+
+ ch = screen(x2,y2);
+ m = fg(x2,y2);
+ fl = screen1(x2,y2) & 0x80; /* Flashing? */
+ erase = 1;
+
+ switch ( ch ) {
+ case SYM_PLAYER:
+ if ( smash )
+ erase = 0;
+ else
+ gp.Status = Status_Dead;
+ break;
+
+ case SYM_GHOST:
+ kill_ghost(x2,y2);
+ break;
+
+ case SYM_CHERRY:
+ take_cherry();
+ break;
+
+ case SYM_BONUS:
+ if ( gp.Bon )
+ gp.Sc += 15*gp.Level; /* This sucks :) */
+ break;
+
+ case SYM_APPLE:
+ erase = 0;
+ color(m+16, 0);
+ lprint(x2,y2,"\xfe");
+ if ( !fl )
+ addaction(x2, y2, gp.Tid+30.0/(gp.Level+1), act_apple);
+ break;
+
+ case SYM_BOMB:
+ case 'H':
+ case 'Y':
+ case 'P':
+ case 'E':
+ case 'R':
+ /* Don't erase, but put it on dark background */
+ if ( x2 != x || y2 != y ) {
+ erase = 0;
+ color(m + (fl ? 16 : 0), 0);
+ lprintf(x2,y2,"%c",ch);
+ }
+ break;
+
+ case SYM_ROCK:
+ fall_rock(x2,y2);
+ erase = 0;
+ break;
+
+ default:
+ break;
+ }
+
+ if ( erase ) {
+ color(m, 0);
+ lprint(x2,y2," ");
+ }
+ }
+
+ /* See if there is something above this row that should be activated */
+ if ( x2 >= 2 && x2 <= 23 ) {
+ ch = screen(x2,y2);
+ m = fg(x2,y2);
+ f = bg(x2,y2);
+ fl = screen1(x2,y2) & 0x80; /* Flashing? */
+
+ if ( ch == SYM_ROCK ) {
+ fall_rock(x2,y2);
+ } else if ( ch == SYM_APPLE && !fl ) {
+ color(16+m,f);
+ lprint(x2,y2,"\xfe");
+ addaction(x2, y2, gp.Tid+30.0/(gp.Level+1), act_apple);
+ }
+ }
+ }
+
+ update_score();
+}
+
+/* Turn on or off the bonus dots */
+static void bonus_on(void)
+{
+ gp.Bon = 1;
+ color(30,0);
+ lprint(25, 1, " BONUS ");
+ addaction(0, 0, gp.Tid+12.0, act_bonus_off);
+}
+
+static void bonus_off(void)
+{
+ gp.Bon = 0;
+ color(0, gp.c);
+ lprint(25, 1, " ");
+ addaction(0, 0, gp.Tid+120.0, act_bonus_on);
+}
+
+
+/* ------------------------------------------------------------------------ *
+ * Action event queue trigger function
+ * ------------------------------------------------------------------------ */
+
+void handle_action(void)
+{
+ struct action action;
+
+ if ( !action_count || actions[0].when > gp.Tid )
+ return; /* No action currently pending */
+
+ action = actions[0];
+
+ /* Remove from queue */
+ memmove(&actions[0], &actions[1], (--action_count)*sizeof(struct action));
+
+ switch ( action.what ) {
+ case act_none:
+ break;
+
+ case act_apple:
+ fall_apple(action.x, action.y);
+ break;
+
+ case act_bomb:
+ blow_bomb(action.x, action.y, 0);
+ break;
+
+ case act_bonus_on:
+ bonus_on();
+ break;
+
+ case act_bonus_off:
+ bonus_off();
+ break;
+
+ case act_smash:
+ blow_bomb(gp.x, gp.y, 1);
+ break;
+
+ case act_toolate:
+ color(20,0);
+ lprint(12,14," OUT OF TIME! ");
+ gp.Status = Status_Dead;
+ break;
+
+ case act_droplevel:
+ gp.ZLevel--;
+ color(0,gp.c);
+ if ( gp.ZLevel > 0 ) {
+ addaction(0, 0, gp.Tid+10.0, act_droplevel);
+ lprintf(25, 2, "JUMP TO %-2d", gp.ZLevel+1);
+ } else {
+ lprint(25, 2, " ");
+ }
+ break;
+
+ case act_door:
+ /* Bombs can blow holes in doors, so don't assume the door is
+ still there */
+ if ( strchr(DOORS, screen(action.x,action.y)) ) {
+ color(fg(action.x,action.y), bg(action.x,action.y));
+ lprint(action.x,action.y," ");
+ if ( screen(action.x-1,action.y) == SYM_ROCK )
+ fall_rock(action.x-1,action.y);
+ }
+ break;
+
+ case act_ghost_thaw:
+ {
+ int e;
+
+ color(28,0);
+ for ( e = 0 ; e < MAXGHOST ; e++ ) {
+ if ( !ghost[e].dead ) {
+ lprint(ghost[e].x, ghost[e].y, "\x02");
+ }
+ }
+ }
+ break;
+
+ case act_ghost_normal:
+ gp.FS = 1; /* Normal ghosts */
+ break;
+ }
+}
+
+
diff --git a/bullets.c b/bullets.c
new file mode 100644
index 0000000..2ee4a36
--- /dev/null
+++ b/bullets.c
@@ -0,0 +1,147 @@
+/*
+ * bullets.c
+ *
+ * Handle bullets (shots and clusterbombs) in flight
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <math.h>
+#include "graphics.h"
+#include "grv.h"
+
+struct bullet {
+ int x, y; /* Current location */
+ int dx, dy; /* Direction of movement */
+ int color; /* Color */
+};
+
+#define MAX_BULLETS 57 /* 8 clusterbombs*7 shards + 1 player shot */
+/* Two lists; flip between loops */
+static struct bullet bullet_lists[2][MAX_BULLETS];
+static struct bullet *bullets = bullet_lists[0];
+static int bullet_list = 0;
+static int nbullets = 0;
+
+/*
+ * Add a bullet to the list
+ */
+void add_bullet(int x, int y, int dx, int dy, int color)
+{
+ int n;
+
+ if ( nbullets >= MAX_BULLETS )
+ return;
+
+ n = nbullets++;
+
+ bullets[n].x = x;
+ bullets[n].y = y;
+ bullets[n].dx = dx;
+ bullets[n].dy = dy;
+ bullets[n].color = color;
+}
+
+/*
+ * Run bullet action
+ */
+void run_bullets(void)
+{
+ struct bullet *b, *bb, *eb;
+ int x, y, s, f;
+ int live;
+ int hpp;
+
+ while ( nbullets ) {
+ bb = bullets;
+ eb = &bullets[nbullets];
+
+ /* Flip lists and initialize to zero */
+ bullet_list ^= 1;
+ bullets = bullet_lists[bullet_list];
+ nbullets = 0;
+
+ for ( b = bb ; b < eb ; b++ ) {
+ live = 1;
+
+ x = (b->x += b->dx);
+ y = (b->y += b->dy);
+
+ if ( x < 2 || x > 23 || y < 1 || y > 40 ) {
+ live = 0;
+ b->color = 0; /* Don't draw */
+ } else {
+ s = screen(x, y);
+ f = bg(x, y);
+
+ if ( strchr("\xfe\xb0\xb1\xe5\xec\x04?" DOORS, s) ) {
+ live = 0;
+ b->color = 0;
+ } else {
+ color(b->color, f);
+ lprint(x,y,"\xf9");
+
+ switch ( s ) {
+ case SYM_CHERRY:
+ take_cherry();
+ live = 0;
+ break;
+ case SYM_DIAMOND:
+ take_diamond();
+ live = 0;
+ break;
+ case SYM_GHOST:
+ kill_ghost(x,y);
+ live = 0;
+ 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);
+ lprintf(25, 31+hpp*2, "%c", s);
+ live = 0;
+ break;
+ case SYM_CLUSTER:
+ {
+ int di, dj;
+ for ( di = -1 ; di <= 1 ; di++ )
+ for ( dj = -1 ; dj <= 1 ; dj++ )
+ if ( (di||dj) && (di!=-b->dx || dj!=-b->dy) )
+ add_bullet(x,y,di,dj,12);
+ live = 0;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if ( live )
+ add_bullet(x,y,b->dx,b->dy,b->color);
+ }
+ update_score();
+
+ mymssleep(10); /* Allow the user to notice :) */
+
+ /* Erase bullets */
+ for ( b = bb ; b < eb ; b++ ) {
+ if ( b->color ) {
+ color(0,bg(b->x,b->y));
+ lprint(b->x,b->y," ");
+ }
+ }
+ }
+}
+
diff --git a/drawlevel.c b/drawlevel.c
new file mode 100644
index 0000000..4112114
--- /dev/null
+++ b/drawlevel.c
@@ -0,0 +1,968 @@
+/*
+ * drawlevel.c
+ *
+ * Code to draw the playing field, status indicators etc.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <stdarg.h>
+#include "graphics.h"
+#include "grv.h"
+
+/* Color scheme */
+#define CCYCLMAX 14 /* Length of color cycle */
+struct mycolors {
+ int main;
+ int anti;
+};
+const struct mycolors colors[CCYCLMAX] = {
+ {1,6},{4,7},{5,2},{2,5},{6,3},{3,1},{7,4},{6,3},{1,6},
+ {5,2},{7,4},{4,7},{2,5},{3,1}
+};
+
+/* Macros to get "unbusy" locations */
+#define unbusy(i,j) do { (i) = irnd(22)+2; (j) = irnd(40)+1; } while ( busy((i),(j)) )
+#define unbusy2(i,j) do { (i) = irnd(21)+2; (j) = irnd(40)+1; } while ( busy((i),(j)) || busy((i)+1,(j)) )
+
+/* Different possible placements of ghosts */
+static const struct xy *Ghosts;
+
+
+/* Draw a standard frame around the level */
+static void level_frame(void)
+{
+ int e;
+
+ color(0,0);
+ lprint(2,1," ");
+ for ( e = 3 ; e <= 22 ; e++ ) {
+ lprint(e,1," ");
+ lprint(e,40," ");
+ }
+ lprint(23,1," ");
+
+ color(0,gp.c);
+}
+
+/* Level 01-05: Basic plain level */
+static void level_plain(void)
+{
+ level_frame();
+}
+
+/* Level 06-10: Crosswall level */
+static void level_crosswall(void)
+{
+ lprint(13,2,"北北北北北鞍氨北北北北北卑鞍北北北北北");
+
+ level_frame();
+}
+
+/* Level 11-15: Wheeling walls */
+static void level_wheel(void)
+{
+ int e;
+
+ lprint(8,2,"北北北北北北北北北北北北鞍");
+ lprint(15,14,"鞍北北北北北北北北北北北北");
+
+ for ( e = 3 ; e <= 14 ; e++ )
+ lprint(e,28,(e > 13) ? "" : "");
+ for ( e = 9 ; e <= 22 ; e++ )
+ lprint(e,13,(e < 11) ? "" : "");
+
+ level_frame();
+}
+
+/* Level 16-19: + walls */
+static void level_cross(void)
+{
+ int e;
+
+ for ( e = 3 ; e <= 22 ; e++ )
+ lprint(e,20,"");
+
+ lprint(13,2,"北北北北北北北北北北北北北北北北北北北");
+
+ level_frame();
+}
+
+/* Level 20-24: Hole I */
+static void level_hole1(void)
+{
+ int e;
+
+ color(0,0);
+ lprint(8,11," ");
+ lprint(13,1," ");
+ lprint(13,31," ");
+ lprint(15,11," ");
+
+ for ( e = 9 ; e <= 14 ; e++ ) {
+ if ( e != 13 ) {
+ lprint(e,11," ");
+ lprint(e,30," ");
+ }
+ }
+
+ for ( e = 17 ; e <= 22 ; e++ )
+ lprint(e,21," ");
+
+ color(0,gp.c2);
+ for ( e = 9 ; e <= 14 ; e++ )
+ lprint(e,12," ");
+
+ level_frame();
+}
+
+/* Level 25-29: Bunker */
+static void level_bunker(void)
+{
+ int e;
+
+ lprint(8,6,"北北北北北 北北北北北北");
+ lprint(20,6,"北北北北北 北北北北北北");
+
+ for ( e = 9 ; e <= 19 ; e++ ) {
+ lprint(e,6," ");
+ }
+
+ color(0,gp.c2);
+ for ( e = 9 ; e <= 19 ; e++ ) {
+ lprint(e,7," ");
+ lprint(e,25," ");
+ }
+
+ gp.Bar = 4;
+ lprint(10,8,"");
+ lprint(10,33,"");
+ lprint(18,8,"");
+ lprint(18,33,"");
+
+ color(14,0);
+ lprint(14,7," ");
+ lprint(14,25," ");
+
+ for ( e = 18 ; e <= 22 ; e++ )
+ lprint(e,20," ");
+
+ color(25,gp.c);
+ lprint(14,20,"");
+ addaction(14, 20, 180.0*rnd(), act_bomb);
+
+ level_frame();
+}
+
+/* Level 30-35: Fredrik I */
+static void level_fred1(void)
+{
+ int i;
+
+ color(0,gp.c2);
+ for ( i = 9 ; i <= 16 ; i++ ) {
+ lprint(i,12," ");
+ }
+
+ color(0,gp.c);
+ for ( i = 3 ; i <= 22 ; i++ ) {
+ lprint(i,20,"北");
+ }
+ lprint(8,11,"北北鞍北北北北鞍北北");
+ lprint(17,11,"北北北北北北北北北北");
+ lprint(5,20,"鞍");
+ lprint(20,20,"鞍");
+ lprint(13,20,"鞍");
+
+ for ( i = 8 ; i <= 17 ; i++ ) {
+ lprint(i,11,"");
+ lprint(i,30,"");
+ }
+
+ lprint(13,2,"北北氨北");
+ lprint(13,31,"北北氨北");
+
+ color(0,gp.c2);
+ lprint(13,13," ");
+ lprint(13,23," ");
+ gp.Bar = 4;
+
+ level_frame();
+}
+
+/* Level 36-40: # walls */
+static void level_hash(void)
+{
+ int e;
+
+ for ( e = 3 ; e <= 22 ; e++ ) {
+ lprint(e,14,"");
+ lprint(e,27,"");
+ }
+
+ lprint(15,2,"北北北北北北北北北鞍北北北北北北北北北");
+ lprint( 8,2,"北北北北北北北北北鞍北北北北北北北北北");
+
+ color(0,gp.c2);
+ for ( e = 9 ; e <= 14 ; e++ ) {
+ lprint(e,15," ");
+ }
+
+ level_frame();
+}
+
+/* Level 41-45: Carl-David I */
+static void level_cdw1(void)
+{
+ static const struct xy Ghost4[] = {{2,1},{2,40},{23,1},{23,40},{13,20},{-1,-1}};
+ static const struct xy cherries[] = {
+ {2,3},{3,1},{2,38},{3,40},{22,1},{23,3},{22,40},{23,38},{12,17},{13,17},{12,24},{13,24},
+ {-1,-1}
+ }, *cp;
+ static const struct { int x, y, t; } doors[] = {
+ {10,17,30},{10,16,30},{10,15,30},{15,17,40},{15,16,40},{15,15,40},
+ {15,24,50},{15,25,50},{15,26,50},{10,24,60},{10,25,60},{10,26,60},
+ {-1,-1,-1}
+ }, *dp;
+
+ int x;
+
+ Ghosts = Ghost4;
+
+ lprint(12,1,"北北北北北北北北北北北北北北北北北北北北");
+ lprint(13,1,"北北北北北北北北北北北北北北北北北北北北");
+
+ for ( x = 3 ; x <= 23 ; x++ )
+ lprint(x,20,"北");
+
+ for ( cp = cherries ; cp->x != -1 ; cp++ ) {
+ lprint(cp->x,cp->y,"");
+ gp.Bar++;
+ }
+
+ color(gp.c,0);
+
+ for ( x = 0 ; x <= 19 ; x++ ) {
+ int a = (int)((double)x*22.0/39.0 + 0.5);
+ lprint(2+a,x+1," ");
+ lprint(23-a,x+1," ");
+ lprint(2+a,39-x," ");
+ lprint(23-a,39-x," ");
+ }
+
+ lprint(2,9," ");
+
+ for ( dp = doors ; dp->x != -1 ; dp++ ) {
+ lprint(dp->x,dp->y,"");
+ addaction(dp->x,dp->y,dp->t,act_door);
+ }
+}
+
+/* Level 46-49: Fredrik II */
+static void level_fred2(void)
+{
+ static const struct xy Ghost2[] = {{23,1},{23,40},{23,20},{23,21},{7,20},{-1,-1}};
+ int e;
+
+ Ghosts = Ghost2;
+
+ color(0,gp.c2);
+
+ for ( e = 8 ; e <= 17 ; e++ )
+ lprint(e,12," ");
+
+ color(0,gp.c);
+ gp.Bar = 4;
+ lprint(13,2,"北北北北卑北北北北腚北北北北氨北北北北");
+ lprint(12,20,"腚");
+
+ for ( e = 9 ; e <= 16 ; e++ ) {
+ lprint(e,18,"北");
+ lprint(e,22,"北");
+ }
+
+ lprint(11,20," ");
+ lprint(14,20," ");
+ lprint(10,20,"鞍");
+ lprint(15,20,"鞍");
+ color(0,0);
+ lprint(7,11," ");
+ lprint(18,11," ");
+
+ for ( e = 8 ; e <= 17 ; e++ ) {
+ if ( e != 13 ) {
+ lprint(e,11," ");
+ lprint(e,30," ");
+ }
+ }
+
+ lprint(8,20," ");
+ lprint(9,20," ");
+ lprint(16,20," ");
+ lprint(17,20," ");
+
+ level_frame();
+}
+
+/* Level 50-56: Breakout */
+static void level_breakout(void)
+{
+ static const struct xy Ghost1[] = {{12,1},{12,40},{10,10},{23,20},{10,31},{-1,-1}};
+ int e;
+
+ Ghosts = Ghost1;
+
+ lprint(6, 10, "北北北北北北北北北北北");
+ lprint(18, 10, "北北北北北北北北北北北");
+
+ for ( e = 7 ; e <= 17 ; e++ ) {
+ lprint(e, 10, "");
+ lprint(e, 31, "");
+ }
+
+ for ( e = 7 ; e <= 22 ; e++ ) {
+ lprint(e, 20, "");
+ }
+
+ color(0,0);
+ lprint(10, 7, " ");
+ lprint(10, 28, " ");
+ lprint(14, 7, " ");
+ lprint(14, 28, " ");
+
+ for ( e = 11 ; e <= 13 ; e++ ) {
+ lprint(e, 7, " ");
+ lprint(e, 13, " ");
+ lprint(e, 28, " ");
+ lprint(e, 34, " ");
+ }
+
+ color(0,gp.c2);
+ for ( e = 11 ; e <= 13 ; e++ ) {
+ lprint(e, 8, " ");
+ lprint(e, 29, " ");
+ }
+
+ lprint(12, 9, "腚");
+ lprint(12, 30, "腚");
+ gp.Bar = 6;
+
+ level_frame();
+}
+
+/* Level 57-62: Arch */
+static void level_arch(void)
+{
+ static const struct xy Ghost3[] = {{12,1},{12,40},{15,14},{15,27},{23,20},{-1,-1}};
+ int x, y;
+
+ Ghosts = Ghost3;
+
+ color(0,gp.c2);
+ for ( x = 12 ; x <= 21 ; x++ )
+ lprint(x, 11, " ");
+
+ color(gp.c,0);
+ for ( x = 3 ; x <= 10 ; x++ )
+ lprint(x, 19, " ");
+
+ lprint(9, 5, " ");
+
+ for ( x = 10 ; x <= 15 ; x++ ) {
+ lprint(x, 5, " ");
+ lprint(x, 36, " ");
+ }
+
+ lprint(12, 14, " ");
+
+ for ( x = 13 ; x <= 15 ; x++ ) {
+ lprint(x, 14, " ");
+ lprint(x, 25, " ");
+ }
+
+ for ( y = 19 ; y <= 22 ; y++ ) {
+ lprint(11, y, "");
+ addaction(11, y, 20, act_door);
+ }
+
+ color(0, gp.c);
+ lprint(7, 3, "北北北北北北北北鞍鞍北北北北北北北北");
+ lprint(11, 10, "北北北北");
+ lprint(11, 23, "北北北北");
+ lprint(22, 10, "北北北北北北北北北北北");
+
+ for ( x = 8 ; x <= 22 ; x++ ) {
+ lprint(x, 3, "");
+ lprint(x, 38, "");
+ }
+
+ for ( x = 12 ; x <= 21 ; x++ ) {
+ lprint(x, 10, "");
+ lprint(x, 31, "");
+ }
+
+ level_frame();
+}
+
+/* Level 63-67: Macke I */
+static void level_macke1(void)
+{
+ static const struct xy Ghost1[] = {{12,1},{12,40},{10,10},{23,20},{10,31},{-1,-1}};
+ int e;
+
+ Ghosts = Ghost1;
+
+ lprint(13, 2, "北北北北北北北北北北北北北北北北北北北");
+
+ color(0,0);
+
+ for ( e = 8 ; e <= 15 ; e++ ) {
+ lprint(e, 10, " ");
+ }
+ for ( e = 16 ; e <= 22 ; e++ ) {
+ lprint(e, 17, " ");
+ lprint(e, 24, " ");
+ }
+
+ color(0,gp.c);
+ for ( e = 3 ; e <= 22 ; e++ ) {
+ lprint(e, 16, "");
+ lprint(e, 25, "");
+ }
+
+ lprint(12, 16, "");
+ lprint(12, 25, "");
+ lprint(13, 16, "");
+ lprint(13, 25, "");
+
+ color(0,gp.c2);
+ for ( e = 16 ; e <= 22 ; e++ ) {
+ lprint(e, 18, " ");
+ }
+ lprint(17, 18, "");
+ lprint(17, 23, "");
+ lprint(22, 19, " ");
+ gp.Bar = 4;
+
+ level_frame();
+}
+
+/* Level 68-71: Carl-David II */
+static void level_cdw2(void)
+{
+ static const struct xy Ghost5[] = {{23,4},{23,37},{23,20},{17,20},{13,20},{-1,-1}};
+ int x;
+
+ Ghosts = Ghost5;
+
+ color(0,gp.c2);
+ for ( x = 5 ; x <= 18 ; x++ ) {
+ lprint(x, 14, " ");
+ }
+
+ color(gp.c,0);
+ lprint(2, 1, " ");
+ lprint(3, 1, " ");
+ lprint(4,13, " ");
+
+ for ( x = 5 ; x <= 23 ; x++ ) {
+ lprint(x, 1, " ");
+ lprint(x, 33, " ");
+ }
+ for ( x = 5 ; x <= 19 ; x++ ) {
+ lprint(x, 13, " ");
+ lprint(x, 28, " ");
+ }
+
+ lprint(19, 13, " ");
+ lprint(7, 15, " ");
+ lprint(17, 15, " ");
+
+ for ( x = 7 ; x <= 16 ; x++ ) {
+ lprint(x, 15, " ");
+ lprint(x, 26, " ");
+ }
+ for ( x = 9 ; x <= 14 ; x++ ) {
+ lprint(x, 18, " ");
+ }
+ for ( x = 20 ; x <= 23 ; x++ ) {
+ lprint(x, 20, " ");
+ }
+
+ for ( x = 1 ; x <= 8 ; x++ ) {
+ lprint(4, x, "");
+ addaction(4, x, 25, act_door);
+ lprint(4, 41-x, "");
+ addaction(4, 41-x, 25, act_door);
+ }
+
+ color(0, gp.c);
+ for ( x = 9 ; x <= 11 ; x++ ) {
+ lprint(4, x, "");
+ addaction(4, x, 25, act_door);
+ lprint(4, 41-x, "");
+ addaction(4, 41-x, 25, act_door);
+ }
+ for ( x = 4 ; x <= 23 ; x++ ) {
+ lprint(x, 12, "");
+ lprint(x, 29, "");
+ }
+
+ for ( x = 5 ; x <= 19 ; x++ ) {
+ if ( x & 3 ) {
+ lprint(x, 1, " ");
+ lprint(x, 5, " ");
+ lprint(x, 34, " ");
+ lprint(x, 38, " ");
+ }
+ }
+
+ lprint(22, 1, " ");
+ lprint(22, 39, " ");
+ lprint(23, 1, " ");
+ lprint(23, 39, " ");
+ lprint(11, 20, "腚");
+ lprint(12, 20, "腚");
+ gp.Bar = 6;
+}
+
+/* Level 72-74: Full */
+static void level_full(void)
+{
+ int i;
+
+ for ( i = 3 ; i <= 22 ; i++ ) {
+ lprint(i, 2, "腚腚腚腚腚腚腚腚腚腚腚腚腚腚腚腚腚腚腚");
+ gp.Bar += 32;
+ }
+
+ color(0,gp.c2);
+ for ( i = 8 ; i <= 17 ; i++ ) {
+ lprint(i, 11, " ");
+ gp.Bar -= 20;
+ }
+
+ level_frame();
+}
+
+/* Level 75: Finale */
+static void level_finale(void)
+{
+ int i, j, e;
+ int nw;
+
+ gp.nextwhite = 0;
+ nw = 0;
+#define ADDW(i,j) ( gp.whitecherrylist[nw].x = (i), \
+ gp.whitecherrylist[nw].y = (j), \
+ lprint((i),(j),""), \
+ nw++ )
+
+ color(15, gp.c);
+ for ( i = 3 ; i <= 22 ; i += 2 ) {
+ ADDW(i,2);
+ ADDW(i,6);
+ ADDW(i,10);
+ ADDW(i,14);
+ ADDW(i,18);
+ ADDW(i,22);
+ ADDW(i,26);
+ ADDW(i,30);
+ ADDW(i,34);
+ ADDW(i,38);
+
+ ADDW(i+1,4);
+ ADDW(i+1,8);
+ ADDW(i+1,12);
+ ADDW(i+1,16);
+ ADDW(i+1,20);
+ ADDW(i+1,24);
+ ADDW(i+1,28);
+ ADDW(i+1,32);
+ ADDW(i+1,36);
+ }
+ gp.Bar = gp.nwhite = nw;
+
+ /* Shuffle the white cherry list */
+ for ( i = nw-1 ; i >= 0 ; i-- ) {
+ struct xy tmp;
+ j = irnd(i+1);
+
+ tmp = gp.whitecherrylist[i];
+ gp.whitecherrylist[i] = gp.whitecherrylist[j];
+ gp.whitecherrylist[j] = tmp;
+ }
+
+ for ( e = 1 ; e <= 5 ; e++ ) {
+ white2black();
+ }
+
+ if ( gp.ZLevel < 0 ) {
+ gp.ZLevel = ELev - 1; /* Shouldn't this be zero? */
+ }
+
+ if ( gp.ZLevel > 0 ) {
+ lprintf(25, 2, "JUMP TO %d", gp.ZLevel + 1);
+ addaction(0, 0, 10, act_droplevel); /* drop 1 reentry level */
+ }
+
+ color(16, gp.c); /* Color 16 for spaces? */
+ lprint(24, 20, " ");
+
+ level_frame();
+}
+
+/* Draw level hyperspace */
+static void draw_hyper(void)
+{
+ int i, j, e, z;
+ static const char *hyper[] = {"H","Y","P","E","R"};
+
+ /* Never skip the last level */
+ z = min((ELev-2)-gp.Level,5);
+
+ for ( e = 0 ; e < z ; e++ ) {
+ unbusy(i,j);
+
+ color(15,bg(i,j));
+ lprint(i,j,hyper[e]);
+ }
+}
+
+/* Draw "standard" cherries */
+static void draw_cherries(void)
+{
+ int bar;
+ int e, i, j;
+
+ bar = 3*gp.Level + 6;
+ bar = min(bar,50);
+
+ gp.Bar += bar;
+
+ for ( e = 0 ; e < bar ; e++ ) {
+ unbusy(i,j);
+
+ color(0,bg(i,j));
+ lprint(i,j,"\xeb");
+ }
+}
+
+/* Draw bombs */
+static void draw_bombs(void)
+{
+ int i, j, e, z, xbg;
+
+ z = gp.Level/10;
+
+ for ( e = 0 ; e < z ; e++ ) {
+ unbusy(i,j);
+
+ xbg = bg(i,j);
+ color(25, xbg);
+ lprint(i,j,"\xe5");
+ addaction(i,j,180.0*rnd(),act_bomb);
+ }
+}
+
+/* Draw shots */
+static void draw_shots(void)
+{
+ int i, j, e, z, xbg;
+
+ z = 1 + (gp.Level > 9) + (gp.Level > 14) + (gp.Level > 24);
+
+ for ( e = 0 ; e < z ; e++ ) {
+ unbusy(i,j);
+
+ xbg = bg(i,j);
+ color(0, xbg);
+ lprint(i,j,"\xec");
+ }
+}
+
+/* Draw cluster bombs */
+static void draw_cluster(void)
+{
+ int i, j, e, z, xbg;
+
+ z = (int)(gp.Level/10.0+rnd());
+
+ for ( e = 0 ; e < z ; e++ ) {
+ unbusy(i,j);
+
+ xbg = bg(i,j);
+ color(28, xbg);
+ lprint(i,j,"\x0f");
+ }
+}
+
+/* Draw mystery ?'s */
+static void draw_mystery(void)
+{
+ int i, j, e, z, xbg;
+
+ z = (gp.Level > 11) + (gp.Level > 19) + (gp.Level > 24);
+
+ for ( e = 0 ; e < z ; e++ ) {
+ unbusy(i,j);
+
+ xbg = bg(i,j);
+ color((xbg >= 2) ? 30 : 26, xbg);
+ lprint(i,j,"?");
+ }
+}
+
+/* Draw apples */
+static void draw_apples(void)
+{
+ int i, j, e, z, xbg;
+
+ z = min(gp.Level+2,20);
+
+ for ( e = 0 ; e < z ; e++ ) {
+ unbusy2(i,j);
+
+ xbg = bg(i,j);
+ color((xbg==4)?0:4, xbg);
+ lprint(i,j,"\xfe");
+ }
+}
+
+/* Draw rocks */
+static void draw_rocks(void)
+{
+ int i, j, e, z;
+
+ z = min(gp.Level+2,17);
+
+ color(8,0);
+ for ( e = 0 ; e < z ; e++ ) {
+ unbusy2(i,j);
+
+ lprint(i,j,"\x04");
+ }
+}
+
+/* Draw bonus dots */
+static void draw_bonus(void)
+{
+ int i, j, e;
+
+ for ( e = 0 ; e < 3 ; e++ ) {
+ unbusy(i,j);
+
+ color(16,bg(i,j));
+ lprint(i,j,"\xf9");
+ }
+ addaction(0, 0, 90.0*rnd(), act_bonus_on);
+}
+
+/* Draw player */
+static void draw_player(void)
+{
+ color(14,0);
+ gp.x = 2; gp.y = 20;
+ lprint(gp.x, gp.y, "\x01");
+}
+
+/* Draw ghosts */
+static void draw_ghosts(void)
+{
+ int e;
+
+ color(9,0);
+ for ( e = 0 ; e < MAXGHOST ; e++ ) {
+ if ( Ghosts->x < 0 ) {
+ ghost[e].x = ghost[e].y = 0;
+ ghost[e].dead = 1;
+ } else {
+ ghost[e].x = Ghosts->x;
+ ghost[e].y = Ghosts->y;
+ ghost[e].dead = 0;
+ lprint(Ghosts->x, Ghosts->y, "\x02");
+ Ghosts++;
+ }
+ }
+}
+
+/* Set up a blank screen appropriate for the current gp.Level */
+void levelscreen(void)
+{
+ gp.c = colors[gp.Level % CCYCLMAX].main;
+ gp.c2 = colors[gp.Level % CCYCLMAX].anti;
+ color(0, gp.c);
+ initscreen(40);
+}
+
+/* Main level-drawing function */
+void drawlevel(void)
+{
+ static const struct xy DefaultGhosts[] = {{12,1},{12,40},{23,1},{23,20},{23,40},{-1,-1}};
+ int level = gp.Level;
+ int e;
+
+ levelscreen();
+ lprintf(1,1,"SCORE:% lld", gp.Sc);
+
+ locate(1,18);
+ color(14,gp.c);
+ for ( e = 1 ; e <= gp.Life ; e++ ) {
+ print("\x01 ");
+ }
+
+ color(0,gp.c);
+ lprintf(1,31,"LEVEL: %d", gp.Level+1);
+
+ Ghosts = DefaultGhosts;
+ gp.Bar = gp.nwhite = 0;
+
+ switch ( level+1 ) {
+ case 1: case 2: case 3: case 4: case 5:
+ level_plain();
+ break;
+ case 6: case 7: case 8: case 9: case 10:
+ level_crosswall();
+ break;
+ case 11: case 12: case 13: case 14: case 15:
+ level_wheel();
+ break;
+ case 16: case 17: case 18: case 19:
+ level_cross();
+ break;
+ case 20: case 21: case 22: case 23: case 24:
+ level_hole1();
+ break;
+ case 25: case 26: case 27: case 28: case 29:
+ level_bunker();
+ break;
+ case 30: case 31: case 32: case 33: case 34: case 35:
+ level_fred1();
+ break;
+ case 36: case 37: case 38: case 39: case 40:
+ level_hash();
+ break;
+ case 41: case 42: case 43: case 44: case 45:
+ level_cdw1();
+ break;
+ case 46: case 47: case 48: case 49:
+ level_fred2();
+ break;
+ case 50: case 51: case 52: case 53: case 54: case 55: case 56:
+ level_breakout();
+ break;
+ case 57: case 58: case 59: case 60: case 61: case 62:
+ level_arch();
+ break;
+ case 63: case 64: case 65: case 66: case 67:
+ level_macke1();
+ break;
+ case 68: case 69: case 70: case 71:
+ level_cdw2();
+ break;
+ case 72: case 73: case 74:
+ level_full();
+ break;
+ case 75:
+ level_finale();
+ break;
+ default:
+ abort();
+ }
+
+ draw_player();
+ draw_ghosts();
+
+ if ( level == ELev-1 ) {
+ /* Very plain level */
+ } else {
+ /* Common level code */
+
+ draw_cherries();
+
+ if ( level >= 29 )
+ draw_bombs();
+
+ if ( gp.Lvf >= 4 ) {
+ draw_hyper();
+ gp.Lvf = 0;
+ }
+
+ draw_cluster();
+
+ draw_shots();
+
+ if ( level < 70 )
+ draw_rocks();
+
+ if ( level >= 12 )
+ draw_mystery();
+
+ if ( level < 29 )
+ draw_apples();
+
+ if ( level >= 24 )
+ draw_bonus();
+ }
+
+ update_shots();
+ update_power();
+}
+
+/*
+ * This is called during gameplay to update the score, shots,
+ * and power displays
+ */
+
+void update_score(void)
+{
+ color(0,gp.c);
+ lprintf(1,7,"%- 11lld",gp.Sc);
+}
+
+void update_shots(void)
+{
+ if ( gp.KulSpr > 0 ) {
+ color(0,gp.c2);
+ lprintf(25,9," SHOTS %2d ", gp.KulSpr);
+ color(0,gp.c);
+ } else {
+ color(0,gp.c);
+ lprint(25,9," ");
+ }
+}
+
+void update_power(void)
+{
+ color(0,gp.c);
+ lprintf(25,20,"POWER %-4d", gp.GkCh);
+}
+
+/*
+ * This is called during gameplay to display a message on line 24
+ */
+void message(int col, const char *fmt, ...)
+{
+ char buffer[4096];
+ char msgbuf[41];
+ int len;
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsprintf(buffer, fmt, ap);
+ va_end(ap);
+
+ len = strlen(buffer);
+ msgbuf[40] = '\0';
+
+ if ( len > 40 ) {
+ memcpy(msgbuf, buffer, 40);
+ } else {
+ memset(msgbuf, ' ', 40);
+ memcpy(msgbuf+(40-len)/2, buffer, len);
+ }
+
+ color(col,gp.c);
+ lprint(24,1,msgbuf);
+}
diff --git a/graphics.c b/graphics.c
new file mode 100644
index 0000000..155d628
--- /dev/null
+++ b/graphics.c
@@ -0,0 +1,409 @@
+/*
+ * graphics.c
+ *
+ * Graphics function emulation
+ * This pretty much attempts to emulate the QuickBasic-style text screen
+ * in 25x40 or 25x80 mode with no scrolling...
+ */
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include "SDL.h"
+#include "grv.h"
+#include "graphics.h"
+
+#define min(x,y) ((x)<(y)?(x):(y))
+#define max(x,y) ((x)>(y)?(x):(y))
+
+#define TS_WIDTH 80
+#define TS_HEIGHT 25
+
+#define FONT_XSIZE 8
+#define FONT_YSIZE 14
+
+#define PX_WIDTH (TS_WIDTH*FONT_XSIZE)
+#define PX_HEIGHT (TS_HEIGHT*FONT_YSIZE)
+
+typedef uint8_t font_t;
+extern font_t grv_font[256][FONT_YSIZE];
+
+#define NCOLORS 16
+
+static uint32_t colors[NCOLORS];
+
+static struct rgba { uint8_t a, r, g, b; } rgbcolors[NCOLORS] = {
+ {0x00,0x00,0x00,0x00}, /* black */
+ {0x00,0x00,0x00,0xaa}, /* dark blue */
+ {0x00,0x00,0xaa,0x00}, /* dark green */
+ {0x00,0x00,0xaa,0xaa}, /* dark cyan */
+ {0x00,0xaa,0x00,0x00}, /* dark red */
+ {0x00,0xaa,0x00,0xaa}, /* dark purple */
+ {0x00,0xaa,0x55,0x00}, /* brown */
+ {0x00,0xaa,0xaa,0xaa}, /* light grey */
+ {0x00,0x55,0x55,0x55}, /* dark grey */
+ {0x00,0x55,0x55,0xff}, /* light blue */
+ {0x00,0x55,0xff,0x55}, /* light green */
+ {0x00,0x55,0xff,0xff}, /* light cyan */
+ {0x00,0xff,0x55,0x55}, /* light red */
+ {0x00,0xff,0x55,0xff}, /* light purple */
+ {0x00,0xff,0xff,0x55}, /* yellow */
+ {0x00,0xff,0xff,0xff}, /* white */
+};
+
+struct screen_char {
+ uint8_t sym; /* Symbol code */
+ uint8_t attr; /* Attribute code */
+};
+
+static struct screen_char screendata[TS_HEIGHT][TS_WIDTH];
+
+static struct current_screen {
+ int x;
+ int y;
+ uint8_t attr;
+ uint8_t dwidth; /* Double width characters, must be 0 or 1 */
+} current = {0, 0, 7, 1}; /* Current screen parameters */
+
+static SDL_Surface *rscreen;
+static volatile uint8_t blink_mask = 0;
+
+/*
+ * Set the attribute: fg, bg
+ * For compatiblity's sake the flashing attribute is in fg, not bg
+ */
+void color(int fg, int bg)
+{
+ current.attr = (fg & 15) | ((bg & 7) << 4) | ((fg & 16) << 3);
+}
+
+/*
+ * Set the cursor position (1-based for compatiblity)
+ */
+void locate(int r, int c)
+{
+ current.y = r-1;
+ current.x = (c-1) << current.dwidth;
+}
+
+/*
+ * Get the symbol at a specific screen coordinate
+ */
+int screen(int r, int c)
+{
+ return screendata[r-1][(c-1) << current.dwidth].sym;
+}
+
+/*
+ * Get the attribute at a specific screen coordinate
+ */
+int screen1(int r, int c)
+{
+ return screendata[r-1][(c-1) << current.dwidth].attr;
+}
+
+/*
+ * Prepare screen for modification
+ */
+static void lock_screen(void)
+{
+ SDL_LockSurface(rscreen);
+}
+
+/*
+ * Update the on-screen structure to match the screendata[]
+ * for character (tx,ty), but don't refresh the rectangle just
+ * yet...
+ *
+ * tx,ty is zero-based and must be adjusted for doublewidth mode
+ */
+static void put_screen(int tx, int ty)
+{
+ font_t *fontp;
+ font_t v;
+ uint32_t *pixelp, fgp, bgp;
+ int x, y, z;
+ int attr;
+ int pxwid = current.dwidth + 1;
+ int bmask = blink_mask;
+
+ attr = screendata[ty][tx].attr;
+ fontp = grv_font[screendata[ty][tx].sym];
+
+ bgp = colors[(attr >> 4) & 7];
+ fgp = (bmask & attr) ? bgp : colors[attr & 15];
+
+ pixelp = ((uint32_t *) rscreen->pixels) +
+ ty*(FONT_XSIZE*FONT_YSIZE*TS_WIDTH) +
+ tx*FONT_XSIZE;
+
+ for ( y = 0 ; y < FONT_YSIZE ; y++ ) {
+ v = fontp[y];
+ for ( x = 0 ; x < FONT_XSIZE ; x++ ) {
+ for ( z = 0 ; z < pxwid ; z++ )
+ *pixelp++ = v & ((font_t)1 << (FONT_XSIZE-1)) ? fgp : bgp;
+ v <<= 1;
+ }
+ pixelp += PX_WIDTH-pxwid*FONT_XSIZE;
+ }
+}
+
+/*
+ * This routine switches the blink status, then goes around the screen
+ * and updates all characters which has the blink attribute set.
+ */
+void update_blink(void)
+{
+ int x, y, xs;
+ int gx, gy, gw, gh;
+ SDL_Rect rects[TS_WIDTH*TS_HEIGHT]; /* Absolute maximum needed */
+ SDL_Rect *rect = rects-1;
+ int nrects = 0;
+
+ blink_mask ^= 0x80;
+
+ xs = current.dwidth+1;
+
+ SDL_LockSurface(rscreen);
+
+ gw = FONT_XSIZE << current.dwidth;
+ gh = FONT_YSIZE;
+
+ for ( y = 0, gy = 0 ; y < TS_HEIGHT ; y++, gy += gh ) {
+ for ( x = 0, gx = 0 ; x < TS_WIDTH ; x += xs, gx += gw ) {
+ if ( screendata[y][x].attr & 0x80 ) {
+ put_screen(x,y);
+ if ( !nrects || rect->y != gy || rect->x+rect->w != gx ) {
+ nrects++;
+ rect++;
+ rect->x = gx; rect->y = gy;
+ rect->w = gw; rect->h = gh;
+ } else {
+ rect->w += gw;
+ }
+ }
+ }
+ }
+
+ SDL_UnlockSurface(rscreen);
+
+ if ( nrects )
+ SDL_UpdateRects(rscreen, nrects, rects);
+}
+
+/*
+ * Refresh rectangle and unlock screen
+ * Coordinates are inclusive and must be adjusted for double-pixel mode
+ */
+static void update_screen(int x0, int y0, int x1, int y1)
+{
+ SDL_UnlockSurface(rscreen);
+
+ SDL_UpdateRect(rscreen, x0*FONT_XSIZE, y0*FONT_YSIZE,
+ (x1-x0+1)*FONT_XSIZE, (y1-y0+1)*FONT_YSIZE);
+}
+
+/*
+ * Display a string of characters and advance cursor
+ */
+void print(const char *str)
+{
+ const unsigned char *p = (const unsigned char *)str;
+ unsigned char ch;
+ int x = current.x;
+ int y = current.y;
+ uint8_t attr = current.attr;
+ int x0 = x; int y0 = y;
+ int x1 = x; int y1 = y;
+ int xwid;
+
+ if ( !*str )
+ return;
+
+ xwid = current.dwidth+1;
+
+ lock_screen();
+
+ while ( (ch = *p++) ) {
+ screendata[y][x].sym = ch;
+ screendata[y][x].attr = attr;
+ put_screen(x,y);
+ x0 = min(x0,x); y0 = min(y0,y);
+ x1 = max(x1,x+xwid-1); y1 = max(y1,y);
+ x += xwid;
+ if ( x >= TS_WIDTH ) {
+ x = 0; y++;
+ if ( y >= TS_HEIGHT ) {
+ y = 0;
+ }
+ }
+ }
+ update_screen(x0,y0,x1,y1);
+
+ current.x = x; current.y = y;
+}
+
+/*
+ * Same except with printf-style formatting
+ */
+void gprintf(const char *fmt, ...)
+{
+ unsigned char buffer[4096];
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsprintf(buffer, fmt, ap);
+ va_end(ap);
+ print(buffer);
+}
+
+/*
+ * locate() and print() as a combined function
+ */
+void lprint(int r, int c, const char *str)
+{
+ locate(r,c);
+ print(str);
+}
+
+/*
+ * locate() and gprintf() as a combined function
+ */
+void lprintf(int r, int c, const char *fmt, ...)
+{
+ unsigned char buffer[4096];
+ va_list ap;
+
+ locate(r,c);
+ va_start(ap, fmt);
+ vsprintf(buffer, fmt, ap);
+ va_end(ap);
+ print(buffer);
+}
+
+/*
+ * Set 40/80 character mode. Unlike hardware this doesn't change the
+ * text currently on the screen, but funny things can happen; cls() is
+ * highly recommended.
+ */
+void width(int wid)
+{
+ switch(wid) {
+ case 40:
+ current.dwidth = 1;
+ current.x &= ~1;
+ break;
+ case 80:
+ current.dwidth = 0;
+ break;
+ default:
+ abort();
+ }
+}
+
+/*
+ * Clear the screen, set all characters to ' ' and set all pixels to the
+ * current background
+ */
+void cls(void)
+{
+ int i;
+ struct screen_char *scp = &screendata[0][0];
+ struct screen_char empty;
+ uint32_t pixelval;
+ SDL_Rect rect;
+
+ lock_screen();
+
+ empty.sym = ' ';
+ empty.attr = current.attr;
+
+ pixelval = colors[(current.attr >> 4) & 7];
+
+ for ( i = TS_WIDTH*TS_HEIGHT ; i ; i-- )
+ *scp++ = empty;
+
+ rect.x = rect.y = 0;
+ rect.w = PX_WIDTH;
+ rect.h = PX_HEIGHT;
+
+ SDL_FillRect(rscreen, &rect, pixelval);
+
+ update_screen(0, 0, TS_WIDTH-1, TS_HEIGHT-1);
+
+ current.x = current.y = 0;
+}
+
+
+/*
+ * Save the screen as well as "current" in a malloc'd buffer
+ */
+struct saved_screen
+{
+ struct current_screen current;
+ struct screen_char screendata[TS_HEIGHT][TS_WIDTH];
+};
+
+struct saved_screen *save_screen(void)
+{
+ struct saved_screen *save = malloc(sizeof(struct saved_screen));
+
+ if ( !save )
+ return NULL;
+
+ memcpy(&save->current, &current, sizeof(current));
+ memcpy(&save->screendata, &screendata, sizeof(screendata));
+
+ return save;
+}
+
+void restore_screen(struct saved_screen *save)
+{
+ int x, y, xs;
+
+ memcpy(&current, &save->current, sizeof(current));
+ memcpy(&screendata, &save->screendata, sizeof(screendata));
+
+ xs = current.dwidth+1;
+
+ lock_screen();
+
+ for ( y = 0 ; y < TS_HEIGHT ; y++ ) {
+ for ( x = 0 ; x < TS_WIDTH ; x += xs ) {
+ put_screen(x,y);
+ }
+ }
+
+ update_screen(0, 0, TS_WIDTH-1, TS_HEIGHT-1);
+}
+
+/*
+ * Initialize SDL and the data structures
+ */
+int screen_init(void)
+{
+ int i;
+
+ if ( SDL_Init(SDL_INIT_TIMER|SDL_INIT_VIDEO) )
+ return -1;
+
+ if ( !(rscreen = SDL_SetVideoMode(PX_WIDTH, PX_HEIGHT, 32, SDL_SWSURFACE)) ) {
+ SDL_Quit();
+ return -1;
+ }
+
+ atexit(SDL_Quit);
+
+ /* Convert colors to preferred machine representation */
+ for ( i = 0 ; i < NCOLORS ; i++ ) {
+ colors[i] = SDL_MapRGB(rscreen->format,
+ rgbcolors[i].r, rgbcolors[i].g, rgbcolors[i].b);
+ }
+
+ cls();
+
+ SDL_AddTimer(400, post_periodic, (void *)event_blink);
+
+ return 0;
+}
diff --git a/graphics.h b/graphics.h
new file mode 100644
index 0000000..fb9511c
--- /dev/null
+++ b/graphics.h
@@ -0,0 +1,32 @@
+/*
+ * graphics.h
+ *
+ * Graphics function emulation
+ * This pretty much attempts to emulate the QuickBasic-style text screen
+ * in 25x40 or 25x80 mode with no scrolling...
+ */
+
+#ifndef GRAPHICS_H
+#define GRAPHICS_H 1
+
+#include <stdlib.h>
+#include <inttypes.h>
+
+int screen_init(void);
+void color(int fg, int bg);
+void locate(int r, int c);
+int screen(int r, int c);
+int screen1(int r, int c);
+void print(const char *str);
+void lprint(int r, int c, const char *str);
+void gprintf(const char *fmt, ...);
+void lprintf(int r, int c, const char *fmt, ...);
+void width(int wid);
+void cls(void);
+void update_blink(void);
+
+struct saved_screen;
+struct saved_screen *save_screen(void);
+void restore_screen(struct saved_screen *);
+
+#endif /* GRAPHICS_H */
diff --git a/grv.c b/grv.c
new file mode 100644
index 0000000..4b3580d
--- /dev/null
+++ b/grv.c
@@ -0,0 +1,105 @@
+/*
+ * grv.c
+ *
+ * Main program - converted from BASIC
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include "graphics.h"
+#include "grv.h"
+
+struct gameparams gp;
+struct monster ghost[MAXGHOST];
+
+void init_gameparams(void)
+{
+ gp.Sc = 0;
+ gp.Life = 3;
+ gp.SBs = 0;
+ gp.Lvf = 0;
+ gp.GkCh = 0;
+ gp.MLev = 0;
+ gp.KulSpr = 0;
+ gp.Level = 0;
+ gp.Cheat = 0;
+}
+
+static int screen_width = 40;
+void initscreen(int w)
+{
+ if ( w != screen_width )
+ width(screen_width = w);
+
+ color(0,gp.c);
+ cls();
+}
+
+void newlevel(void)
+{
+ gp.Bon = 0;
+ gp.FrSpok = -1;
+ gp.TF = -1;
+ gp.Hyp = 0;
+ gp.Bar = 0;
+ gp.ChBd = gp.GkCh;
+ gp.FS = 1; /* Ghosts are not frozen or reversed */
+ /* 150 ms/step is the speed for level 0 */
+ gp.Speed = (150*ELev)/(gp.Level+ELev);
+ gp.EOLWait = 1;
+ gp.Tid = 0.0;
+
+ reset_actions();
+ addaction(0, 0, 1800.0, act_toolate); /* 30 minutes -> dead */
+
+ if ( gp.Level == ELev-1 ) {
+ /* Last level */
+ gp.KulSpr = 0;
+ } else {
+ /* Not the last level */
+ int i, e;
+ i = gp.KulSpr;
+ for ( e = 0 ; e < i ; e++ ) {
+ if ( rnd() < 0.9 )
+ gp.KulSpr--;
+ }
+ if ( gp.KulSpr > 80 )
+ gp.KulSpr = 80;
+ }
+
+ if ( gp.Level > gp.MLev )
+ gp.MLev = gp.Level;
+
+ drawlevel();
+
+ play();
+}
+
+void game(void)
+{
+ init_gameparams();
+ while ( gp.Level < ELev && gp.Life >= 0 ) {
+ newlevel();
+ if ( gp.EOLWait )
+ mymssleep(4000);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ randomize();
+
+ if ( screen_init() ) {
+ fprintf(stderr, "%s: Cannot initalize graphics!\n", argv[0]);
+ exit(1);
+ }
+
+ while ( 1 ) {
+ game();
+ break;
+ }
+
+ return 0;
+}
diff --git a/grv.h b/grv.h
new file mode 100644
index 0000000..c5b2021
--- /dev/null
+++ b/grv.h
@@ -0,0 +1,166 @@
+#ifndef GRV_H
+#define GRV_H 1
+
+#include <inttypes.h>
+#include "graphics.h"
+#include "SDL.h"
+#include "SDL_keyboard.h"
+
+#define min(x,y) ((x)<(y)?(x):(y))
+#define max(x,y) ((x)>(y)?(x):(y))
+
+/* Get a random floating-point number between 0 and 1 */
+extern void init_genrand(unsigned long);
+extern double genrand_res53(void);
+extern unsigned long genrand_int32(void);
+#define randomize() init_genrand(time(0))
+#define rnd() genrand_res53()
+#define irnd(n) ((int)(genrand_int32() % (n)))
+
+
+#define ELev 75 /* Ending level */
+
+struct xy {
+ int x, y;
+};
+
+struct gameparams {
+ /* Yes, score can be negative, although it's highly unlikely */
+ int64_t Sc; /* Score */
+ int64_t StScore; /* Score at start of level */
+ int Life; /* Lives left */
+ int SBs;
+ int Lvf;
+ int GkCh;
+ int Level; /* Current level */
+ int MLev; /* Max level achieved */
+ int ZLevel;
+ int KulSpr;
+ int Bon; /* Bonus available right now */
+ int FrSpok; /* double? */
+ int TF; /* double? */
+ int FS; /* Ghost speed (1 normal, -1 scared, 0 frozen) */
+ int x, y; /* Player coordinates */
+ int XWk, YWk; /* Player movement */
+ int Hyp;
+ int ChBd;
+ int Bar; /* Number of cherries on screen */
+ int OrigBar; /* Cherries at level start */
+ int c; /* Main level color */
+ int c2; /* Alternate level color */
+ int Cheat; /* Cheat codes used */
+ int EOLWait; /* Standard death/end of level pause? */
+ int TZero; /* Time at start of level (ms) */
+ int Speed; /* Desired game speed ms/game round */
+ double Tid; /* Time since level start (float s) */
+ enum {
+ Status_Live, /* Player alive */
+ Status_Dead, /* Player dead */
+ Status_Done, /* Player done with level */
+ Status_Quit, /* Level exit (treasure) */
+ } Status;
+ int nwhite; /* Number of white cherries */
+ int nextwhite; /* Next white cherry (if any) */
+ struct xy whitecherrylist[190];
+};
+extern struct gameparams gp;
+
+enum actions {
+ act_none, /* No action */
+ act_apple, /* Apple fall */
+ act_bomb, /* Bomb goes off */
+ act_bonus_on, /* BONUS light on */
+ act_bonus_off, /* BONUS light off */
+ act_smash, /* "Smash" */
+ act_toolate, /* Time expired */
+ act_droplevel, /* Drop one level (finale level) */
+ act_door, /* Open door */
+ act_ghost_thaw, /* Thaw frozen ghosts */
+ act_ghost_normal, /* Return ghosts to normal */
+};
+
+struct monster {
+ int x, y;
+ int dead;
+};
+#define MAXGHOST 5
+extern struct monster ghost[MAXGHOST];
+
+/* Keyboard handling */
+#define NKEYS 11
+extern SDLKey kbd_keys[NKEYS];
+
+/* User-defined SDL event types */
+enum user_event {
+ event_next_round, /* Next game round */
+ event_blink, /* Handle blinking */
+ event_sleep, /* Sleep expired */
+};
+
+/* Character symbols */
+#define SYM_PLAYER 0x01
+#define SYM_GHOST 0x02
+#define SYM_ROCK 0x04
+#define SYM_CLUSTER 0x0f
+#define SYM_NOTHING ' '
+#define SYM_DIAMOND '*'
+#define SYM_MYSTERY '?'
+#define SYM_POROUS_WALL 0xb0
+#define SYM_WALL 0xb1
+#define SYM_CHERRY 0xeb
+#define SYM_BOMB 0xe5
+#define SYM_SHOT 0xec
+#define SYM_BONUS 0xf9
+#define SYM_APPLE 0xfe
+/* All possible doors */
+#define DOORS "\xb9\xba\xbb\xbc\xc8\xc9\xca\xcb\xcc\xcd\xce"
+
+/* utils.c */
+int fg(int r, int c);
+int bg(int r, int c);
+int busy(int r, int c);
+Uint32 post_periodic(Uint32 interval, void *param);
+Uint32 post_oneshot(Uint32 interval, void *param);
+void mymssleep(int ms);
+char *format_time(double t);
+
+/* drawlevel.c */
+void drawlevel(void);
+void levelscreen(void);
+
+/* grv.c */
+void initscreen(int w);
+void update_score(void);
+void update_shots(void);
+void update_power(void);
+void message(int col, const char *fmt, ...);
+
+/* play.c */
+void white2black(void);
+void play(void);
+void fall_rock(int x, int y);
+void fall_apple(int x, int y);
+void kill_ghost(int x, int y);
+void take_cherry(void);
+void take_diamond(void);
+
+/* keyboard.c */
+void push_key(SDL_KeyboardEvent *ke);
+SDL_KeyboardEvent *poll_key(void);
+SDL_KeyboardEvent *get_key(void);
+
+/* action.c */
+void reset_actions(void);
+void addaction(int x, int y, double when, enum actions what);
+void removeaction(int x, int y, enum actions what);
+void handle_action(void);
+void retime_all(double when, enum actions what);
+
+/* bullets.c */
+void add_bullet(int x, int y, int dx, int dy, int color);
+void run_bullets(void);
+
+/* mystery.c */
+void mystery(void);
+
+#endif /* GRV_H */
diff --git a/grvfont.c b/grvfont.c
new file mode 100644
index 0000000..f8076b1
--- /dev/null
+++ b/grvfont.c
@@ -0,0 +1,258 @@
+unsigned char grv_font[256][14] = {
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xff,0x81,0xa5,0x81,0x81,0xa5,0x99,0x81,0xff,0x00,0x00,0x00},
+ {0x00,0x00,0x7c,0xfe,0xd6,0xfe,0xfe,0xc6,0xba,0xfe,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x6c,0xee,0xfe,0xfe,0xfe,0x7c,0x38,0x10,0x00,0x00,0x00},
+ {0x00,0x00,0x3c,0x7e,0x7e,0xff,0xff,0xff,0xff,0xff,0xff,0x7e,0x7e,0x3c},
+ {0x00,0x00,0x00,0x10,0x38,0x10,0x6c,0xee,0x6c,0x10,0x38,0x00,0x00,0x00},
+ {0x00,0x00,0x10,0x38,0x7c,0x7c,0xfe,0xfe,0x6c,0x10,0x38,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x3c,0x3c,0x3c,0x3c,0x00,0x00,0x00,0x00,0x00},
+ {0xff,0xff,0xff,0xff,0xff,0xc3,0xc3,0xc3,0xc3,0xff,0xff,0xff,0xff,0xff},
+ {0x00,0x00,0x00,0x00,0x7e,0x7e,0x66,0x66,0x7e,0x7e,0x00,0x00,0x00,0x00},
+ {0xff,0xff,0xff,0xff,0x81,0x81,0x99,0x99,0x81,0x81,0xff,0xff,0xff,0xff},
+ {0x00,0x00,0x0f,0x07,0x0d,0x18,0xfc,0xcc,0xcc,0xcc,0xfc,0x00,0x00,0x00},
+ {0x00,0x00,0x7e,0x66,0x66,0x66,0x7e,0x18,0x7e,0x18,0x18,0x00,0x00,0x00},
+ {0x00,0x00,0x1e,0x1a,0x1e,0x18,0x18,0x18,0xf8,0xf8,0xf8,0x00,0x00,0x00},
+ {0x00,0x3e,0x36,0x3e,0x36,0x36,0xf6,0xf6,0xf6,0x1e,0x1e,0x1e,0x00,0x00},
+ {0x00,0x18,0xdb,0x7e,0x3c,0x24,0x24,0x3c,0x7e,0xdb,0x18,0x00,0x00,0x00},
+ {0x00,0x00,0x80,0xe0,0xf0,0xfc,0xfe,0xfc,0xf0,0xe0,0x80,0x00,0x00,0x00},
+ {0x00,0x00,0x02,0x0e,0x3e,0x7e,0xfe,0x7e,0x3e,0x0e,0x02,0x00,0x00,0x00},
+ {0x00,0x00,0x18,0x3c,0x7e,0x18,0x18,0x18,0x7e,0x3c,0x18,0x00,0x00,0x00},
+ {0x00,0x00,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x66,0x66,0x00,0x00,0x00},
+ {0x00,0x00,0xff,0xdb,0xdb,0xdb,0xfb,0x1b,0x1b,0x1b,0x1b,0x00,0x00,0x00},
+ {0x00,0xfe,0xc0,0xc0,0xc0,0xfe,0xc6,0xc6,0xfe,0x06,0x06,0x06,0xfe,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0xfe,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x18,0x3c,0x7e,0x18,0x18,0x7e,0x3c,0x18,0x7e,0x00,0x00,0x00},
+ {0x00,0x00,0x18,0x3c,0x7e,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00},
+ {0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x7e,0x3c,0x18,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x0c,0x0e,0xff,0x0e,0x0c,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x30,0x70,0xfe,0x70,0x30,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xfe,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x24,0x66,0xff,0x66,0x24,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x10,0x38,0x38,0x38,0x7c,0x7c,0xfe,0xfe,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0xfe,0x7c,0x7c,0x7c,0x38,0x38,0x10,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x18,0x18,0x00,0x00,0x00},
+ {0x00,0x36,0x36,0x36,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x6c,0x6c,0x6c,0xfe,0x6c,0x6c,0xfe,0x6c,0x6c,0x00,0x00,0x00},
+ {0x00,0x18,0x18,0xfe,0xc0,0xc0,0xfe,0x06,0x06,0x06,0xfe,0x18,0x18,0x00},
+ {0x00,0x00,0x00,0x00,0x62,0x66,0x0c,0x18,0x30,0x66,0xc6,0x00,0x00,0x00},
+ {0x00,0x00,0x7c,0x6c,0x38,0x38,0x76,0xf6,0xce,0xcc,0xf6,0x00,0x00,0x00},
+ {0x00,0x0c,0x0c,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x38,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x38,0x00,0x00,0x00},
+ {0x00,0x00,0x1c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x1c,0x00,0x00,0x00},
+ {0x00,0x00,0x18,0x2c,0x46,0x83,0x46,0x3c,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x18,0x18,0x7e,0x18,0x18,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0c,0x0c,0x18,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x02,0x06,0x0c,0x18,0x30,0x60,0xc0,0x80,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0xc6,0xc6,0xc6,0xd6,0xc6,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x38,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0x06,0x06,0x06,0xfe,0xc0,0xc0,0xc0,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0x06,0x06,0x06,0x3e,0x06,0x06,0x06,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0xc6,0xc6,0xc6,0xc6,0xfe,0x06,0x06,0x06,0x06,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0xc0,0xc0,0xc0,0xfe,0x06,0x06,0x06,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0xc0,0xc0,0xc0,0xfe,0xc6,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0xc6,0xc6,0xc6,0xfe,0xc6,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0xc6,0xc6,0xc6,0xfe,0x06,0x06,0x06,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x0c,0x0c,0x00,0x00,0x00,0x0c,0x0c,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x0c,0x0c,0x00,0x00,0x00,0x0c,0x0c,0x18,0x00,0x00},
+ {0x00,0x00,0x00,0x0c,0x18,0x30,0x60,0x30,0x18,0x0c,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x7c,0x00,0x7c,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x30,0x18,0x0c,0x06,0x0c,0x18,0x30,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0xc6,0x06,0x06,0x1e,0x18,0x00,0x18,0x18,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0xc6,0xc6,0xde,0xde,0xde,0xde,0xc0,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0xc6,0xc6,0xc6,0xfe,0xc6,0xc6,0xc6,0xc6,0x00,0x00,0x00},
+ {0x00,0x00,0xfc,0xcc,0xcc,0xcc,0xfe,0xc6,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0xfc,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xfc,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0xc0,0xc0,0xc0,0xf8,0xc0,0xc0,0xc0,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0xc0,0xc0,0xc0,0xf8,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0xc6,0xc0,0xc0,0xce,0xc6,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0xc6,0xc6,0xc6,0xc6,0xfe,0xc6,0xc6,0xc6,0xc6,0x00,0x00,0x00},
+ {0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00},
+ {0x00,0x00,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0xcc,0xcc,0xfc,0x00,0x00,0x00},
+ {0x00,0x00,0xc6,0xcc,0xd8,0xf0,0xe0,0xf0,0xd8,0xcc,0xc6,0x00,0x00,0x00},
+ {0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0xc6,0xc6,0xee,0xfe,0xd6,0xc6,0xc6,0xc6,0xc6,0x00,0x00,0x00},
+ {0x00,0x00,0xc6,0xe6,0xf6,0xde,0xce,0xc6,0xc6,0xc6,0xc6,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0xc6,0xc6,0xc6,0xfe,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xd6,0xfe,0x06,0x00,0x00},
+ {0x00,0x00,0xfe,0xc6,0xc6,0xc6,0xfe,0xf0,0xd8,0xcc,0xc6,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0xc0,0xc0,0xc0,0xfe,0x06,0x06,0x06,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x7e,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00},
+ {0x00,0x00,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0xc6,0xc6,0xc6,0xc6,0xcc,0xd8,0xf0,0xe0,0xc0,0x00,0x00,0x00},
+ {0x00,0x00,0xc6,0xc6,0xc6,0xc6,0xd6,0xfe,0xee,0xc6,0xc6,0x00,0x00,0x00},
+ {0x00,0x00,0xc6,0xc6,0x6c,0x38,0x38,0x38,0x6c,0xc6,0xc6,0x00,0x00,0x00},
+ {0x00,0x00,0x66,0x66,0x66,0x66,0x7e,0x18,0x18,0x18,0x18,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0x06,0x0c,0x18,0x30,0x60,0xc0,0xc0,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x7c,0x60,0x60,0x60,0x60,0x60,0x60,0x60,0x7c,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x80,0xc0,0x60,0x30,0x18,0x0c,0x06,0x02,0x00,0x00,0x00},
+ {0x00,0x00,0x7c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x7c,0x00,0x00,0x00},
+ {0x00,0x10,0x38,0x6c,0xc6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff},
+ {0x00,0x18,0x18,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xfc,0x0c,0xfc,0xcc,0xcc,0xfc,0x00,0x00,0x00},
+ {0x00,0x00,0xc0,0xc0,0xc0,0xfe,0xc6,0xc6,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xfe,0xc0,0xc0,0xc0,0xc0,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x06,0x06,0x06,0xfe,0xc6,0xc6,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xfe,0xc6,0xfe,0xc0,0xc0,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x3c,0x30,0x30,0x30,0x78,0x30,0x30,0x30,0x30,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xfe,0xc6,0xc6,0xc6,0xfe,0x06,0x06,0xfe,0x00},
+ {0x00,0x00,0xc0,0xc0,0xc0,0xfe,0xc6,0xc6,0xc6,0xc6,0xc6,0x00,0x00,0x00},
+ {0x00,0x00,0x18,0x18,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00},
+ {0x00,0x00,0x0c,0x0c,0x00,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x1c,0x00},
+ {0x00,0x00,0xc0,0xc0,0xc0,0xcc,0xd8,0xf0,0xd8,0xcc,0xc6,0x00,0x00,0x00},
+ {0x00,0x00,0x38,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xfe,0xd6,0xd6,0xc6,0xc6,0xc6,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xfe,0xc6,0xc6,0xc6,0xc6,0xc6,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xfe,0xc6,0xc6,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xfe,0xc6,0xc6,0xc6,0xfe,0xc0,0xc0,0xc0,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xfe,0xc6,0xc6,0xc6,0xfe,0x06,0x06,0x06,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xfc,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xfe,0xc0,0xfe,0x06,0x06,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x30,0x30,0x30,0x78,0x30,0x30,0x30,0x30,0x38,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xc6,0xc6,0xc6,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xc6,0xcc,0xd8,0xf0,0xe0,0xc0,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xc6,0xc6,0xc6,0xd6,0xd6,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xc6,0x6c,0x38,0x38,0x6c,0xc6,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xc6,0xc6,0xc6,0xc6,0xfe,0x06,0x06,0xfe,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xfe,0x0c,0x18,0x30,0x60,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x01,0x03,0x06,0x06,0x06,0x06,0x06,0x06,0x03,0x01,0x00,0x00},
+ {0x00,0xfe,0x83,0x01,0x7c,0xc6,0xc0,0xc0,0xc6,0x7c,0x01,0x83,0xfe,0x00},
+ {0x00,0x00,0x00,0x80,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x80,0x00,0x00,0x00},
+ {0x00,0x00,0xf6,0xde,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x10,0x38,0x38,0x6c,0x6c,0xfe,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xfe,0x0c,0x0c,0x3c,0x00},
+ {0x00,0x00,0xc6,0xc6,0x00,0xc6,0xc6,0xc6,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x0c,0x18,0x30,0x00,0xfe,0xc6,0xfe,0xc0,0xc0,0xfe,0x00,0x00,0x00},
+ {0x10,0x38,0x6c,0xc6,0x00,0xfe,0x06,0xfe,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x6c,0x6c,0x00,0xfe,0x06,0xfe,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x60,0x30,0x18,0x00,0xfe,0x06,0xfe,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x7c,0x6c,0x7c,0x00,0xfe,0x06,0xfe,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0xfe,0xc0,0xc0,0xc0,0xc0,0xfe,0x0c,0x0c,0x3c,0x00},
+ {0x10,0x38,0x6c,0xc6,0x00,0xfe,0xc6,0xfe,0xc0,0xc0,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x6c,0x6c,0x00,0xfe,0xc6,0xfe,0xc0,0xc0,0xfe,0x00,0x00,0x00},
+ {0x00,0x30,0x18,0x0c,0x00,0xfe,0xc6,0xfe,0xc0,0xc0,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x66,0x66,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00},
+ {0x00,0x18,0x3c,0x66,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00},
+ {0x00,0x60,0x30,0x18,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00},
+ {0x00,0xc6,0xc6,0x00,0xfe,0xc6,0xc6,0xfe,0xc6,0xc6,0xc6,0x00,0x00,0x00},
+ {0x7c,0x6c,0x7c,0x00,0xfe,0xc6,0xc6,0xfe,0xc6,0xc6,0xc6,0x00,0x00,0x00},
+ {0x0c,0x18,0x30,0x00,0xfe,0xc0,0xc0,0xf8,0xc0,0xc0,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0xff,0x1b,0x1b,0xff,0xd8,0xd8,0xff,0x00,0x00,0x00},
+ {0x00,0xfe,0xd8,0xd8,0xd8,0xd8,0xfe,0xd8,0xd8,0xd8,0xde,0x00,0x00,0x00},
+ {0x10,0x38,0x6c,0xc6,0x00,0xfe,0xc6,0xc6,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x6c,0x6c,0x00,0xfe,0xc6,0xc6,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x30,0x18,0x0c,0x00,0xfe,0xc6,0xc6,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x10,0x38,0x6c,0xc6,0x00,0xc6,0xc6,0xc6,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x60,0x30,0x18,0x00,0xc6,0xc6,0xc6,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x6c,0x6c,0x00,0xc6,0xc6,0xc6,0xc6,0xfe,0x06,0x06,0xfe,0x00},
+ {0x00,0xc6,0xc6,0x00,0xfe,0xc6,0xc6,0xc6,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0xc6,0xc6,0x00,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x18,0x18,0xfe,0xc0,0xc0,0xc0,0xfe,0x18,0x18,0x00,0x00,0x00},
+ {0x00,0x00,0x7c,0x6c,0x60,0x60,0xf0,0x60,0x60,0x60,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x66,0x66,0x7e,0x18,0x7e,0x18,0x3c,0x18,0x18,0x00,0x00,0x00},
+ {0x00,0xfe,0xc6,0xfe,0xc0,0xcc,0xde,0xcc,0xcc,0xcc,0xce,0x00,0x00,0x00},
+ {0x00,0x1c,0x18,0x18,0x18,0x18,0x3c,0x18,0x18,0x18,0x18,0x18,0x38,0x00},
+ {0x00,0x0c,0x18,0x30,0x00,0xfe,0x06,0xfe,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x0c,0x18,0x30,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00},
+ {0x00,0x0c,0x18,0x30,0x00,0xfe,0xc6,0xc6,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x0c,0x18,0x30,0x00,0xc6,0xc6,0xc6,0xc6,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0xf6,0xde,0x00,0xfe,0xc6,0xc6,0xc6,0xc6,0xc6,0x00,0x00,0x00},
+ {0x00,0xf6,0xde,0x00,0xc6,0xe6,0xf6,0xde,0xce,0xc6,0xc6,0x00,0x00,0x00},
+ {0x00,0x3e,0x66,0x66,0x3e,0x00,0x7e,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x7e,0x66,0x66,0x7e,0x00,0x7e,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x30,0x30,0x00,0x30,0xf0,0xc0,0xc0,0xc6,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x7e,0x60,0x60,0x60,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x7e,0x06,0x06,0x06,0x00,0x00,0x00,0x00},
+ {0x00,0x38,0x18,0x18,0x18,0x00,0x7e,0x00,0x7e,0x06,0x7e,0x60,0x7e,0x00},
+ {0x00,0x1c,0x0c,0x0c,0x0c,0x00,0x7e,0x00,0x66,0x66,0x7e,0x06,0x06,0x00},
+ {0x00,0x00,0x18,0x18,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x36,0x6c,0xd8,0x6c,0x36,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0xd8,0x6c,0x36,0x6c,0xd8,0x00,0x00,0x00,0x00,0x00},
+ {0x11,0x44,0x10,0x44,0x11,0x44,0x01,0x44,0x11,0x40,0x11,0x04,0x11,0x44},
+ {0x44,0xaa,0x55,0x2a,0x55,0xaa,0x45,0x2a,0x55,0xaa,0x51,0xaa,0x15,0xaa},
+ {0xdd,0x77,0xdd,0x77,0xdd,0x77,0xdd,0x77,0xdd,0x77,0xdd,0x77,0xdd,0x77},
+ {0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18},
+ {0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xf8,0x18,0x18,0x18,0x18,0x18,0x18},
+ {0x18,0x18,0x18,0x18,0x18,0xf8,0x18,0xf8,0x18,0x18,0x18,0x18,0x18,0x18},
+ {0x36,0x36,0x36,0x36,0x36,0x36,0x36,0xf6,0x36,0x36,0x36,0x36,0x36,0x36},
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe,0x36,0x36,0x36,0x36,0x36,0x36},
+ {0x00,0x00,0x00,0x00,0x00,0xf8,0x18,0xf8,0x18,0x18,0x18,0x18,0x18,0x18},
+ {0x36,0x36,0x36,0x36,0x36,0xf6,0x06,0xf6,0x36,0x36,0x36,0x36,0x36,0x36},
+ {0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36},
+ {0x00,0x00,0x00,0x00,0x00,0xfe,0x06,0xf6,0x36,0x36,0x36,0x36,0x36,0x36},
+ {0x36,0x36,0x36,0x36,0x36,0xf6,0x06,0xfe,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x36,0x36,0x36,0x36,0x36,0x36,0x36,0xfe,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x18,0x18,0x18,0x18,0x18,0xf8,0x18,0xf8,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf8,0x18,0x18,0x18,0x18,0x18,0x18},
+ {0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x1f,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xff,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x18,0x18,0x18,0x18,0x18,0x18},
+ {0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x1f,0x18,0x18,0x18,0x18,0x18,0x18},
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xff,0x18,0x18,0x18,0x18,0x18,0x18},
+ {0x18,0x18,0x18,0x18,0x18,0x1f,0x18,0x1f,0x18,0x18,0x18,0x18,0x18,0x18},
+ {0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x37,0x36,0x36,0x36,0x36,0x36,0x36},
+ {0x36,0x36,0x36,0x36,0x36,0x37,0x30,0x3f,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x3f,0x30,0x37,0x36,0x36,0x36,0x36,0x36,0x36},
+ {0x36,0x36,0x36,0x36,0x36,0xf7,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xff,0x00,0xf7,0x36,0x36,0x36,0x36,0x36,0x36},
+ {0x36,0x36,0x36,0x36,0x36,0x37,0x30,0x37,0x36,0x36,0x36,0x36,0x36,0x36},
+ {0x00,0x00,0x00,0x00,0x00,0xff,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x36,0x36,0x36,0x36,0x36,0xf7,0x00,0xf7,0x36,0x36,0x36,0x36,0x36,0x36},
+ {0x18,0x18,0x18,0x18,0x18,0xff,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x36,0x36,0x36,0x36,0x36,0x36,0x36,0xff,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xff,0x00,0xff,0x18,0x18,0x18,0x18,0x18,0x18},
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x36,0x36,0x36,0x36,0x36,0x36},
+ {0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x3f,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x18,0x18,0x18,0x18,0x18,0x1f,0x18,0x1f,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x1f,0x18,0x1f,0x18,0x18,0x18,0x18,0x18,0x18},
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3f,0x36,0x36,0x36,0x36,0x36,0x36},
+ {0x36,0x36,0x36,0x36,0x36,0x36,0x36,0xff,0x36,0x36,0x36,0x36,0x36,0x36},
+ {0x18,0x18,0x18,0x18,0x18,0xff,0x18,0xff,0x18,0x18,0x18,0x18,0x18,0x18},
+ {0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xf8,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1f,0x18,0x18,0x18,0x18,0x18,0x18},
+ {0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff},
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff},
+ {0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0,0xf0},
+ {0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f},
+ {0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0xfe,0xd8,0xd8,0xd8,0xd8,0xd8,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0xfc,0xcc,0xcc,0xfe,0xc6,0xc6,0xfe,0xc0,0xc0,0x00},
+ {0x00,0x00,0xfe,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xfe,0x6c,0x6c,0x6c,0x6c,0x6c,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0xc0,0x60,0x30,0x18,0x30,0x60,0xc0,0xfe,0x00,0x00,0x00},
+ {0x22,0x08,0x1a,0x18,0x3c,0x7e,0xf3,0xfb,0xff,0xff,0x7e,0x3c,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0xc6,0xc6,0xc6,0xc6,0xfe,0xc0,0xc0,0xc0,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x7e,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0x38,0xfe,0xc6,0xc6,0xc6,0xfe,0x38,0xfe,0x00,0x00,0x00},
+ {0x00,0x00,0x7c,0xee,0xc6,0xc6,0xfe,0xc6,0xc6,0xee,0x7c,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0xc6,0xc6,0xc6,0xc6,0xee,0x6c,0x6c,0xee,0x00,0x00,0x00},
+ {0x00,0x00,0x20,0x60,0x60,0x30,0x7c,0xee,0xc6,0xee,0x7c,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x3c,0x3c,0x3c,0x7e,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x06,0x0c,0xfe,0xc6,0xc6,0xfe,0x60,0xc0,0x00,0x00,0x00},
+ {0x00,0x00,0x18,0x30,0x60,0x60,0x78,0x60,0x60,0x30,0x18,0x00,0x00,0x00},
+ {0x00,0x00,0xfe,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xfe,0x00,0xfe,0x00,0xfe,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x18,0x18,0x7e,0x18,0x18,0x00,0x7e,0x00,0x00,0x00},
+ {0x00,0x00,0x30,0x18,0x0c,0x06,0x0c,0x18,0x30,0x00,0x7e,0x00,0x00,0x00},
+ {0x00,0x00,0x0c,0x18,0x30,0x60,0x30,0x18,0x0c,0x00,0x7e,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x1e,0x1a,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18},
+ {0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x58,0x58,0x78,0x00,0x00},
+ {0x00,0x00,0x00,0x18,0x18,0x00,0x7e,0x00,0x18,0x18,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xf6,0xde,0x00,0xf6,0xde,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xfc,0xcc,0xcc,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x1f,0x18,0x18,0x18,0x18,0xd8,0x78,0x38,0x18,0x00,0x00},
+ {0x00,0xfc,0xcc,0xcc,0xcc,0xcc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0xf8,0x18,0xf8,0xc0,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
+ {0x00,0x0c,0x18,0x18,0x7e,0xff,0xff,0x3f,0x1f,0x3f,0xff,0xff,0x7e,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
+};
diff --git a/grvfont.psf b/grvfont.psf
new file mode 100644
index 0000000..fac51ff
--- /dev/null
+++ b/grvfont.psf
Binary files differ
diff --git a/keyboard.c b/keyboard.c
new file mode 100644
index 0000000..06d57b9
--- /dev/null
+++ b/keyboard.c
@@ -0,0 +1,75 @@
+/*
+ * keyboard.c
+ *
+ * Basic keyboard handing functions -- allows event-handling loops
+ * to push keyboard events onto a queue so they can be processed at
+ * the appropriate time in the game rounds loop
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <math.h>
+#include "graphics.h"
+#include "grv.h"
+
+#define KEYQUEUELEN 16
+
+static SDL_KeyboardEvent keyqueue[KEYQUEUELEN];
+static SDL_KeyboardEvent *kqh = keyqueue;
+static SDL_KeyboardEvent *kqt = keyqueue;
+static int queuedkeys = 0;
+
+/*
+ * Push a keyboard event onto the queue; use this in event loops
+ */
+void push_key(SDL_KeyboardEvent *ke)
+{
+ if ( queuedkeys >= KEYQUEUELEN )
+ return; /* Drop it */
+
+ queuedkeys++;
+ *kqh++ = *ke;
+ if ( kqh >= &keyqueue[KEYQUEUELEN] )
+ kqh = keyqueue;
+}
+
+/*
+ * Poll the keyboard queue for a key event
+ */
+SDL_KeyboardEvent *poll_key(void)
+{
+ SDL_KeyboardEvent *ke;
+
+ if ( queuedkeys ) {
+ queuedkeys--;
+ ke = kqt++;
+ if ( kqt >= &keyqueue[KEYQUEUELEN] )
+ kqt = keyqueue;
+ return ke;
+ } else {
+ return NULL;
+}
+}
+
+/*
+ * Get a key, pausing if necessary
+ */
+SDL_KeyboardEvent *get_key(void)
+{
+ static SDL_Event event;
+ SDL_KeyboardEvent *ke;
+
+ if ( (ke = poll_key()) )
+ return ke;
+
+ while ( SDL_WaitEvent(&event) ) {
+ if ( event.type == SDL_KEYDOWN )
+ return &event.key;
+ else if ( event.type == SDL_USEREVENT && event.user.code == event_blink )
+ update_blink();
+ }
+ return NULL;
+}
+
diff --git a/mystery.c b/mystery.c
new file mode 100644
index 0000000..f5b8f3a
--- /dev/null
+++ b/mystery.c
@@ -0,0 +1,108 @@
+/*
+ * mystery.c
+ *
+ * Handle "mystery treasures" (?-marks)
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <math.h>
+#include "graphics.h"
+#include "grv.h"
+
+void mystery(void)
+{
+ int myst;
+ int tlife;
+
+ color(0,gp.c);
+
+ again:
+ tlife = (6-gp.Life)*20;
+ myst = irnd(1230+tlife) - tlife;
+
+ if ( myst < 0 ) {
+ /* Extra life */
+ gp.Life++;
+ message(0,"Treasure: Extra Life");
+ color(14,gp.c);
+ lprintf(1,16+2*gp.Life, "\x01");
+ } else if ( myst < 150 ) {
+ /* Stingy extra points */
+ int SSq = (irnd(200)+100)*gp.Level;
+
+ gp.Sc += SSq;
+ message(0,"Treasure: %d points", SSq);
+ update_score();
+ } else if ( myst < 350 ) {
+ /* Moderate extra points */
+ int SSq = (irnd(800)+400)*gp.Level;
+
+ gp.Sc += SSq;
+ message(0,"Treasure: %d points", SSq);
+ update_score();
+ } else if ( myst < 400 ) {
+ /* Decent extra points */
+ int SSq = (irnd(2000)+1000)*gp.Level;
+
+ gp.Sc += SSq;
+ message(0,"Treasure: %d points", SSq);
+ update_score();
+ } else if ( myst < 470 ) {
+ message(0,"Treasure: End of Level");
+ mymssleep(gp.Speed*7);
+ gp.Status = Status_Quit;
+ } else if ( myst < 650 ) {
+ /* Ghost freeze */
+ int e;
+ double thaw;
+
+ if ( gp.FS != 0 )
+ goto again;
+
+ message(0,"Treasure: Ghost freeze");
+ gp.FS = 0; /* Ghosts frozen */
+
+ color(10,0);
+ for ( e = 0 ; e < MAXGHOST ; e++ ) {
+ if ( !ghost[e].dead )
+ lprint(ghost[e].x, ghost[e].y, "\x02");
+ }
+
+ /* Just in case we have scared ghosts right now... */
+ removeaction(0, 0, act_ghost_thaw);
+ removeaction(0, 0, act_ghost_normal);
+
+ thaw = gp.Tid + 32.0 + 60.0*rnd();
+ addaction(0, 0, thaw, act_ghost_thaw);
+ addaction(0, 0, thaw+8.0, act_ghost_normal);
+ } else if ( myst < 790 ) {
+ /* Extra ammo */
+ int NewKulSpr = irnd(3) + 2;
+
+ gp.KulSpr += NewKulSpr;
+ message(0,"Treasure: %d extra shots", NewKulSpr);
+ update_shots();
+ } else if ( myst < 930 ) {
+ /* Bomb detonation */
+
+ message(0,"Treasure: Bomb detonation... OOPS!");
+ retime_all(gp.Tid+4.0, act_bomb);
+ } else if ( myst < 1060 ) {
+ /* Smash! */
+
+ message(0,"Treasure: *** SMASH ***");
+ addaction(0, 0, gp.Tid+5.0, act_smash);
+ } else {
+ /* Scared ghosts */
+ if ( gp.FS != 1 )
+ goto again;
+
+ message(0, "Treasure: Scared ghosts");
+
+ gp.FS = -1; /* Reverse direction */
+ addaction(0, 0, gp.Tid+80.0, act_ghost_normal);
+ }
+}
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);
+ }
+}
diff --git a/psftoc.pl b/psftoc.pl
new file mode 100755
index 0000000..0038bdf
--- /dev/null
+++ b/psftoc.pl
@@ -0,0 +1,24 @@
+#!/usr/bin/perl
+#
+# Convert PSF input to a C structure
+#
+
+eval { use bytes; };
+
+read(STDIN, $hdr, 4);
+
+($magic, $mode, $size) = unpack("vCC", $hdr);
+
+die "$0: Not a PSF1 file on input\n" if ( $magic != 0x0436 );
+
+print "unsigned char grv_font[256][$size] = {\n";
+
+for ( $i = 0 ; $i < 256 ; $i++ ) {
+ print " ";
+ for ( $j = 0 ; $j < $size ; $j++ ) {
+ read(STDIN, $byte, 1);
+ printf "%s0x%02x", ($j == 0 ? '{' : ','), unpack("C",$byte);
+ }
+ print "},\n";
+}
+print "};\n";
diff --git a/random.c b/random.c
new file mode 100644
index 0000000..e86b14d
--- /dev/null
+++ b/random.c
@@ -0,0 +1,171 @@
+/*
+ A C-program for MT19937, with initialization improved 2002/1/26.
+ Coded by Takuji Nishimura and Makoto Matsumoto.
+
+ Before using, initialize the state by using init_genrand(seed)
+ or init_by_array(init_key, key_length).
+
+ Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ 3. The names of its contributors may not be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+ Any feedback is very welcome.
+ http://www.math.keio.ac.jp/matumoto/emt.html
+ email: matumoto@math.keio.ac.jp
+*/
+
+#include <stdio.h>
+
+/* Period parameters */
+#define N 624
+#define M 397
+#define MATRIX_A 0x9908b0dfUL /* constant vector a */
+#define UPPER_MASK 0x80000000UL /* most significant w-r bits */
+#define LOWER_MASK 0x7fffffffUL /* least significant r bits */
+
+static unsigned long mt[N]; /* the array for the state vector */
+static int mti=N+1; /* mti==N+1 means mt[N] is not initialized */
+
+/* initializes mt[N] with a seed */
+void init_genrand(unsigned long s)
+{
+ mt[0]= s & 0xffffffffUL;
+ for (mti=1; mti<N; mti++) {
+ mt[mti] =
+ (1812433253UL * (mt[mti-1] ^ (mt[mti-1] >> 30)) + mti);
+ /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
+ /* In the previous versions, MSBs of the seed affect */
+ /* only MSBs of the array mt[]. */
+ /* 2002/01/09 modified by Makoto Matsumoto */
+ mt[mti] &= 0xffffffffUL;
+ /* for >32 bit machines */
+ }
+}
+
+/* initialize by an array with array-length */
+/* init_key is the array for initializing keys */
+/* key_length is its length */
+void init_by_array(init_key, key_length)
+unsigned long init_key[], key_length;
+{
+ int i, j, k;
+ init_genrand(19650218UL);
+ i=1; j=0;
+ k = (N>key_length ? N : key_length);
+ for (; k; k--) {
+ mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1664525UL))
+ + init_key[j] + j; /* non linear */
+ mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */
+ i++; j++;
+ if (i>=N) { mt[0] = mt[N-1]; i=1; }
+ if (j>=key_length) j=0;
+ }
+ for (k=N-1; k; k--) {
+ mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1566083941UL))
+ - i; /* non linear */
+ mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */
+ i++;
+ if (i>=N) { mt[0] = mt[N-1]; i=1; }
+ }
+
+ mt[0] = 0x80000000UL; /* MSB is 1; assuring non-zero initial array */
+}
+
+/* generates a random number on [0,0xffffffff]-interval */
+unsigned long genrand_int32(void)
+{
+ unsigned long y;
+ static unsigned long mag01[2]={0x0UL, MATRIX_A};
+ /* mag01[x] = x * MATRIX_A for x=0,1 */
+
+ if (mti >= N) { /* generate N words at one time */
+ int kk;
+
+ if (mti == N+1) /* if init_genrand() has not been called, */
+ init_genrand(5489UL); /* a default initial seed is used */
+
+ for (kk=0;kk<N-M;kk++) {
+ y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
+ mt[kk] = mt[kk+M] ^ (y >> 1) ^ mag01[y & 0x1UL];
+ }
+ for (;kk<N-1;kk++) {
+ y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
+ mt[kk] = mt[kk+(M-N)] ^ (y >> 1) ^ mag01[y & 0x1UL];
+ }
+ y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK);
+ mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1UL];
+
+ mti = 0;
+ }
+
+ y = mt[mti++];
+
+ /* Tempering */
+ y ^= (y >> 11);
+ y ^= (y << 7) & 0x9d2c5680UL;
+ y ^= (y << 15) & 0xefc60000UL;
+ y ^= (y >> 18);
+
+ return y;
+}
+
+/* generates a random number on [0,0x7fffffff]-interval */
+long genrand_int31(void)
+{
+ return (long)(genrand_int32()>>1);
+}
+
+/* generates a random number on [0,1]-real-interval */
+double genrand_real1(void)
+{
+ return genrand_int32()*(1.0/4294967295.0);
+ /* divided by 2^32-1 */
+}
+
+/* generates a random number on [0,1)-real-interval */
+double genrand_real2(void)
+{
+ return genrand_int32()*(1.0/4294967296.0);
+ /* divided by 2^32 */
+}
+
+/* generates a random number on (0,1)-real-interval */
+double genrand_real3(void)
+{
+ return (((double)genrand_int32()) + 0.5)*(1.0/4294967296.0);
+ /* divided by 2^32 */
+}
+
+/* generates a random number on [0,1) with 53-bit resolution*/
+double genrand_res53(void)
+{
+ unsigned long a=genrand_int32()>>5, b=genrand_int32()>>6;
+ return(a*67108864.0+b)*(1.0/9007199254740992.0);
+}
diff --git a/test.c b/test.c
new file mode 100644
index 0000000..cc634dc
--- /dev/null
+++ b/test.c
@@ -0,0 +1,25 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "graphics.h"
+
+int main(int argc, char *argv[])
+{
+ int i, j;
+
+ screen_init();
+ color(0, 1);
+ cls();
+
+ for ( i = 0 ; i < 16 ; i++ ) {
+ for ( j = 0 ; j < 8 ; j++ ) {
+ locate(j+1,i+1);
+ color(i,j);
+ print("\xfe");
+ }
+ }
+
+ while ( getchar() != '\n' );
+
+ return 0;
+}
diff --git a/utils.c b/utils.c
new file mode 100644
index 0000000..1544a02
--- /dev/null
+++ b/utils.c
@@ -0,0 +1,86 @@
+/*
+ * utils.c
+ *
+ * Utility functions
+ */
+
+#include "grv.h"
+
+/* Foreground color of a specific character */
+int fg(int r, int c)
+{
+ return screen1(r,c) & 15;
+}
+
+/* Background color of a specific character */
+int bg(int r, int c)
+{
+ return (screen1(r,c) >> 4) & 7;
+}
+
+/* Is there something at this screen location? */
+int busy(int r, int c)
+{
+ return (screen(r,c) != ' ') || (bg(r,c) == 0);
+}
+
+/* Format time elapsed in MM:SS.s format */
+char *format_time(double t)
+{
+ static char ts[10];
+ int ds = (int)(t*10.0); /* Deciseconds */
+
+ sprintf(ts, "%02d:%02d.%01d", ds/600, (ds%600)/10, ds%10);
+
+ return ts;
+}
+
+/* Standard callback routine to post a periodic user event */
+/* (int)param is the event code */
+Uint32 post_periodic(Uint32 interval, void *param)
+{
+ SDL_Event event;
+ event.type = SDL_USEREVENT;
+ event.user.code = (int)param;
+ event.user.data1 = event.user.data2 = 0;
+
+ SDL_PushEvent(&event);
+
+ return interval;
+}
+
+/* Standard callback routine to post a periodic user event */
+/* (int)param is the event code */
+Uint32 post_oneshot(Uint32 interval, void *param)
+{
+ SDL_Event event;
+ event.type = SDL_USEREVENT;
+ event.user.code = (int)param;
+ event.user.data1 = event.user.data2 = 0;
+
+ (void)interval;
+
+ SDL_PushEvent(&event);
+
+ return 0;
+}
+
+/* Very simple event handler which just posts a timer and handles
+ blink events until the timer expires */
+void mymssleep(int ms)
+{
+ SDL_TimerID mytimer = SDL_AddTimer(ms, post_oneshot, (void *)event_sleep);
+ SDL_Event event;
+
+ while ( SDL_WaitEvent(&event) ) {
+ if ( event.type == SDL_USEREVENT ) {
+ if ( event.user.code == event_sleep ) {
+ SDL_RemoveTimer(mytimer);
+ return;
+ } else if ( event.user.code == event_blink )
+ update_blink();
+ } else if ( event.type == SDL_KEYDOWN ) {
+ push_key(&event.key);
+ }
+ }
+}