renamed another preprocessor constant
[rocksndiamonds.git] / src / game_bd / bd_cave.c
index 2ffb071e365097f3b117888785b61a8c606bd91d..d946bac4c203a3decaa24f2304211802e40a217c 100644 (file)
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <glib.h>
-#include <glib/gi18n.h>
-
 #include "main_bd.h"
 
 
-/* arrays for movements */
-/* also no1 and bd2 cave data import helpers; line direction coordinates */
+// arrays for movements
+// also no1 and bd2 cave data import helpers; line direction coordinates
 const int gd_dx[] =
 {
   0, 0, 1, 1, 1, 0, -1, -1, -1, 0, 2, 2, 2, 0, -2, -2, -2
@@ -31,8 +28,8 @@ const int gd_dy[] =
   0, -1, -1, 0, 1, 1, 1, 0, -1, -2, -2, 0, 2, 2, 2, 0, -2
 };
 
-/* TRANSLATORS:
-   None here means "no direction to move"; when there is no gravity while stirring the pot. */
+// TRANSLATORS:
+// None here means "no direction to move"; when there is no gravity while stirring the pot.
 static const char* direction_name[] =
 {
   N_("None"),
@@ -81,16 +78,16 @@ static const char* scheduling_filename[] =
   "bd2ckatari"
 };
 
-static GHashTable *name_to_element;
+static HashTable *name_to_element;
 GdElement gd_char_to_element[256];
 
-/* color of flashing the screen, gate opening to exit */
+// color of flashing the screen, gate opening to exit
 const GdColor gd_flash_color = 0xFFFFC0;
 
-/* selected object in editor */
+// selected object in editor
 const GdColor gd_select_color = 0x8080FF;
 
-/* direction to string and vice versa */
+// direction to string and vice versa
 const char *gd_direction_get_visible_name(GdDirection dir)
 {
   return direction_name[dir];
@@ -105,7 +102,7 @@ GdDirection gd_direction_from_string(const char *str)
 {
   int i;
 
-  for (i = 1; i<G_N_ELEMENTS(direction_filename); i++)
+  for (i = 1; i < ARRAY_SIZE(direction_filename); i++)
     if (strcasecmp(str, direction_filename[i]) == 0)
       return (GdDirection) i;
 
@@ -113,7 +110,7 @@ GdDirection gd_direction_from_string(const char *str)
   return GD_MV_DOWN;
 }
 
-/* scheduling name to string and vice versa */
+// scheduling name to string and vice versa
 const char *gd_scheduling_get_filename(GdScheduling sched)
 {
   return scheduling_filename[sched];
@@ -128,7 +125,7 @@ GdScheduling gd_scheduling_from_string(const char *str)
 {
   int i;
 
-  for (i = 0; i < G_N_ELEMENTS(scheduling_filename); i++)
+  for (i = 0; i < ARRAY_SIZE(scheduling_filename); i++)
     if (strcasecmp(str, scheduling_filename[i]) == 0)
       return (GdScheduling) i;
 
@@ -143,7 +140,7 @@ GdScheduling gd_scheduling_from_string(const char *str)
   "properties" describes the structure and its pointers,
   "defaults" are the pieces of data which will be copied to str.
 */
-void gd_struct_set_defaults_from_array(gpointer str,
+void gd_struct_set_defaults_from_array(void *str,
                                       const GdStructDescriptor *properties,
                                       GdPropertyDefault *defaults)
 {
@@ -151,8 +148,8 @@ void gd_struct_set_defaults_from_array(gpointer str,
 
   for (i = 0; defaults[i].offset != -1; i++)
   {
-    gpointer pvalue = G_STRUCT_MEMBER_P(str, defaults[i].offset);
-    /* these point to the same, but to avoid the awkward cast syntax */
+    void *pvalue = STRUCT_MEMBER_P(str, defaults[i].offset);
+    // these point to the same, but to avoid the awkward cast syntax
     int *ivalue = pvalue;
     GdElement *evalue = pvalue;
     GdDirection *dvalue = pvalue;
@@ -161,7 +158,7 @@ void gd_struct_set_defaults_from_array(gpointer str,
     GdColor *cvalue = pvalue;
     int j, n;
 
-    /* check which property we are talking about: find it in gd_cave_properties. */
+    // check which property we are talking about: find it in gd_cave_properties.
     n = defaults[i].property_index;
     if (n == 0)
     {
@@ -169,25 +166,25 @@ void gd_struct_set_defaults_from_array(gpointer str,
             properties[n].offset != defaults[i].offset)
        n++;
 
-      /* remember so we will be fast later*/
+      // remember so we will be fast later
       defaults[i].property_index = n;
     }
 
-    /* some properties are arrays. this loop fills all with the same values */
+    // some properties are arrays. this loop fills all with the same values
     for (j = 0; j < properties[n].count; j++)
     {
       switch (properties[n].type)
       {
-       /* these are for the gui; do nothing */
+       // these are for the gui; do nothing
        case GD_TAB:
        case GD_LABEL:
-         /* no default value for strings */
+         // no default value for strings
        case GD_TYPE_STRING:
        case GD_TYPE_LONGSTRING:
          break;
 
        case GD_TYPE_RATIO:
-         /* this is also an integer, difference is only when saving to bdcff */
+         // this is also an integer, difference is only when saving to bdcff
        case GD_TYPE_INT:
          if (defaults[i].defval < properties[n].min ||
              defaults[i].defval > properties[n].max)
@@ -196,7 +193,7 @@ void gd_struct_set_defaults_from_array(gpointer str,
          break;
 
        case GD_TYPE_PROBABILITY:
-         /* floats are stored as integer, /million; but are integers */
+         // floats are stored as integer, /million; but are integers
          if (defaults[i].defval < 0 ||
              defaults[i].defval > 1000000)
            Warn("integer property %s out of range", properties[n].identifier);
@@ -228,26 +225,27 @@ void gd_struct_set_defaults_from_array(gpointer str,
   }
 }
 
-/* creates the character->element conversion table; using
-   the fixed-in-the-bdcff characters. later, this table
-   may be filled with more elements.
+/*
+  creates the character->element conversion table; using
+  the fixed-in-the-bdcff characters. later, this table
+  may be filled with more elements.
 */
 void gd_create_char_to_element_table(void)
 {
   int i;
 
-  /* fill all with unknown */
-  for (i = 0; i < G_N_ELEMENTS(gd_char_to_element); i++)
+  // fill all with unknown
+  for (i = 0; i < ARRAY_SIZE(gd_char_to_element); i++)
     gd_char_to_element[i] = O_UNKNOWN;
 
-  /* then set fixed characters */
+  // then set fixed characters
   for (i = 0; i < O_MAX; i++)
   {
     int c = gd_elements[i].character;
 
     if (c)
     {
-      if (gd_char_to_element[c]!=O_UNKNOWN)
+      if (gd_char_to_element[c] != O_UNKNOWN)
        Warn("Character %c already used for element %x", c, gd_char_to_element[c]);
 
       gd_char_to_element[c] = i;
@@ -255,8 +253,8 @@ void gd_create_char_to_element_table(void)
   }
 }
 
-/* search the element database for the specified character, and return the element. */
-GdElement gd_get_element_from_character (guint8 character)
+// search the element database for the specified character, and return the element.
+GdElement gd_get_element_from_character (byte character)
 {
   if (gd_char_to_element[character] != O_UNKNOWN)
     return gd_char_to_element[character];
@@ -273,58 +271,57 @@ void gd_cave_init(void)
 {
   int i;
 
-  /* put names to a hash table */
-  /* this is a helper for file read operations */
-  /* maps g_strdupped strings to elemenets (integers) */
-  name_to_element = g_hash_table_new_full(gd_str_case_hash, gd_str_case_equal,
-                                         free, NULL);
+  // put names to a hash table
+  // this is a helper for file read operations
+  // maps copied strings to elements (integers)
+  name_to_element = create_hashtable(gd_str_case_hash, gd_str_case_equal, NULL, NULL);
 
   for (i = 0; i < O_MAX; i++)
   {
     char *key;
 
-    key = g_ascii_strup(gd_elements[i].filename, -1);
+    key = getStringToUpper(gd_elements[i].filename);
 
-    if (g_hash_table_lookup_extended(name_to_element, key, NULL, NULL))
+    if (hashtable_exists(name_to_element, key))                // hash value may be 0
       Warn("Name %s already used for element %x", key, i);
 
-    g_hash_table_insert(name_to_element, key, GINT_TO_POINTER(i));
-    /* ^^^ do not free "key", as hash table needs it during the whole time! */
+    hashtable_insert(name_to_element, key, INT_TO_PTR(i));
+    // ^^^ do not free "key", as hash table needs it during the whole time!
 
-    key = g_strdup_printf("SCANNED_%s", key);        /* new string */
+    key = getStringCat2("SCANNED_", key);              // new string
 
-    g_hash_table_insert(name_to_element, key, GINT_TO_POINTER(i));
-    /* once again, do not free "key" ^^^ */
+    hashtable_insert(name_to_element, key, INT_TO_PTR(i));
+    // once again, do not free "key" ^^^
   }
 
-  /* for compatibility with tim stridmann's memorydump->bdcff converter... .... ... */
-  g_hash_table_insert(name_to_element, "HEXPANDING_WALL", GINT_TO_POINTER(O_H_EXPANDING_WALL));
-  g_hash_table_insert(name_to_element, "FALLING_DIAMOND", GINT_TO_POINTER(O_DIAMOND_F));
-  g_hash_table_insert(name_to_element, "FALLING_BOULDER", GINT_TO_POINTER(O_STONE_F));
-  g_hash_table_insert(name_to_element, "EXPLOSION1S", GINT_TO_POINTER(O_EXPLODE_1));
-  g_hash_table_insert(name_to_element, "EXPLOSION2S", GINT_TO_POINTER(O_EXPLODE_2));
-  g_hash_table_insert(name_to_element, "EXPLOSION3S", GINT_TO_POINTER(O_EXPLODE_3));
-  g_hash_table_insert(name_to_element, "EXPLOSION4S", GINT_TO_POINTER(O_EXPLODE_4));
-  g_hash_table_insert(name_to_element, "EXPLOSION5S", GINT_TO_POINTER(O_EXPLODE_5));
-  g_hash_table_insert(name_to_element, "EXPLOSION1D", GINT_TO_POINTER(O_PRE_DIA_1));
-  g_hash_table_insert(name_to_element, "EXPLOSION2D", GINT_TO_POINTER(O_PRE_DIA_2));
-  g_hash_table_insert(name_to_element, "EXPLOSION3D", GINT_TO_POINTER(O_PRE_DIA_3));
-  g_hash_table_insert(name_to_element, "EXPLOSION4D", GINT_TO_POINTER(O_PRE_DIA_4));
-  g_hash_table_insert(name_to_element, "EXPLOSION5D", GINT_TO_POINTER(O_PRE_DIA_5));
-  g_hash_table_insert(name_to_element, "WALL2", GINT_TO_POINTER(O_STEEL_EXPLODABLE));
-
-  /* compatibility with old bd-faq (pre disassembly of bladder) */
-  g_hash_table_insert(name_to_element, "BLADDERd9", GINT_TO_POINTER(O_BLADDER_8));
-
-  /* create table to show errors at the start of the application */
+  // for compatibility with tim stridmann's memorydump->bdcff converter... .... ...
+  hashtable_insert(name_to_element, "HEXPANDING_WALL", INT_TO_PTR(O_H_EXPANDING_WALL));
+  hashtable_insert(name_to_element, "FALLING_DIAMOND", INT_TO_PTR(O_DIAMOND_F));
+  hashtable_insert(name_to_element, "FALLING_BOULDER", INT_TO_PTR(O_STONE_F));
+  hashtable_insert(name_to_element, "EXPLOSION1S", INT_TO_PTR(O_EXPLODE_1));
+  hashtable_insert(name_to_element, "EXPLOSION2S", INT_TO_PTR(O_EXPLODE_2));
+  hashtable_insert(name_to_element, "EXPLOSION3S", INT_TO_PTR(O_EXPLODE_3));
+  hashtable_insert(name_to_element, "EXPLOSION4S", INT_TO_PTR(O_EXPLODE_4));
+  hashtable_insert(name_to_element, "EXPLOSION5S", INT_TO_PTR(O_EXPLODE_5));
+  hashtable_insert(name_to_element, "EXPLOSION1D", INT_TO_PTR(O_PRE_DIA_1));
+  hashtable_insert(name_to_element, "EXPLOSION2D", INT_TO_PTR(O_PRE_DIA_2));
+  hashtable_insert(name_to_element, "EXPLOSION3D", INT_TO_PTR(O_PRE_DIA_3));
+  hashtable_insert(name_to_element, "EXPLOSION4D", INT_TO_PTR(O_PRE_DIA_4));
+  hashtable_insert(name_to_element, "EXPLOSION5D", INT_TO_PTR(O_PRE_DIA_5));
+  hashtable_insert(name_to_element, "WALL2", INT_TO_PTR(O_STEEL_EXPLODABLE));
+
+  // compatibility with old bd-faq (pre disassembly of bladder)
+  hashtable_insert(name_to_element, "BLADDERd9", INT_TO_PTR(O_BLADDER_8));
+
+  // create table to show errors at the start of the application
   gd_create_char_to_element_table();
 }
 
-/* search the element database for the specified name, and return the element */
+// search the element database for the specified name, and return the element
 GdElement gd_get_element_from_string (const char *string)
 {
-  char *upper = g_ascii_strup(string, -1);
-  gpointer value;
+  char *upper = getStringToUpper(string);
+  void *value;
   boolean found;
 
   if (!string)
@@ -333,12 +330,15 @@ GdElement gd_get_element_from_string (const char *string)
     return O_UNKNOWN;
   }
 
-  found = g_hash_table_lookup_extended(name_to_element, upper, NULL, &value);
+  found = hashtable_exists(name_to_element, upper);    // hash value may be 0
+  if (found)
+    value = hashtable_search(name_to_element, upper);
   free(upper);
   if (found)
-    return (GdElement) (GPOINTER_TO_INT(value));
+    return (GdElement) (PTR_TO_INT(value));
+
+  Warn("Invalid string representing element: '%s'", string);
 
-  Warn("Invalid string representing element: %s", string);
   return O_UNKNOWN;
 }
 
@@ -357,7 +357,7 @@ void gd_cave_set_gdash_defaults(GdCave* cave)
 
   gd_cave_set_defaults_from_array(cave, gd_cave_defaults_gdash);
 
-  /* these did not fit into the descriptor array */
+  // these did not fit into the descriptor array
   for (i = 0; i < 5; i++)
   {
     cave->level_rand[i] = i;
@@ -365,8 +365,8 @@ void gd_cave_set_gdash_defaults(GdCave* cave)
   }
 }
 
-/* for quicksort. compares two highscores. */
-int gd_highscore_compare(gconstpointer a, gconstpointer b)
+// for quicksort. compares two highscores.
+int gd_highscore_compare(const void *a, const void *b)
 {
   const GdHighScore *ha = a;
   const GdHighScore *hb = b;
@@ -389,10 +389,10 @@ boolean gd_has_highscore(GdHighScore *hs)
   return hs[0].score > 0;
 }
 
-/* return true if score achieved is a highscore */
+// return true if score achieved is a highscore
 boolean gd_is_highscore(GdHighScore *scores, int score)
 {
-  /* if score is above zero AND bigger than the last one */
+  // if score is above zero AND bigger than the last one
   if (score > 0 && score > scores[GD_HIGHSCORE_NUM-1].score)
     return TRUE;
 
@@ -406,34 +406,33 @@ int gd_add_highscore(GdHighScore *highscores, const char *name, int score)
   if (!gd_is_highscore(highscores, score))
     return -1;
 
-  /* overwrite the last one */
+  // overwrite the last one
   gd_strcpy(highscores[GD_HIGHSCORE_NUM-1].name, name);
   highscores[GD_HIGHSCORE_NUM-1].score = score;
 
-  /* and sort */
+  // and sort
   qsort(highscores, GD_HIGHSCORE_NUM, sizeof(GdHighScore), gd_highscore_compare);
 
   for (i = 0; i < GD_HIGHSCORE_NUM; i++)
-    if (g_str_equal(highscores[i].name, name) && highscores[i].score == score)
+    if (strEqual(highscores[i].name, name) && highscores[i].score == score)
       return i;
 
   return -1;
 }
 
-/* for the case-insensitive hash keys */
-boolean gd_str_case_equal(gconstpointer s1, gconstpointer s2)
+// for the case-insensitive hash keys
+int gd_str_case_equal(void *s1, void *s2)
 {
   return strcasecmp(s1, s2) == 0;
 }
 
-guint gd_str_case_hash(gconstpointer v)
+unsigned int gd_str_case_hash(void *v)
 {
-  char *upper;
-  guint hash;
+  char *upper = getStringToUpper(v);
+  unsigned int hash = get_hash_from_string(upper);
 
-  upper = g_ascii_strup(v, -1);
-  hash = g_str_hash(v);
   free(upper);
+
   return hash;
 }
 
@@ -443,54 +442,49 @@ guint gd_str_case_hash(gconstpointer v)
 */
 GdCave *gd_cave_new(void)
 {
-  int i;
   GdCave *cave;
 
   cave = checked_calloc(sizeof(GdCave));
 
-  /* hash table which stores unknown tags as strings. */
-  cave->tags = g_hash_table_new_full(gd_str_case_hash, gd_str_case_equal, free, free);
-
-  /* for strings */
-  for (i = 0; gd_cave_properties[i].identifier != NULL; i++)
-    if (gd_cave_properties[i].type == GD_TYPE_LONGSTRING)
-      G_STRUCT_MEMBER(GString *, cave, gd_cave_properties[i].offset) = g_string_new(NULL);
+  // hash table which stores unknown tags as strings.
+  cave->tags = create_hashtable(gd_str_case_hash, gd_str_case_equal, free, free);
 
   gd_cave_set_gdash_defaults(cave);
 
   return cave;
 }
 
-/* cave maps.
-   cave maps are continuous areas in memory. the allocated memory
-   is width * height * bytes_per_cell long.
-   the cave map[0] stores the pointer given by g_malloc().
-   the map itself is also an allocated array of pointers to the
-   beginning of rows.
-   therefore:
-   rows = new (pointers to rows);
-   rows[0] = new map
-   rows[1..h-1] = rows[0] + width * bytes
-
-   freeing this:
-   free(rows[0])
-   free(rows)
+/*
+  cave maps.
+  cave maps are continuous areas in memory. the allocated memory
+  is width * height * bytes_per_cell long.
+  the cave map[0] stores the pointer given by g_malloc().
+  the map itself is also an allocated array of pointers to the
+  beginning of rows.
+  therefore:
+  rows = new (pointers to rows);
+  rows[0] = new map
+  rows[1..h-1] = rows[0] + width * bytes
+
+  freeing this:
+  free(rows[0])
+  free(rows)
 */
 
 /*
   allocate a cave map-like array, and initialize to zero.
   one cell is cell_size bytes long.
 */
-gpointer gd_cave_map_new_for_cave(const GdCave *cave, const int cell_size)
+void *gd_cave_map_new_for_cave(const GdCave *cave, const int cell_size)
 {
-  gpointer *rows;                /* this is void**, pointer to array of ... */
+  void **rows;                // this is void**, pointer to array of ...
   int y;
 
-  rows = checked_malloc((cave->h) * sizeof(gpointer));
+  rows = checked_malloc((cave->h) * sizeof(void *));
   rows[0] = checked_calloc(cell_size * cave->w * cave->h);
 
   for (y = 1; y < cave->h; y++)
-    /* base pointer + num_of_bytes_per_element * width * number_of_row; as sizeof(char) = 1 */
+    // base pointer + num_of_bytes_per_element * width * number_of_row; as sizeof(char) = 1
     rows[y] = (char *)rows[0] + cell_size * cave->w * y;
 
   return rows;
@@ -501,17 +495,17 @@ gpointer gd_cave_map_new_for_cave(const GdCave *cave, const int cell_size)
 
   if map is null, this also returns null.
 */
-gpointer gd_cave_map_dup_size(const GdCave *cave, const gpointer map, const int cell_size)
+void *gd_cave_map_dup_size(const GdCave *cave, const void *map, const int cell_size)
 {
-  gpointer *rows;
-  gpointer *maplines = (gpointer *)map;
+  void **rows;
+  void **maplines = (void **)map;
   int y;
 
   if (!map)
     return NULL;
 
-  rows = checked_malloc((cave->h) * sizeof(gpointer));
-  rows[0] = g_memdup (maplines[0], cell_size * cave->w * cave->h);
+  rows = checked_malloc((cave->h) * sizeof(void *));
+  rows[0] = get_memcpy (maplines[0], cell_size * cave->w * cave->h);
 
   for (y = 1; y < cave->h; y++)
     rows[y] = (char *)rows[0] + cell_size * cave->w * y;
@@ -519,9 +513,9 @@ gpointer gd_cave_map_dup_size(const GdCave *cave, const gpointer map, const int
   return rows;
 }
 
-void gd_cave_map_free(gpointer map)
+void gd_cave_map_free(void *map)
 {
-  gpointer *maplines = (gpointer *) map;
+  void **maplines = (void **) map;
 
   if (!map)
     return;
@@ -541,94 +535,94 @@ void gd_cave_free(GdCave *cave)
     return;
 
   if (cave->tags)
-    g_hash_table_destroy(cave->tags);
+    hashtable_destroy(cave->tags);
 
-  if (cave->random)    /* random generator is a GRand * */
-    g_rand_free(cave->random);
+  if (cave->random)    // random generator is a GdRand *
+    gd_rand_free(cave->random);
 
-  /* free GStrings */
+  // free strings
   for (i = 0; gd_cave_properties[i].identifier != NULL; i++)
-    if (gd_cave_properties[i].type==GD_TYPE_LONGSTRING)
-      g_string_free(G_STRUCT_MEMBER(GString *, cave, gd_cave_properties[i].offset), TRUE);
+    if (gd_cave_properties[i].type == GD_TYPE_LONGSTRING)
+      checked_free(STRUCT_MEMBER(char *, cave, gd_cave_properties[i].offset));
 
-  /* map */
+  // map
   gd_cave_map_free(cave->map);
 
-  /* rendered data */
+  // rendered data
   gd_cave_map_free(cave->objects_order);
 
-  /* hammered walls to reappear data */
+  // hammered walls to reappear data
   gd_cave_map_free(cave->hammered_reappear);
 
-  /* free objects */
-  g_list_foreach(cave->objects, (GFunc) free, NULL);
-  g_list_free (cave->objects);
+  // free objects
+  list_foreach(cave->objects, (list_fn) free, NULL);
+  list_free(cave->objects);
 
-  /* free replays */
-  g_list_foreach(cave->replays, (GFunc) gd_replay_free, NULL);
-  g_list_free(cave->replays);
+  // free replays
+  list_foreach(cave->replays, (list_fn) gd_replay_free, NULL);
+  list_free(cave->replays);
 
-  /* freeing main pointer */
+  // freeing main pointer
   free (cave);
 }
 
-static void hash_copy_foreach(const char *key, const char *value, GHashTable *dest)
+static void hash_copy_foreach(const char *key, const char *value, HashTable *dest)
 {
-  g_hash_table_insert(dest, g_strdup(key), g_strdup(value));
+  hashtable_insert(dest, getStringCopy(key), getStringCopy(value));
 }
 
-/* copy cave from src to destination, with duplicating dynamically allocated data */
+// copy cave from src to destination, with duplicating dynamically allocated data
 void gd_cave_copy(GdCave *dest, const GdCave *src)
 {
   int i;
 
-  /* copy entire data */
-  g_memmove(dest, src, sizeof(GdCave));
+  // copy entire data
+  memmove(dest, src, sizeof(GdCave));
+
+  // but duplicate dynamic data
+  dest->tags = create_hashtable(gd_str_case_hash, gd_str_case_equal, free, free);
 
-  /* but duplicate dynamic data */
-  dest->tags = g_hash_table_new_full(gd_str_case_hash, gd_str_case_equal,
-                                    free, free);
   if (src->tags)
-    g_hash_table_foreach(src->tags, (GHFunc) hash_copy_foreach, dest->tags);
+    hashtable_foreach(src->tags, (hashtable_fn)hash_copy_foreach, dest->tags);
 
   dest->map = gd_cave_map_dup(src, map);
   dest->hammered_reappear = gd_cave_map_dup(src, hammered_reappear);
 
-  /* for longstrings */
+  // for longstrings
   for (i = 0; gd_cave_properties[i].identifier != NULL; i++)
-    if (gd_cave_properties[i].type==GD_TYPE_LONGSTRING)
-      G_STRUCT_MEMBER(GString *, dest, gd_cave_properties[i].offset) =
-       g_string_new(G_STRUCT_MEMBER(GString *, src, gd_cave_properties[i].offset)->str);
+    if (gd_cave_properties[i].type == GD_TYPE_LONGSTRING)
+      STRUCT_MEMBER(char *, dest, gd_cave_properties[i].offset) =
+       getStringCopy(STRUCT_MEMBER(char *, src, gd_cave_properties[i].offset));
 
-  /* no reason to copy this */
+  // no reason to copy this
   dest->objects_order = NULL;
 
-  /* copy objects list */
+  // copy objects list
   if (src->objects)
   {
-    GList *iter;
+    List *iter;
 
-    dest->objects = NULL;    /* new empty list */
-    for (iter = src->objects; iter != NULL; iter = iter->next) /* do a deep copy */
-      dest->objects = g_list_append(dest->objects, g_memdup (iter->data, sizeof (GdObject)));
+    dest->objects = NULL;    // new empty list
+    for (iter = src->objects; iter != NULL; iter = iter->next) // do a deep copy
+      dest->objects = list_append(dest->objects, get_memcpy (iter->data, sizeof (GdObject)));
   }
 
-  /* copy replays */
+  // copy replays
   if (src->replays)
   {
-    GList *iter;
+    List *iter;
 
     dest->replays = NULL;
-    for (iter = src->replays; iter != NULL; iter = iter->next) /* do a deep copy */
-      dest->replays = g_list_append(dest->replays, gd_replay_new_from_replay(iter->data));
+    for (iter = src->replays; iter != NULL; iter = iter->next) // do a deep copy
+      dest->replays = list_append(dest->replays, gd_replay_new_from_replay(iter->data));
   }
 
-  /* copy random number generator */
+  // copy random number generator
   if (src->random)
-    dest->random = g_rand_copy(src->random);
+    dest->random = gd_rand_copy(src->random);
 }
 
-/* create new cave, which is a copy of the cave given. */
+// create new cave, which is a copy of the cave given.
 GdCave *gd_cave_new_from_cave(const GdCave *orig)
 {
   GdCave *cave;
@@ -644,24 +638,25 @@ GdCave *gd_cave_new_from_cave(const GdCave *orig)
   Performs range checking.
   If wraparound objects are selected, wraps around x coordinates, with or without lineshift.
   (The y coordinate is not wrapped, as it did not work like that on the c64)
-  order is a pointer to the GdObject describing this object. Thus the editor can identify which cell was created by which object.
+  order is a pointer to the GdObject describing this object. Thus the editor can identify
+  which cell was created by which object.
 */
 void gd_cave_store_rc(GdCave *cave, int x, int y, const GdElement element, const void *order)
 {
-  /* if we do not need to draw, exit now */
+  // if we do not need to draw, exit now
   if (element == O_NONE)
     return;
 
-  /* check bounds */
+  // check bounds
   if (cave->wraparound_objects)
   {
     if (cave->lineshift)
     {
-      /* fit x coordinate within range, with correcting y at the same time */
+      // fit x coordinate within range, with correcting y at the same time
       while (x < 0)
       {
-       x += cave->w;    /* out of bounds on the left... */
-       y--;             /* previous row */
+       x += cave->w;    // out of bounds on the left...
+       y--;             // previous row
       }
 
       while (x >= cave->w)
@@ -670,20 +665,20 @@ void gd_cave_store_rc(GdCave *cave, int x, int y, const GdElement element, const
        y++;
       }
 
-      /* lineshifting does not fix the y coordinates.
-        if out of bounds, element will not be displayed. */
-      /* if such an object appeared in the c64 game, well, it was a buffer overrun. */
+      // lineshifting does not fix the y coordinates.
+      // if out of bounds, element will not be displayed.
+      // if such an object appeared in the c64 game, well, it was a buffer overrun.
     }
     else
     {
-      /* non lineshifting: changing x does not change y coordinate. */
+      // non lineshifting: changing x does not change y coordinate.
       while (x < 0)
        x += cave->w;
 
       while (x >= cave->w)
        x -= cave->w;
 
-      /* after that, fix y coordinate */
+      // after that, fix y coordinate
       while (y < 0)
        y += cave->h;
 
@@ -692,8 +687,8 @@ void gd_cave_store_rc(GdCave *cave, int x, int y, const GdElement element, const
     }
   }
 
-  /* if the above wraparound code fixed the coordinates, this will always be true. */
-  /* but see the above comment for lineshifting y coordinate */
+  // if the above wraparound code fixed the coordinates, this will always be true.
+  // but see the above comment for lineshifting y coordinate
   if (x >= 0 && x < cave->w && y >= 0 && y < cave->h)
   {
     cave->map[y][x] = element;
@@ -703,16 +698,16 @@ void gd_cave_store_rc(GdCave *cave, int x, int y, const GdElement element, const
 
 GdElement gd_cave_get_rc(const GdCave *cave, int x, int y)
 {
-  /* always fix coordinates as if cave was wraparound. */
+  // always fix coordinates as if cave was wraparound.
 
-  /* fix x coordinate */
+  // fix x coordinate
   if (cave->lineshift)
   {
-    /* fit x coordinate within range, with correcting y at the same time */
+    // fit x coordinate within range, with correcting y at the same time
     while (x < 0)
     {
-      x += cave->w;    /* out of bounds on the left... */
-      y--;             /* previous row */
+      x += cave->w;    // out of bounds on the left...
+      y--;             // previous row
     }
     while (x >= cave->w)
     {
@@ -722,7 +717,7 @@ GdElement gd_cave_get_rc(const GdCave *cave, int x, int y)
   }
   else
   {
-    /* non lineshifting: changing x does not change y coordinate. */
+    // non lineshifting: changing x does not change y coordinate.
     while (x < 0)
       x += cave->w;
 
@@ -730,7 +725,7 @@ GdElement gd_cave_get_rc(const GdCave *cave, int x, int y)
       x -= cave->w;
   }
 
-  /* after that, fix y coordinate */
+  // after that, fix y coordinate
   while (y < 0)
     y += cave->h;
 
@@ -790,10 +785,188 @@ void gd_cave_c64_random_set_seed(GdCave *cave, int seed1, int seed2)
 static inline void swap(int *i1, int *i2)
 {
   int t = *i1;
+
   *i1 = *i2;
   *i2 = t;
 }
 
+void gd_cave_set_random_c64_colors(GdCave *cave)
+{
+  const int bright_colors[] = { 1, 3, 7 };
+  const int dark_colors[] = { 2, 6, 8, 9, 11 };
+
+  // always black
+  cave->colorb = gd_c64_color(0);
+  cave->color0 = gd_c64_color(0);
+
+  // choose some bright color for brick
+  cave->color3 = gd_c64_color(bright_colors[gd_random_int_range(0, ARRAY_SIZE(bright_colors))]);
+
+  // choose a dark color for dirt, but should not be == color of brick
+  do
+  {
+    cave->color1 = gd_c64_color(dark_colors[gd_random_int_range(0, ARRAY_SIZE(dark_colors))]);
+  }
+  while (cave->color1 == cave->color3);    // so it is not the same as color 1
+
+  // choose any but black for steel wall, but should not be == brick or dirt
+  do
+  {
+    // between 1 and 15 - do not use black for this.
+    cave->color2 = gd_c64_color(gd_random_int_range(1, 16));
+  }
+  while (cave->color1 == cave->color2 || cave->color2 == cave->color3);    // so colors are not the same
+
+  // copy amoeba and slime color
+  cave->color4 = cave->color3;
+  cave->color5 = cave->color1;
+}
+
+static void cave_set_random_indexed_colors(GdCave *cave, GdColor (*color_indexer_func) (int, int))
+{
+  int hue = gd_random_int_range(0, 15);
+  int hue_spread = gd_random_int_range(1, 6);    // 1..5
+
+  // we only use 0..6, as saturation 15 is too bright (almost always white)
+  // also, saturation 0..1..2 is too dark. the color0=black is there for dark.
+  // so this is also 1..5. when hue spread is low, brightness spread is high
+  int bri_spread = 6 - hue_spread;
+  int bri1 = 8, bri2 = 8 - bri_spread, bri3 = 8 + bri_spread;
+
+  // there are 15 valid choices for hue, so we do a %15
+  int col1 = hue, col2 = (hue + hue_spread + 15) % 15, col3 = (hue - hue_spread + 15) % 15;
+
+  // this makes up a random color, and selects a color triad by hue+5 and hue+10.
+  // also creates a random saturation.
+  // color of brick is 8+sat, so it is always a bright color.
+  // another two are 8-sat and 8.
+  // order of colors is also changed randomly.
+  if (gd_random_boolean())    swap(&bri1, &bri2);
+
+  // we do not touch bri3 (8+sat), as it should be a bright color
+  if (gd_random_boolean())    swap(&col1, &col2);
+  if (gd_random_boolean())    swap(&col2, &col3);
+  if (gd_random_boolean())    swap(&col1, &col3);
+
+  cave->colorb = color_indexer_func(0, 0);
+  cave->color0 = color_indexer_func(0, 0);
+  cave->color1 = color_indexer_func(col1 + 1, bri1);
+  cave->color2 = color_indexer_func(col2 + 1, bri2);
+  cave->color3 = color_indexer_func(col3 + 1, bri3);
+  // amoeba and slime are different
+  // some green thing
+  cave->color4 = color_indexer_func(gd_random_int_range(11, 13), gd_random_int_range(6, 12));
+  // some blueish thing
+  cave->color5 = color_indexer_func(gd_random_int_range(7, 10),  gd_random_int_range(0, 6));
+}
+
+static void gd_cave_set_random_atari_colors(GdCave *cave)
+{
+  cave_set_random_indexed_colors(cave, gd_atari_color_huesat);
+}
+
+static void gd_cave_set_random_c64dtv_colors(GdCave *cave)
+{
+  cave_set_random_indexed_colors(cave, gd_c64dtv_color_huesat);
+}
+
+static inline void swapd(double *i1, double *i2)
+{
+  double t = *i1;
+
+  *i1 = *i2;
+  *i2 = t;
+}
+
+static void gd_cave_set_random_rgb_colors(GdCave *cave)
+{
+  const double hue_max = 10.0 / 30.0;
+  // any hue allowed
+  double hue = gd_random_double();
+  // hue 360 degress=1.  hue spread is min. 24 degrees, max 120 degrees (1/3)
+  double hue_spread = gd_random_double_range(2.0 / 30.0, hue_max);
+  double h1 = hue, h2 = hue + hue_spread, h3 = hue + 2 * hue_spread;
+  double v1, v2, v3;
+  double s1, s2, s3;
+
+  if (gd_random_boolean())
+  {
+    // when hue spread is low, brightness(saturation) spread is high
+    // this formula gives a number (x) between 0.1 and 0.4,
+    // which will be 0.5-x and 0.5+x, so the range is 0.1->0.9
+    double spread = 0.1 + 0.3 * (1 - hue_spread / hue_max);
+    v1 = 0.6;                // brightness variation, too
+    v2 = 0.7;
+    v3 = 0.8;
+    s1 = 0.5;                // saturation is different
+    s2 = 0.5 - spread;
+    s3 = 0.5 + spread;
+  }
+  else
+  {
+    // when hue spread is low, brightness(saturation) spread is high
+    // this formula gives a number (x) between 0.1 and 0.25,
+    // which will be 0.5+x and 0.5+2x, so the range is 0.5->0.9
+    double spread = 0.1 + 0.15 * (1 - hue_spread / hue_max);
+    v1 = 0.5;                // brightness is different
+    v2 = 0.5 + spread;
+    v3 = 0.5 + 2 * spread;
+    s1 = 0.7;                // saturation is same - a not fully saturated one
+    s2 = 0.8;
+    s3 = 0.9;
+  }
+
+  // randomly change values, but do not touch v3, as cave->color3 should be a bright color
+  if (gd_random_boolean())    swapd(&v1, &v2);
+
+  // randomly change hues and saturations
+  if (gd_random_boolean())    swapd(&h1, &h2);
+  if (gd_random_boolean())    swapd(&h2, &h3);
+  if (gd_random_boolean())    swapd(&h1, &h3);
+  if (gd_random_boolean())    swapd(&s1, &s2);
+  if (gd_random_boolean())    swapd(&s2, &s3);
+  if (gd_random_boolean())    swapd(&s1, &s3);
+
+  h1 = h1 * 360.0;
+  h2 = h2 * 360.0;
+  h3 = h3 * 360.0;
+
+  cave->colorb = gd_color_get_from_hsv(0, 0, 0);
+  cave->color0 = gd_color_get_from_hsv(0, 0, 0);       // black for background
+  cave->color1 = gd_color_get_from_hsv(h1, s1, v1);    // dirt
+  cave->color2 = gd_color_get_from_hsv(h2, s2, v2);    // steel
+  cave->color3 = gd_color_get_from_hsv(h3, s3, v3);    // brick
+  // green(120+-20) with the saturation and brightness of brick
+  cave->color4 = gd_color_get_from_hsv(gd_random_int_range(100, 140), s2, v2);
+  // blue(240+-20) with saturation and brightness of dirt
+  cave->color5 = gd_color_get_from_hsv(gd_random_int_range(220, 260), s1, v1);
+}
+
+void gd_cave_set_random_colors(GdCave *cave, GdColorType type)
+{
+  switch (type)
+  {
+    case GD_COLOR_TYPE_RGB:
+      gd_cave_set_random_rgb_colors(cave);
+      break;
+
+    case GD_COLOR_TYPE_C64:
+      gd_cave_set_random_c64_colors(cave);
+      break;
+
+    case GD_COLOR_TYPE_C64DTV:
+      gd_cave_set_random_c64dtv_colors(cave);
+      break;
+
+    case GD_COLOR_TYPE_ATARI:
+      gd_cave_set_random_atari_colors(cave);
+      break;
+
+    default:
+      break;
+  }
+}
+
 /*
   shrink cave
   if last line or last row is just steel wall (or (invisible) outbox).
@@ -812,15 +985,15 @@ void gd_cave_auto_shrink(GdCave *cave)
   }
   empty;
 
-  /* set to maximum size, then try to shrink */
+  // set to maximum size, then try to shrink
   cave->x1 = 0;
   cave->y1 = 0;
   cave->x2 = cave->w - 1;
   cave->y2 = cave->h - 1;
 
-  /* search for empty, steel-wall-only last rows. */
-  /* clear all lines, which are only steel wall.
-   * and clear only one line, which is steel wall, but also has a player or an outbox. */
+  // search for empty, steel-wall-only last rows.
+  // clear all lines, which are only steel wall.
+  // and clear only one line, which is steel wall, but also has a player or an outbox.
   empty = STEEL_ONLY;
 
   do
@@ -831,7 +1004,7 @@ void gd_cave_auto_shrink(GdCave *cave)
       {
        switch (gd_cave_get_rc (cave, x, y))
        {
-         /* if steels only, this is to be deleted. */
+         // if steels only, this is to be deleted.
          case O_STEEL:
            break;
 
@@ -841,26 +1014,26 @@ void gd_cave_auto_shrink(GdCave *cave)
            if (empty == STEEL_OR_OTHER)
              empty = NO_SHRINK;
 
-           /* if this, delete only this one, and exit. */
+           // if this, delete only this one, and exit.
            if (empty == STEEL_ONLY)
              empty = STEEL_OR_OTHER;
            break;
 
          default:
-           /* anything else, that should be left in the cave. */
+           // anything else, that should be left in the cave.
            empty = NO_SHRINK;
            break;
        }
       }
     }
 
-    /* shrink if full steel or steel and player/outbox. */
+    // shrink if full steel or steel and player/outbox.
     if (empty != NO_SHRINK)
-      cave->y2--;            /* one row shorter */
+      cave->y2--;            // one row shorter
   }
-  while (empty == STEEL_ONLY);    /* if found just steels, repeat. */
+  while (empty == STEEL_ONLY);    // if found just steels, repeat.
 
-  /* search for empty, steel-wall-only first rows. */
+  // search for empty, steel-wall-only first rows.
   empty = STEEL_ONLY;
 
   do
@@ -877,11 +1050,11 @@ void gd_cave_auto_shrink(GdCave *cave)
          case O_PRE_OUTBOX:
          case O_PRE_INVIS_OUTBOX:
          case O_INBOX:
-           /* shrink only lines, which have only ONE player or outbox.
-              this is for bd4 intermission 2, for example. */
-           if (empty==STEEL_OR_OTHER)
+           // shrink only lines, which have only ONE player or outbox.
+           // this is for bd4 intermission 2, for example.
+           if (empty == STEEL_OR_OTHER)
              empty = NO_SHRINK;
-           if (empty==STEEL_ONLY)
+           if (empty == STEEL_ONLY)
              empty = STEEL_OR_OTHER;
            break;
 
@@ -895,9 +1068,9 @@ void gd_cave_auto_shrink(GdCave *cave)
     if (empty != NO_SHRINK)
       cave->y1++;
   }
-  while (empty == STEEL_ONLY);    /* if found one, repeat. */
+  while (empty == STEEL_ONLY);    // if found one, repeat.
 
-  /* empty last columns. */
+  // empty last columns.
   empty = STEEL_ONLY;
 
   do
@@ -914,9 +1087,9 @@ void gd_cave_auto_shrink(GdCave *cave)
          case O_PRE_OUTBOX:
          case O_PRE_INVIS_OUTBOX:
          case O_INBOX:
-           if (empty==STEEL_OR_OTHER)
+           if (empty == STEEL_OR_OTHER)
              empty = NO_SHRINK;
-           if (empty==STEEL_ONLY)
+           if (empty == STEEL_ONLY)
              empty = STEEL_OR_OTHER;
            break;
 
@@ -927,14 +1100,14 @@ void gd_cave_auto_shrink(GdCave *cave)
       }
     }
 
-    /* just remember that one column shorter.
-       free will know the size of memchunk, no need to realloc! */
+    // just remember that one column shorter.
+    // free will know the size of memchunk, no need to realloc!
     if (empty != NO_SHRINK)
       cave->x2--;
   }
-  while (empty == STEEL_ONLY);    /* if found one, repeat. */
+  while (empty == STEEL_ONLY);    // if found one, repeat.
 
-  /* empty first columns. */
+  // empty first columns.
   empty = STEEL_ONLY;
 
   do
@@ -951,9 +1124,9 @@ void gd_cave_auto_shrink(GdCave *cave)
          case O_PRE_OUTBOX:
          case O_PRE_INVIS_OUTBOX:
          case O_INBOX:
-           if (empty==STEEL_OR_OTHER)
+           if (empty == STEEL_OR_OTHER)
              empty = NO_SHRINK;
-           if (empty==STEEL_ONLY)
+           if (empty == STEEL_ONLY)
              empty = STEEL_OR_OTHER;
            break;
 
@@ -967,16 +1140,17 @@ void gd_cave_auto_shrink(GdCave *cave)
     if (empty != NO_SHRINK)
       cave->x1++;
   }
-  while (empty == STEEL_ONLY);    /* if found one, repeat. */
+  while (empty == STEEL_ONLY);    // if found one, repeat.
 }
 
-/* check if cave visible part coordinates
-   are outside cave sizes, or not in the right order.
-   correct them if needed.
+/*
+  check if cave visible part coordinates
+  are outside cave sizes, or not in the right order.
+  correct them if needed.
 */
 void gd_cave_correct_visible_size(GdCave *cave)
 {
-  /* change visible coordinates if they do not point to upperleft and lowerright */
+  // change visible coordinates if they do not point to upperleft and lowerright
   if (cave->x2 < cave->x1)
   {
     int t = cave->x2;
@@ -1053,18 +1227,18 @@ static void cave_set_ckdelay_extra_for_animation(GdCave *cave)
     cave->ckdelay_extra_for_animation += 2600;
 }
 
-/* do some init - setup some cave variables before the game. */
+// do some init - setup some cave variables before the game.
 void gd_cave_setup_for_game(GdCave *cave)
 {
   int x, y;
 
   cave_set_ckdelay_extra_for_animation(cave);
 
-  /* find the player which will be the one to scroll to at the beginning of the game
-     (before the player's birth) */
+  // find the player which will be the one to scroll to at the beginning of the game
+  // (before the player's birth)
   if (cave->active_is_first_found)
   {
-    /* uppermost player is active */
+    // uppermost player is active
     for (y = cave->h - 1; y >= 0; y--)
     { 
      for (x = cave->w - 1; x >= 0; x--)
@@ -1079,7 +1253,7 @@ void gd_cave_setup_for_game(GdCave *cave)
   }
   else
   {
-    /* lowermost player is active */
+    // lowermost player is active
     for (y = 0; y < cave->h; y++)
     {
       for (x = 0; x < cave->w; x++)
@@ -1093,7 +1267,7 @@ void gd_cave_setup_for_game(GdCave *cave)
     }
   }
 
-  /* select number of milliseconds (for pal and ntsc) */
+  // select number of milliseconds (for pal and ntsc)
   cave->timing_factor = cave->pal_timing ? 1200 : 1000;
 
   cave->time                   *= cave->timing_factor;
@@ -1106,16 +1280,16 @@ void gd_cave_setup_for_game(GdCave *cave)
     cave->hammered_reappear = gd_cave_map_new(cave, int);
 }
 
-/* cave diamonds needed can be set to n<=0. */
-/* if so, count the diamonds at the time of the hatching, and decrement that value from */
-/* the number of diamonds found. */
-/* of course, this function is to be called from the cave engine, at the exact time of hatching. */
+// cave diamonds needed can be set to n<=0.
+// if so, count the diamonds at the time of the hatching, and decrement that value from
+// the number of diamonds found.
+// of course, this function is to be called from the cave engine, at the exact time of hatching.
 void gd_cave_count_diamonds(GdCave *cave)
 {
   int x, y;
 
-  /* if automatically counting diamonds. if this was negative,
-   * the sum will be this less than the number of all the diamonds in the cave */
+  // if automatically counting diamonds. if this was negative,
+  // the sum will be this less than the number of all the diamonds in the cave
   if (cave->diamonds_needed <= 0)
   {
     for (y = 0; y < cave->h; y++)
@@ -1123,55 +1297,57 @@ void gd_cave_count_diamonds(GdCave *cave)
        if (cave->map[y][x] == O_DIAMOND)
          cave->diamonds_needed++;
 
-    /* if still below zero, let this be 0, so gate will be open immediately */
+    // if still below zero, let this be 0, so gate will be open immediately
     if (cave->diamonds_needed < 0)
       cave->diamonds_needed = 0;
   }
 }
 
-/* takes a cave and a gfx buffer, and fills the buffer with cell indexes.
-   the indexes might change if bonus life flash is active (small lines in
-   "SPACE" cells),
-   for the paused state (which is used in gdash but not in sdash) - yellowish
-   color.
-   also one can select the animation frame (0..7) to draw the cave on. so the
-   caller manages
-   increasing that.
-
-   if a cell is changed, it is flagged with GD_REDRAW; the flag can be cleared
-   by the caller.
+/*
+  takes a cave and a gfx buffer, and fills the buffer with cell indexes.
+  the indexes might change if bonus life flash is active (small lines in
+  "SPACE" cells),
+  for the paused state (which is used in gdash but not in sdash) - yellowish
+  color.
+  also one can select the animation frame (0..7) to draw the cave on. so the
+  caller manages
+  increasing that.
+
+  if a cell is changed, it is flagged with GD_REDRAW; the flag can be cleared
+  by the caller.
 */
-void gd_drawcave_game(const GdCave *cave, int **element_buffer, int **gfx_buffer,
+void gd_drawcave_game(const GdCave *cave,
+                     int **element_buffer, int **last_element_buffer, int **gfx_buffer,
                      boolean bonus_life_flash, int animcycle, boolean hate_invisible_outbox)
 {
   static int player_blinking = 0;
   static int player_tapping = 0;
-  int elemmapping[O_MAX];
-  int elemdrawing[O_MAX];
+  int elemmapping[O_MAX_ALL];
+  int elemdrawing[O_MAX_ALL];
   int x, y, map, draw;
 
   if (cave->last_direction)
   {
-    /* he is moving, so stop blinking and tapping. */
+    // he is moving, so stop blinking and tapping.
     player_blinking = 0;
     player_tapping = 0;
   }
   else
   {
-    /* he is idle, so animations can be done. */
+    // he is idle, so animations can be done.
     if (animcycle == 0)
     {
-      /* blinking and tapping is started at the beginning of animation sequences. */
-      /* 1/4 chance of blinking, every sequence. */
-      player_blinking = g_random_int_range(0, 4) == 0;
+      // blinking and tapping is started at the beginning of animation sequences.
+      // 1/4 chance of blinking, every sequence.
+      player_blinking = gd_random_int_range(0, 4) == 0;
 
-      /* 1/16 chance of starting or stopping tapping. */
-      if (g_random_int_range(0, 16) == 0)
+      // 1/16 chance of starting or stopping tapping.
+      if (gd_random_int_range(0, 16) == 0)
        player_tapping = !player_tapping;
     }
   }
 
-  for (x = 0; x < O_MAX; x++)
+  for (x = 0; x < O_MAX_ALL; x++)
   {
     elemmapping[x] = x;
     elemdrawing[x] = gd_elements[x].image_game;
@@ -1199,11 +1375,11 @@ void gd_drawcave_game(const GdCave *cave, int **element_buffer, int **gfx_buffer
   elemdrawing[O_REPLICATOR_SWITCH] = gd_elements[cave->replicators_active ? O_REPLICATOR_SWITCH_ON : O_REPLICATOR_SWITCH_OFF].image_game;
 
   if (cave->replicators_active)
-    /* if the replicators are active, animate them. */
+    // if the replicators are active, animate them.
     elemmapping[O_REPLICATOR] = O_REPLICATOR_ACTIVE;
 
   if (!cave->replicators_active)
-    /* if the replicators are inactive, do not animate them. */
+    // if the replicators are inactive, do not animate them.
     elemdrawing[O_REPLICATOR] = ABS(elemdrawing[O_REPLICATOR]);
 
   elemmapping[O_CONVEYOR_SWITCH] = (cave->conveyor_belts_active ? O_CONVEYOR_SWITCH_ON : O_CONVEYOR_SWITCH_OFF);
@@ -1211,7 +1387,7 @@ void gd_drawcave_game(const GdCave *cave, int **element_buffer, int **gfx_buffer
 
   if (cave->conveyor_belts_direction_changed)
   {
-    /* if direction is changed, animation is changed. */
+    // if direction is changed, animation is changed.
     int temp;
 
     elemmapping[O_CONVEYOR_LEFT] = O_CONVEYOR_RIGHT;
@@ -1232,23 +1408,23 @@ void gd_drawcave_game(const GdCave *cave, int **element_buffer, int **gfx_buffer
 
   if (cave->conveyor_belts_active)
   {
-    /* keep potentially changed direction */
+    // keep potentially changed direction
     int offset = (O_CONVEYOR_LEFT_ACTIVE - O_CONVEYOR_LEFT);
 
-    /* if they are running, animate them. */
+    // if they are running, animate them.
     elemmapping[O_CONVEYOR_LEFT]  += offset;
     elemmapping[O_CONVEYOR_RIGHT] += offset;
   }
   if (!cave->conveyor_belts_active)
   {
-    /* if they are not running, do not animate them. */
+    // if they are not running, do not animate them.
     elemdrawing[O_CONVEYOR_LEFT] = ABS(elemdrawing[O_CONVEYOR_LEFT]);
     elemdrawing[O_CONVEYOR_RIGHT] = ABS(elemdrawing[O_CONVEYOR_RIGHT]);
   }
 
   if (animcycle & 2)
   {
-    /* also a hack, like biter_switch */
+    // also a hack, like biter_switch
     elemdrawing[O_PNEUMATIC_ACTIVE_LEFT]  += 2;
     elemdrawing[O_PNEUMATIC_ACTIVE_RIGHT] += 2;
     elemdrawing[O_PLAYER_PNEUMATIC_LEFT]  += 2;
@@ -1257,7 +1433,7 @@ void gd_drawcave_game(const GdCave *cave, int **element_buffer, int **gfx_buffer
 
   if ((cave->last_direction) == GD_MV_STILL)
   {
-    /* player is idle. */
+    // player is idle.
     if (player_blinking && player_tapping)
     {
       map = O_PLAYER_TAP_BLINK;
@@ -1279,6 +1455,16 @@ void gd_drawcave_game(const GdCave *cave, int **element_buffer, int **gfx_buffer
       draw = gd_elements[O_PLAYER].image_game;
     }
   }
+  else if (cave->last_direction == GD_MV_UP && use_bd_up_down_graphics())
+  {
+    map = O_PLAYER_UP;
+    draw = gd_elements[O_PLAYER_UP].image_game;
+  }
+  else if (cave->last_direction == GD_MV_DOWN && use_bd_up_down_graphics())
+  {
+    map = O_PLAYER_DOWN;
+    draw = gd_elements[O_PLAYER_DOWN].image_game;
+  }
   else if (cave->last_horizontal_direction == GD_MV_LEFT)
   {
     map = O_PLAYER_LEFT;
@@ -1286,7 +1472,7 @@ void gd_drawcave_game(const GdCave *cave, int **element_buffer, int **gfx_buffer
   }
   else
   {
-    /* of course this is GD_MV_RIGHT. */
+    // of course this is GD_MV_RIGHT.
     map = O_PLAYER_RIGHT;
     draw = gd_elements[O_PLAYER_RIGHT].image_game;
   }
@@ -1297,12 +1483,15 @@ void gd_drawcave_game(const GdCave *cave, int **element_buffer, int **gfx_buffer
   elemdrawing[O_PLAYER] = draw;
   elemdrawing[O_PLAYER_GLUED] = draw;
 
-  /* player with bomb does not blink or tap - no graphics drawn for that.
-     running is drawn using w/o bomb cells */
-  if (cave->last_direction!=GD_MV_STILL)
+  // player with bomb/rocketlauncher does not blink or tap - no graphics drawn for that.
+  // running is drawn using w/o bomb/rocketlauncher cells */
+  if (cave->last_direction != GD_MV_STILL)
   {
     elemmapping[O_PLAYER_BOMB] = map;
     elemdrawing[O_PLAYER_BOMB] = draw;
+
+    elemmapping[O_PLAYER_ROCKET_LAUNCHER] = map;
+    elemdrawing[O_PLAYER_ROCKET_LAUNCHER] = draw;
   }
 
   elemmapping[O_INBOX] = (cave->inbox_flash_toggle ? O_INBOX_OPEN : O_INBOX_CLOSED);
@@ -1311,30 +1500,30 @@ void gd_drawcave_game(const GdCave *cave, int **element_buffer, int **gfx_buffer
   elemmapping[O_OUTBOX] = (cave->inbox_flash_toggle ? O_OUTBOX_OPEN : O_OUTBOX_CLOSED);
   elemdrawing[O_OUTBOX] = gd_elements[cave->inbox_flash_toggle ? O_OUTBOX_OPEN : O_OUTBOX_CLOSED].image_game;
 
-  /* hack, not fit into gd_elements */
+  // hack, not fit into gd_elements
   elemmapping[O_BITER_SWITCH] = O_BITER_SWITCH_1 + cave->biter_delay_frame;
-  /* hack, not fit into gd_elements */
+  // hack, not fit into gd_elements
   elemdrawing[O_BITER_SWITCH] = gd_elements[O_BITER_SWITCH].image_game + cave->biter_delay_frame;
 
-  /* visual effects */
+  // visual effects
   elemmapping[O_DIRT] = cave->dirt_looks_like;
   elemmapping[O_EXPANDING_WALL] = cave->expanding_wall_looks_like;
   elemmapping[O_V_EXPANDING_WALL] = cave->expanding_wall_looks_like;
   elemmapping[O_H_EXPANDING_WALL] = cave->expanding_wall_looks_like;
   elemmapping[O_AMOEBA_2] = cave->amoeba_2_looks_like;
 
-  /* visual effects */
+  // visual effects
   elemdrawing[O_DIRT] = elemdrawing[cave->dirt_looks_like];
   elemdrawing[O_EXPANDING_WALL] = elemdrawing[cave->expanding_wall_looks_like];
   elemdrawing[O_V_EXPANDING_WALL] = elemdrawing[cave->expanding_wall_looks_like];
   elemdrawing[O_H_EXPANDING_WALL] = elemdrawing[cave->expanding_wall_looks_like];
   elemdrawing[O_AMOEBA_2] = elemdrawing[cave->amoeba_2_looks_like];
 
-  /* change only graphically */
+  // change only graphically
   if (hate_invisible_outbox)
   {
-    elemmapping[O_PRE_INVIS_OUTBOX] = O_PRE_OUTBOX;
-    elemmapping[O_INVIS_OUTBOX] = O_OUTBOX;
+    elemmapping[O_PRE_INVIS_OUTBOX] = elemmapping[O_PRE_OUTBOX];
+    elemmapping[O_INVIS_OUTBOX] = elemmapping[O_OUTBOX];
   }
 
   if (hate_invisible_outbox)
@@ -1349,27 +1538,49 @@ void gd_drawcave_game(const GdCave *cave, int **element_buffer, int **gfx_buffer
     {
       GdElement actual = cave->map[y][x];
 
-      /* if covered, real element is not important */
+      // if covered, real element is not important
       if (actual & COVERED)
        map = O_COVERED;
       else
        map = elemmapping[actual];
 
-      /* if covered, real element is not important */
+      // if covered, real element is not important
       if (actual & COVERED)
        draw = gd_elements[O_COVERED].image_game;
       else
        draw = elemdrawing[actual];
 
-      /* if negative, animated. */
+      // draw special graphics if player is pushing something
+      if (use_bd_pushing_graphics() &&
+         (cave->last_direction == GD_MV_LEFT || cave->last_direction == GD_MV_RIGHT) &&
+         is_player(cave, x, y) && can_be_pushed_dir(cave, x, y, cave->last_direction))
+      {
+       // special check needed when smooth game element movements selected in setup menu:
+       // last element must either be player (before pushing) or pushable element (while pushing)
+       // (extra check needed to prevent pushing animation when moving towards pushable element)
+       if (!use_bd_smooth_movements() || last_element_buffer[y][x] != O_SPACE)
+       {
+         if (cave->last_direction == GD_MV_LEFT)
+           map = O_PLAYER_PUSH_LEFT;
+         else
+           map = O_PLAYER_PUSH_RIGHT;
+
+         if (cave->last_direction == GD_MV_LEFT)
+           draw = elemdrawing[O_PLAYER_PUSH_LEFT];
+         else
+           draw = elemdrawing[O_PLAYER_PUSH_RIGHT];
+       }
+      }
+
+      // if negative, animated.
       if (draw < 0)
        draw = -draw + animcycle;
 
-      /* flash */
+      // flash
       if (cave->gate_open_flash)
        draw += GD_NUM_OF_CELLS;
 
-      /* set to buffer, with caching */
+      // set to buffer, with caching
       if (element_buffer[y][x] != map)
        element_buffer[y][x] = map;
 
@@ -1379,12 +1590,15 @@ void gd_drawcave_game(const GdCave *cave, int **element_buffer, int **gfx_buffer
   }
 }
 
-/* cave time is rounded _UP_ to seconds. so at the exact moment when it
-   changes from
-   2sec remaining to 1sec remaining, the player has exactly one second.
-   when it changes
-   to zero, it is the exact moment of timeout. */
-/* internal time is milliseconds (or 1200 milliseconds for pal timing). */
+/*
+  cave time is rounded _UP_ to seconds. so at the exact moment when it
+  changes from
+  2sec remaining to 1sec remaining, the player has exactly one second.
+  when it changes
+  to zero, it is the exact moment of timeout.
+
+  internal time is milliseconds (or 1200 milliseconds for pal timing).
+*/
 int gd_cave_time_show(const GdCave *cave, int internal_time)
 {
   return (internal_time + cave->timing_factor - 1) / cave->timing_factor;
@@ -1395,10 +1609,7 @@ GdReplay *gd_replay_new(void)
   GdReplay *rep;
 
   rep = checked_calloc(sizeof(GdReplay));
-
-  /* create dynamic objects */
-  rep->comment = g_string_new(NULL);
-  rep->movements = g_byte_array_new();
+  rep->movements = checked_calloc(sizeof(GdReplayMovements));
 
   return rep;
 }
@@ -1407,157 +1618,43 @@ GdReplay *gd_replay_new_from_replay(GdReplay *orig)
 {
   GdReplay *rep;
 
-  rep = g_memdup(orig, sizeof(GdReplay));
+  rep = get_memcpy(orig, sizeof(GdReplay));
 
-  /* replicate dynamic data */
-  rep->comment = g_string_new(orig->comment->str);
-  rep->movements = g_byte_array_new();
-  g_byte_array_append(rep->movements, orig->movements->data, orig->movements->len);
+  // replicate dynamic data
+  rep->comment = getStringCopy(orig->comment);
+  rep->movements = get_memcpy(orig->movements, sizeof(GdReplayMovements));
 
   return rep;
 }
 
 void gd_replay_free(GdReplay *replay)
 {
-  g_byte_array_free(replay->movements, TRUE);
-  g_string_free(replay->comment, TRUE);
+  checked_free(replay->movements);
+  checked_free(replay->comment);
   free(replay);
 }
 
-/* store movement in a replay */
+// store movement in a replay
 void gd_replay_store_movement(GdReplay *replay, GdDirection player_move,
                              boolean player_fire, boolean suicide)
 {
-  guint8 data[1];
+  byte data[1];
 
   data[0] = ((player_move) |
             (player_fire ? GD_REPLAY_FIRE_MASK : 0) |
             (suicide ? GD_REPLAY_SUICIDE_MASK : 0));
 
-  g_byte_array_append(replay->movements, data, 1);
-}
-
-/* get next available movement from a replay; store variables to player_move,
-   player_fire, suicide */
-/* return true if successful */
-boolean gd_replay_get_next_movement(GdReplay *replay, GdDirection *player_move,
-                                   boolean *player_fire, boolean *suicide)
-{
-  guint8 data;
-
-  /* if no more available movements */
-  if (replay->current_playing_pos >= replay->movements->len)
-    return FALSE;
-
-  data = replay->movements->data[replay->current_playing_pos++];
-  *suicide = (data & GD_REPLAY_SUICIDE_MASK) != 0;
-  *player_fire = (data & GD_REPLAY_FIRE_MASK) != 0;
-  *player_move = (data & GD_REPLAY_MOVE_MASK);
-
-  return TRUE;
-}
-
-void gd_replay_rewind(GdReplay *replay)
-{
-  replay->current_playing_pos = 0;
-}
-
-#define REPLAY_BDCFF_UP                        "u"
-#define REPLAY_BDCFF_UP_RIGHT          "ur"
-#define REPLAY_BDCFF_RIGHT             "r"
-#define REPLAY_BDCFF_DOWN_RIGHT                "dr"
-#define REPLAY_BDCFF_DOWN              "d"
-#define REPLAY_BDCFF_DOWN_LEFT         "dl"
-#define REPLAY_BDCFF_LEFT              "l"
-#define REPLAY_BDCFF_UP_LEFT           "ul"
-/* when not moving */
-#define REPLAY_BDCFF_STILL             "."
-/* when the fire is pressed */
-#define REPLAY_BDCFF_FIRE              "F"
-#define REPLAY_BDCFF_SUICIDE           "k"
-
-static char *direction_to_bdcff(GdDirection mov)
-{
-  switch (mov)
-  {
-    /* not moving */
-    case GD_MV_STILL:          return REPLAY_BDCFF_STILL;
-
-      /* directions */
-    case GD_MV_UP:             return REPLAY_BDCFF_UP;
-    case GD_MV_UP_RIGHT:       return REPLAY_BDCFF_UP_RIGHT;
-    case GD_MV_RIGHT:          return REPLAY_BDCFF_RIGHT;
-    case GD_MV_DOWN_RIGHT:     return REPLAY_BDCFF_DOWN_RIGHT;
-    case GD_MV_DOWN:           return REPLAY_BDCFF_DOWN;
-    case GD_MV_DOWN_LEFT:      return REPLAY_BDCFF_DOWN_LEFT;
-    case GD_MV_LEFT:           return REPLAY_BDCFF_LEFT;
-    case GD_MV_UP_LEFT:                return REPLAY_BDCFF_UP_LEFT;
-
-    default:
-      return REPLAY_BDCFF_STILL;
-  }
-}
-
-/* same as above; pressing fire will be a capital letter. */
-static char *direction_fire_to_bdcff(GdDirection dir, boolean fire)
-{
-  static char mov[10];
-
-  strcpy(mov, direction_to_bdcff(dir));
-
-  if (fire)
+  if (replay->movements->len < MAX_REPLAY_LEN)
   {
-    int i;
+    replay->movements->data[replay->movements->len++] = data[0];
 
-    for (i = 0; mov[i] != 0; i++)
-      mov[i] = g_ascii_toupper(mov[i]);
+    if (replay->movements->len == MAX_REPLAY_LEN)
+      Warn("BD replay truncated: size exceeds maximum replay size %d", MAX_REPLAY_LEN);
   }
-
-  return mov;
 }
 
-char *gd_replay_movements_to_bdcff(GdReplay *replay)
-{
-  int pos;
-  GString *str;
-
-  str = g_string_new(NULL);
-
-  for (pos = 0; pos < replay->movements->len; pos++)
-  {
-    int num = 1;
-    guint8 data;
-
-    /* if this is not the first movement, append a space. */
-    if (str->len != 0)
-      g_string_append_c(str, ' ');
-
-    /* if same byte appears, count number of occurrences - something like an rle compression. */
-    /* be sure not to cross the array boundaries */
-    while (pos < replay->movements->len - 1 &&
-          replay->movements->data[pos] == replay->movements->data[pos + 1])
-    {
-      pos++;
-      num++;
-    }
-
-    data = replay->movements->data[pos];
-
-    if (data & GD_REPLAY_SUICIDE_MASK)
-      g_string_append(str, REPLAY_BDCFF_SUICIDE);
-
-    g_string_append(str, direction_fire_to_bdcff(data & GD_REPLAY_MOVE_MASK,
-                                                data & GD_REPLAY_FIRE_MASK));
-
-    if (num != 1)
-      g_string_append_printf(str, "%d", num);
-  }
-
-  return g_string_free(str, FALSE);
-}
-
-/* calculate adler checksum for a rendered cave; this can be used for more caves. */
-void gd_cave_adler_checksum_more(GdCave *cave, guint32 *a, guint32 *b)
+// calculate adler checksum for a rendered cave; this can be used for more caves.
+void gd_cave_adler_checksum_more(GdCave *cave, unsigned int *a, unsigned int *b)
 {
   int x, y;
 
@@ -1572,19 +1669,12 @@ void gd_cave_adler_checksum_more(GdCave *cave, guint32 *a, guint32 *b)
     }
 }
 
-/* calculate adler checksum for a single rendered cave. */
-guint32
-gd_cave_adler_checksum(GdCave *cave)
+// calculate adler checksum for a single rendered cave.
+unsigned int gd_cave_adler_checksum(GdCave *cave)
 {
-  guint32 a = 1;
-  guint32 b = 0;
+  unsigned int a = 1;
+  unsigned int b = 0;
 
   gd_cave_adler_checksum_more(cave, &a, &b);
   return (b << 16) + a;
 }
-
-/* return c64 color with index. */
-GdColor gd_c64_color(int index)
-{
-  return (GD_COLOR_TYPE_C64 << 24) + index;
-}