1 /* This program was written by Alexander Siegel in September of 1989   */
2 /* at Cornell University.  It may may copied freely for private use or */
3 /* public dispersion provided that this comment is not removed.  This  */
4 /* program, any portion of this program, or any derivative of this     */
5 /* program may not be sold or traded for financial gain.               */
6 
7 #include <sys/types.h>
8 #include <sys/param.h>
9 #include <sys/stat.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <X11/Xlib.h>
13 #include <X11/keysym.h>
14 #include <X11/Xutil.h>
15 #include <errno.h>
16 #include <string.h>
17 
18 #define GOLDDIG_EXTERN
19 
20 #include <golddig.h>
21 /* Include all the bitmaps for the terrain blocks */
22 #include <bitmaps.h>
23 
24 int savehighscore = 1; /* by default, do save highscores */
25 
26 /* All in and out movements except up */
27 #define NOUPBITS   DLEAVE | LLEAVE | RLEAVE | HENTER | VENTER
28 /* All in and out movements */
29 #define MOVEBITS   NOUPBITS | ULEAVE
30 /* Standard bit pattern for empty space */
31 #define SPACEBITS  NOUPBITS | DIGUND | DFALL
32 /* Generic item which can be picked up */
33 #define ITEMBITS   SPACEBITS | PICKUP
34 /* Bit pattern used for dug holes */
35 #define HOLEBITS   SPACEBITS | STOPBAD | NODRAW
36 
37 /* Structure describing all the characteristics of all blocks.  Refer */
38 /* to the defines and structure definition in golddig.h. */
39 struct symbs_s symbs[] =
40   {{SPACE,SPACE,"space",space_bits,SPACEBITS,XK_space,0, "black"},
41    {'!','|',"escape",escape_bits,MOVEBITS | DIGUND | INACTIVE,XK_exclam,XK_1, "blue"},
42    {BRICK,BRICK,"wall",wall_bits,CANDIG | KILLIN,XK_3,XK_numbersign, "firebrick"},
43    {'$','$',"gold",gold_bits,ITEMBITS | TREASURE,XK_4,XK_dollar, "gold"},
44    {'-','-',"rope",rope_bits,NOUPBITS | DIGUND,XK_minus,0, "wheat"},
45    {HOLE1,HOLE1,"hole1",hole1_bits,HOLEBITS,0,0, "firebrick"},
46    {HOLE1+1,HOLE1+1,"hole2",hole2_bits,HOLEBITS,0,0, "firebrick"},
47    {HOLE1+2,HOLE1+2,"hole3",hole3_bits,HOLEBITS,0,0, "firebrick"},
48    {HOLE1+3,HOLE1+3,"hole4",hole4_bits,HOLEBITS,0,0, "firebrick"},
49    {HOLE1+4,HOLE1+4,"hole5",hole5_bits,HOLEBITS,0,0, "firebrick"},
50    {HOLE1+5,HOLE1+5,"hole6",hole6_bits,HOLEBITS,0,0, "firebrick"},
51    {HOLE1+6,HOLE1+6,"hole7",hole7_bits,HOLEBITS,0,0, "firebrick"},
52    {'<','<',"left-arrow",larrow_bits,LLEAVE | HENTER | VENTER | LFALL | DIGUND,
53       XK_comma,XK_less, "orange"},
54    {'=','=',"tube",tube_bits,RLEAVE | LLEAVE | HENTER,XK_equal,0, "purple"},
55    {'>','>',"right-arrow",rarrow_bits,RLEAVE | HENTER | VENTER | RFALL |
56       DIGUND,XK_period,XK_greater, "orange"},
57    {STONE,STONE,"stone",stone_bits,KILLIN,XK_2,XK_at, "dimgrey"},
58    {'^','^',"anti-space",anti_bits,ULEAVE | LLEAVE | RLEAVE | HENTER |
59       VENTER | DIGUND | UFALL,XK_6,XK_asciicircum, "PaleGreen"},
60    {'a','a',"armor",armor_bits,ITEMBITS | ARMOR,XK_A,XK_a, "yellow"},
61    {BADGUY,BADGUY,"bad-guy",badguy_bits,SPACEBITS,XK_B,XK_b, "white"},
62    {'c','c',"chute",chute_bits,DLEAVE | DFALL | VENTER,XK_C,XK_c, "purple"},
63    {'d','d',"down-arrow",darrow_bits,DLEAVE | HENTER | VENTER | DIGUND |
64       DFALL,XK_D,XK_d, "orange"},
65    {'e','e',"reverse-monster",reverse_bits,ITEMBITS | REVERSE, XK_E,XK_e, "ForestGreen"},
66    {'f','f',"up-arrow",uarrow_bits,ULEAVE | HENTER | VENTER | UFALL |
67       DIGUND,XK_F,XK_f, "orange"},
68    {'g',BRICK,"ghost-brick",ghost_bits,(SPACEBITS) & ~HENTER,XK_G,XK_g, "firebrick"},
69    {'i',SPACE,"invisible-block",invis_bits,KILLIN,XK_I,XK_i,"firebrick"},
70    {'j','j',"jump-pad",jump_bits,NOUPBITS | UPTWO,XK_J,XK_j, "IndianRed"},
71    {'k','k',"kill-zone",kill_bits,HENTER | VENTER | DIGUND |
72       KILLIN,XK_K,XK_k, "green"},
73    {'l','l',"power-shovel",pshovel_bits,ITEMBITS | PSHOVEL,XK_L,XK_l, "SandyBrown"},
74    {'m','m',"super-jump-pad",sjump_bits,NOUPBITS | UPALL,XK_M,XK_m, "red"},
75    {'n','n',"nuclear-shovel",nshovel_bits,ITEMBITS | NSHOVEL,XK_N,XK_n, "tan"},
76    {'o','o',"anchor",anchor_bits,ITEMBITS | ANCHOR,XK_O,XK_o, "DodgerBlue"},
77    {PLAYER,PLAYER,"player",player_bits,SPACEBITS,XK_P,XK_p, "white"},
78    {'q','q',"speed-boot",speed_bits,ITEMBITS | SPEED,XK_Q,XK_q, "sienna"},
79    {'r','r',"parachute",parac_bits,ITEMBITS | STOPFALL,XK_R,XK_r, "LightCyan"},
80    {'s','s',"step-ladder",steplad_bits,MOVEBITS | PICKUP,XK_S,XK_s, "turquoise"},
81    {'t','t',"teleporter",teleport_bits,SPACEBITS | TELEPORT,XK_T,XK_t, "VioletRed"},
82    {'u','u',"leave-level",uplevel_bits,SPACEBITS | UPLEVEL |
83       INACTIVE,XK_U,XK_u, "brown"},
84    {'v','v',"vertical-rope",vrope_bits,ULEAVE | DLEAVE |
85       HENTER | VENTER,XK_V,XK_v, "wheat"},
86    {'w','w',"window",window_bits,SPACEBITS | UPLEVEL,XK_W,XK_w, "cyan"},
87    {'x','x',"extra-brick",rshovel_bits,ITEMBITS | RSHOVEL,XK_X,XK_x, "tomato"},
88    {'y','y',"heavy-fog",fog_bits,SPACEBITS,XK_Y,XK_y, "DarkSlateGrey"},
89    {'z','z',"time-stop",hourgl_bits,ITEMBITS | TIMESTOP,XK_Z,XK_z, "magenta"},
90    {'|','|',"ladder",ladder_bits,MOVEBITS,XK_backslash,XK_bar, "CornflowerBlue"},
91    {'~','-',"portable-rope",portable_bits,NOUPBITS | DIGUND |
92       PICKUP,XK_asciitilde,XK_quoteleft, "wheat"},
93 
94    /* List terminator */
95    {'\0','\0',(char *) 0,(char *) 0,0,0,0}};
96 
97 Font scorefont;     /* Font used to display score */
98 XFontStruct *scorestruct;
99 int scoresize;      /* Size of score font */
100 GC scoregc;         /* GC used to draw score */
101 GC blackgc;         /* Simple black foreground GC */
102 GC livesgc;         /* For drawing player in score line */
103 
104 /* These are the graphics cursors used for drawing the player at */
105 /* various times. */
106 GC standgc,angelgc,angelugc,angellgc,flygc,hang1gc,hang2gc,up1gc,up2gc;
107 GC left1gc,left2gc,right1gc,right2gc;
108 /* Graphics cursors for drawing the possible states of the badguys */
109 GC badguy1gc,badguy2gc,badguy3gc;
110 
111 /* Manufaction a 16x16 graphics cursor used in a XFill... operation. */
112 /* The FillTiled style is used. */
113 
fill_gc(func,bits,item,color)114 static GC fill_gc(func, bits, item, color)
115 int func;       /* Drawing function such as GXcopy or GXor. */
116 char *bits;    /* Bits describing fill pattern.  Produced in an X11 */
117                 /* bitmap file usually. */
118 char *item, *color; /* item and color */
119 
120 {
121   XGCValues gcv;
122   XColor got, hardw;
123   Pixmap pmap;
124 
125   /* Build X11 bitmap from data in bits */
126 
127   if (DisplayCells(disp, scrn) <= 2) {
128 
129     /*
130      * monochrome
131      */
132 
133     pmap = XCreatePixmapFromBitmapData
134       (disp,wind,bits,16,16,BlackPixel(disp,scrn),WhitePixel(disp,scrn),
135        DisplayPlanes(disp,scrn));
136     gcv.foreground = BlackPixel(disp,scrn);
137     gcv.background = WhitePixel(disp,scrn);
138 
139   } else {
140 
141     /*
142      * color
143      */
144 
145     if (!XAllocNamedColor(disp, XDefaultColormap(disp, scrn), color,
146 			  &got, &hardw)) {
147       printf("XAllocNamedColor failed for %s color %s!\n", item, color);
148       printf("using WhitePixel\n");
149       hardw.pixel = WhitePixel(disp, scrn);
150     }
151 
152     pmap = XCreatePixmapFromBitmapData
153       (disp,wind,bits,16,16, hardw.pixel, background,
154        DisplayPlanes(disp,scrn));
155 
156     gcv.foreground = hardw.pixel;
157     gcv.background = background;
158 
159   }
160 
161   /* Assign the graphics cursor parameters */
162   gcv.function = func;
163   gcv.tile = pmap;
164   gcv.fill_style = FillTiled;
165   /* Return the created graphics cursor */
166   return(XCreateGC(disp,wind,GCFunction | GCForeground | GCBackground |
167                    GCTile | GCFillStyle,&gcv));
168 }
169 
170 /* Manufaction a 16x16 graphics cursor used in a XFill... operation. */
171 /* The FillStippled style is used. */
stip_gc(func,bits,pix)172 static GC stip_gc(func, bits, pix)
173 int func, pix;
174 char bits[];
175 {
176   XGCValues gcv;
177   Pixmap pmap;
178 
179   /* Build X11 bitmap from data in bits */
180   pmap = XCreateBitmapFromData(disp,wind,bits,16,16);
181   /* Assign the graphics cursor parameters */
182   gcv.function = func;
183   gcv.foreground = pix;
184   gcv.fill_style = FillStippled;
185   gcv.stipple = pmap;
186   /* Return the created graphics cursor */
187   return(XCreateGC(disp,wind,GCFunction | GCFillStyle |
188 		   GCForeground | GCStipple,&gcv));
189 }
190 
191 /* Start X11 and do some basic initialization */
xstart(evmask,argc,argv)192 void xstart(evmask, argc, argv)
193 long evmask;    /* Event mask which will be used in XSelectInput */
194 int argc;
195 char **argv;
196 {
197   register int i;
198   XGCValues xgcv;
199   XWMHints wmhints;
200   XSetWindowAttributes xswa;
201   unsigned int xswamask;
202   XSizeHints hints;
203   int x,y;
204   unsigned int w,h;
205   Window rootW;
206   char *resource;
207   XColor hardw, got;
208   unsigned long tmp, score_fg, score_bg, mecol, badcol;
209 
210   /* Open up the display */
211   disp = XOpenDisplay(NULL);
212   /* Check to see if the open display succeeded */
213   if(disp == NULL) {
214     fprintf(stderr,"Display open failed.  Check DISPLAY environment variable.\n");
215     exit(-1);
216   }
217 
218   /* Set the default screen number */
219   scrn = DefaultScreen(disp);
220   /* Get the root window */
221   rootW = RootWindow(disp,scrn);
222   /* Get the window position, width, and height */
223   x = y = w = h = 1;
224   i = XParseGeometry (geom, &x, &y, &w, &h);
225 
226   /* Set values of fields in hints structure */
227   if(i & WidthValue) hints.width = w;   else hints.width = 50 << 4;
228   if(i & HeightValue) hints.height = h; else hints.height =  (30 << 4)
229     + scoresize;
230   if(i & XValue) {
231     if(i & XNegative)
232       hints.x = XDisplayWidth(disp,scrn) - hints.width - abs(x);
233     else
234       hints.x = x;
235   }
236   else
237     hints.x = 20;
238   if(i & YValue) {
239     if(i & YNegative)
240       hints.y = XDisplayHeight(disp,scrn) - hints.height - abs(y);
241     else
242       hints.y = y;
243   }
244   else
245     hints.y = 20;
246   hints.max_width = DisplayWidth(disp,scrn);
247   hints.max_height = DisplayHeight(disp,scrn);
248   hints.flags = PMaxSize | PPosition | PSize;
249 
250   /* Build window attributes */
251   xswa.background_pixel = BlackPixel(disp,scrn);
252   xswa.border_pixel = WhitePixel(disp,scrn);
253   xswamask = CWBackPixel | CWBorderPixel;
254   /* Create Window */
255   wind = XCreateWindow(disp, rootW, hints.x, hints.y, hints.width,
256                        hints.height, 2, 0, CopyFromParent,
257                        CopyFromParent, xswamask, &xswa);
258   /* Check to see if the open window succeeded */
259   if(wind == 0) {
260     fprintf(stderr,"Window open failed.\n");
261     XCloseDisplay(disp);
262     exit(-1);
263   }
264   /* Set other properties on window */
265   XSetStandardProperties(disp,wind,"Golddig","Golddig",None,argv,argc,&hints);
266 
267   if (DisplayCells(disp, scrn) <= 2)
268     background = WhitePixel(disp, scrn);
269   else {
270 
271     resource = XGetDefault(disp, "Golddig", "background");
272     if (resource == NULL)
273       resource = "black";
274     if (!XAllocNamedColor(disp, XDefaultColormap(disp, scrn), resource,
275 			  &got, &hardw)) {
276       printf("XAllocNamedColor failed for background color %s!\n", resource);
277       printf("using BlackPixel\n");
278       hardw.pixel = BlackPixel(disp, scrn);
279     }
280     background = hardw.pixel;
281   }
282 
283   /* Clear fast block type lookup table */
284   for(i=0;i<256;++i) {
285     fast_lookup[i].gc = NULL;
286     /* Everything starts out looking like a space */
287     fast_lookup[i].code = SPACEBITS;
288   }
289   /* Generate block type lookup table from symbs array defined above. */
290   /* After this the symbs array will be used very rarely. */
291   for(i=0;symbs[i].symb != '\0';++i) {
292     fast_lookup[symbs[i].symb].gc  =
293       fill_gc(GXcopy,symbs[i].bits, symbs[i].name,
294 	((resource = XGetDefault(disp, "Golddig", symbs[i].name)) == NULL) ?
295 	      symbs[i].dcolor: resource);
296     fast_lookup[symbs[i].symb].code = symbs[i].code;
297   }
298   /* Load in the font used to display the score */
299   scorefont = XLoadFont(disp,SCOREFONT);
300   scorestruct = XQueryFont(disp, scorefont);
301   scoresize = scorestruct->max_bounds.ascent +
302     scorestruct->max_bounds.descent;
303 
304   /* Create GC which will be used from drawing score */
305 
306   xgcv.function = GXcopy;
307   xgcv.font = scorefont;
308 
309   if (DisplayCells(disp, scrn) <= 2) {
310 
311     score_fg = xgcv.foreground = WhitePixel(disp,scrn);
312     score_bg = xgcv.background = BlackPixel(disp,scrn);
313 
314   } else {
315 
316     resource = XGetDefault(disp, "Golddig", "score-fg");
317     if (resource == NULL)
318       resource = "black";
319     if (!XAllocNamedColor(disp, XDefaultColormap(disp, scrn), resource,
320 			  &got, &hardw)) {
321       printf("XAllocNamedColor failed for score-fg color %s!\n", resource);
322       printf("using BlackPixel\n");
323       hardw.pixel = BlackPixel(disp, scrn);
324     }
325     score_fg = xgcv.foreground = hardw.pixel;
326     resource = XGetDefault(disp, "Golddig", "score-bg");
327     if (resource == NULL)
328       resource = "grey";
329     if (!XAllocNamedColor(disp, XDefaultColormap(disp, scrn), resource,
330 			  &got, &hardw)) {
331       printf("XAllocNamedColor failed for score-bg color %s!\n", resource);
332       printf("using WhitePixel\n");
333       hardw.pixel = WhitePixel(disp, scrn);
334     }
335     score_bg = xgcv.background = hardw.pixel;
336 
337   }
338 
339   scoregc = XCreateGC(disp,wind,
340                       GCFunction | GCFont | GCForeground | GCBackground,
341                       &xgcv);
342 
343   /* Create GC which will be used for clearing score line */
344 
345   xgcv.function = GXcopy;
346   tmp = xgcv.foreground;
347   xgcv.foreground = score_bg;
348   xgcv.background = score_fg;
349   blackgc = XCreateGC(disp,wind,
350                       GCFunction | GCForeground | GCBackground,
351                       &xgcv);
352   /* Create GC used for drawing men in score line */
353   livesgc = stip_gc(GXcopy,player_bits, score_fg);
354 
355   if (DisplayCells(disp, scrn) <= 2) {
356     mecol = badcol = BlackPixel(disp, scrn);
357   } else {
358     resource = XGetDefault(disp, "Golddig", "mycolor");
359     if (resource == NULL)
360       resource = "white";
361     if (!XAllocNamedColor(disp, XDefaultColormap(disp, scrn), resource,
362 			  &got, &hardw)) {
363       printf("XAllocNamedColor failed for mycolor (color %s).\n", resource);
364       printf("using WhitePixel\n");
365       hardw.pixel = WhitePixel(disp, scrn);
366     }
367     mecol = hardw.pixel;
368 
369     resource = XGetDefault(disp, "Golddig", "badcol");
370     if (resource == NULL)
371       resource = "white";
372     if (!XAllocNamedColor(disp, XDefaultColormap(disp, scrn), resource,
373 			  &got, &hardw)) {
374       printf("XAllocNamedColor failed for badcol (color %s).\n", resource);
375       printf("using WhitePixel\n");
376       hardw.pixel = WhitePixel(disp, scrn);
377     }
378     badcol = hardw.pixel;
379   }
380 
381   /* compute all the graphics cursors for drawing the player in his */
382   /* various states. */
383   standgc = stip_gc(GXcopy,player_bits, mecol);
384   angelgc = stip_gc(GXcopy,angel_bits, mecol);
385   angelugc = stip_gc(GXcopy,angelu_bits,mecol);
386   angellgc = stip_gc(GXcopy,angell_bits,mecol);
387   flygc = stip_gc(GXcopy,fly_bits,mecol);
388   hang1gc = stip_gc(GXcopy,hang1_bits,mecol);
389   hang2gc = stip_gc(GXcopy,hang2_bits,mecol);
390   up1gc = stip_gc(GXcopy,up1_bits,mecol);
391   up2gc = stip_gc(GXcopy,up2_bits,mecol);
392   left1gc = stip_gc(GXcopy,left1_bits,mecol);
393   left2gc = stip_gc(GXcopy,left2_bits,mecol);
394   right1gc = stip_gc(GXcopy,right1_bits,mecol);
395   right2gc = stip_gc(GXcopy,right2_bits,mecol);
396 
397   /* Generate graphics cursors for drawing bad guy */
398   badguy1gc = stip_gc(GXcopy,badguy_bits,badcol);
399   badguy2gc = stip_gc(GXcopy,badguy2_bits,badcol);
400   badguy3gc = stip_gc(GXcopy,badguy3_bits,badcol);
401 
402   /* Tell the WM that we want input... */
403   wmhints.input = True;
404   wmhints.flags = InputHint;
405   XSetWMHints(disp, wind, &wmhints);
406 
407   /* Select the interesting window events */
408   XSelectInput(disp,wind,evmask);
409 
410   /* Name and raise the window */
411   XMapRaised(disp,wind);
412 
413   /* Flush and synchronize the server */
414   XFlush(disp);
415   XSync(disp,False);
416 }
417 
418 /* Gracefully shut X windows down.  It is not strictly necessary to */
419 /* call this function. */
xend()420 void xend()
421 {
422   XUnloadFont(disp,scorefont);
423   XUnmapWindow(disp,wind);
424   XDestroyWindow(disp,wind);
425   XCloseDisplay(disp);
426 }
427 
428 /* Draw a block from the level array in the output window. */
draw_block(x,y)429 void draw_block(x,y)
430 int x,y;    /* Position of block in array */
431 {
432   register unsigned char curchar;
433   GC drawgc;
434 
435   /* Get the block character out of the level array */
436   curchar = level[x*ysize + y];
437   /* If there is gold left and this block is inactive, replace it with */
438   /* a space. */
439   if(goldleft > 0 && (fast_lookup[curchar].code & INACTIVE))
440     curchar = SPACE;
441   /* Get the graphics cursor */
442   drawgc = fast_lookup[curchar].gc;
443   /* Replace questionable characters with spaces */
444   if(drawgc == NULL)
445     drawgc = fast_lookup[SPACE].gc;
446   /* Fill the block */
447   XFillRectangle(disp,wind,drawgc,x << 4,y << 4,16,16);
448 }
449 
450 /* Change a block character in the level array.  The block is redrawn. */
setchar(x,y,ch)451 void setchar(x,y,ch)
452 int x,y;    /* Position of block character to change. */
453 char ch;    /* Character to change it to */
454 {
455   if(level[x*ysize + y] != ch) {
456     level[x*ysize + y] = ch;
457     draw_block(x,y);
458   }
459 }
460 
461 /* Draw the score and level number */
draw_score()462 void draw_score()
463 {
464   char buf[50];
465   int textwidth,i;
466 
467   /* Build the output string */
468   sprintf(buf,"score: %d  level: %d  speed: %d%s",
469     score, levelnum, speed,angelleft ? " You are dead! " : "");
470   /* Clear the current score line */
471   XFillRectangle(disp,wind,blackgc,0,ysize << 4,xsize << 4,scoresize);
472   /* Actually draw the text */
473   i = strlen(buf);
474   XDrawString(disp,wind,scoregc,0,(ysize << 4) + scoresize - 1,buf, i);
475   /* find size of string we just drew */
476   textwidth = XTextWidth(scorestruct, buf, i);
477   i = (textwidth + 16) & ~15;   /* starting point */
478   textwidth = lives * 16;
479   XFillRectangle(disp,wind,livesgc, i, (ysize << 4), textwidth, 16);
480 }
481 
482 /* Redraw the entire level */
draw_level()483 void draw_level()
484 {
485   int x,y;
486 
487   /* Change the window size */
488   XResizeWindow(disp,wind,xsize << 4,(ysize << 4) + scoresize);
489   /* Draw the score and level number */
490   draw_score();
491   /* Iterate through each block position and draw it */
492   for(x=0;x < xsize;++x)
493     for(y=0;y < ysize;++y)
494       draw_block(x,y);
495 }
496 
497 /* Load a level out of a file.  The global variables worldname and */
498 /* levelnum are used to produce the file name which is stored in */
499 /* filename. */
load_level()500 void load_level()
501 {
502   FILE *levelfile;
503   register int i,j;
504   int x,y;
505   char buf[MAXPATHLEN];
506 
507   /* Manufaction the file name by starting with the world name and */
508   /* appending the level number to it. */
509   /* Try looking in ~/.golddig first, then in the global levels. */
510   snprintf(buf, sizeof(buf), "%s/.golddig/%s%03d", getenv("HOME"),
511 	   worldname, levelnum);
512   /* Open level file for reading */
513   levelfile = fopen(buf,"r");
514 
515   if (levelfile == NULL) {
516     /* If local file doesn't exist, try global one. */
517     snprintf(buf, sizeof(buf), "%s/%s%03d", LIB, worldname, levelnum);
518     /* Open level file for reading */
519     levelfile = fopen(buf,"r");
520   } else {
521     printf("Using %s\n", buf);
522     savehighscore = 0;
523   }
524 
525   /* If level file does not exist, use the default level file. */
526   if(levelfile == NULL) {
527     /* Build the default level name */
528     snprintf(buf, sizeof(buf), "%s/default", LIB);
529     /* Open default level file for reading */
530     levelfile = fopen(buf,"r");
531     if(levelfile == NULL) {
532       perror(buf);
533       exit(1);
534     }
535   }
536 
537   /* Load the first line of the level file */
538   if(fgets(buf,300,levelfile) == NULL) {
539     x = 50;
540     y = 30;
541   }
542   else {
543     /* Extract the level size */
544     sscanf(buf,"%d %d",&x,&y);
545   }
546   /* Change level size only if it is necessary */
547   if(xsize == -1)
548     xsize = x;
549   if(ysize == -1)
550     ysize = y;
551   /* Carefully check the sanity of the size parameters */
552   if(xsize < 5)
553     xsize = 5;
554   if(xsize > 250)
555     xsize = 250;
556   if(ysize < 5)
557     ysize = 5;
558   if(ysize > 250)
559     ysize = 250;
560   if(xsize * ysize > MAXLEVEL) {
561     if(xsize > ysize)
562       xsize = MAXLEVEL / ysize;
563     else
564       ysize = MAXLEVEL / xsize;
565   }
566   /* Iterate through each horizontal line */
567   for(i=0;i<ysize;++i) {
568     /* Load the next line from the file */
569     if(fgets(buf,300,levelfile) != NULL) {
570       /* Go through each horizontal position and copy the data into */
571       /* the level array. */
572       for(j=0;j<xsize;++j) {
573         /* Break out if line ends prematurely */
574         if(buf[j] == '\n' || buf[j] == '\0')
575           break;
576         level[j*ysize + i] = buf[j];
577       }
578     }
579     else
580       j = 0;
581     /* Fill in rest of premature lines with spaces */
582     for(;j<xsize;++j)
583       level[j*ysize + i] = SPACE;
584   }
585   /* Close the level file */
586   fclose(levelfile);
587 }
588 
589 /* Save the current level back into a file.  The global variables */
590 /* worldname and levelnum are used to determine the file name. */
save_level()591 void save_level()
592 {
593   FILE *levelfile;
594   char buf[MAXPATHLEN+1];
595   char dir[MAXPATHLEN+1];
596   register int i,j;
597   struct stat sb;
598 
599   /* Check existence of $HOME/.golddig; create if not existant. */
600   snprintf(dir, sizeof(dir), "%s/.golddig", getenv("HOME"));
601   if ((stat(dir, &sb) == -1)
602       && (mkdir(dir, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) == -1)) {
603     perror("error accessing or creating ~/.golddig");
604     exit(1);
605   }
606 
607   /* Write levels in ~/.golddig. */
608   snprintf(buf, sizeof(buf), "%s/%s%03d", dir, worldname, levelnum);
609   /* Open level file for writing */
610   levelfile = fopen(buf,"w");
611 
612   if (levelfile == NULL) {
613     /* If local file doesn't exist, try global one. */
614     snprintf(buf, sizeof(buf), "%s/%s%03d", LIB, worldname, levelnum);
615     /* Open level file for reading */
616     levelfile = fopen(buf,"r");
617   }
618 
619   /* Open the data file */
620   levelfile = fopen(buf,"w");
621   if(levelfile == NULL) {
622     perror(buf);
623     exit(1);
624   }
625   /* Write out the size of the level.  Normal text is used so that */
626   /* levels can be easily copied across architectures. */
627   fprintf(levelfile,"%d %d\n",xsize,ysize);
628   /* Terminate the lines for writing out the horizontal level lines */
629   buf[xsize] = '\n';
630   buf[xsize+1] = '\0';
631   /* Iterate through each vertical position */
632   for(i=0;i<ysize;++i) {
633     /* Copy each horizontal line into the output buffer */
634     for(j=0;j<xsize;++j)
635       buf[j] = level[j*ysize + i];
636     /* Write the line out to the file */
637     fputs(buf,levelfile);
638   }
639   /* Close the data file */
640   fclose(levelfile);
641 }
642