/
generate.c
468 lines (394 loc) · 12.4 KB
/
generate.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "lib/duktape.h"
#include "lib/lodepng.h"
#include "algorithm.h"
/* uncomment this line to enable the unsafe, deprecated use of the 'gets' function. This will add the function 'prompt' to the js environment */
//#define USE_PROMPT
#define VERSION "0.9"
/* how to save a block-type */
struct color {
unsigned char r, g, b;
};
/* store all block-ids in here -> the level*/
int *js_level;
/* store all blocks here (colors on png when exported) */
struct color *blocks;
/* How many blocks are stored, how much space is allocated for them */
int blocks_len = 0,
blocks_max_len = 90;
/* default level dimensions, may be overwritten by command line args */
int js_level_width = 120,
js_level_height = 70;
/* prints how to use the program */
void print_usage(const char *arg0) {
puts("Version:");
puts(" v" VERSION);
puts("");
puts("Usage:");
printf(" %s <filename.js> [flags ..]\n", arg0);
puts("");
puts(" -- Flags --");
puts(" -w, --width <int> : Set level width");
puts(" -h, --height <int> : Set level height");
puts(" -c, --count <int> : How many maps to generate");
puts(" -o, --output <file> : Export image named <file>");
puts(" -n, --no-export : Don't export the map");
puts("");
puts(" -- Functions --");
puts(" This map generator uses the Duktape (duktape.org)");
puts(" Javascript engine which follows the EcmaScript 5/");
puts(" 5.1 specification.");
puts(" Additional functions are listed in README.md");
#ifdef USE_PROMPT
puts(" Compiled with USE_PROMPT flag");
#endif
puts("");
}
/* read a file and return its content */
const char* read_file(const char *filename, int *length) {
char *buf;
/* try opening the file */
FILE *fp = fopen(filename, "r");
if (!fp) {
printf("Failed to open file '%s'\n", filename);
return NULL;
}
/* get length of file */
fseek(fp, 0, SEEK_END);
*length = ftell(fp);
fseek(fp, 0, SEEK_SET);
/* allocate enough memory */
buf = malloc(*length);
/* read the content */
if (buf)
fread(buf, 1, *length, fp);
/* close the file */
fclose(fp);
return buf;
}
/* reads a file from path and returns it as string */
int js_read_file(duk_context *ctx) {
/* return error if no filename is given */
if (duk_get_top(ctx) == 0) {
printf("'read' expects one argument: 0 given\n");
return DUK_RET_TYPE_ERROR;
}
/* also needs to be a string */
if (!duk_is_string(ctx, 0)) {
printf("'read' expects argument 1 of type string\n");
return DUK_RET_TYPE_ERROR;
}
/* get the first argument */
const char *filename = duk_get_string(ctx, 0);
/* save file contents in this buffer */
char *buffer = 0;
/* how big is the file? */
long length;
/* try opening the file */
FILE *fp = fopen(filename, "r");
if (!fp) {
printf("Failed to open file '%s'\n", filename);
return DUK_RET_TYPE_ERROR;
}
/* get the files length... */
fseek(fp, 0, SEEK_END);
length = ftell(fp);
fseek(fp, 0, SEEK_SET);
/* ...allocate memory for its content... */
buffer = malloc(length);
/* ...and get its content if possible */
if (buffer)
fread(buffer, 1, length, fp);
/* close the file again */
fclose(fp);
/* finally, push the string on the js stack */
duk_push_string(ctx, buffer);
free(buffer);
return 1;
}
#ifdef USE_PROMPT
/* displays a message and returns user input */
int js_prompt(duk_context *ctx) {
/* display message */
printf("%s", duk_to_string(ctx, 0));
/* FIXME: Max 128 bytes? Using gets in C99? please don't be mad :( */
char buf[128];
gets(buf);
/* return the string */
duk_push_string(ctx, buf);
return 1;
}
#endif
/* create a new struct color */
const struct color color_new(const int r, const int g, const int b) {
struct color new_color;
new_color.r = (unsigned char)r;
new_color.g = (unsigned char)g;
new_color.b = (unsigned char)b;
return new_color;
}
/* register a block for global blocks */
void register_block(const int r, const int g, const int b) {
const struct color new_block = color_new(r, g, b);
/* if free memory for blocks is less than 6 blocks, allocate 15 new */
if (blocks_max_len - blocks_len <= 5) {
/* DEBUG: Print message on realloc
printf("Reached %d blocks. Reallocating space to %d.",
blocks_len, blocks_max_len + 20);
*/
blocks_max_len += 15;
blocks = realloc(blocks, sizeof(struct color) * blocks_max_len);
}
/* append new_block to blocks */
blocks[blocks_len] = new_block;
/* update length */
++blocks_len;
}
/* registers a block r,g,b and returns it's id */
int js_register_block(duk_context *ctx) {
/* get r, g, b values */
const int r = duk_get_int(ctx, 0);
const int g = duk_get_int(ctx, 1);
const int b = duk_get_int(ctx, 2);
/* registers block, may allocate more memory */
register_block(r, g, b);
/* return block's id */
duk_push_int(ctx, blocks_len - 1);
return 1;
}
/* sets block x|y to id */
int js_level_set(duk_context *ctx) {
const int x = duk_get_int(ctx, 0);
const int y = duk_get_int(ctx, 1);
const int id = duk_get_int(ctx, 2);
/* bound checking */
if (x < 0 || x >= js_level_width || y < 0 || y >= js_level_height)
return 0;
js_level[x + y * js_level_width] = id;
return 0;
}
/* get block id */
int js_level_get(duk_context *ctx) {
/* use x|y coordinates */
const int x = duk_get_int(ctx, 0);
const int y = duk_get_int(ctx, 1);
/* do bounds check, return block id 0 -> void-block */
if (x < 0 || x >= js_level_width || y < 0 || y >= js_level_height) {
duk_push_int(ctx, 0);
return 1;
}
/* get block id */
const int id = js_level[x + y * js_level_width];
duk_push_int(ctx, id);
return 1;
}
/* load and eval a file */
int js_load_file(duk_context *ctx) {
const char *file = duk_get_string(ctx, 0);
duk_eval_file(ctx, file);
return 0;
}
/* exports the level as png image */
void export_level(char *filename) {
/* add file extension if not given */
if (!strstr(filename, ".png")) {
strcat(filename, ".png");
}
/* allocate memory for every block * 4 (RGBA) */
unsigned char *image = malloc(js_level_width * js_level_height * 4);
/* visit every R index and write R, G, B, A --> 4 steps */
for (int i = 0; i < js_level_width * js_level_height * 4; i += 4) {
/* get color */
const struct color block = blocks[js_level[i / 4]];
/* set R, G, B and A */
image[i + 0] = block.r;
image[i + 1] = block.g;
image[i + 2] = block.b;
image[i + 3] = 255;
}
/* encode the image with lodepng */
unsigned error = lodepng_encode32_file(filename,
image,
js_level_width,
js_level_height);
/* if there's an error, print it! */
if (error)
printf("Error %u: %s\n", error, lodepng_error_text(error));
/* free memory for image */
free(image);
}
/* Find all occurences of block type and return their positions */
int js_algo_findall(duk_context *ctx) {
/* block id */
const int search_id = duk_to_int(ctx, 0);
/* finds all blocks of id */
struct point *result = algo_findall(js_level, js_level_width, js_level_height, search_id);
/* pushes points 'result' as array on the js stack --> [[x, y], [x2, y2], ...] */
algo_push_point_array(ctx, result);
return 1;
}
int js_algo_fill(duk_context *ctx) {
const int fill_id = duk_to_int(ctx, 0);
algo_fill(js_level, js_level_width, js_level_height, fill_id);
return 0;
}
int js_algo_neighbors_of(duk_context *ctx) {
const int xp = duk_get_int(ctx, 0);
const int yp = duk_get_int(ctx, 1);
const int id = duk_get_int(ctx, 2);
const int n = algo_neighbors_of(js_level, js_level_width, js_level_height, xp, yp, id);
duk_push_int(ctx, n);
return 1;
}
/* casts a ray in a direction and returns a point where it collides */
int js_algo_raycast(duk_context *ctx) {
/* get x, y, dir */
const int x = duk_to_int(ctx, 0);
const int y = duk_to_int(ctx, 1);
const int dir = duk_to_int(ctx, 2);
/* how many ignored blocks */
duk_get_prop_string(ctx, 3, "length");
const int ids_len = duk_get_int(ctx, -1);
/* allocate memory for ignored blocks */
int *ids = malloc(sizeof(int) * ids_len);
/* get all ids to be ignored */
for (int i = 0; i < ids_len; ++i) {
duk_get_prop_index(ctx, 3, i);
ids[i] = duk_get_int(ctx, -1);
duk_pop(ctx);
}
/* calculate distance in dir */
const int distance = algo_distance(js_level, js_level_width,
js_level_height, x, y, dir, ids, ids_len);
/* no longer need ignored_ids */
free(ids);
/* return position in array */
const int arr_idx = duk_push_array(ctx);
if (dir == 1)
duk_push_int(ctx, x + distance);
else if (dir == -1)
duk_push_int(ctx, x - distance);
else
duk_push_int(ctx, x);
duk_put_prop_index(ctx, arr_idx, 0);
if (dir == -2)
duk_push_int(ctx, y - distance);
else if (dir == 2)
duk_push_int(ctx, y + distance);
else
duk_push_int(ctx, y);
duk_put_prop_index(ctx, arr_idx, 1);
return 1;
}
int main(int argc, char *argv[]) {
/* initialize js environment */
duk_context *ctx = duk_create_heap_default();
/* add native functions to js environment */
duk_push_global_object(ctx);
/* 'read' */
duk_push_c_function(ctx, js_read_file, 1);
duk_put_prop_string(ctx, -2, "read");
#ifdef USE_PROMPT
/* 'prompt' */
duk_push_c_function(ctx, js_prompt, 1);
duk_put_prop_string(ctx, -2, "prompt");
#endif
/* 'get' */
duk_push_c_function(ctx, js_level_get, 2);
duk_put_prop_string(ctx, -2, "get");
/* 'set' */
duk_push_c_function(ctx, js_level_set, 3);
duk_put_prop_string(ctx, -2, "set");
/* 'register' */
duk_push_c_function(ctx, js_register_block, 3);
duk_put_prop_string(ctx, -2, "register");
/* 'load_file' */
duk_push_c_function(ctx, js_load_file, 1);
duk_put_prop_string(ctx, -2, "load_file");
/* 'fill' */
duk_push_c_function(ctx, js_algo_fill, 1);
duk_put_prop_string(ctx, -2, "fill");
/* 'findall' */
duk_push_c_function(ctx, js_algo_findall, 1);
duk_put_prop_string(ctx, -2, "findall");
/* 'neighbors_of' */
duk_push_c_function(ctx, js_algo_neighbors_of, 3);
duk_put_prop_string(ctx, -2, "neighbors_of");
/* 'distance' */
duk_push_c_function(ctx, js_algo_raycast, 4);
duk_put_prop_string(ctx, -2, "raycast");
/* pop global object */
duk_pop(ctx);
/* try to load some basic functionality */
if (duk_peval_file(ctx, "base/base.js") != 0) {
//printf("Failed to load 'base/base.js': %s\n", duk_safe_to_string(ctx, -1));
/* When failing to load base/base.js, se simple, default module loader. */
duk_eval_string_noresult(ctx, "Duktape.modSearch = function(id) { return read(id); };");
}
duk_pop(ctx);
if (argc > 1) {
/* run garbage collector (twice as told by the docs) */
duk_gc(ctx, 0);
duk_gc(ctx, 0);
/* where to save the png */
char *export_file = "generated_map";
int generate_num = 1;
int save_png_disabled = 0;
/* parse command line flags */
for (int i = 0; i < argc; ++i) {
if (!strcmp(argv[i], "--width") || !strcmp(argv[i], "-w")) {
if (i + 1 < argc)
js_level_width = atoi(argv[i + 1]);
} else if (!strcmp(argv[i], "--height") || !strcmp(argv[i], "-h")) {
if (i + 1 < argc)
js_level_height = atoi(argv[i + 1]);
} else if (!strcmp(argv[i], "-o") || !strcmp(argv[i], "--output")) {
if (i + 1 < argc)
export_file = argv[i + 1];
} else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--count")) {
if (i + 1 < argc)
generate_num = atoi(argv[i + 1]);
} else if (!strcmp(argv[i], "-n") || !strcmp(argv[i], "--no-export")) {
save_png_disabled = 1;
}
}
/* reserve enough memory for level */
js_level = malloc(sizeof(int) * js_level_width * js_level_height);
blocks = malloc(sizeof(struct color) * blocks_max_len);
/* set all zeroes, maybe change this use to calloc */
memset(js_level, 0, sizeof(int) * js_level_width * js_level_height);
/* declare & assign global variable Width & Height */
duk_push_int(ctx, js_level_width);
duk_put_global_string(ctx, "Width");
duk_push_int(ctx, js_level_height);
duk_put_global_string(ctx, "Height");
/* can generate multiple maps */
for (int i = 0; i < generate_num; ++i) {
if (duk_peval_file(ctx, argv[1]) != 0) {
printf("('%s' #%d) %s\n", argv[1], i, duk_safe_to_string(ctx, -1));
}
duk_pop(ctx);
/* export the level with a special name */
char export_file_num[256];
if (generate_num > 1) {
sprintf(export_file_num, "%s_%d.png", export_file, i);
} else {
sprintf(export_file_num, "%s.png", export_file);
}
/* export the level as png */
if (!save_png_disabled)
export_level(export_file_num);
}
/* free all memory */
free(js_level);
free(blocks);
} else {
/* tell the user how to use this */
print_usage(argv[0]);
}
/* clean up */
duk_destroy_heap(ctx);
return 0;
}