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