added scr_lvled, a bunch of libraries, and improved framework code
[raydungeon] / src / level.c
1 #include <stdio.h>
2 #include <string.h>
3 #include <ctype.h>
4 #include <errno.h>
5 #include "level.h"
6 #include "darray.h"
7 #include "assfile.h"
8 #include "util.h"
9
10 static void parse_cell(struct level *lvl, int x, int y, char c);
11
12 void init_level(struct level *lvl)
13 {
14         memset(lvl, 0, sizeof *lvl);
15
16         lvl->scale = 1.0f;
17         lvl->rects = darr_alloc(0, sizeof *lvl->rects);
18 }
19
20 void destroy_level(struct level *lvl)
21 {
22         darr_free(lvl->rects);
23 }
24
25 static int rect_area(struct level_rect *rect)
26 {
27         return rect->w * rect->h;
28 }
29
30 static int grow_rect(struct level *lvl, struct level_cell *orgcell, struct level_rect *rect)
31 {
32         int i, j, x, y, area_a, area_b;
33         struct level_rect ra, rb;
34         struct level_cell *cell;
35
36         ra = rb = *rect;
37
38         /* try expanding horizontally first */
39         cell = orgcell + 1;
40         x = ra.x + 1;
41         while(x < lvl->xsz && cell->type != CELL_SOLID) {
42                 ra.w++;
43                 cell++;
44         }
45         /* then for each line below, expand as long as we have at least ra.w space */
46         cell = orgcell + lvl->xsz;
47         y = ra.y + 1;
48         while(y++ < lvl->ysz) {
49                 int rowsz = 0;
50                 x = ra.x;
51                 while(x++ < lvl->xsz && cell->type != CELL_SOLID) {
52                         rowsz++;
53                         cell++;
54                 }
55                 if(rowsz < ra.w) break;
56                 ra.h++;
57                 cell += lvl->xsz - rowsz;
58         }
59
60         /* then try the same thing, but vertical first */
61         cell = orgcell + lvl->xsz;
62         y = rb.y + 1;
63         while(y++ < lvl->ysz && cell->type != CELL_SOLID) {
64                 rb.h++;
65                 cell += lvl->xsz;
66         }
67         /* then for each next column, expand as long as we have at least rb.h space */
68         x = rb.x + 1;
69         while(x < lvl->xsz) {
70                 int colsz = 0;
71                 y = rb.y;
72                 cell = lvl->cells + y * lvl->xsz + x;
73                 while(y++ < lvl->ysz && cell->type != CELL_SOLID) {
74                         colsz++;
75                         cell += lvl->xsz;
76                 }
77                 if(colsz < rb.h) break;
78                 rb.w++;
79                 x++;
80         }
81
82         /* return the rect with the largest area */
83         area_a = rect_area(&ra);
84         area_b = rect_area(&rb);
85
86         *rect = area_a > area_b ? ra : rb;
87
88         cell = orgcell;
89         for(i=0; i<rect->h; i++) {
90                 for(j=0; j<rect->w; j++) {
91                         cell[j].visited = 1;
92                 }
93                 cell += lvl->xsz;
94         }
95         return 0;
96 }
97
98 void lvl_gen_rects(struct level *lvl)
99 {
100         int i, j;
101         struct level_cell *cell;
102         struct level_rect rect;
103
104         darr_clear(lvl->rects);
105         cell = lvl->cells;
106         for(i=0; i<lvl->ysz; i++) {
107                 for(j=0; j<lvl->xsz; j++) {
108                         cell[j].visited = 0;
109                 }
110                 cell += lvl->xsz;
111         }
112
113         cell = lvl->cells;
114         for(i=0; i<lvl->ysz; i++) {
115                 for(j=0; j<lvl->xsz; j++) {
116                         if(cell->type != CELL_SOLID && !cell->visited) {
117                                 rect.x = j;
118                                 rect.y = i;
119                                 rect.w = rect.h = 1;
120                                 if(grow_rect(lvl, cell, &rect) != -1) {
121                                         rect.dbgcol[0] = (rand() & 0x7f) + 0x7f;
122                                         rect.dbgcol[1] = (rand() & 0x7f) + 0x7f;
123                                         rect.dbgcol[2] = (rand() & 0x7f) + 0x7f;
124
125                                         darr_push(lvl->rects, &rect);
126                                 }
127                         }
128                         cell++;
129                 }
130         }
131 }
132
133 int save_level(const struct level *lvl, const char *fname)
134 {
135         FILE *fp;
136         int i, j, num;
137         struct level_cell *cell;
138         struct level_rect *rect;
139
140         if(!(fp = fopen(fname, "wb"))) {
141                 fprintf(stderr, "save_level: failed to open: %s: %s\n", fname, strerror(errno));
142                 return -1;
143         }
144
145         fprintf(fp, "RDLEVEL\n");
146         fprintf(fp, "size = %dx%d\n", lvl->xsz, lvl->ysz);
147         fprintf(fp, "scale = %f\n", lvl->scale);
148
149         fputs("\nMAPSTART\n", fp);
150         cell = lvl->cells;
151         for(i=0; i<lvl->ysz; i++) {
152                 for(j=0; j<lvl->xsz; j++) {
153                         switch(cell->type & 0xff) {
154                         case CELL_OPEN:
155                                 if(lvl->sx == j && lvl->sy == i) {
156                                         fputc('S', fp);
157                                 } else if((cell->type & CELL_WALK) == 0) {
158                                         fputc('.', fp);
159                                 } else {
160                                         fputc(' ', fp);
161                                 }
162                                 break;
163
164                         case CELL_SOLID:
165                         default:
166                                 fputc('#', fp);
167                         }
168                         cell++;
169                 }
170                 fputc('\n', fp);
171         }
172         fputs("MAPEND\n", fp);
173
174         if(!darr_empty(lvl->rects)) {
175                 num = darr_size(lvl->rects);
176                 fprintf(fp, "\nRECTSTART %d\n", num);
177                 rect = lvl->rects;
178                 for(i=0; i<num; i++) {
179                         fprintf(fp, "rect %d %d %d %d\n", rect->x, rect->y, rect->w, rect->h);
180                         rect++;
181                 }
182                 fprintf(fp, "RECTEND\n\n");
183         }
184
185         fclose(fp);
186         return 0;
187 }
188
189 static char *clean_line(char *s)
190 {
191         char *end;
192
193         while(*s && isspace(*s)) s++;
194
195         if(!(end = strchr(s, '#'))) {
196                 end = s + strlen(s) - 1;
197         }
198         while(end >= s && isspace(*end)) *end-- = 0;
199
200         return *s ? s : 0;
201 }
202
203 int load_level(struct level *lvl, const char *fname)
204 {
205         ass_file *fp;
206         char buf[512];
207         char *line, *val, *endp;
208         float fnum;
209         int state = 0;
210         int i, nrow;
211         struct level_rect rect;
212
213         if(!(fp = ass_fopen(fname, "rb"))) {
214                 fprintf(stderr, "load_level: failed to open: %s\n", fname);
215                 return -1;
216         }
217
218         while(ass_fgets(buf, sizeof buf, fp)) {
219                 switch(state) {
220                 case 0:
221                         if(memcmp(buf, "RDLEVEL", 7) != 0) {
222                                 fprintf(stderr, "load_level: invalid level file: %s\n", fname);
223                                 ass_fclose(fp);
224                                 return -1;
225                         }
226                         state++;
227                         break;
228
229                 case 1:
230                         if(!(line = clean_line(buf)) || *line == '#') {
231                                 continue;
232                         }
233
234                         if((val = strchr(line, '='))) {
235                                 *val++ = 0;
236                                 clean_line(line);
237                                 if(!(val = clean_line(val))) {
238                                         fprintf(stderr, "load_level: ignoring invalid value for %s: %s\n", line, val);
239                                         continue;
240                                 }
241                                 if(strcmp(line, "size") == 0) {
242                                         int x, y;
243                                         if(sscanf(val, "%dx%d", &x, &y) != 2) {
244                                                 fprintf(stderr, "load_level: ignoring invalid size: %s\n", val);
245                                                 continue;
246                                         }
247                                         lvl->xsz = x;
248                                         lvl->ysz = y;
249                                 } else if(strcmp(line, "scale") == 0) {
250                                         fnum = strtod(val, &endp);
251                                         if(endp == val) {
252                                                 fprintf(stderr, "load_level: ignoring invalid scale: %s\n", val);
253                                                 continue;
254                                         }
255                                         lvl->scale = fnum;
256                                 } else {
257                                         fprintf(stderr, "load_level: ignoring unknown option: %s\n", line);
258                                         continue;
259                                 }
260
261                         } else if(strcmp(line, "MAPSTART") == 0) {
262                                 if(!lvl->xsz || !lvl->ysz) {
263                                         fprintf(stderr, "load_level: missing size before level data\n");
264                                         ass_fclose(fp);
265                                         return -1;
266                                 }
267
268                                 lvl->cells = calloc_nf(lvl->xsz * lvl->ysz, sizeof *lvl->cells);
269                                 nrow = 0;
270                                 state++;
271
272                         } else if(sscanf(line, "rect %d %d %d %d", &rect.x, &rect.y, &rect.w, &rect.h) == 4) {
273                                 rect.dbgcol[0] = (rand() & 0x7f) + 0x7f;
274                                 rect.dbgcol[1] = (rand() & 0x7f) + 0x7f;
275                                 rect.dbgcol[2] = (rand() & 0x7f) + 0x7f;
276                                 darr_push(lvl->rects, &rect);
277
278                         }
279                         break;
280
281                 case 2:
282                         if(memcmp(buf, "MAPEND", 6) == 0) {
283                                 state = 1;
284                                 break;
285                         }
286                         for(i=0; i<lvl->xsz; i++) {
287                                 if(!buf[i]) break;
288                                 parse_cell(lvl, i, nrow, buf[i]);
289                         }
290                         if(++nrow >= lvl->ysz) {
291                                 state = 1;
292                                 break;
293                         }
294                         break;
295                 }
296         }
297
298         ass_fclose(fp);
299         return 0;
300 }
301
302 static void parse_cell(struct level *lvl, int x, int y, char c)
303 {
304         struct level_cell *cell = lvl->cells + y * lvl->xsz + x;
305
306         switch(c) {
307         case ' ':
308                 cell->type = CELL_OPEN | CELL_WALK;
309                 break;
310
311         case 'S':
312                 cell->type = CELL_OPEN | CELL_WALK;
313                 lvl->sx = x;
314                 lvl->sy = y;
315                 break;
316
317         case 't':
318         case '.':
319                 cell->type = CELL_OPEN;
320                 break;
321
322         default:
323                 break;
324         }
325 }