1 /*
2     XorGramana Copyright 2009 James W. Morris, james@jwm-art.net
3 
4     This file is part of XorGramana.
5 
6     XorGramana is free software: you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation, either version 3 of the License, or
9     (at your option) any later version.
10 
11     XorGramana is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with XorGramana.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 #include "map.h"
20 #include "icons.h"
21 
22 #include <string.h>
23 #include <stdlib.h>
24 
25 #include "options.h"
26 #include "actions.h"
27 #include "player.h"
28 #include "game_display.h" /* purely for map_check_solved */
29 
30 struct xg_map* map=0;
31 
32 /*  xg_map_validate ensures that any objects that gravitate
33     are suspended by *something*. fish and h-bombs should not
34     hang in the air, and chickens and v-bombs should not be
35     being held back by nothing, waiting to race left. returns
36     0 for invalid maps, 1 for valid maps.
37 
38     xg_map_validate is called by xg_map_load, which will exit
39     on invalid map encounters, ooh-err.
40 */
41 su_t xg_map_validate();
42 
43 
xg_map_create(const char * title,int width,int height)44 void xg_map_create(const char* title, int width,int height)
45 {
46     int x,y;
47     if(!title||width<2||width>MAP_MAX_W
48      ||height<2||height>MAP_MAX_H||strlen(title)>MAX_BUF_LEN)
49         return;
50     if(!(map=malloc(sizeof(struct xg_map))))
51         return;
52     map->title=0;
53     map->data=0;
54     if(!(map->title=calloc(strlen(title)+1,sizeof(char)))){
55         free(map);
56         map=0;
57         return;
58     }
59     strcpy(map->title,title);
60     /* map files don't include boundary wall */
61     map->width=width+2;
62     map->height=height+2;
63     if(!(map->data=calloc(map->height,sizeof(map_t*)))){
64         xg_map_destroy(map);
65         map=0;
66         fprintf(stderr,"Failed to allocate for map data\n");
67         return;
68     }
69     for(y=0;y<map->height;y++){
70         if(!(map->data[y]=calloc(map->width,sizeof(map_t)))){
71             xg_map_destroy(map);
72             map=0;
73         }
74     }
75     /* set boundary wall */
76     #ifdef MAP_DEBUG
77     printf("setting boundary wall (%d x %d)\n",map->width,map->height);
78     #endif
79     for(x=0;x<map->width;x++)
80         map->data[0][x]=map->data[map->height-1][x]=ICON_WALL;
81     for(y=1;y<map->height;y++)
82         map->data[y][0]=map->data[y][map->width-1]=ICON_WALL;
83     map->teleport[0].x=map->teleport[0].y=0;
84     map->teleport[1].x=map->teleport[1].y=0;
85     for(x=0;x<=MAX_WORDS_TO_SOLVE;x++){
86         map->solve_word[x]=0;
87         map->solve_unblock_xy[x].x=0;
88         map->solve_unblock_xy[x].y=0;
89         map->solved[x]=FALSE;
90     }
91     map->exit.x=map->player[0].x=map->player[1].x=map->width;
92     map->exit.y=map->player[0].y=map->player[1].y=map->height;
93     map->mask_count=0;
94 }
95 
xg_map_destroy()96 void xg_map_destroy()
97 {
98     xy_t y;
99     if(map){
100         if(map->filename)
101             free(map->filename);
102         if(map->title)
103             free(map->title);
104         if(map->data){
105             for(y=0;y<map->height;y++)
106                 if(map->data[y])
107                     free(map->data[y]);
108             free(map->data);
109         }
110         for(y=0;y<=MAX_WORDS_TO_SOLVE;y++)
111             if(map->solve_word[y])
112                 free(map->solve_word[y]);
113         free(map);
114         map=0;
115     }
116 }
117 
xg_map_read_name(FILE * fp)118 char* xg_map_read_name(FILE* fp)
119 {
120     char tmpbuf[80]={0};
121     su_t l;
122     char* ret=0;
123     char* cp=0;
124     if(fgets(tmpbuf, 80, fp)!=tmpbuf)
125         return 0;
126     if((cp=strchr(tmpbuf,'\r')))
127         *cp=0;
128     else if((cp=strchr(tmpbuf,'\n')))
129         *cp=0;
130     l=strlen(tmpbuf);
131     if(l>=MAPNAME_MAXCHARS)
132         l=MAPNAME_MAXCHARS;
133     if(!(ret=malloc(l+1)))
134         return 0;
135     strncpy(ret,tmpbuf,l);
136     ret[l]=0;
137     return ret;
138 }
139 
xg_map_load_read_name(su_t level)140 char* xg_map_load_read_name(su_t level)
141 {
142     char* fname=0;
143     FILE* fp=0;
144     char* ret=0;
145     if(!(fname=options_map_filename(level)))
146         if(!(fname=options_map_filename(level)))
147             return 0;
148     if(!(fp=fopen(fname, "r"))){
149         free(fname);
150         return 0;
151     }
152     ret=xg_map_read_name(fp);
153     free(fname);
154     fclose(fp);
155     return ret;
156 }
157 
xg_map_load(su_t level)158 void xg_map_load(su_t level)
159 {
160     int moves,width,height,tmpx,tmpy,subx,suby; /* int for fscanf %d */
161     xy_t    x,y;
162     ctr_t   n;
163     su_t    t=0;
164     FILE    *fp;
165     char    buf[MAX_BUF_LEN]={0};
166     char    sc;
167     char*   fname=0;
168     char*   ptr;
169     bool    sdone;
170     bool    exitdone=FALSE;
171     if(map)
172         xg_map_destroy(map);
173     if(!(fname=options_map_filename(level))){
174         if(!(fname=options_map_filename(level)))
175             return;
176     }
177     #ifdef MAP_DEBUG
178     printf("Opening map:%s\n",fname);
179     #endif
180     if (!(fp=fopen(fname,"rb"))){
181         fprintf(stderr,"Cannot open map: %s\n",fname);
182         free(fname);
183         return;
184     }
185     if(!(fgets(buf,MAX_BUF_LEN,fp))){
186         fprintf(stderr,"Cannot read map name from map: %s\n",fname);
187         free(fname);
188         return;
189     }
190     for(n=0;n<MAX_BUF_LEN;n++)
191         if(buf[n]=='\n'||buf[n]=='\0'){
192             buf[n]='\0';
193             break;
194         }
195     if(n==MAX_BUF_LEN)
196         buf[n]=0;
197     if(options->game){
198         moves=2000;
199         width=30;
200         height=30;
201     }
202     else{
203         if(fscanf(fp,"%d %d %d",&moves,&width,&height)!=3){
204             fprintf(stderr,"Cannot read map move count and dimensions from map: %s\n",fname);
205             fclose(fp);
206             free(fname);
207             return;
208         }
209     }
210     #ifdef MAP_DEBUG
211     printf("map: '%s' max moves: %d",buf,moves);
212     printf("map: '%s' dimensions: w:%d h:%d\n",buf,width,height);
213     #endif
214     xg_map_create(buf,width,height);
215     if(!map){
216         fprintf(stderr,"Failed to create map: %s dimensions %d x %d\n",
217             fname,width,height);
218         fclose(fp);
219         free(fname);
220         return;
221     }
222     map->level=level;
223     map->max_moves=moves;
224     map->filename=fname;
225     for(y=1;y<map->height-1;y++){
226         do{
227             if(!fgets(buf,MAX_BUF_LEN,fp)){
228                 fprintf(stderr,
229                     "Failed reading map data from map: %s, line:%d\n",
230                     fname,y);
231                 fclose(fp);
232                 xg_map_destroy(map);
233                 return;
234             }
235         }while(buf[0]=='\n');
236         if(buf[map->width-1]){/* remember map width+=2 for boundary */
237             fprintf(stderr,
238                 "Excess characters in map: %s, map row:%d\n",fname,y);
239             fclose(fp);
240             xg_map_destroy(map);
241             return;
242         }
243         switch(strlen(buf)<map->width-1){
244             case '\n':
245             case '\0':
246                 break;
247             default:
248                 fprintf(stderr,
249                     "Too few characters in map: %s, map row:%d\n",fname,y);
250                 fprintf(stderr,"('%s')\n",map->data[y]);
251                 fclose(fp);
252                 xg_map_destroy(map);
253                 return;
254         }
255         for(x=1;x<map->width-1;x++){
256             if(options->game){
257                 switch(buf[x-1]){
258                     case '2': buf[x-1]=XOR_PLAYER2; break;
259                     case '!': buf[x-1]=XOR_FISH;    break;
260                     case '<': buf[x-1]=XOR_CHICKEN; break;
261                     case 'o': buf[x-1]=XOR_HBOMB;   break;
262                     case 'D': buf[x-1]=XOR_DOLL;    break;
263                     case 'S': buf[x-1]=XOR_SWITCH;  break;
264                     default:
265                         break;
266                 }
267             }
268             switch((map->data[y][x]=n=mapchar_to_icon(buf[x-1]))){
269                 case ICON_MASK:
270                     map->mask_count++;
271                     break;
272                 case ICON_PLAYER0:
273                 case ICON_PLAYER1:
274                     map->player[n-ICON_PLAYER0].x=x;
275                     map->player[n-ICON_PLAYER0].y=y;
276                     break;
277                 case ICON_TELEPORT:
278                     if(t<2){
279                         map->teleport[t].x=x;
280                         map->teleport[t].y=y;
281                         t++;
282                     }else{
283                         fprintf(stderr,
284                             "Too many teleports in map %s, map row:%d (%d)\n",
285                             fname,y,x);
286                         fclose(fp);
287                         xg_map_destroy(map);
288                         return;
289                     }
290                     break;
291                 case ICON_EXIT:
292                     #ifdef MAP_DEBUG
293                     printf("Found exit at x:%d y:%d\n",x,y);
294                     #endif
295                     if(exitdone){
296                         fprintf(stderr,
297                             "WARNING: too many exits in map %s\n",fname);
298                         map->data[map->exit.y][map->exit.x]=ICON_FLOOR;
299                     }
300                     map->exit.x=x;
301                     map->exit.y=y;
302                     exitdone=TRUE;
303                     break;
304                 #ifdef MAP_DEBUG
305                 case ICON_MAP:
306                     printf("Map piece located @ x:%d y:%d\n",x,y);
307                     break;
308                 #endif
309             }
310         }
311     }
312     for(n=0;n<2;n++){
313         if(map->player[n].x==map->width||map->player[n].y==map->height){
314             fprintf(stderr,"Player %d missing from map %s\n",n,fname);
315             fclose(fp);
316             xg_map_destroy(map);
317             return;
318         }
319     }
320     if(map->exit.x==map->width||map->exit.y==map->height){
321         fprintf(stderr,"No exit found in map %s\n",fname);
322         fclose(fp);
323         xg_map_destroy(map);
324         return;
325     }
326     for(n=0;n<4;n++){
327         if(fscanf(fp, "%d %d", &tmpx,&tmpy)<2){
328             fprintf(stderr,"Error loading map piece %d data in map %s\n",n,fname);
329             fclose(fp);
330             xg_map_destroy(map);
331             return;
332         }
333         else{
334             map->mappc[n].x=tmpx;
335             map->mappc[n].y=tmpy;
336         }
337         if(map->data[map->mappc[n].y][map->mappc[n].x]!=ICON_MAP){
338             fprintf(stderr,"Mismatched map piece %d location data in map %s\n",n,fname);
339             fprintf(stderr,"mappc[%d] x:%d y:%d\n",n,map->mappc[n].x,map->mappc[n].y);
340             fclose(fp);
341             xg_map_destroy(map);
342             return;
343         }
344     }
345     if(options->game){
346         fclose(fp);
347         return;
348     }
349     n=0;
350     sdone=FALSE;
351     while(!sdone)
352     {
353         switch(fscanf(fp,"%s %d %d %c %d %d",buf,&tmpx,&tmpy,&sc,&subx,&suby)){
354             case EOF:
355             case 0:
356                 if(n>0){
357                     sdone=TRUE;
358                     break;
359                 }
360                 fprintf(stderr,
361                     "Missing solution word in map %s\n",fname);
362             case 1:
363                 fprintf(stderr,
364                     "Missing solution word x position in map %s\n",fname);
365             case 2:
366                 fprintf(stderr,
367                     "Missing solution word y position in map %s\n",fname);
368             case 3:
369                 fprintf(stderr,
370                     "Missing solution word direction in map %s\n",fname);
371             case 4:
372                 fprintf(stderr,
373                     "Missing solution word x unblock in map %s\n",fname);
374             case 5:
375                 fprintf(stderr,
376                     "Missing solution word y unblock in map %s\n",fname);
377                 fclose(fp);
378                 xg_map_destroy(map);
379                 return;
380             default:
381             {
382                 if(n==MAX_WORDS_TO_SOLVE){
383                     fprintf(stderr,
384                         "WARNING: Too many words to solve in map %s\n",fname);
385 
386                     break;
387                 }
388                 if(!(map->solve_word[n]=malloc(strlen(buf)+1))){
389                     fprintf(stderr,
390                         "Map solution word allocation error word %s map %s\n",
391                         buf,fname);
392                     fclose(fp);
393                     xg_map_destroy(map);
394                     return;
395                 }
396                 ptr=buf;
397                 x=0;
398                 while(*ptr){
399                     map->solve_word[n][x]=mapchar_to_icon(*ptr);
400                     ptr++;
401                     x++;
402                 }
403                 map->solve_word[n][x]=0;
404                 map->solve_xy[n].x=tmpx;
405                 map->solve_xy[n].y=tmpy;
406                 map->solve_unblock_xy[n].x=subx;
407                 map->solve_unblock_xy[n].y=suby;
408                 switch(sc){
409                     case 'd':
410                         map->solve_dir[n]=MV_DOWN;    break;
411                     case 'r':
412                         map->solve_dir[n]=MV_RIGHT;   break;
413                     default:
414                         fprintf(stderr,
415                             "Invalid solution word direction spec (%c) in map %s\n",
416                             sc,
417                             fname);
418                         xg_map_destroy(map);
419                         return;
420                 }
421                 n++;
422             }
423             break;
424         }
425     }
426     fclose(fp);
427     #ifdef MAP_DEBUG
428     printf("-----map data-----\n");
429     for(y=0;y<map->height;y++){
430         printf("data[%2d][]={ ",y);
431         for(x=0;x<map->width;x++)
432             printf("%2d ",map->data[y][x]);
433         printf("}\n");
434     }
435     #endif
436 }
437 
xg_map_init_views(xy_t view_w,xy_t view_h)438 void xg_map_init_views(xy_t view_w, xy_t view_h)
439 {
440     int x,y;
441     int hx=view_w/2;
442     int hy=view_h/2;
443     su_t p;
444     if(hx<view_w/2.0f)
445         hx++;
446     if(hy<view_h/2.0f)
447         hy++;
448     if(!map){
449         fprintf(stderr,"xg_map_init_views called for non-existing map\n");
450         return;
451     }
452     for(p=0;p<2;p++){
453         x=map->player[p].x-hx;
454         y=map->player[p].y-hy;
455         if(x<0)
456             x=0;
457         else if(x+view_w>=map->width)
458             x=map->width-view_w;
459         if(y<0)
460             y=0;
461         else if(y+view_h>=map->height)
462             y=map->height-view_h;
463         map->view[p].x=x;
464         map->view[p].y=y;
465     }
466 }
467 
map_get_teleport(xy_t x,xy_t y)468 su_t map_get_teleport(xy_t x, xy_t y)
469 {
470     su_t i;
471     for(i=0;i<2;i++)
472         if(map->teleport[i].x==x&&map->teleport[i].y==y)
473             return i;
474     return 99;
475 }
476 
map_check_solved()477 bool map_check_solved()
478 {
479     xy_t x,y,ux,uy;
480     su_t n=0;
481     map_t* ptr=0;
482     bool solved=TRUE;
483     bool exit_exists;
484     switch(map->data[map->exit.y][map->exit.x]){
485         case ICON_EXIT_OPEN:    exit_exists=TRUE;   break;
486         case ICON_EXIT:         exit_exists=TRUE;   break;
487         default:                exit_exists=FALSE;  break;
488     }
489     if(options->game){
490         if(player.masks_collected==map->mask_count&&exit_exists==TRUE){
491             map->data[map->exit.y][map->exit.x]=ICON_EXIT_OPEN;
492             return TRUE;
493         }
494         return FALSE;
495     }
496     /* was returning if problem solved... but forgot, it is *
497      * always possible to un-solve it. mwuh! ha! ha! ha!    */
498     while((ptr=map->solve_word[n])){
499         x=map->solve_xy[n].x;
500         y=map->solve_xy[n].y;
501         map->solved[n]=TRUE;
502         while(*ptr&&map->solved[n]){
503             if(*ptr!=map->data[y][x])
504                 solved=map->solved[n]=FALSE;
505             ptr++;
506             switch(map->solve_dir[n]){
507                 case MV_DOWN:   y++;    break;
508                 default:        x++;    break;
509             }
510         }
511         ux=map->solve_unblock_xy[n].x;
512         uy=map->solve_unblock_xy[n].y;
513         if(ux&&uy&&map->solved[n]){
514             map->data[uy][ux]=ICON_FLOOR;
515             map->solve_unblock_xy[n].x=0;
516             map->solve_unblock_xy[n].y=0;
517             game_win_display();
518             #ifdef MAPDEVMODE
519             map_dump_solve_state();
520             #endif
521         }
522         n++;
523     }
524     if(!exit_exists)
525         return solved;
526     if(!solved)
527         map->data[map->exit.y][map->exit.x]=ICON_EXIT;
528     else
529         map->data[map->exit.y][map->exit.x]=ICON_EXIT_OPEN;
530     return solved;
531 }
532 
xg_map_load_error(FILE * fp,char * filename,char * msg)533 void xg_map_load_error(FILE* fp,char* filename, char* msg)
534 {
535     if(fp)
536         fclose(fp);
537 
538     if(filename)
539         fprintf(stderr,"Error in map!\n\tfile:%s\n\t%s\n",filename,msg);
540     else
541         fprintf(stderr,"Error in map!\n\t%s\n",msg);
542     xg_map_destroy(map);
543     exit(1);
544 }
545 
546 #ifdef MAPDEVMODE
map_dump_solve_state()547 void map_dump_solve_state()
548 {
549     int n;
550     char word[24];
551     map_t* ptr;
552     char* cptr;
553     printf("\n");
554     for(n=0;(ptr=map->solve_word[n])!=0;n++){
555         cptr=word;
556         while(*ptr){
557             *cptr=icon_details[*ptr].mapchar;
558             ptr++;
559             cptr++;
560         }
561         *cptr='\0';
562         printf("word: %s @x,y:%d,%d dir:%s unblock@x,y:%d,%d state:%s\n",
563             word,
564             map->solve_xy[n].x,
565             map->solve_xy[n].y,
566             (map->solve_dir[n]==MV_DOWN?"down":"right"),
567             map->solve_unblock_xy[n].x,
568             map->solve_unblock_xy[n].y,
569             (map->solve_unblock_xy[n].x&&map->solve_unblock_xy[n].y?"UNSOLVED":"SOLVED")
570             );
571     }
572 }
573 #endif
574 
is_wall(xy_t x,xy_t y)575 bool is_wall(xy_t x, xy_t y)
576 {
577     if(map->data[y][x]==ICON_WALL)
578         return TRUE;
579     if(map->data[y][x]>=ICON_EDGE_T&&map->data[y][x]<=ICON_EDGE_TBLR)
580         return TRUE;
581     return FALSE;
582 }
583 
xg_map_apply_edging()584 void xg_map_apply_edging()
585 {
586     xy_t x,y;
587     su_t o,i;
588     if(!map)
589         return;
590     for(y=0;y<map->height;y++){
591         for(x=0;x<map->width;x++){
592             if(is_wall(x,y)){
593                 o=0;
594                 if(y>0)
595                     if(!is_wall(x,y-1))
596                         o|=EDGE_T;
597                 if(y<map->height-1)
598                     if(!is_wall(x,y+1))
599                         o|=EDGE_B;
600                 if(x>0)
601                     if(!is_wall(x-1,y))
602                         o|=EDGE_L;
603                 if(x<map->width-1)
604                     if(!is_wall(x+1,y))
605                         o|=EDGE_R;
606                 switch(o){
607                     case EDGE_T:    i=ICON_EDGE_T;      break;
608                     case EDGE_B:    i=ICON_EDGE_B;      break;
609                     case EDGE_L:    i=ICON_EDGE_L;      break;
610                     case EDGE_R:    i=ICON_EDGE_R;      break;
611                     case EDGE_TB:   i=ICON_EDGE_TB;     break;
612                     case EDGE_LR:   i=ICON_EDGE_LR;     break;
613                     case EDGE_TL:   i=ICON_EDGE_TL;     break;
614                     case EDGE_TR:   i=ICON_EDGE_TR;     break;
615                     case EDGE_BL:   i=ICON_EDGE_BL;     break;
616                     case EDGE_BR:   i=ICON_EDGE_BR;     break;
617                     case EDGE_TBL:  i=ICON_EDGE_TBL;    break;
618                     case EDGE_TBR:  i=ICON_EDGE_TBR;    break;
619                     case EDGE_TLR:  i=ICON_EDGE_TLR;    break;
620                     case EDGE_BLR:  i=ICON_EDGE_BLR;    break;
621                     case EDGE_TBLR: i=ICON_EDGE_TBLR;   break;
622                     default:        i=ICON_WALL;        break;
623                 }
624                 map->data[y][x]=i;
625             }
626         }
627     }
628 }
629