2 * Copyright (c) 2007, 2008 Czirkos Zoltan <cirix@fw.hu>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 #include <glib/gi18n.h>
22 #include "caveobject.h"
26 /* description of coordinates, elements - used by editor properties dialog. */
27 GdObjectDescription gd_object_description
[]={
29 { /* point */ N_("Point"), N_("Coordinates"), NULL
, NULL
, NULL
, N_("Element"), NULL
, N_("Draw"), NULL
},
30 { /* line */ N_("Line"), N_("Starting coordinates"), N_("Ending coordinates"), NULL
, NULL
, N_("Element"), NULL
, N_("Draw"), NULL
},
31 { /* rect */ N_("Outline"), N_("Starting coordinates"), N_("Ending coordinates"), NULL
, NULL
, N_("Element"), NULL
, N_("Draw"), NULL
},
32 { /* frect */ N_("Rectangle"), N_("Starting coordinates"), N_("Ending coordinates"), NULL
, NULL
, N_("Border element"), N_("Fill element"), N_("Draw"), N_("Fill") },
33 { /* rastr */ N_("Raster"), N_("Starting coordinates"), N_("Ending coordinates"), N_("Distance"), NULL
, N_("Element"), NULL
, N_("Draw"), NULL
},
34 { /* join */ N_("Join"), NULL
, NULL
, N_("Distance"), NULL
, N_("Search element"), N_("Add element"), N_("Find"), N_("Draw") },
35 { /* flodr */ N_("Replace fill"), N_("Starting coordinates"), NULL
, NULL
, NULL
, N_("Search element"), N_("Fill element"), NULL
, N_("Replace") },
36 { /* flodb */ N_("Fill to border"), N_("Starting coordinates"), NULL
, NULL
, NULL
, N_("Border element"), N_("Fill element"), N_("Border"), N_("Fill") },
37 { /* maze */ N_("Maze"), N_("Starting coordinates"), N_("Ending coordinates"), N_("Wall and path"), N_("Random seed %d"), N_("Wall element"), N_("Path element"), N_("Wall"), N_("Path") , N_("Horizontal (%%)")},
38 { /* umaze */ N_("Unicursal maze"), N_("Starting coordinates"), N_("Ending coordinates"), N_("Wall and path"), N_("Random seed %d"), N_("Wall element"), N_("Path element"), N_("Wall"), N_("Path") , N_("Horizontal (%%)")},
39 { /* Bmaze */ N_("Braid maze"), N_("Starting coordinates"), N_("Ending coordinates"), N_("Wall and path"), N_("Random seed %d"), N_("Wall element"), N_("Path element"), N_("Wall"), N_("Path") , N_("Horizontal (%%)")},
40 { /* random */N_("Random fill"), N_("Starting coordinates"), N_("Ending coordinates"), NULL
, N_("Random seed %d"), N_("Replace only this element"), NULL
, N_("Random 1"), N_("Initial"), NULL
, N_("C64 random numbers")},
41 { /* copyps */N_("Copy and paste"), N_("Starting coordinates"), N_("Width and height"), N_("Paste coordinates"), NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, N_("Mirror"), N_("Flip")},
45 GdObjectLevels gd_levels_mask
[]={GD_OBJECT_LEVEL1
, GD_OBJECT_LEVEL2
, GD_OBJECT_LEVEL3
, GD_OBJECT_LEVEL4
, GD_OBJECT_LEVEL5
};
50 /* bdcff text description of object. caller should free string. */
52 gd_object_to_bdcff(const GdObject
*object
)
58 switch (object
->type
) {
60 return g_strdup_printf("Point=%d %d %s", object
->x1
, object
->y1
, gd_elements
[object
->element
].filename
);
63 return g_strdup_printf("Line=%d %d %d %d %s", object
->x1
, object
->y1
, object
->x2
, object
->y2
, gd_elements
[object
->element
].filename
);
66 return g_strdup_printf("Rectangle=%d %d %d %d %s", object
->x1
, object
->y1
, object
->x2
, object
->y2
, gd_elements
[object
->element
].filename
);
68 case FILLED_RECTANGLE
:
69 /* if elements are not the same */
70 if (object
->fill_element
!=object
->element
)
71 return g_strdup_printf("FillRect=%d %d %d %d %s %s", object
->x1
, object
->y1
, object
->x2
, object
->y2
, gd_elements
[object
->element
].filename
, gd_elements
[object
->fill_element
].filename
);
72 /* they are the same */
73 return g_strdup_printf("FillRect=%d %d %d %d %s", object
->x1
, object
->y1
, object
->x2
, object
->y2
, gd_elements
[object
->element
].filename
);
76 return g_strdup_printf("Raster=%d %d %d %d %d %d %s", object
->x1
, object
->y1
, (object
->x2
-object
->x1
)/object
->dx
+1, (object
->y2
-object
->y1
)/object
->dy
+1, object
->dx
, object
->dy
, gd_elements
[object
->element
].filename
);
79 return g_strdup_printf("Add=%d %d %s %s", object
->dx
, object
->dy
, gd_elements
[object
->element
].filename
, gd_elements
[object
->fill_element
].filename
);
81 case FLOODFILL_BORDER
:
82 return g_strdup_printf("BoundaryFill=%d %d %s %s", object
->x1
, object
->y1
, gd_elements
[object
->fill_element
].filename
, gd_elements
[object
->element
].filename
);
84 case FLOODFILL_REPLACE
:
85 return g_strdup_printf("FloodFill=%d %d %s %s", object
->x1
, object
->y1
, gd_elements
[object
->fill_element
].filename
, gd_elements
[object
->element
].filename
);
90 switch(object
->type
) {
91 case MAZE
: type
="perfect"; break;
92 case MAZE_UNICURSAL
: type
="unicursal"; break;
93 case MAZE_BRAID
: type
="braid"; break;
95 g_assert_not_reached();
97 return g_strdup_printf("Maze=%d %d %d %d %d %d %d %d %d %d %d %d %s %s %s", object
->x1
, object
->y1
, object
->x2
, object
->y2
, object
->dx
, object
->dy
, object
->horiz
, object
->seed
[0], object
->seed
[1], object
->seed
[2], object
->seed
[3], object
->seed
[4], gd_elements
[object
->element
].filename
, gd_elements
[object
->fill_element
].filename
, type
);
100 str
=g_string_new(NULL
);
101 g_string_append_printf(str
, "%s=%d %d %d %d %d %d %d %d %d %s", object
->c64_random
?"RandomFillC64":"RandomFill", object
->x1
, object
->y1
, object
->x2
, object
->y2
, object
->seed
[0], object
->seed
[1], object
->seed
[2], object
->seed
[3], object
->seed
[4], gd_elements
[object
->fill_element
].filename
); /* seed and initial fill */
103 if (object
->random_fill_probability
[j
]!=0)
104 g_string_append_printf(str
, " %s %d", gd_elements
[object
->random_fill
[j
]].filename
, object
->random_fill_probability
[j
]);
105 if (object
->element
!=O_NONE
)
106 g_string_append_printf(str
, " %s", gd_elements
[object
->element
].filename
);
108 /* free string but do not free char *; return char *. */
109 return g_string_free(str
, FALSE
);
112 return g_strdup_printf("CopyPaste=%d %d %d %d %d %d %s %s", object
->x1
, object
->y1
, object
->x2
, object
->y2
, object
->dx
, object
->dy
, object
->mirror
?"mirror":"nomirror", object
->flip
?"flip":"noflip");
116 g_assert_not_reached();
122 /* create new object from bdcff description.
123 return new object if ok; return null if failed.
126 gd_object_new_from_string(char *str
)
131 char elem0
[100], elem1
[100];
133 equalsign
=strchr(str
, '=');
137 /* split string by replacing the equal sign with zero */
142 /* INDIVIDUAL POINT CAVE OBJECT */
143 if (g_ascii_strcasecmp(name
, "Point")==0) {
145 if (sscanf(param
, "%d %d %s", &object
.x1
, &object
.y1
, elem0
)==3) {
146 object
.element
=gd_get_element_from_string(elem0
);
147 return g_memdup(&object
, sizeof (GdObject
));
153 if (g_ascii_strcasecmp(name
, "Line")==0) {
155 if (sscanf(param
, "%d %d %d %d %s", &object
.x1
, &object
.y1
, &object
.x2
, &object
.y2
, elem0
)==5) {
156 object
.element
=gd_get_element_from_string(elem0
);
157 return g_memdup(&object
, sizeof (GdObject
));
162 /* RECTANGLE OBJECT */
163 if (g_ascii_strcasecmp(name
, "Rectangle")==0) {
164 if (sscanf (param
, "%d %d %d %d %s", &object
.x1
, &object
.y1
, &object
.x2
, &object
.y2
, elem0
)==5) {
165 object
.type
=RECTANGLE
;
166 object
.element
=gd_get_element_from_string (elem0
);
167 return g_memdup(&object
, sizeof (GdObject
));
172 /* FILLED RECTANGLE OBJECT */
173 if (g_ascii_strcasecmp(name
, "FillRect")==0) {
176 paramcount
=sscanf(param
, "%d %d %d %d %s %s", &object
.x1
, &object
.y1
, &object
.x2
, &object
.y2
, elem0
, elem1
);
177 object
.type
=FILLED_RECTANGLE
;
179 object
.element
=gd_get_element_from_string (elem0
);
180 object
.fill_element
=gd_get_element_from_string (elem1
);
181 return g_memdup(&object
, sizeof (GdObject
));
184 object
.element
=object
.fill_element
=gd_get_element_from_string (elem0
);
185 return g_memdup(&object
, sizeof (GdObject
));
191 if (g_ascii_strcasecmp(name
, "Raster")==0) {
194 if (sscanf (param
, "%d %d %d %d %d %d %s", &object
.x1
, &object
.y1
, &nx
, &ny
, &object
.dx
, &object
.dy
, elem0
)==7) {
196 object
.x2
=object
.x1
+ nx
*object
.dx
;
197 object
.y2
=object
.y1
+ ny
*object
.dy
;
199 object
.element
=gd_get_element_from_string (elem0
);
200 return g_memdup(&object
, sizeof (GdObject
));
206 if (g_ascii_strcasecmp(name
, "Join")==0 || g_ascii_strcasecmp(name
, "Add")==0) {
207 if (sscanf (param
, "%d %d %s %s", &object
.dx
, &object
.dy
, elem0
, elem1
)==4) {
209 object
.element
=gd_get_element_from_string (elem0
);
210 object
.fill_element
=gd_get_element_from_string (elem1
);
211 return g_memdup(&object
, sizeof (GdObject
));
216 /* FILL TO BORDER OBJECT */
217 if (g_ascii_strcasecmp(name
, "BoundaryFill")==0) {
218 if (sscanf (param
, "%d %d %s %s", &object
.x1
, &object
.y1
, elem0
, elem1
)==4) {
219 object
.type
=FLOODFILL_BORDER
;
220 object
.fill_element
=gd_get_element_from_string (elem0
);
221 object
.element
=gd_get_element_from_string (elem1
);
222 return g_memdup(&object
, sizeof (GdObject
));
227 /* REPLACE FILL OBJECT */
228 if (g_ascii_strcasecmp(name
, "FloodFill")==0) {
229 if (sscanf (param
, "%d %d %s %s", &object
.x1
, &object
.y1
, elem0
, elem1
)==4) {
230 object
.type
=FLOODFILL_REPLACE
;
231 object
.fill_element
=gd_get_element_from_string (elem0
);
232 object
.element
=gd_get_element_from_string (elem1
);
233 return g_memdup(&object
, sizeof (GdObject
));
239 /* MAZE UNICURSAL OBJECT */
240 /* BRAID MAZE OBJECT */
241 if (g_ascii_strcasecmp(name
, "Maze")==0) {
242 char type
[100]="perfect";
244 if (sscanf (param
, "%d %d %d %d %d %d %d %d %d %d %d %d %s %s %s", &object
.x1
, &object
.y1
, &object
.x2
, &object
.y2
, &object
.dx
, &object
.dy
, &object
.horiz
, &object
.seed
[0], &object
.seed
[1], &object
.seed
[2], &object
.seed
[3], &object
.seed
[4], elem0
, elem1
, type
)>=14) {
245 if (g_ascii_strcasecmp(type
, "unicursal")==0)
246 object
.type
=MAZE_UNICURSAL
;
247 else if (g_ascii_strcasecmp(type
, "perfect")==0)
249 else if (g_ascii_strcasecmp(type
, "braid")==0)
250 object
.type
=MAZE_BRAID
;
252 g_warning("unknown maze type: %s, defaulting to perfect", type
);
255 object
.element
=gd_get_element_from_string (elem0
);
256 object
.fill_element
=gd_get_element_from_string (elem1
);
257 return g_memdup(&object
, sizeof (GdObject
));
262 /* RANDOM FILL OBJECT */
263 if (g_ascii_strcasecmp(name
, "RandomFill")==0 || g_ascii_strcasecmp(name
, "RandomFillC64")==0) {
264 static char **words
=NULL
;
267 object
.type
=RANDOM_FILL
;
268 if (g_ascii_strcasecmp(name
, "RandomFillC64")==0) /* totally the same, but uses c64 random generator */
269 object
.c64_random
=TRUE
;
271 object
.c64_random
=FALSE
;
272 if (sscanf(param
, "%d %d %d %d", &object
.x1
, &object
.y1
, &object
.x2
, &object
.y2
)!=4)
276 words
=g_strsplit_set(param
, " ", -1);
277 l
=g_strv_length(words
);
281 if (sscanf(words
[4+i
], "%d", &object
.seed
[i
])!=1)
283 object
.fill_element
=gd_get_element_from_string(words
[9]);
284 for (i
=0; i
<4; i
++) {
285 object
.random_fill
[i
]=O_DIRT
;
286 object
.random_fill_probability
[i
]=0;
288 for (i
=10; i
<l
-1; i
+=2) {
289 object
.random_fill
[(i
-10)/2]=gd_get_element_from_string(words
[i
]);
290 if (sscanf(words
[i
+1], "%d", &object
.random_fill_probability
[(i
-10)/2])==0)
293 object
.element
=O_NONE
;
295 object
.element
=gd_get_element_from_string(words
[l
-1]);
297 return g_memdup(&object
, sizeof (GdObject
));
300 /* COPY PASTE OBJECT */
301 if (g_ascii_strcasecmp(name
, "CopyPaste")==0) {
302 char mirror
[100]="nomirror";
303 char flip
[100]="noflip";
304 object
.type
=COPY_PASTE
;
306 object
.flip
=object
.mirror
=FALSE
;
308 if (sscanf(param
, "%d %d %d %d %d %d %s %s", &object
.x1
, &object
.y1
, &object
.x2
, &object
.y2
, &object
.dx
, &object
.dy
, mirror
, flip
)<6)
310 /* MIRROR PROPERTY */
311 if (g_ascii_strcasecmp(mirror
, "mirror")==0)
314 if (g_ascii_strcasecmp(mirror
, "nomirror")==0)
317 g_warning("invalid setting for copypaste mirror property: %s", mirror
);
319 if (g_ascii_strcasecmp(flip
, "flip")==0)
322 if (g_ascii_strcasecmp(flip
, "noflip")==0)
325 g_warning("invalid setting for copypaste flip property: %s", flip
);
327 return g_memdup(&object
, sizeof(GdObject
));
336 gd_get_object_description_markup (GdObject
*selected
)
338 g_return_val_if_fail (selected
!= NULL
, NULL
);
340 switch (selected
->type
) {
342 return g_markup_printf_escaped(_("Point of <b>%s</b> at %d,%d"), gd_elements
[selected
->element
].lowercase_name
, selected
->x1
, selected
->y1
);
345 return g_markup_printf_escaped(_("Line from %d,%d to %d,%d of <b>%s</b>"), selected
->x1
, selected
->y1
, selected
->x2
, selected
->y2
, gd_elements
[selected
->element
].lowercase_name
);
348 return g_markup_printf_escaped(_("Rectangle from %d,%d to %d,%d of <b>%s</b>"), selected
->x1
, selected
->y1
, selected
->x2
, selected
->y2
, gd_elements
[selected
->element
].lowercase_name
);
350 case FILLED_RECTANGLE
:
351 return g_markup_printf_escaped(_("Rectangle from %d,%d to %d,%d of <b>%s</b>, filled with <b>%s</b>"), selected
->x1
, selected
->y1
, selected
->x2
, selected
->y2
, gd_elements
[selected
->element
].lowercase_name
, gd_elements
[selected
->fill_element
].lowercase_name
);
354 return g_markup_printf_escaped(_("Raster from %d,%d to %d,%d of <b>%s</b>, distance %+d,%+d"), selected
->x1
, selected
->y1
, selected
->x2
, selected
->y2
, gd_elements
[selected
->element
].lowercase_name
, selected
->dx
, selected
->dy
);
357 return g_markup_printf_escaped(_("Join <b>%s</b> to every <b>%s</b>, distance %+d,%+d"), gd_elements
[selected
->fill_element
].lowercase_name
, gd_elements
[selected
->element
].lowercase_name
, selected
->dx
, selected
->dy
);
359 case FLOODFILL_BORDER
:
360 return g_markup_printf_escaped(_("Floodfill from %d,%d of <b>%s</b>, border <b>%s</b>"), selected
->x1
, selected
->y1
, gd_elements
[selected
->fill_element
].lowercase_name
, gd_elements
[selected
->element
].lowercase_name
);
362 case FLOODFILL_REPLACE
:
363 return g_markup_printf_escaped(_("Floodfill from %d,%d of <b>%s</b>, replacing <b>%s</b>"), selected
->x1
, selected
->y1
, gd_elements
[selected
->fill_element
].lowercase_name
, gd_elements
[selected
->element
].lowercase_name
);
366 return g_markup_printf_escaped(_("Maze from %d,%d to %d,%d, wall <b>%s</b>, path <b>%s</b>"), selected
->x1
, selected
->y1
, selected
->x2
, selected
->y2
, gd_elements
[selected
->element
].lowercase_name
, gd_elements
[selected
->fill_element
].lowercase_name
);
369 return g_markup_printf_escaped(_("Unicursal maze from %d,%d to %d,%d, wall <b>%s</b>, path <b>%s</b>"), selected
->x1
, selected
->y1
, selected
->x2
, selected
->y2
, gd_elements
[selected
->element
].lowercase_name
, gd_elements
[selected
->fill_element
].lowercase_name
);
372 return g_markup_printf_escaped(_("Braid maze from %d,%d to %d,%d, wall <b>%s</b>, path <b>%s</b>"), selected
->x1
, selected
->y1
, selected
->x2
, selected
->y2
, gd_elements
[selected
->element
].lowercase_name
, gd_elements
[selected
->fill_element
].lowercase_name
);
375 return g_markup_printf_escaped(_("Random fill from %d,%d to %d,%d, replacing %s"), selected
->x1
, selected
->y1
, selected
->x2
, selected
->y2
, gd_elements
[selected
->element
].lowercase_name
);
378 return g_markup_printf_escaped(_("Copy from %d,%d-%d,%d, paste to %d,%d"), selected
->x1
, selected
->y1
, selected
->x2
, selected
->y2
, selected
->dx
, selected
->dy
);
382 g_assert_not_reached();
391 gd_get_object_coordinates_text (GdObject
*selected
)
393 g_return_val_if_fail (selected
!=NULL
, NULL
);
395 switch (selected
->type
) {
397 case FLOODFILL_BORDER
:
398 case FLOODFILL_REPLACE
:
399 return g_strdup_printf("%d,%d", selected
->x1
, selected
->y1
);
403 case FILLED_RECTANGLE
:
405 return g_strdup_printf("%d,%d-%d,%d", selected
->x1
, selected
->y1
, selected
->x2
, selected
->y2
);
411 return g_strdup_printf("%d,%d-%d,%d (%d,%d)", selected
->x1
, selected
->y1
, selected
->x2
, selected
->y2
, selected
->dx
, selected
->dy
);
414 return g_strdup_printf("%+d,%+d", selected
->dx
, selected
->dy
);
417 return g_strdup_printf("%d,%d-%d,%d, %d,%d", selected
->x1
, selected
->y1
, selected
->x2
, selected
->y2
, selected
->dx
, selected
->dy
);
420 g_assert_not_reached();
433 /** drawing a line, using bresenham's */
435 draw_line (Cave
*cave
, const GdObject
*object
)
437 int x
, y
, x1
, y1
, x2
, y2
;
439 int error
, dx
, dy
, ystep
;
442 y1
=object
->y1
, x2
=object
->x2
;
444 steep
=ABS (y2
- y1
) > ABS (x2
- x1
);
465 ystep
=(y1
< y2
) ? 1 : -1;
466 for (x
=x1
; x
<= x2
; x
++) {
468 gd_cave_store_rc (cave
, y
, x
, object
->element
, object
);
470 gd_cave_store_rc (cave
, x
, y
, object
->element
, object
);
472 if (error
* 2 >= dx
) {
482 draw_fill_replace_proc(Cave
*cave
, int x
, int y
, const GdObject
*object
)
484 /* fill with border so we do not come back */
485 gd_cave_store_rc(cave
, x
, y
, object
->fill_element
, object
);
487 if (x
>0 && gd_cave_get_rc(cave
, x
-1, y
)==object
->element
) draw_fill_replace_proc(cave
, x
-1, y
, object
);
488 if (y
>0 && gd_cave_get_rc(cave
, x
, y
-1)==object
->element
) draw_fill_replace_proc(cave
, x
, y
-1, object
);
489 if (x
<cave
->w
-1 && gd_cave_get_rc(cave
, x
+1, y
)==object
->element
) draw_fill_replace_proc(cave
, x
+1, y
, object
);
490 if (y
<cave
->h
-1 && gd_cave_get_rc(cave
, x
, y
+1)==object
->element
) draw_fill_replace_proc(cave
, x
, y
+1, object
);
494 draw_fill_replace (Cave
*cave
, const GdObject
*object
)
497 if (object
->x1
<0 || object
->y1
<0 || object
->x1
>=cave
->w
|| object
->y1
>=cave
->h
)
499 if (object
->element
==object
->fill_element
)
501 /* this procedure fills the area with the object->element. */
502 draw_fill_replace_proc(cave
, object
->x1
, object
->y1
, object
);
508 draw_fill_border_proc (Cave
*cave
, int x
, int y
, const GdObject
*object
)
510 /* fill with border so we do not come back */
511 gd_cave_store_rc(cave
, x
, y
, object
->element
, object
);
513 if (x
>0 && gd_cave_get_rc(cave
, x
-1, y
)!=object
->element
) draw_fill_border_proc(cave
, x
-1, y
, object
);
514 if (y
>0 && gd_cave_get_rc(cave
, x
, y
-1)!=object
->element
) draw_fill_border_proc(cave
, x
, y
-1, object
);
515 if (x
<cave
->w
-1 && gd_cave_get_rc(cave
, x
+1, y
)!=object
->element
) draw_fill_border_proc(cave
, x
+1, y
, object
);
516 if (y
<cave
->h
-1 && gd_cave_get_rc(cave
, x
, y
+1)!=object
->element
) draw_fill_border_proc(cave
, x
, y
+1, object
);
520 draw_fill_border (Cave
*cave
, const GdObject
*object
)
525 if (object
->x1
<0 || object
->y1
<0 || object
->x1
>=cave
->w
|| object
->y1
>=cave
->h
)
528 /* this procedure fills the area with the object->element. */
529 draw_fill_border_proc(cave
, object
->x1
, object
->y1
, object
);
531 /* after the fill, we change all filled cells to the fill_element. */
532 /* we find those by looking at the object_order[][] */
533 for (y
=0; y
<cave
->h
; y
++)
534 for (x
=0; x
<cave
->w
; x
++)
535 if (cave
->objects_order
[y
][x
]==object
)
536 cave
->map
[y
][x
]=object
->fill_element
;
541 /* rectangle, frame only */
543 draw_rectangle(Cave
*cave
, const GdObject
*object
)
545 int x1
, y1
, x2
, y2
, x
, y
;
547 /* reorder coordinates if not drawing from northwest to southeast */
549 y1
=object
->y1
, x2
=object
->x2
;
561 for (x
=x1
; x
<= x2
; x
++) {
562 gd_cave_store_rc (cave
, x
, object
->y1
, object
->element
, object
);
563 gd_cave_store_rc (cave
, x
, object
->y2
, object
->element
, object
);
565 for (y
=y1
; y
<= y2
; y
++) {
566 gd_cave_store_rc (cave
, object
->x1
, y
, object
->element
, object
);
567 gd_cave_store_rc (cave
, object
->x2
, y
, object
->element
, object
);
573 /* rectangle, filled one */
575 draw_filled_rectangle(Cave
*cave
, const GdObject
*object
)
577 int x1
, y1
, x2
, y2
, x
, y
;
579 /* reorder coordinates if not drawing from northwest to southeast */
581 y1
=object
->y1
, x2
=object
->x2
;
593 for (y
=y1
; y
<= y2
; y
++)
594 for (x
=x1
; x
<= x2
; x
++)
595 gd_cave_store_rc (cave
, x
, y
, (y
==object
->y1
|| y
==object
->y2
|| x
==object
->x1
|| x
==object
->x2
) ? object
->element
: object
->fill_element
, object
);
600 /* something like ordered fill, increment is dx and dy. */
602 draw_raster(Cave
*cave
, const GdObject
*object
)
604 int x
, y
, x1
, y1
, x2
, y2
;
607 /* reorder coordinates if not drawing from northwest to southeast */
624 for (y
=y1
; y
<=y2
; y
+=dy
)
625 for (x
=x1
; x
<=x2
; x
+=dx
)
626 gd_cave_store_rc (cave
, x
, y
, object
->element
, object
);
631 /* find every object, and put fill_element next to it. relative coordinates dx,dy */
633 draw_join(Cave
*cave
, const GdObject
*object
)
637 for (y
=0; y
<cave
->h
; y
++)
638 for (x
=0; x
<cave
->w
; x
++)
639 if (cave
->map
[y
][x
]==object
->element
) {
640 int nx
=x
+ object
->dx
;
641 int ny
=y
+ object
->dy
;
642 /* this one implements wraparound for joins. it is needed by many caves in profi boulder series */
645 gd_cave_store_rc (cave
, nx
, ny
, object
->fill_element
, object
);
650 /* create a maze in a gboolean **maze. */
651 /* recursive algorithm. */
653 mazegen(GRand
*rand
, gboolean
**maze
, int width
, int height
, int x
, int y
, int horiz
)
661 dir
=g_rand_int_range(rand
, 0, 100)<horiz
?2:0; /* horiz or vert */
662 /* if no horizontal movement possible, choose vertical */
663 if (dir
==2 && (dirmask
&12)==0)
665 else if (dir
==0 && (dirmask
&3)==0) /* and vice versa */
667 dir
+=g_rand_int_range(rand
, 0, 2); /* dir */
668 if (dirmask
&(1<<dir
)) {
673 if (y
>=2 && !maze
[y
-2][x
]) {
675 mazegen(rand
, maze
, width
, height
, x
, y
-2, horiz
);
679 if (y
<height
-2 && !maze
[y
+2][x
]) {
681 mazegen(rand
, maze
, width
, height
, x
, y
+2, horiz
);
685 if (x
>=2 && !maze
[y
][x
-2]) {
687 mazegen(rand
, maze
, width
, height
, x
-2, y
, horiz
);
691 if (x
<width
-2 && !maze
[y
][x
+2]) {
693 mazegen(rand
, maze
, width
, height
, x
+2, y
, horiz
);
697 g_assert_not_reached();
705 #define CELL_TO_POINTER(x,y) (GUINT_TO_POINTER(((y)<<16)+(x)))
706 #define X_FROM_POINTER(p) (GPOINTER_TO_UINT(p)&65535)
707 #define Y_FROM_POINTER(p) (GPOINTER_TO_UINT(p)>>16)
709 ptr_int_compare(gconstpointer p1
, gconstpointer p2
)
711 return GPOINTER_TO_INT(p1
)-GPOINTER_TO_INT(p2
);
714 /* maze generation algorithm from crli */
716 mazegen(GRand
*rand
, gboolean
**maze
, int width
, int height
, int x
, int y
, int horiz
)
720 cells
=g_list_append(cells
, CELL_TO_POINTER(x
,y
));
722 while (cells
!=NULL
) {
729 gboolean possible_dirs
[4];
731 x
=X_FROM_POINTER(iter
->data
);
732 y
=Y_FROM_POINTER(iter
->data
);
734 possible_dirs
[0]=y
>=2 && !maze
[y
-2][x
];
735 possible_dirs
[1]=x
>=2 && !maze
[y
][x
-2];
736 possible_dirs
[2]=y
<height
-2 && !maze
[y
+2][x
];
737 possible_dirs
[3]=x
<width
-2 && !maze
[y
][x
+2];
739 if (possible_dirs
[0] || possible_dirs
[1] || possible_dirs
[2] || possible_dirs
[3]) {
740 /* there is at least one direction, so choose one */
741 switch (g_rand_int_range(rand
, 0, 4)) {
743 if (possible_dirs
[0]) {
746 cells
=g_list_insert_sorted(cells
, CELL_TO_POINTER(x
, y
-2), ptr_int_compare
);
750 if (possible_dirs
[1]) {
753 cells
=g_list_insert_sorted(cells
, CELL_TO_POINTER(x
-2, y
), ptr_int_compare
);
757 if (possible_dirs
[2]) {
760 cells
=g_list_insert_sorted(cells
, CELL_TO_POINTER(x
, y
+2), ptr_int_compare
);
764 if (possible_dirs
[3]) {
767 cells
=g_list_insert_sorted(cells
, CELL_TO_POINTER(x
+2, y
), ptr_int_compare
);
771 g_assert_not_reached();
779 cells
=g_list_remove_link(cells
, iter
);
786 #undef CELL_TO_POINTER
787 #undef X_FROM_POINTER
788 #undef Y_FROM_POINTER
792 #define CELL_TO_POINTER(x,y) (GUINT_TO_POINTER(((y)<<16)+(x)))
793 #define X_FROM_POINTER(p) (GPOINTER_TO_UINT(p)&65535)
794 #define Y_FROM_POINTER(p) (GPOINTER_TO_UINT(p)>>16)
795 /* growing tree maze generation algorithm. */
797 mazegen(GRand
*rand
, gboolean
**maze
, int width
, int height
, int x
, int y
, int complexity
)
801 cells
=g_ptr_array_sized_new(width
*height
/4);
802 g_ptr_array_add(cells
, CELL_TO_POINTER(x
,y
));
804 while (cells
->len
!=0) {
806 int possible_dirs
[4], dirnum
;
810 if (complexity
>=g_rand_int_range(rand
, 0, 100))
813 l
=g_rand_int_range(rand
, 0, cells
->len
);
815 x
=X_FROM_POINTER(g_ptr_array_index(cells
, i
));
816 y
=Y_FROM_POINTER(g_ptr_array_index(cells
, i
));
819 if (y
>=2 && !maze
[y
-2][x
])
820 possible_dirs
[dirnum
++]=0;
821 if (x
>=2 && !maze
[y
][x
-2])
822 possible_dirs
[dirnum
++]=1;
823 if (y
<height
-2 && !maze
[y
+2][x
])
824 possible_dirs
[dirnum
++]=2;
825 if (x
<width
-2 && !maze
[y
][x
+2])
826 possible_dirs
[dirnum
++]=3;
827 if (dirnum
==0) /* no possible direction, remove from list */
828 g_ptr_array_remove_index(cells
, i
);
830 /* there is at least one direction, so choose one */
831 int random_dir
=possible_dirs
[g_rand_int_range(rand
, 0, dirnum
)];
833 switch (random_dir
) {
837 g_ptr_array_add(cells
, CELL_TO_POINTER(x
, y
-2));
842 g_ptr_array_add(cells
, CELL_TO_POINTER(x
-2, y
));
847 g_ptr_array_add(cells
, CELL_TO_POINTER(x
, y
+2));
852 g_ptr_array_add(cells
, CELL_TO_POINTER(x
+2, y
));
855 g_assert_not_reached();
859 g_ptr_array_free(cells
, TRUE
);
861 #undef CELL_TO_POINTER
862 #undef X_FROM_POINTER
863 #undef Y_FROM_POINTER
867 braidmaze(GRand
*rand
, gboolean
**maze
, int w
, int h
)
872 for (x
=0; x
<w
; x
+=2) {
873 int closed
=0, dirs
=0;
876 /* if it is the edge of the map, OR no path carved, then we can't go in that direction. */
877 if (x
<1 || !maze
[y
][x
-1]) {
878 closed
++; /* closed from this side. */
879 /* if not the edge, we might open this wall (carve a path) to remove a dead end */
881 closed_dirs
[dirs
++]=MV_LEFT
;
883 /* other 3 directions similar */
884 if (y
<1 || !maze
[y
-1][x
]) {
887 closed_dirs
[dirs
++]=MV_UP
;
889 if (x
>=w
-1 || !maze
[y
][x
+1]) {
892 closed_dirs
[dirs
++]=MV_RIGHT
;
894 if (y
>=h
-1 || !maze
[y
+1][x
]) {
897 closed_dirs
[dirs
++]=MV_DOWN
;
900 /* if closed from 3 sides, then it is a dead end. also check dirs!=0, that might fail for a 1x1 maze :) */
901 if (closed
==3 && dirs
!=0) {
902 /* make up a random direction, and open in that direction, so dead end is removed */
903 int dir
=closed_dirs
[g_rand_int_range(rand
, 0, dirs
)];
907 maze
[y
][x
-1]=TRUE
; break;
909 maze
[y
-1][x
]=TRUE
; break;
911 maze
[y
][x
+1]=TRUE
; break;
913 maze
[y
+1][x
]=TRUE
; break;
923 draw_maze(Cave
*cave
, const GdObject
*object
, int level
)
931 int w
, h
, path
, wall
;
936 /* change coordinates if not in correct order */
954 /* calculate the width and height of the maze.
955 n=number of passages, path=path width, wall=wall width, maze=maze width.
956 if given the number of passages, the width of the maze is:
958 n*path+(n-1)*wall=maze
959 n*path+n*wall-wall=maze
960 n*(path+wall)=maze+wall
961 n=(maze+wall)/(path+wall)
963 /* number of passages for each side */
964 w
=(x2
-x1
+1+wall
)/(path
+wall
);
965 h
=(y2
-y1
+1+wall
)/(path
+wall
);
966 /* and we calculate the size of the internal map */
967 if (object
->type
==MAZE_UNICURSAL
) {
968 /* for unicursal maze, width and height must be mod2=0, and we will convert to paths&walls later */
972 /* for normal maze */
977 /* twodimensional boolean array to generate map in */
978 map
=g_new(gboolean
*, h
);
980 map
[y
]=g_new0(gboolean
, w
);
982 /* start generation, if map is big enough.
983 otherwise the application would crash, as the editor places maze objects during mouse click&drag that
985 rand
=g_rand_new_with_seed(object
->seed
[level
]==-1?g_rand_int(cave
->random
):object
->seed
[level
]);
987 mazegen(rand
, map
, w
, h
, 0, 0, object
->horiz
);
988 if (object
->type
==MAZE_BRAID
)
989 braidmaze(rand
, map
, w
, h
);
992 if (w
>=1 && h
>=1 && object
->type
==MAZE_UNICURSAL
) {
993 gboolean
**unicursal
;
995 /* convert to unicursal maze */
1011 unicursal
=g_new(gboolean
*, h
*2-1);
1012 for (y
=0; y
<h
*2-1; y
++)
1013 unicursal
[y
]=g_new0(gboolean
, w
*2-1);
1016 for(x
=0; x
<w
; x
++) {
1018 unicursal
[y
*2][x
*2]=TRUE
;
1019 unicursal
[y
*2][x
*2+2]=TRUE
;
1020 unicursal
[y
*2+2][x
*2]=TRUE
;
1021 unicursal
[y
*2+2][x
*2+2]=TRUE
;
1023 if (x
<1 || !map
[y
][x
-1]) unicursal
[y
*2+1][x
*2]=TRUE
;
1024 if (y
<1 || !map
[y
-1][x
]) unicursal
[y
*2][x
*2+1]=TRUE
;
1025 if (x
>=w
-1 || !map
[y
][x
+1]) unicursal
[y
*2+1][x
*2+2]=TRUE
;
1026 if (y
>=h
-1 || !map
[y
+1][x
]) unicursal
[y
*2+2][x
*2+1]=TRUE
;
1030 /* free original map */
1035 /* change to new map - the unicursal maze */
1041 /* copy map to cave with correct elements and size */
1042 /* now copy the map into the cave. the copying works like this...
1049 columns and rows denoted with "p" are to be drawn with path width, the others with wall width. */
1051 for (y
=0; y
<h
; y
++) {
1052 for (i
=0; i
<(y
%2==0?path
:wall
); i
++) {
1055 for (j
=0; j
<(x
%2==0?path
:wall
); j
++)
1056 gd_cave_store_rc(cave
, xk
++, yk
, map
[y
][x
]?object
->fill_element
:object
->element
, object
);
1058 /* if width is smaller than requested, fill with wall */
1059 for(x
=xk
; x
<=x2
; x
++)
1060 gd_cave_store_rc(cave
, x
, yk
, object
->element
, object
);
1065 /* if height is smaller than requested, fill with wall */
1066 for (y
=yk
; y
<=y2
; y
++)
1067 for (x
=x1
; x
<=x2
; x
++)
1068 gd_cave_store_rc(cave
, x
, y
, object
->element
, object
);
1078 draw_random_fill(Cave
*cave
, const GdObject
*object
, int level
)
1086 GdC64RandomGenerator c64_rand
;
1089 /* -1 means that it should be different every time played. */
1090 if (object
->seed
[level
]==-1)
1091 seed
=g_rand_int(cave
->random
);
1093 seed
=object
->seed
[level
];
1095 rand
=g_rand_new_with_seed(seed
);
1096 /* for c64 random, use the 2*8 lsb. */
1097 gd_c64_random_set_seed(&c64_rand
, seed
/256%256, seed
%256);
1100 /* change coordinates if not in correct order */
1111 for (y
=y1
; y
<=y2
; y
++)
1112 for (x
=x1
; x
<=x2
; x
++) {
1116 if (object
->c64_random
) /* use c64 random generator */
1117 randm
=gd_c64_random(&c64_rand
);
1118 else /* use the much better glib random generator */
1119 randm
=g_rand_int_range(rand
, 0, 256);
1121 element
=object
->fill_element
;
1122 if (randm
<object
->random_fill_probability
[0])
1123 element
=object
->random_fill
[0];
1124 if (randm
<object
->random_fill_probability
[1])
1125 element
=object
->random_fill
[1];
1126 if (randm
<object
->random_fill_probability
[2])
1127 element
=object
->random_fill
[2];
1128 if (randm
<object
->random_fill_probability
[3])
1129 element
=object
->random_fill
[3];
1131 if (object
->element
==O_NONE
|| gd_cave_get_rc(cave
, x
, y
)==object
->element
)
1132 gd_cave_store_rc(cave
, x
, y
, element
, object
);
1139 draw_copy_paste(Cave
*cave
, const GdObject
*object
)
1141 int x1
=object
->x1
, y1
=object
->y1
, x2
=object
->x2
, y2
=object
->y2
;
1142 int x
, y
; /* iterators */
1144 GdElement
*clipboard
;
1146 /* reorder coordinates if not drawing from northwest to southeast */
1159 clipboard
=g_new(GdElement
, w
*h
);
1160 /* copy to "clipboard" */
1163 clipboard
[y
*w
+x
]=gd_cave_get_rc(cave
, x
+x1
, y
+y1
);
1165 for (y
=0; y
<h
; y
++) {
1168 ydest
=object
->flip
?h
-1-y
:y
;
1169 for (x
=0; x
<w
; x
++) {
1172 xdest
=object
->mirror
?w
-1-x
:x
;
1173 /* dx and dy are used here are "paste to" coordinates */
1174 gd_cave_store_rc(cave
, object
->dx
+xdest
, object
->dy
+ydest
, clipboard
[y
*w
+x
], object
);
1183 /* draw the specified game object into cave's data.
1184 also remember, which cell was set by which cave object. */
1186 gd_cave_draw_object(Cave
*cave
, const GdObject
*object
, int level
)
1188 g_assert (cave
!=NULL
);
1189 g_assert (cave
->map
!=NULL
);
1190 g_assert (cave
->objects_order
!=NULL
);
1191 g_assert (object
!=NULL
);
1192 g_assert (level
==cave
->rendered
-1);
1194 switch (object
->type
) {
1197 gd_cave_store_rc(cave
, object
->x1
, object
->y1
, object
->element
, object
);
1201 draw_line(cave
, object
);
1205 draw_rectangle(cave
, object
);
1208 case FILLED_RECTANGLE
:
1209 draw_filled_rectangle(cave
, object
);
1213 draw_raster(cave
, object
);
1217 draw_join(cave
, object
);
1220 case FLOODFILL_BORDER
:
1221 draw_fill_border(cave
, object
);
1224 case FLOODFILL_REPLACE
:
1225 draw_fill_replace(cave
, object
);
1229 case MAZE_UNICURSAL
:
1231 draw_maze(cave
, object
, level
);
1235 draw_random_fill(cave
, object
, level
);
1239 draw_copy_paste(cave
, object
);
1243 g_assert_not_reached();
1247 g_critical("Unknown object %d", object
->type
);
1256 /* load cave to play... also can be called rendering the cave elements */
1258 gd_cave_new_rendered (const Cave
*data
, const int level
, const guint32 seed
)
1266 cave
=gd_cave_new_from_cave (data
);
1267 cave
->rendered
=level
+1;
1269 cave
->render_seed
=seed
;
1270 cave
->random
=g_rand_new_with_seed(cave
->render_seed
);
1272 /* maps needed during drawing and gameplay */
1273 cave
->objects_order
=gd_cave_map_new(cave
, gpointer
);
1275 cave
->time
=data
->level_time
[level
];
1276 cave
->timevalue
=data
->level_timevalue
[level
];
1277 cave
->diamonds_needed
=data
->level_diamonds
[level
];
1278 cave
->magic_wall_time
=data
->level_magic_wall_time
[level
];
1279 cave
->slime_permeability
=data
->level_slime_permeability
[level
];
1280 cave
->slime_permeability_c64
=data
->level_slime_permeability_c64
[level
];
1281 cave
->time_bonus
=data
->level_bonus_time
[level
];
1282 cave
->time_penalty
=data
->level_penalty_time
[level
];
1283 cave
->amoeba_time
=data
->level_amoeba_time
[level
];
1284 cave
->amoeba_max_count
=data
->level_amoeba_threshold
[level
];
1285 cave
->amoeba_2_time
=data
->level_amoeba_2_time
[level
];
1286 cave
->amoeba_2_max_count
=data
->level_amoeba_2_threshold
[level
];
1287 cave
->hatching_delay_time
=data
->level_hatching_delay_time
[level
];
1288 cave
->hatching_delay_frame
=data
->level_hatching_delay_frame
[level
];
1291 /* if we have no map, fill with predictable random generator. */
1292 cave
->map
=gd_cave_map_new (cave
, GdElement
);
1293 /* IF CAVE HAS NO MAP, USE THE RANDOM NUMBER GENERATOR */
1294 /* init c64 randomgenerator */
1295 if (data
->level_rand
[level
]<0)
1296 gd_cave_c64_random_set_seed(cave
, g_rand_int_range(cave
->random
, 0, 256), g_rand_int_range(cave
->random
, 0, 256));
1298 gd_cave_c64_random_set_seed(cave
, 0, data
->level_rand
[level
]);
1300 /* generate random fill
1301 * start from row 1 (0 skipped), and fill also the borders on left and right hand side,
1302 * as c64 did. this way works the original random generator the right way.
1303 * also, do not fill last row, that is needed for the random seeds to be correct
1304 * after filling! predictable slime will use it. */
1305 for (y
=1; y
<cave
->h
-1; y
++) {
1306 for (x
=0; x
<cave
->w
; x
++) {
1309 if (data
->level_rand
[level
]<0)
1310 randm
=g_rand_int_range(cave
->random
, 0, 256); /* use the much better glib random generator */
1312 randm
=gd_cave_c64_random(cave
); /* use c64 */
1314 element
=data
->initial_fill
;
1315 if (randm
<data
->random_fill_probability
[0])
1316 element
=data
->random_fill
[0];
1317 if (randm
<data
->random_fill_probability
[1])
1318 element
=data
->random_fill
[1];
1319 if (randm
<data
->random_fill_probability
[2])
1320 element
=data
->random_fill
[2];
1321 if (randm
<data
->random_fill_probability
[3])
1322 element
=data
->random_fill
[3];
1324 gd_cave_store_rc(cave
, x
, y
, element
, NULL
);
1328 /* draw initial border */
1329 for (y
=0; y
<cave
->h
; y
++) {
1330 gd_cave_store_rc(cave
, 0, y
, cave
->initial_border
, NULL
);
1331 gd_cave_store_rc(cave
, cave
->w
-1, y
, cave
->initial_border
, NULL
);
1333 for (x
=0; x
<cave
->w
; x
++) {
1334 gd_cave_store_rc(cave
, x
, 0, cave
->initial_border
, NULL
);
1335 gd_cave_store_rc(cave
, x
, cave
->h
-1, cave
->initial_border
, NULL
);
1339 /* IF CAVE HAS A MAP, SIMPLY USE IT... no need to fill with random elements */
1341 /* initialize c64 predictable random for slime. the values were taken from afl bd, see docs/internals.txt */
1342 gd_cave_c64_random_set_seed(cave
, 0, 0x1e);
1345 if (data
->level_slime_seed_c64
[level
]!=-1) {
1346 /* if a specific slime seed is requested, change it now. */
1348 gd_cave_c64_random_set_seed(cave
, data
->level_slime_seed_c64
[level
]/256, data
->level_slime_seed_c64
[level
]%256);
1351 /* render cave objects above random data or map */
1352 for (iter
=data
->objects
; iter
; iter
=g_list_next (iter
)) {
1353 GdObject
*object
=(GdObject
*)iter
->data
;
1355 if (object
->levels
& gd_levels_mask
[level
])
1356 gd_cave_draw_object(cave
, iter
->data
, level
);
1359 /* check if we use c64 ckdelay or milliseconds for timing */
1360 if (cave
->scheduling
==GD_SCHEDULING_MILLISECONDS
)
1361 cave
->speed
=data
->level_speed
[level
]; /* exact timing */
1363 cave
->speed
=120; /* delay loop based timing... set something for first iteration, then later it will be calculated */
1364 cave
->c64_timing
=data
->level_ckdelay
[level
]; /* this one may be used by iterate routine to calculate actual delay if c64scheduling is selected */
1367 gd_cave_correct_visible_size(cave
);
1375 render cave at specified level.
1376 copy result to the map; remove objects.
1377 the cave will be map-based.
1380 gd_flatten_cave(Cave
*cave
, const int level
)
1384 g_return_if_fail(cave
!= NULL
);
1386 /* render cave at specified level to obtain map. seed=0 */
1387 rendered
=gd_cave_new_rendered(cave
, level
, 0);
1388 /* forget old map without objects */
1389 gd_cave_map_free(cave
->map
);
1390 /* copy new map to cave */
1391 cave
->map
=gd_cave_map_dup(rendered
, map
);
1392 gd_cave_free(rendered
);
1394 /* forget objects */
1395 g_list_foreach(cave
->objects
, (GFunc
) g_free
, NULL
);