added GDash source files prepared for game engine integration into R'n'D
[rocksndiamonds.git] / src / game_bd / bd_caveobject.c
diff --git a/src/game_bd/bd_caveobject.c b/src/game_bd/bd_caveobject.c
new file mode 100644 (file)
index 0000000..254f373
--- /dev/null
@@ -0,0 +1,1608 @@
+/*
+ * Copyright (c) 2007, 2008, 2009, Czirkos Zoltan <cirix@fw.hu>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "main_bd.h"
+
+
+GdObjectLevels gd_levels_mask[] =
+{
+  GD_OBJECT_LEVEL1,
+  GD_OBJECT_LEVEL2,
+  GD_OBJECT_LEVEL3,
+  GD_OBJECT_LEVEL4,
+  GD_OBJECT_LEVEL5
+};
+
+/* bdcff text description of object. caller should free string. */
+char *gd_object_get_bdcff(const GdObject *object)
+{
+  GString *str;
+  int j;
+  const char *type;
+
+  switch (object->type)
+  {
+    case GD_POINT:
+      return g_strdup_printf("Point=%d %d %s",
+                            object->x1, object->y1,
+                            gd_elements[object->element].filename);
+
+    case GD_LINE:
+      return g_strdup_printf("Line=%d %d %d %d %s",
+                            object->x1, object->y1, object->x2, object->y2,
+                            gd_elements[object->element].filename);
+
+    case GD_RECTANGLE:
+      return g_strdup_printf("Rectangle=%d %d %d %d %s",
+                            object->x1, object->y1, object->x2, object->y2,
+                            gd_elements[object->element].filename);
+
+    case GD_FILLED_RECTANGLE:
+      /* if elements are not the same */
+      if (object->fill_element != object->element)
+       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);
+
+      /* they are the same */
+      return g_strdup_printf("FillRect=%d %d %d %d %s",
+                            object->x1, object->y1, object->x2, object->y2,
+                            gd_elements[object->element].filename);
+
+    case GD_RASTER:
+      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);
+
+    case GD_JOIN:
+      return g_strdup_printf("Add=%d %d %s %s",
+                            object->dx, object->dy,
+                            gd_elements[object->element].filename,
+                            gd_elements[object->fill_element].filename);
+
+    case GD_FLOODFILL_BORDER:
+      return g_strdup_printf("BoundaryFill=%d %d %s %s",
+                            object->x1, object->y1,
+                            gd_elements[object->fill_element].filename,
+                            gd_elements[object->element].filename);
+
+    case GD_FLOODFILL_REPLACE:
+      return g_strdup_printf("FloodFill=%d %d %s %s",
+                            object->x1, object->y1,
+                            gd_elements[object->fill_element].filename,
+                            gd_elements[object->element].filename);
+
+    case GD_MAZE:
+    case GD_MAZE_UNICURSAL:
+    case GD_MAZE_BRAID:
+      switch (object->type)
+      {
+       case GD_MAZE: type = "perfect"; break;
+       case GD_MAZE_UNICURSAL: type = "unicursal"; break;
+       case GD_MAZE_BRAID: type = "braid"; break;
+       default: break;
+      }
+
+      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);
+
+    case GD_RANDOM_FILL:
+      str = g_string_new(NULL);
+      /* seed and initial fill */
+      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);
+
+      for (j = 0; j < 4; j++)
+      {
+       if (object->random_fill_probability[j] != 0)
+         g_string_append_printf(str, " %s %d",
+                                gd_elements[object->random_fill[j]].filename,
+                                object->random_fill_probability[j]);
+      }
+
+      if (object->element!=O_NONE)
+       g_string_append_printf(str, " %s",
+                              gd_elements[object->element].filename);
+
+      /* free string but do not free char *; return char *. */
+      return g_string_free(str, FALSE);
+
+    case GD_COPY_PASTE:
+      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");
+
+    case NONE:
+      break;
+  }
+
+  return NULL;
+}
+
+/* create an INDIVIDUAL POINT CAVE OBJECT */
+GdObject *gd_object_new_point(GdObjectLevels levels, int x, int y, GdElement elem)
+{
+  GdObject *newobj = checked_calloc(sizeof(GdObject));
+
+  newobj->levels = levels;
+  newobj->type = GD_POINT;
+  newobj->x1 = x;
+  newobj->y1 = y;
+  newobj->element = elem;
+
+  return newobj;
+}
+
+/* create a LINE OBJECT */
+GdObject *gd_object_new_line(GdObjectLevels levels, int x1, int y1, int x2, int y2,
+                            GdElement elem)
+{
+  GdObject *newobj = checked_calloc(sizeof(GdObject));
+
+  newobj->levels = levels;
+  newobj->type = GD_LINE;
+  newobj->x1 = x1;
+  newobj->y1 = y1;
+  newobj->x2 = x2;
+  newobj->y2 = y2;
+  newobj->element = elem;
+
+  return newobj;
+}
+
+/* create a RECTANGLE OBJECT */
+GdObject *gd_object_new_rectangle(GdObjectLevels levels, int x1, int y1, int x2, int y2,
+                                 GdElement elem)
+{
+  GdObject *newobj = checked_calloc(sizeof(GdObject));
+
+  newobj->levels = levels;
+  newobj->type = GD_RECTANGLE;
+  newobj->x1 = x1;
+  newobj->y1 = y1;
+  newobj->x2 = x2;
+  newobj->y2 = y2;
+  newobj->element = elem;
+
+  return newobj;
+}
+
+/* create a RECTANGLE OBJECT */
+GdObject *gd_object_new_filled_rectangle(GdObjectLevels levels, int x1, int y1, int x2, int y2,
+                                        GdElement elem, GdElement fill_elem)
+{
+  GdObject *newobj = checked_calloc(sizeof(GdObject));
+
+  newobj->levels = levels;
+  newobj->type = GD_FILLED_RECTANGLE;
+  newobj->x1 = x1;
+  newobj->y1 = y1;
+  newobj->x2 = x2;
+  newobj->y2 = y2;
+  newobj->element = elem;
+  newobj->fill_element = fill_elem;
+
+  return newobj;
+}
+
+/* create a raster object */
+GdObject *gd_object_new_raster(GdObjectLevels levels, int x1, int y1, int x2, int y2,
+                              int dx, int dy, GdElement elem)
+{
+  GdObject *newobj = checked_calloc(sizeof(GdObject));
+
+  newobj->levels = levels;
+  newobj->type = GD_RASTER;
+  newobj->x1 = x1;
+  newobj->y1 = y1;
+  newobj->x2 = x2;
+  newobj->y2 = y2;
+  newobj->dx = dx;
+  newobj->dy = dy;
+  newobj->element = elem;
+
+  return newobj;
+}
+
+/* create a raster object */
+GdObject *gd_object_new_join(GdObjectLevels levels, int dx, int dy,
+                            GdElement search, GdElement replace)
+{
+  GdObject *newobj = checked_calloc(sizeof(GdObject));
+
+  newobj->levels = levels;
+  newobj->type = GD_JOIN;
+  newobj->dx = dx;
+  newobj->dy = dy;
+  newobj->element = search;
+  newobj->fill_element = replace;
+
+  return newobj;
+}
+
+/* create a new boundary fill object */
+GdObject *gd_object_new_floodfill_border(GdObjectLevels levels, int x1, int y1,
+                                        GdElement fill, GdElement border)
+{
+  GdObject *newobj = checked_calloc(sizeof(GdObject));
+
+  newobj->levels = levels;
+  newobj->type = GD_FLOODFILL_BORDER;
+  newobj->x1 = x1;
+  newobj->y1 = y1;
+  newobj->element = border;
+  newobj->fill_element = fill;
+
+  return newobj;
+}
+
+GdObject *gd_object_new_floodfill_replace(GdObjectLevels levels, int x1, int y1,
+                                         GdElement fill, GdElement to_replace)
+{
+  GdObject *newobj = checked_calloc(sizeof(GdObject));
+
+  newobj->levels = levels;
+  newobj->type = GD_FLOODFILL_REPLACE;
+  newobj->x1 = x1;
+  newobj->y1 = y1;
+  newobj->element = to_replace;
+  newobj->fill_element = fill;
+
+  return newobj;
+}
+
+GdObject *gd_object_new_maze(GdObjectLevels levels, int x1, int y1, int x2, int y2,
+                            int wall_w, int path_w, GdElement wall_e, GdElement path_e,
+                            int horiz_percent, const gint32 seed[5])
+{
+  int i;
+  GdObject *newobj = checked_calloc(sizeof(GdObject));
+
+  newobj->levels = levels;
+  newobj->type = GD_MAZE;
+  newobj->x1 = x1;
+  newobj->y1 = y1;
+  newobj->x2 = x2;
+  newobj->y2 = y2;
+  newobj->dx = wall_w;
+  newobj->dy = path_w;
+  newobj->element = wall_e;
+  newobj->fill_element = path_e;
+  newobj->horiz = horiz_percent;
+
+  for (i = 0; i < 5; ++i)
+    newobj->seed[i] = seed[i];
+
+  return newobj;
+}
+
+GdObject *gd_object_new_maze_unicursal(GdObjectLevels levels, int x1, int y1, int x2, int y2,
+                                      int wall_w, int path_w, GdElement wall_e, GdElement path_e,
+                                      int horiz_percent, const gint32 seed[5])
+{
+  int i;
+  GdObject *newobj = checked_calloc(sizeof(GdObject));
+
+  newobj->levels = levels;
+  newobj->type = GD_MAZE_UNICURSAL;
+  newobj->x1 = x1;
+  newobj->y1 = y1;
+  newobj->x2 = x2;
+  newobj->y2 = y2;
+  newobj->dx = wall_w;
+  newobj->dy = path_w;
+  newobj->element = wall_e;
+  newobj->fill_element = path_e;
+  newobj->horiz = horiz_percent;
+
+  for (i = 0; i < 5; ++i)
+    newobj->seed[i] = seed[i];
+
+  return newobj;
+}
+
+GdObject *gd_object_new_maze_braid(GdObjectLevels levels, int x1, int y1, int x2, int y2,
+                                  int wall_w, int path_w, GdElement wall_e, GdElement path_e,
+                                  int horiz_percent, const gint32 seed[5])
+{
+  int i;
+  GdObject *newobj = checked_calloc(sizeof(GdObject));
+
+  newobj->levels = levels;
+  newobj->type = GD_MAZE_BRAID;
+  newobj->x1 = x1;
+  newobj->y1 = y1;
+  newobj->x2 = x2;
+  newobj->y2 = y2;
+  newobj->dx = wall_w;
+  newobj->dy = path_w;
+  newobj->element = wall_e;
+  newobj->fill_element = path_e;
+  newobj->horiz = horiz_percent;
+
+  for (i = 0; i < 5; ++i)
+    newobj->seed[i] = seed[i];
+
+  return newobj;
+}
+
+GdObject *gd_object_new_random_fill(GdObjectLevels levels, int x1, int y1, int x2, int y2,
+                                   const gint32 seed[5], GdElement initial,
+                                   const GdElement random[4], const gint32 prob[4],
+                                   GdElement replace_only, boolean c64)
+{
+  int i;
+  GdObject *newobj = checked_calloc(sizeof(GdObject));
+
+  newobj->levels = levels;
+  newobj->type = GD_RANDOM_FILL;
+  newobj->x1 = x1;
+  newobj->y1 = y1;
+  newobj->x2 = x2;
+  newobj->y2 = y2;
+  newobj->fill_element = initial;
+
+  for (i = 0; i < 5; ++i)
+    newobj->seed[i] = seed[i];
+
+  for (i = 0; i < 4; ++i)
+  {
+    newobj->random_fill[i] = random[i];
+    newobj->random_fill_probability[i] = prob[i];
+  }
+
+  newobj->element = replace_only;
+  newobj->c64_random = c64;
+
+  return newobj;
+}
+
+GdObject *gd_object_new_copy_paste(GdObjectLevels levels, int x1, int y1, int x2, int y2,
+                                  int dx, int dy, boolean mirror, boolean flip)
+{
+  GdObject *newobj = checked_calloc(sizeof(GdObject));
+
+  newobj->levels = levels;
+  newobj->type = GD_COPY_PASTE;
+  newobj->x1 = x1;
+  newobj->y1 = y1;
+  newobj->x2 = x2;
+  newobj->y2 = y2;
+  newobj->dx = dx;
+  newobj->dy = dy;
+  newobj->mirror = mirror;
+  newobj->flip = flip;
+
+  return newobj;
+}
+
+/* create new object from bdcff description.
+   return new object if ok; return null if failed.
+ */
+GdObject *gd_object_new_from_string(char *str)
+{
+  char *equalsign;
+  char *name, *param;
+  GdObject object;
+  char elem0[100], elem1[100];
+
+  equalsign = strchr(str, '=');
+  if (!equalsign)
+    return NULL;
+
+  /* split string by replacing the equal sign with zero */
+  *equalsign = '\0';
+  name = str;
+  param = equalsign + 1;
+
+  /* INDIVIDUAL POINT CAVE OBJECT */
+  if (strcasecmp(name, "Point") == 0)
+  {
+    object.type = GD_POINT;
+    if (sscanf(param, "%d %d %s", &object.x1, &object.y1, elem0) == 3)
+    {
+      object.element = gd_get_element_from_string(elem0);
+
+      return g_memdup(&object, sizeof (GdObject));
+    }
+
+    return NULL;
+  }
+
+  /* LINE OBJECT */
+  if (strcasecmp(name, "Line") == 0)
+  {
+    object.type = GD_LINE;
+    if (sscanf(param, "%d %d %d %d %s", &object.x1, &object.y1, &object.x2, &object.y2, elem0) == 5)
+    {
+      object.element = gd_get_element_from_string(elem0);
+
+      return g_memdup(&object, sizeof (GdObject));
+    }
+
+    return NULL;
+  }
+
+  /* RECTANGLE OBJECT */
+  if (strcasecmp(name, "Rectangle") == 0)
+  {
+    if (sscanf(param, "%d %d %d %d %s", &object.x1, &object.y1, &object.x2, &object.y2, elem0) == 5)
+    {
+      object.type = GD_RECTANGLE;
+      object.element = gd_get_element_from_string (elem0);
+
+      return g_memdup(&object, sizeof (GdObject));
+    }
+
+    return NULL;
+  }
+
+  /* FILLED RECTANGLE OBJECT */
+  if (strcasecmp(name, "FillRect") == 0)
+  {
+    int paramcount;
+
+    paramcount = sscanf(param, "%d %d %d %d %s %s", &object.x1, &object.y1, &object.x2, &object.y2, elem0, elem1);
+    object.type = GD_FILLED_RECTANGLE;
+
+    if (paramcount == 6)
+    {
+      object.element = gd_get_element_from_string (elem0);
+      object.fill_element = gd_get_element_from_string (elem1);
+
+      return g_memdup(&object, sizeof (GdObject));
+    }
+
+    if (paramcount == 5)
+    {
+      object.element = object.fill_element = gd_get_element_from_string (elem0);
+
+      return g_memdup(&object, sizeof (GdObject));
+    }
+
+    return NULL;
+  }
+
+  /* RASTER */
+  if (strcasecmp(name, "Raster") == 0)
+  {
+    int nx, ny;
+
+    if (sscanf(param, "%d %d %d %d %d %d %s", &object.x1, &object.y1, &nx, &ny, &object.dx, &object.dy, elem0) == 7)
+    {
+      nx--;
+      ny--;
+      object.x2 = object.x1 + nx * object.dx;
+      object.y2 = object.y1 + ny * object.dy;
+      object.type = GD_RASTER;
+      object.element = gd_get_element_from_string (elem0);
+
+      return g_memdup(&object, sizeof (GdObject));
+    }
+
+    return NULL;
+  }
+
+  /* JOIN */
+  if (strcasecmp(name, "Join") == 0 ||
+      strcasecmp(name, "Add") == 0)
+  {
+    if (sscanf(param, "%d %d %s %s", &object.dx, &object.dy, elem0, elem1) == 4)
+    {
+      object.type = GD_JOIN;
+      object.element = gd_get_element_from_string (elem0);
+      object.fill_element = gd_get_element_from_string (elem1);
+
+      return g_memdup(&object, sizeof (GdObject));
+    }
+
+    return NULL;
+  }
+
+  /* FILL TO BORDER OBJECT */
+  if (strcasecmp(name, "BoundaryFill") == 0)
+  {
+    if (sscanf(param, "%d %d %s %s", &object.x1, &object.y1, elem0, elem1) == 4)
+    {
+      object.type = GD_FLOODFILL_BORDER;
+      object.fill_element = gd_get_element_from_string (elem0);
+      object.element = gd_get_element_from_string (elem1);
+
+      return g_memdup(&object, sizeof (GdObject));
+    }
+
+    return NULL;
+  }
+
+  /* REPLACE FILL OBJECT */
+  if (strcasecmp(name, "FloodFill") == 0)
+  {
+    if (sscanf(param, "%d %d %s %s", &object.x1, &object.y1, elem0, elem1) == 4)
+    {
+      object.type = GD_FLOODFILL_REPLACE;
+      object.fill_element = gd_get_element_from_string (elem0);
+      object.element = gd_get_element_from_string (elem1);
+
+      return g_memdup(&object, sizeof (GdObject));
+    }
+
+    return NULL;
+  }
+
+  /* MAZE OBJECT */
+  /* MAZE UNICURSAL OBJECT */
+  /* BRAID MAZE OBJECT */
+  if (strcasecmp(name, "Maze") == 0)
+  {
+    char type[100] = "perfect";
+
+    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)
+    {
+      if (strcasecmp(type, "unicursal") == 0)
+       object.type = GD_MAZE_UNICURSAL;
+      else if (strcasecmp(type, "perfect") == 0)
+       object.type = GD_MAZE;
+      else if (strcasecmp(type, "braid") == 0)
+       object.type = GD_MAZE_BRAID;
+      else
+      {
+       Warn("unknown maze type: %s, defaulting to perfect", type);
+       object.type = GD_MAZE;
+      }
+
+      object.element = gd_get_element_from_string (elem0);
+      object.fill_element = gd_get_element_from_string (elem1);
+
+      return g_memdup(&object, sizeof (GdObject));
+    }
+
+    return NULL;
+  }
+
+  /* RANDOM FILL OBJECT */
+  if (strcasecmp(name, "RandomFill") == 0 ||
+      strcasecmp(name, "RandomFillC64") == 0)
+  {
+    static char **words = NULL;
+    int l, i;
+
+    object.type = GD_RANDOM_FILL;
+    if (strcasecmp(name, "RandomFillC64") == 0)
+      /* totally the same, but uses c64 random generator */
+      object.c64_random = TRUE;
+    else
+      object.c64_random = FALSE;
+
+    if (sscanf(param, "%d %d %d %d", &object.x1, &object.y1, &object.x2, &object.y2) != 4)
+      return NULL;
+
+    if (words)
+      g_strfreev(words);
+
+    words = g_strsplit_set(param, " ", -1);
+    l = g_strv_length(words);
+
+    if (l < 10 || l > 19)
+      return NULL;
+
+    for (i = 0; i < 5; i++)
+      if (sscanf(words[4 + i], "%d", &object.seed[i]) != 1)
+       return NULL;
+
+    object.fill_element = gd_get_element_from_string(words[9]);
+
+    for (i = 0; i < 4; i++)
+    {
+      object.random_fill[i] = O_DIRT;
+      object.random_fill_probability[i] = 0;
+    }
+
+    for (i = 10; i < l - 1; i += 2)
+    {
+      object.random_fill[(i - 10) / 2] = gd_get_element_from_string(words[i]);
+      if (sscanf(words[i + 1], "%d", &object.random_fill_probability[(i - 10) / 2]) == 0)
+       return NULL;
+    }
+
+    object.element = O_NONE;
+
+    if (l > 10 && l % 2 == 1)
+      object.element = gd_get_element_from_string(words[l - 1]);
+
+    return g_memdup(&object, sizeof (GdObject));
+  }
+
+  /* COPY PASTE OBJECT */
+  if (strcasecmp(name, "CopyPaste") == 0)
+  {
+    char mirror[100] = "nomirror";
+    char flip[100] = "noflip";
+    object.type = GD_COPY_PASTE;
+
+    object.flip = object.mirror = FALSE;
+
+    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)
+      return NULL;
+
+    /* MIRROR PROPERTY */
+    if (strcasecmp(mirror, "mirror") == 0)
+      object.mirror = TRUE;
+    else if (strcasecmp(mirror, "nomirror") == 0)
+      object.mirror = FALSE;
+    else
+      Warn("invalid setting for copypaste mirror property: %s", mirror);
+
+    /* FLIP PROPERTY */
+    if (strcasecmp(flip, "flip") == 0)
+      object.flip = TRUE;
+    else if (strcasecmp(flip, "noflip") == 0)
+      object.flip = FALSE;
+    else
+      Warn("invalid setting for copypaste flip property: %s", flip);
+
+    return g_memdup(&object, sizeof(GdObject));
+  }
+
+  return NULL;
+}
+
+/** drawing a line, using bresenham's */
+static void draw_line (GdCave *cave, const GdObject *object)
+{
+  int x, y, x1, y1, x2, y2;
+  boolean steep;
+  int error, dx, dy, ystep;
+
+  x1 = object->x1;
+  y1 = object->y1, x2 = object->x2;
+  y2 = object->y2;
+  steep = ABS (y2 - y1) > ABS (x2 - x1);
+
+  if (steep)
+  {
+    x = x1;
+    x1 = y1;
+    y1 = x;
+    x = x2;
+    x2 = y2;
+    y2 = x;
+  }
+
+  if (x1 > x2)
+  {
+    x = x1;
+    x1 = x2;
+    x2 = x;
+    x = y1;
+    y1 = y2;
+    y2 = x;
+  }
+
+  dx = x2 - x1;
+  dy = ABS (y2 - y1);
+  y = y1;
+  error = 0;
+  ystep = (y1 < y2) ? 1 : -1;
+
+  for (x = x1; x <= x2; x++)
+  {
+    if (steep)
+      gd_cave_store_rc (cave, y, x, object->element, object);
+    else
+      gd_cave_store_rc (cave, x, y, object->element, object);
+
+    error += dy;
+
+    if (error * 2 >= dx)
+    {
+      y += ystep;
+      error -= dx;
+    }
+  }
+}
+
+
+
+static void draw_fill_replace_proc(GdCave *cave, int x, int y, const GdObject *object)
+{
+  /* fill with border so we do not come back */
+  gd_cave_store_rc(cave, x, y, object->fill_element, object);
+
+  if (x > 0 && gd_cave_get_rc(cave, x - 1, y) == object->element)
+    draw_fill_replace_proc(cave, x - 1, y, object);
+
+  if (y > 0 && gd_cave_get_rc(cave, x, y - 1) == object->element)
+    draw_fill_replace_proc(cave, x, y - 1, object);
+
+  if (x < cave->w - 1 && gd_cave_get_rc(cave, x + 1, y) == object->element)
+    draw_fill_replace_proc(cave, x + 1, y, object);
+
+  if (y < cave->h - 1 && gd_cave_get_rc(cave, x, y + 1) == object->element)
+    draw_fill_replace_proc(cave, x, y + 1, object);
+}
+
+static void draw_fill_replace (GdCave *cave, const GdObject *object)
+{
+  /* check bounds */
+  if (object->x1 < 0 ||
+      object->y1 < 0 ||
+      object->x1 >= cave->w ||
+      object->y1 >= cave->h)
+    return;
+
+  if (object->element == object->fill_element)
+    return;
+
+  /* this procedure fills the area with the object->element. */
+  draw_fill_replace_proc(cave, object->x1, object->y1, object);
+}
+
+static void draw_fill_border_proc (GdCave *cave, int x, int y, const GdObject *object)
+{
+  /* fill with border so we do not come back */
+  gd_cave_store_rc(cave, x, y, object->element, object);
+
+  if (x > 0 && gd_cave_get_rc(cave, x - 1, y) != object->element)
+    draw_fill_border_proc(cave, x - 1, y, object);
+
+  if (y > 0 && gd_cave_get_rc(cave, x, y - 1) != object->element)
+    draw_fill_border_proc(cave, x, y - 1, object);
+
+  if (x < cave->w - 1 && gd_cave_get_rc(cave, x + 1, y) != object->element)
+    draw_fill_border_proc(cave, x + 1, y, object);
+
+  if (y < cave->h-1 && gd_cave_get_rc(cave, x, y + 1) != object->element)
+    draw_fill_border_proc(cave, x, y + 1, object);
+}
+
+static void draw_fill_border (GdCave *cave, const GdObject *object)
+{
+  int x, y;
+
+  /* check bounds */
+  if (object->x1 < 0 ||
+      object->y1 < 0 ||
+      object->x1 >= cave->w ||
+      object->y1 >= cave->h)
+    return;
+
+  /* this procedure fills the area with the object->element. */
+  draw_fill_border_proc(cave, object->x1, object->y1, object);
+
+  /* after the fill, we change all filled cells to the fill_element. */
+  /* we find those by looking at the object_order[][] */
+  for (y = 0; y < cave->h; y++)
+    for (x = 0; x < cave->w; x++)
+      if (cave->objects_order[y][x] == object)
+       cave->map[y][x] = object->fill_element;
+}
+
+/* rectangle, frame only */
+static void draw_rectangle(GdCave *cave, const GdObject *object)
+{
+  int x1, y1, x2, y2, x, y;
+
+  /* reorder coordinates if not drawing from northwest to southeast */
+  x1 = object->x1;
+  y1 = object->y1, x2 = object->x2;
+  y2 = object->y2;
+
+  if (y1 > y2)
+  {
+    y = y1;
+    y1 = y2;
+    y2 = y;
+  }
+
+  if (x1 > x2)
+  {
+    x = x1;
+    x1 = x2;
+    x2 = x;
+  }
+
+  for (x = x1; x <= x2; x++)
+  {
+    gd_cave_store_rc(cave, x, object->y1, object->element, object);
+    gd_cave_store_rc(cave, x, object->y2, object->element, object);
+  }
+
+  for (y = y1; y <= y2; y++)
+  {
+    gd_cave_store_rc(cave, object->x1, y, object->element, object);
+    gd_cave_store_rc(cave, object->x2, y, object->element, object);
+  }
+}
+
+/* rectangle, filled one */
+static void draw_filled_rectangle(GdCave *cave, const GdObject *object)
+{
+  int x1, y1, x2, y2, x, y;
+
+  /* reorder coordinates if not drawing from northwest to southeast */
+  x1 = object->x1;
+  y1 = object->y1, x2 = object->x2;
+  y2 = object->y2;
+
+  if (y1 > y2)
+  {
+    y = y1;
+    y1 = y2;
+    y2 = y;
+  }
+
+  if (x1 > x2)
+  {
+    x = x1;
+    x1 = x2;
+    x2 = x;
+  }
+
+  for (y = y1; y <= y2; y++)
+    for (x = x1; x <= x2; x++)
+      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);
+}
+
+/* something like ordered fill, increment is dx and dy. */
+static void draw_raster(GdCave *cave, const GdObject *object)
+{
+  int x, y, x1, y1, x2, y2;
+  int dx, dy;
+
+  /* reorder coordinates if not drawing from northwest to southeast */
+  x1 = object->x1;
+  y1 = object->y1;
+  x2 = object->x2;
+  y2 = object->y2;
+
+  if (y1 > y2)
+  {
+    y = y1;
+    y1 = y2;
+    y2 = y;
+  }
+
+  if (x1 > x2)
+  {
+    x = x1;
+    x1 = x2;
+    x2 = x;
+  }
+
+  dx = object->dx;
+
+  if (dx < 1)
+    dx = 1;
+
+  dy = object->dy;
+
+  if (dy < 1)
+    dy = 1;
+
+  for (y = y1; y <= y2; y += dy)
+    for (x = x1; x <= x2; x += dx)
+      gd_cave_store_rc(cave, x, y, object->element, object);
+}
+
+/* find every object, and put fill_element next to it. relative coordinates dx,dy */
+static void draw_join(GdCave *cave, const GdObject *object)
+{
+  int x, y;
+
+  for (y = 0; y < cave->h; y++)
+  {
+    for (x = 0; x < cave->w; x++)
+    {
+      if (cave->map[y][x] == object->element)
+      {
+       int nx = x + object->dx;
+       int ny = y + object->dy;
+       /* this one implements wraparound for joins.
+          it is needed by many caves in profi boulder series */
+       while (nx >= cave->w)
+         nx -= cave->w, ny++;
+
+       gd_cave_store_rc(cave, nx, ny, object->fill_element, object);
+      }
+    }
+  }
+}
+
+/* create a maze in a boolean **maze. */
+/* recursive algorithm. */
+static void mazegen(GRand *rand, boolean **maze, int width, int height, int x, int y, int horiz)
+{
+  int dirmask = 15;
+
+  maze[y][x] = TRUE;
+  while (dirmask != 0)
+  {
+    int dir;
+
+    /* horiz or vert */
+    dir = g_rand_int_range(rand, 0, 100) <horiz ? 2 : 0;
+
+    /* if no horizontal movement possible, choose vertical */
+    if (dir == 2 && (dirmask & 12) == 0)
+      dir = 0;
+    else if (dir == 0 && (dirmask&3) == 0)    /* and vice versa */
+      dir = 2;
+
+    dir += g_rand_int_range(rand, 0, 2);                /* dir */
+    if (dirmask & (1 << dir))
+    {
+      dirmask &= ~(1 << dir);
+
+      switch (dir)
+      {
+       case 0:    /* up */
+         if (y >= 2 && !maze[y - 2][x])
+         {
+           maze[y - 1][x] = TRUE;
+           mazegen(rand, maze, width, height, x, y - 2, horiz);
+         }
+         break;
+
+       case 1:    /* down */
+         if (y < height-2 && !maze[y + 2][x]) {
+           maze[y + 1][x] = TRUE;
+           mazegen(rand, maze, width, height, x, y + 2, horiz);
+         }
+         break;
+
+       case 2:    /* left */
+         if (x >= 2 && !maze[y][x - 2]) {
+           maze[y][x - 1] = TRUE;
+           mazegen(rand, maze, width, height, x - 2, y, horiz);
+         }
+         break;
+
+       case 3:    /* right */
+         if (x < width - 2 && !maze[y][x + 2]) {
+           maze[y][x + 1] = TRUE;
+           mazegen(rand, maze, width, height, x + 2, y, horiz);
+         }
+         break;
+
+       default:
+         break;
+      }
+    }
+  }
+}
+
+static void braidmaze(GRand *rand, boolean **maze, int w, int h)
+{
+  int x, y;
+
+  for (y = 0; y < h; y += 2)
+  {
+    for (x = 0; x < w; x += 2)
+    {
+      int closed = 0, dirs = 0;
+      int closed_dirs[4];
+
+      /* if it is the edge of the map, OR no path carved, then we can't go in that direction. */
+      if (x < 1 || !maze[y][x - 1])
+      {
+       /* closed from this side. */
+       closed++;
+
+       /* if not the edge, we might open this wall (carve a path) to remove a dead end */
+       if (x > 0)
+         closed_dirs[dirs++] = GD_MV_LEFT;
+      }
+
+      /* other 3 directions similar */
+      if (y < 1 || !maze[y - 1][x])
+      {
+       closed++;
+       if (y > 0)
+         closed_dirs[dirs++] = GD_MV_UP;
+      }
+
+      if (x >= w - 1 || !maze[y][x + 1])
+      {
+       closed++;
+       if (x < w - 1)
+         closed_dirs[dirs++] = GD_MV_RIGHT;
+      }
+
+      if (y >= h - 1 || !maze[y + 1][x]) {
+       closed++;
+       if (y < h - 1)
+         closed_dirs[dirs++] = GD_MV_DOWN;
+      }
+
+      /* if closed from 3 sides, then it is a dead end. also check dirs != 0,
+        that might fail for a 1x1 maze :) */
+      if (closed == 3 && dirs != 0)
+      {
+       /* make up a random direction, and open in that direction, so dead end is removed */
+       int dir = closed_dirs[g_rand_int_range(rand, 0, dirs)];
+
+       switch (dir)
+       {
+         case GD_MV_LEFT:
+           maze[y][x - 1] = TRUE; break;
+         case GD_MV_UP:
+           maze[y - 1][x] = TRUE; break;
+         case GD_MV_RIGHT:
+           maze[y][x + 1] = TRUE; break;
+         case GD_MV_DOWN:
+           maze[y + 1][x] = TRUE; break;
+       }
+      }
+    }
+  }
+}
+
+static void draw_maze(GdCave *cave, const GdObject *object, int level)
+{
+  int x, y;
+  boolean **map;
+  int x1 = object->x1;
+  int y1 = object->y1;
+  int x2 = object->x2;
+  int y2 = object->y2;
+  int w, h, path, wall;
+  int xk, yk;
+  GRand *rand;
+  int i,j;
+
+  /* change coordinates if not in correct order */
+  if (y1 > y2)
+  {
+    y = y1;
+    y1 = y2;
+    y2 = y;
+  }
+
+  if (x1 > x2)
+  {
+    x = x1;
+    x1 = x2;
+    x2 = x;
+  }
+
+  wall = object->dx;
+  if (wall < 1)
+    wall = 1;
+
+  path = object->dy;
+  if (path < 1)
+    path = 1;
+
+  /* calculate the width and height of the maze.
+     n = number of passages, path = path width, wall = wall width, maze = maze width.
+     if given the number of passages, the width of the maze is:
+
+     n * path + (n - 1) * wall = maze
+     n * path + n * wall - wall = maze
+     n * (path + wall) = maze + wall
+     n = (maze + wall) / (path + wall)
+  */
+
+  /* number of passages for each side */
+  w = (x2 - x1 + 1 + wall) / (path + wall);
+  h = (y2 - y1 + 1 + wall) / (path + wall);
+
+  /* and we calculate the size of the internal map */
+  if (object->type == GD_MAZE_UNICURSAL)
+  {
+    /* for unicursal maze, width and height must be mod2 = 0,
+       and we will convert to paths & walls later */
+    w = w / 2 * 2;
+    h = h / 2 * 2;
+  }
+  else
+  {
+    /* for normal maze */
+    w = 2 * (w - 1) + 1;
+    h = 2 * (h - 1) + 1;
+  }
+
+  /* twodimensional boolean array to generate map in */
+  map = checked_malloc((h) * sizeof(boolean *));
+  for (y = 0; y < h; y++)
+    map[y] = checked_calloc(w * sizeof(boolean));
+
+  /* start generation, if map is big enough.
+     otherwise the application would crash, as the editor places maze objects
+     during mouse click & drag that have no sense */
+  rand = g_rand_new_with_seed(object->seed[level] == -1 ?
+                             g_rand_int(cave->random) : object->seed[level]);
+
+  if (w >= 1 && h >= 1)
+    mazegen(rand, map, w, h, 0, 0, object->horiz);
+
+  if (object->type == GD_MAZE_BRAID)
+    braidmaze(rand, map, w, h);
+
+  g_rand_free(rand);
+
+  if (w >= 1 && h >= 1 && object->type == GD_MAZE_UNICURSAL)
+  {
+    boolean **unicursal;
+
+    /* convert to unicursal maze */
+    /* original:
+        xxx x
+          x x
+        xxxxx
+
+        unicursal:
+        xxxxxxx xxx
+        x     x x x
+        xxxxx x x x
+            x x x x
+        xxxxx xxx x
+        x         x
+        xxxxxxxxxxx
+    */
+
+    unicursal = checked_malloc((h * 2 - 1) * sizeof(boolean *));
+
+    for (y = 0; y < h * 2 - 1; y++)
+      unicursal[y] = checked_calloc((w * 2 - 1) * sizeof(boolean));
+
+    for (y = 0; y < h; y++)
+    {
+      for(x = 0; x < w; x++)
+      {
+       if (map[y][x]) {
+         unicursal[y * 2][x * 2] = TRUE;
+         unicursal[y * 2][x * 2 + 2] = TRUE;
+         unicursal[y * 2 + 2][x * 2] = TRUE;
+         unicursal[y * 2 + 2][x * 2 + 2] = TRUE;
+
+         if (x < 1      || !map[y][x - 1]) unicursal[y * 2 + 1][x * 2] = TRUE;
+         if (y < 1      || !map[y - 1][x]) unicursal[y * 2][x * 2 + 1] = TRUE;
+         if (x >= w - 1 || !map[y][x + 1]) unicursal[y * 2 + 1][x * 2 + 2] = TRUE;
+         if (y >= h - 1 || !map[y + 1][x]) unicursal[y * 2 + 2][x * 2 + 1] = TRUE;
+       }
+      }
+    }
+
+    /* free original map */
+    for (y = 0; y < h; y++)
+      free(map[y]);
+    free(map);
+
+    /* change to new map - the unicursal maze */
+    map = unicursal;
+    h = h * 2 - 1;
+    w = w * 2 - 1;
+  }
+
+  /* copy map to cave with correct elements and size */
+  /* now copy the map into the cave. the copying works like this...
+     pwpwp
+     xxxxx p
+     x x   w
+     x xxx p
+     x     w
+     xxxxx p
+     columns and rows denoted with "p" are to be drawn with path width,
+     the others with wall width. */
+
+  yk = y1;
+
+  for (y = 0; y < h; y++)
+  {
+    for (i = 0; i < (y % 2 == 0 ? path : wall); i++)
+    {
+      xk = x1;
+
+      for (x = 0; x < w; x++)
+       for (j = 0; j < (x % 2 == 0 ? path : wall); j++)
+         gd_cave_store_rc(cave, xk++, yk, map[y][x] ? object->fill_element : object->element, object);
+
+      /* if width is smaller than requested, fill with wall */
+      for(x = xk; x <= x2; x++)
+       gd_cave_store_rc(cave, x, yk, object->element, object);
+
+      yk++;
+    }
+  }
+
+  /* if height is smaller than requested, fill with wall */
+  for (y = yk; y <= y2; y++)
+    for (x = x1; x <= x2; x++)
+      gd_cave_store_rc(cave, x, y, object->element, object);
+
+  /* free map */
+  for (y = 0; y < h; y++)
+    free(map[y]);
+  free(map);
+}
+
+static void draw_random_fill(GdCave *cave, const GdObject *object, int level)
+{
+  int x, y;
+  int x1 = object->x1;
+  int y1 = object->y1;
+  int x2 = object->x2;
+  int y2 = object->y2;
+  GRand *rand;
+  GdC64RandomGenerator c64_rand;
+  guint32 seed;
+
+  /* -1 means that it should be different every time played. */
+  if (object->seed[level] == -1)
+    seed = g_rand_int(cave->random);
+  else
+    seed = object->seed[level];
+
+  rand = g_rand_new_with_seed(seed);
+  /* for c64 random, use the 2*8 lsb. */
+  gd_c64_random_set_seed(&c64_rand, seed/256%256, seed%256);
+
+  /* change coordinates if not in correct order */
+  if (y1 > y2)
+  {
+    y = y1;
+    y1 = y2;
+    y2 = y;
+  }
+
+  if (x1 > x2)
+  {
+    x = x1;
+    x1 = x2;
+    x2 = x;
+  }
+
+  for (y = y1; y <= y2; y++)
+  {
+    for (x = x1; x <= x2; x++)
+    {
+      unsigned int randm;
+      GdElement element;
+
+      if (object->c64_random)
+       /* use c64 random generator */
+       randm = gd_c64_random(&c64_rand);
+      else
+       /* use the much better glib random generator */
+       randm = g_rand_int_range(rand, 0, 256);
+
+      element = object->fill_element;
+      if (randm < object->random_fill_probability[0])
+       element = object->random_fill[0];
+      if (randm < object->random_fill_probability[1])
+       element = object->random_fill[1];
+      if (randm < object->random_fill_probability[2])
+       element = object->random_fill[2];
+      if (randm < object->random_fill_probability[3])
+       element = object->random_fill[3];
+
+      if (object->element==O_NONE ||
+         gd_cave_get_rc(cave, x, y) == object->element)
+       gd_cave_store_rc(cave, x, y, element, object);
+    }
+  }
+
+  g_rand_free(rand);
+}
+
+
+static void draw_copy_paste(GdCave *cave, const GdObject *object)
+{
+  int x1 = object->x1, y1 = object->y1, x2 = object->x2, y2 = object->y2;
+  int x, y;    /* iterators */
+  int w, h;
+  GdElement *clipboard;
+
+  /* reorder coordinates if not drawing from northwest to southeast */
+  if (x2 < x1)
+  {
+    x = x2;
+    x2 = x1;
+    x1 = x;
+  }
+
+  if (y2 < y1)
+  {
+    y = y2;
+    y2 = y1;
+    y1 = y;
+  }
+
+  w = x2 - x1 + 1;
+  h = y2 - y1 + 1;
+
+  clipboard = checked_malloc((w * h) * sizeof(GdElement));
+
+  /* copy to "clipboard" */
+  for (y = 0; y < h; y++)
+    for (x = 0; x < w; x++)
+      clipboard[y * w + x] = gd_cave_get_rc(cave, x + x1, y + y1);
+
+  for (y = 0; y < h; y++)
+  {
+    int ydest;
+
+    ydest = object->flip ? h - 1 - y : y;
+
+    for (x = 0; x < w; x++)
+    {
+      int xdest;
+
+      xdest = object->mirror ? w - 1 - x : x;
+
+      /* dx and dy are used here are "paste to" coordinates */
+      gd_cave_store_rc(cave, object->dx + xdest, object->dy + ydest,
+                      clipboard[y * w + x], object);
+    }
+  }
+
+  free(clipboard);
+}
+
+/* draw the specified game object into cave's data.
+   also remember, which cell was set by which cave object. */
+void gd_cave_draw_object(GdCave *cave, const GdObject *object, int level)
+{
+  switch (object->type)
+  {
+    case GD_POINT:
+      /* single point */
+      gd_cave_store_rc(cave, object->x1, object->y1, object->element, object);
+      break;
+
+    case GD_LINE:
+      draw_line(cave, object);
+      break;
+
+    case GD_RECTANGLE:
+      draw_rectangle(cave, object);
+      break;
+
+    case GD_FILLED_RECTANGLE:
+      draw_filled_rectangle(cave, object);
+      break;
+
+    case GD_RASTER:
+      draw_raster(cave, object);
+      break;
+
+    case GD_JOIN:
+      draw_join(cave, object);
+      break;
+
+    case GD_FLOODFILL_BORDER:
+      draw_fill_border(cave, object);
+      break;
+
+    case GD_FLOODFILL_REPLACE:
+      draw_fill_replace(cave, object);
+      break;
+
+    case GD_MAZE:
+    case GD_MAZE_UNICURSAL:
+    case GD_MAZE_BRAID:
+      draw_maze(cave, object, level);
+      break;
+
+    case GD_RANDOM_FILL:
+      draw_random_fill(cave, object, level);
+      break;
+
+    case GD_COPY_PASTE:
+      draw_copy_paste(cave, object);
+      break;
+
+    case NONE:
+      break;
+
+    default:
+      Error("Unknown object %d", object->type);
+      break;
+  }
+}
+
+/* load cave to play... also can be called rendering the cave elements */
+GdCave *gd_cave_new_rendered(const GdCave *data, const int level, const guint32 seed)
+{
+  GdCave *cave;
+  GdElement element;
+  int x, y;
+  GList *iter;
+
+  /* make a copy */
+  cave = gd_cave_new_from_cave(data);
+  cave->rendered = level + 1;
+
+  cave->render_seed = seed;
+  cave->random = g_rand_new_with_seed(cave->render_seed);
+
+  /* maps needed during drawing and gameplay */
+  cave->objects_order = gd_cave_map_new(cave, gpointer);
+
+  cave->time                   = data->level_time[level];
+  cave->timevalue              = data->level_timevalue[level];
+  cave->diamonds_needed        = data->level_diamonds[level];
+  cave->magic_wall_time        = data->level_magic_wall_time[level];
+  cave->slime_permeability     = data->level_slime_permeability[level];
+  cave->slime_permeability_c64 = data->level_slime_permeability_c64[level];
+  cave->time_bonus             = data->level_bonus_time[level];
+  cave->time_penalty           = data->level_penalty_time[level];
+  cave->amoeba_time            = data->level_amoeba_time[level];
+  cave->amoeba_max_count       = data->level_amoeba_threshold[level];
+  cave->amoeba_2_time          = data->level_amoeba_2_time[level];
+  cave->amoeba_2_max_count     = data->level_amoeba_2_threshold[level];
+  cave->hatching_delay_time    = data->level_hatching_delay_time[level];
+  cave->hatching_delay_frame   = data->level_hatching_delay_frame[level];
+
+  if (!cave->map)
+  {
+    /* if we have no map, fill with predictable random generator. */
+    cave->map = gd_cave_map_new(cave, GdElement);
+
+    /* IF CAVE HAS NO MAP, USE THE RANDOM NUMBER GENERATOR */
+    /* init c64 randomgenerator */
+    if (data->level_rand[level] < 0)
+      gd_cave_c64_random_set_seed(cave, g_rand_int_range(cave->random, 0, 256),
+                                 g_rand_int_range(cave->random, 0, 256));
+    else
+      gd_cave_c64_random_set_seed(cave, 0, data->level_rand[level]);
+
+    /* generate random fill
+     * start from row 1 (0 skipped), and fill also the borders on left and right hand side,
+     * as c64 did. this way works the original random generator the right way.
+     * also, do not fill last row, that is needed for the random seeds to be correct
+     * after filling! predictable slime will use it. */
+    for (y = 1; y < cave->h - 1; y++)
+    {
+      for (x = 0; x < cave->w; x++)
+      {
+       unsigned int randm;
+
+       if (data->level_rand[level] < 0)
+         /* use the much better glib random generator */
+         randm = g_rand_int_range(cave->random, 0, 256);
+       else
+         /* use c64 */
+         randm = gd_cave_c64_random(cave);
+
+       element = data->initial_fill;
+       if (randm < data->random_fill_probability[0])
+         element = data->random_fill[0];
+       if (randm < data->random_fill_probability[1])
+         element = data->random_fill[1];
+       if (randm < data->random_fill_probability[2])
+         element = data->random_fill[2];
+       if (randm < data->random_fill_probability[3])
+         element = data->random_fill[3];
+
+       gd_cave_store_rc(cave, x, y, element, NULL);
+      }
+    }
+
+    /* draw initial border */
+    for (y = 0; y < cave->h; y++)
+    {
+      gd_cave_store_rc(cave, 0,           y, cave->initial_border, NULL);
+      gd_cave_store_rc(cave, cave->w - 1, y, cave->initial_border, NULL);
+    }
+
+    for (x = 0; x < cave->w; x++)
+    {
+      gd_cave_store_rc(cave, x,           0, cave->initial_border, NULL);
+      gd_cave_store_rc(cave, x, cave->h - 1, cave->initial_border, NULL);
+    }
+  }
+  else
+  {
+    /* IF CAVE HAS A MAP, SIMPLY USE IT... no need to fill with random elements */
+
+    /* initialize c64 predictable random for slime.
+       the values were taken from afl bd, see docs/internals.txt */
+    gd_cave_c64_random_set_seed(cave, 0, 0x1e);
+  }
+
+  if (data->level_slime_seed_c64[level] != -1)
+  {
+    /* if a specific slime seed is requested, change it now. */
+
+    gd_cave_c64_random_set_seed(cave,
+                               data->level_slime_seed_c64[level] / 256,
+                               data->level_slime_seed_c64[level] % 256);
+  }
+
+  /* render cave objects above random data or map */
+  for (iter = data->objects; iter; iter = g_list_next(iter))
+  {
+    GdObject *object = (GdObject *)iter->data;
+
+    if (object->levels & gd_levels_mask[level])
+      gd_cave_draw_object(cave, iter->data, level);
+  }
+
+  /* check if we use c64 ckdelay or milliseconds for timing */
+  if (cave->scheduling == GD_SCHEDULING_MILLISECONDS)
+    cave->speed = data->level_speed[level];        /* exact timing */
+  else
+  {
+    /* delay loop based timing... set something for first iteration,
+       then later it will be calculated */
+    cave->speed = 120;
+
+    /* this one may be used by iterate routine to calculate actual delay
+       if c64scheduling is selected */
+    cave->c64_timing = data->level_ckdelay[level];
+  }
+
+  gd_cave_correct_visible_size(cave);
+
+  return cave;
+}
+
+/*
+   render cave at specified level.
+   copy result to the map; remove objects.
+   the cave will be map-based.
+ */
+void gd_flatten_cave(GdCave *cave, const int level)
+{
+  GdCave *rendered;
+
+  if (cave == NULL)
+    return;
+
+  /* render cave at specified level to obtain map. seed = 0 */
+  rendered = gd_cave_new_rendered(cave, level, 0);
+
+  /* forget old map without objects */
+  gd_cave_map_free(cave->map);
+
+  /* copy new map to cave */
+  cave->map = gd_cave_map_dup(rendered, map);
+  gd_cave_free(rendered);
+
+  /* forget objects */
+  g_list_foreach(cave->objects, (GFunc) free, NULL);
+  cave->objects = NULL;
+}