20100212
[gdash.git] / src / cave.c
blob6b377ed40176d343ca902b4959775dd416ae65d4
1 /*
2 * Copyright (c) 2007, 2008, 2009, Czirkos Zoltan <cirix@fw.hu>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 #include <glib.h>
17 #include <glib/gi18n.h>
18 #include <string.h>
19 #include <stdio.h>
20 #include <stdlib.h>
22 #include "config.h"
24 #include "cave.h"
25 #include "cavedb.h"
26 #include "caveset.h"
27 #include "caveobject.h"
29 /* arrays for movements */
30 /* also no1 and bd2 cave data import helpers; line direction coordinates */
31 const int gd_dx[]={ 0, 0, 1, 1, 1, 0, -1, -1, -1, 0, 2, 2, 2, 0, -2, -2, -2 };
32 const int gd_dy[]={ 0, -1, -1, 0, 1, 1, 1, 0, -1, -2, -2, 0, 2, 2, 2, 0, -2 };
33 /* TRANSLATORS: None here means "no direction to move"; when there is no gravity while stirring the pot. */
34 static const char* direction_name[]={ N_("None"), N_("Up"), N_("Up+right"), N_("Right"), N_("Down+right"), N_("Down"), N_("Down+left"), N_("Left"), N_("Up+left") };
35 static const char* direction_filename[]={ "none", "up", "upright", "right", "downright", "down", "downleft", "left", "upleft" };
36 static const char* scheduling_name[]={ N_("Milliseconds"), "BD1", "BD2", "Construction Kit", "Crazy Dream 7", "Atari BD1", "Atari BD2/Construction Kit" };
37 static const char* scheduling_filename[]={ "ms", "bd1", "bd2", "plck", "crdr7", "bd1atari", "bd2ckatari" };
39 static GHashTable *name_to_element;
40 GdElement gd_char_to_element[256];
42 /* color of flashing the screen, gate opening to exit */
43 const GdColor gd_flash_color=0xFFFFC0;
44 /* selected object in editor */
45 const GdColor gd_select_color=0x8080FF;
49 /* direction to string and vice versa */
50 const char *
51 gd_direction_get_visible_name(GdDirection dir)
53 g_assert(dir>=0 && dir<G_N_ELEMENTS(direction_name));
54 return direction_name[dir];
57 const char *
58 gd_direction_get_filename(GdDirection dir)
60 g_assert(dir>=0 && dir<G_N_ELEMENTS(direction_filename));
61 return direction_filename[dir];
64 GdDirection
65 gd_direction_from_string(const char *str)
67 int i;
69 g_assert(str!=NULL);
70 for (i=1; i<G_N_ELEMENTS(direction_filename); i++)
71 if (g_ascii_strcasecmp(str, direction_filename[i])==0)
72 return (GdDirection) i;
74 g_warning("invalid direction name '%s', defaulting to down", str);
75 return MV_DOWN;
81 /* scheduling name to string and vice versa */
82 const char *
83 gd_scheduling_get_filename(GdScheduling sched)
85 g_assert(sched>=0 && sched<G_N_ELEMENTS(scheduling_filename));
86 return scheduling_filename[sched];
89 const char *
90 gd_scheduling_get_visible_name(GdScheduling sched)
92 g_assert(sched>=0 && sched<G_N_ELEMENTS(scheduling_name));
93 return scheduling_name[sched];
96 GdScheduling
97 gd_scheduling_from_string(const char *str)
99 int i;
101 g_assert(str!=NULL);
102 for (i=0; i<G_N_ELEMENTS(scheduling_filename); i++)
103 if (g_ascii_strcasecmp(str, scheduling_filename[i])==0)
104 return (GdScheduling) i;
106 g_warning("invalid scheduling name '%s', defaulting to plck", str);
107 return GD_SCHEDULING_PLCK;
123 fill a given struct with default properties.
124 "str" is the struct (data),
125 "properties" describes the structure and its pointers,
126 "defaults" are the pieces of data which will be copied to str.
128 void
129 gd_struct_set_defaults_from_array(gpointer str, const GdStructDescriptor *properties, GdPropertyDefault *defaults)
131 int i;
133 for (i=0; defaults[i].offset!=-1; i++) {
134 gpointer pvalue=G_STRUCT_MEMBER_P(str, defaults[i].offset);
135 int *ivalue=pvalue; /* these point to the same, but to avoid the awkward cast syntax */
136 GdElement *evalue=pvalue;
137 GdDirection *dvalue=pvalue;
138 GdScheduling *svalue=pvalue;
139 gboolean *bvalue=pvalue;
140 GdColor *cvalue=pvalue;
141 int j, n;
143 /* check which property we are talking about: find it in gd_cave_properties. */
144 n=defaults[i].property_index;
145 if (n==0) {
146 while(properties[n].identifier!=NULL && properties[n].offset!=defaults[i].offset)
147 n++;
148 /* make sure we found it. */
149 g_assert(properties[n].identifier!=NULL);
151 /* remember so we will be fast later*/
152 defaults[i].property_index=n;
155 /* some properties are arrays. this loop fills all with the same values */
156 for (j=0; j<properties[n].count; j++)
157 switch (properties[n].type) {
158 /* these are for the gui; do nothing */
159 case GD_TAB:
160 case GD_LABEL:
161 /* no default value for strings */
162 case GD_TYPE_STRING:
163 case GD_TYPE_LONGSTRING:
164 break;
166 case GD_TYPE_RATIO: /* this is also an integer, difference is only when saving to bdcff */
167 case GD_TYPE_INT:
168 if (defaults[i].defval<properties[n].min || defaults[i].defval>properties[n].max)
169 g_warning("integer property %s out of range", properties[n].identifier);
170 ivalue[j]=defaults[i].defval;
171 break;
172 case GD_TYPE_PROBABILITY: /* floats are stored as integer, /million; but are integers */
173 if (defaults[i].defval<0 || defaults[i].defval>1000000)
174 g_warning("integer property %s out of range", properties[n].identifier);
175 ivalue[j]=defaults[i].defval;
176 break;
177 case GD_TYPE_BOOLEAN:
178 bvalue[j]=defaults[i].defval!=0;
179 break;
180 case GD_TYPE_ELEMENT:
181 case GD_TYPE_EFFECT:
182 evalue[j]=(GdElement) defaults[i].defval;
183 break;
184 case GD_TYPE_COLOR:
185 cvalue[j]=gd_c64_color(defaults[i].defval);
186 break;
187 case GD_TYPE_DIRECTION:
188 dvalue[j]=(GdDirection) defaults[i].defval;
189 break;
190 case GD_TYPE_SCHEDULING:
191 svalue[j]=(GdScheduling) defaults[i].defval;
192 break;
204 /* creates the character->element conversion table; using
205 the fixed-in-the-bdcff characters. later, this table
206 may be filled with more elements.
208 void
209 gd_create_char_to_element_table()
211 int i;
213 /* fill all with unknown */
214 for (i=0; i<G_N_ELEMENTS(gd_char_to_element); i++)
215 gd_char_to_element[i]=O_UNKNOWN;
217 /* then set fixed characters */
218 for (i=0; i<O_MAX; i++) {
219 int c=gd_elements[i].character;
221 if (c) {
222 if (gd_char_to_element[c]!=O_UNKNOWN)
223 g_warning("Character %c already used for element %x", c, gd_char_to_element[c]);
224 gd_char_to_element[c]=i;
230 /* search the element database for the specified character, and return the element. */
231 GdElement
232 gd_get_element_from_character (guint8 character)
234 if (gd_char_to_element[character]!=O_UNKNOWN)
235 return gd_char_to_element[character];
237 g_warning ("Invalid character representing element: %c", character);
238 return O_UNKNOWN;
245 do some init; this function is to be called at the start of the application
247 void
248 gd_cave_init()
250 int i;
252 g_assert(MV_MAX==G_N_ELEMENTS(gd_dx));
253 g_assert(MV_MAX==G_N_ELEMENTS(gd_dy));
254 g_assert(GD_SCHEDULING_MAX==G_N_ELEMENTS(scheduling_filename));
255 g_assert(GD_SCHEDULING_MAX==G_N_ELEMENTS(scheduling_name));
257 /* put names to a hash table */
258 /* this is a helper for file read operations */
259 /* maps g_strdupped strings to elemenets (integers) */
260 name_to_element=g_hash_table_new_full(gd_str_case_hash, gd_str_case_equal, g_free, NULL);
262 for (i=0; i<O_MAX; i++) {
263 char *key;
264 g_assert(gd_elements[i].filename!=NULL && !g_str_equal(gd_elements[i].filename, ""));
266 key=g_ascii_strup(gd_elements[i].filename, -1);
267 if (g_hash_table_lookup_extended(name_to_element, key, NULL, NULL))
268 g_warning("Name %s already used for element %x", key, i);
269 g_hash_table_insert(name_to_element, key, GINT_TO_POINTER(i));
270 /* ^^^ do not free "key", as hash table needs it during the whole time! */
272 key=g_strdup_printf("SCANNED_%s", key); /* new string */
273 g_hash_table_insert(name_to_element, key, GINT_TO_POINTER(i));
274 /* once again, do not free "key" ^^^ */
276 /* for compatibility with tim stridmann's memorydump->bdcff converter... .... ... */
277 g_hash_table_insert(name_to_element, "HEXPANDING_WALL", GINT_TO_POINTER(O_H_EXPANDING_WALL));
278 g_hash_table_insert(name_to_element, "FALLING_DIAMOND", GINT_TO_POINTER(O_DIAMOND_F));
279 g_hash_table_insert(name_to_element, "FALLING_BOULDER", GINT_TO_POINTER(O_STONE_F));
280 g_hash_table_insert(name_to_element, "EXPLOSION1S", GINT_TO_POINTER(O_EXPLODE_1));
281 g_hash_table_insert(name_to_element, "EXPLOSION2S", GINT_TO_POINTER(O_EXPLODE_2));
282 g_hash_table_insert(name_to_element, "EXPLOSION3S", GINT_TO_POINTER(O_EXPLODE_3));
283 g_hash_table_insert(name_to_element, "EXPLOSION4S", GINT_TO_POINTER(O_EXPLODE_4));
284 g_hash_table_insert(name_to_element, "EXPLOSION5S", GINT_TO_POINTER(O_EXPLODE_5));
285 g_hash_table_insert(name_to_element, "EXPLOSION1D", GINT_TO_POINTER(O_PRE_DIA_1));
286 g_hash_table_insert(name_to_element, "EXPLOSION2D", GINT_TO_POINTER(O_PRE_DIA_2));
287 g_hash_table_insert(name_to_element, "EXPLOSION3D", GINT_TO_POINTER(O_PRE_DIA_3));
288 g_hash_table_insert(name_to_element, "EXPLOSION4D", GINT_TO_POINTER(O_PRE_DIA_4));
289 g_hash_table_insert(name_to_element, "EXPLOSION5D", GINT_TO_POINTER(O_PRE_DIA_5));
290 g_hash_table_insert(name_to_element, "WALL2", GINT_TO_POINTER(O_STEEL_EXPLODABLE));
291 /* compatibility with old bd-faq (pre disassembly of bladder) */
292 g_hash_table_insert(name_to_element, "BLADDERd9", GINT_TO_POINTER(O_BLADDER_8));
294 /* create table to show errors at the start of the application */
295 gd_create_char_to_element_table();
299 /* search the element database for the specified name, and return the element */
300 GdElement
301 gd_get_element_from_string (const char *string)
303 char *upper=g_ascii_strup(string, -1);
304 gpointer value;
305 gboolean found;
307 if (!string) {
308 g_warning("Invalid string representing element: (null)");
309 return O_UNKNOWN;
312 found=g_hash_table_lookup_extended(name_to_element, upper, NULL, &value);
313 g_free(upper);
314 if (found)
315 return (GdElement) (GPOINTER_TO_INT(value));
317 g_warning("Invalid string representing element: %s", string);
318 return O_UNKNOWN;
328 void
329 gd_cave_set_defaults_from_array(GdCave* cave, GdPropertyDefault *defaults)
331 gd_struct_set_defaults_from_array(cave, gd_cave_properties, defaults);
336 load default values from description array
337 these are default for gdash and bdcff.
339 void
340 gd_cave_set_gdash_defaults(GdCave* cave)
342 int i;
344 gd_cave_set_defaults_from_array(cave, gd_cave_defaults_gdash);
346 /* these did not fit into the descriptor array */
347 for (i=0; i<5; i++) {
348 cave->level_rand[i]=i;
349 cave->level_timevalue[i]=i+1;
358 /* for quicksort. compares two highscores. */
360 gd_highscore_compare(gconstpointer a, gconstpointer b)
362 const GdHighScore *ha=a;
363 const GdHighScore *hb=b;
364 return hb->score - ha->score;
367 void
368 gd_clear_highscore(GdHighScore *hs)
370 int i;
372 for (i=0; i<GD_HIGHSCORE_NUM; i++) {
373 strcpy(hs[i].name, "");
374 hs[i].score=0;
378 gboolean
379 gd_has_highscore(GdHighScore *hs)
381 return hs[0].score>0;
384 void
385 gd_cave_clear_highscore(GdCave *cave)
387 gd_clear_highscore(cave->highscore);
391 /* return true if score achieved is a highscore */
392 gboolean gd_is_highscore(GdHighScore *scores, int score)
394 /* if score is above zero AND bigger than the last one */
395 if (score>0 && score>scores[GD_HIGHSCORE_NUM-1].score)
396 return TRUE;
398 return FALSE;
402 gd_add_highscore(GdHighScore *highscores, const char *name, int score)
404 int i;
406 if (!gd_is_highscore(highscores, score))
407 return -1;
409 /* overwrite the last one */
410 gd_strcpy(highscores[GD_HIGHSCORE_NUM-1].name, name);
411 highscores[GD_HIGHSCORE_NUM-1].score=score;
412 /* and sort */
413 qsort(highscores, GD_HIGHSCORE_NUM, sizeof(GdHighScore), gd_highscore_compare);
415 for (i=0; i<GD_HIGHSCORE_NUM; i++)
416 if (g_str_equal(highscores[i].name, name) && highscores[i].score==score)
417 return i;
419 g_assert_not_reached();
420 return -1;
427 /* for the case-insensitive hash keys */
428 gboolean
429 gd_str_case_equal(gconstpointer s1, gconstpointer s2)
431 return g_ascii_strcasecmp(s1, s2)==0;
434 guint
435 gd_str_case_hash(gconstpointer v)
437 char *upper;
438 guint hash;
440 upper=g_ascii_strup(v, -1);
441 hash=g_str_hash(v);
442 g_free(upper);
443 return hash;
459 create new cave with default values.
460 sets every value, also default size, diamond value etc.
462 GdCave *
463 gd_cave_new(void)
465 int i;
466 GdCave *cave;
468 cave=g_new0(GdCave, 1);
470 /* hash table which stores unknown tags as strings. */
471 cave->tags=g_hash_table_new_full(gd_str_case_hash, gd_str_case_equal, g_free, g_free);
472 /* for strings */
473 for (i=0; gd_cave_properties[i].identifier!=NULL; i++)
474 if (gd_cave_properties[i].type==GD_TYPE_LONGSTRING)
475 G_STRUCT_MEMBER(GString *, cave, gd_cave_properties[i].offset)=g_string_new(NULL);
478 gd_cave_set_gdash_defaults (cave);
480 return cave;
483 /* cave maps.
484 cave maps are continuous areas in memory. the allocated memory
485 is width*height*bytes_per_cell long.
486 the cave map[0] stores the pointer given by g_malloc().
487 the map itself is also an allocated array of pointers to the
488 beginning of rows.
489 therefore:
490 rows=new (pointers to rows);
491 rows[0]=new map
492 rows[1..h-1]=rows[0]+width*bytes
494 freeing this:
495 free(rows[0])
496 free(rows)
500 allocate a cave map-like array, and initialize to zero.
501 one cell is cell_size bytes long.
503 gpointer
504 gd_cave_map_new_for_cave(const GdCave *cave, const int cell_size)
506 gpointer *rows; /* this is void**, pointer to array of ... */
507 int y;
509 rows=g_new(gpointer, cave->h);
510 rows[0]=g_malloc0 (cell_size*cave->w*cave->h);
511 for (y=1; y<cave->h; y++)
512 /* base pointer+num_of_bytes_per_element*width*number_of_row; as sizeof(char)=1 */
513 rows[y]=(char *)rows[0]+cell_size*cave->w*y;
514 return rows;
518 duplicate map
520 if map is null, this also returns null.
522 gpointer
523 gd_cave_map_dup_size(const GdCave *cave, const gpointer map, const int cell_size)
525 gpointer *rows;
526 gpointer *maplines=(gpointer *)map;
527 int y;
529 if (!map)
530 return NULL;
532 rows=g_new (gpointer, cave->h);
533 rows[0]=g_memdup (maplines[0], cell_size * cave->w * cave->h);
535 for (y=1; y < cave->h; y++)
536 rows[y]=(char *)rows[0]+cell_size*cave->w*y;
538 return rows;
541 void
542 gd_cave_map_free(gpointer map)
544 gpointer *maplines=(gpointer *) map;
546 if (!map)
547 return;
549 g_free(maplines[0]);
550 g_free(map);
560 frees memory associated to cave
562 void
563 gd_cave_free(GdCave *cave)
565 int i;
567 if (!cave)
568 return;
570 if (cave->tags)
571 g_hash_table_destroy(cave->tags);
573 if (cave->random) /* random generator is a GRand * */
574 g_rand_free(cave->random);
576 /* free GStrings */
577 for (i=0; gd_cave_properties[i].identifier!=NULL; i++)
578 if (gd_cave_properties[i].type==GD_TYPE_LONGSTRING)
579 g_string_free(G_STRUCT_MEMBER(GString *, cave, gd_cave_properties[i].offset), TRUE);
581 /* map */
582 gd_cave_map_free(cave->map);
583 /* rendered data */
584 gd_cave_map_free(cave->objects_order);
585 /* hammered walls to reappear data */
586 gd_cave_map_free(cave->hammered_reappear);
587 /* free objects */
588 g_list_foreach(cave->objects, (GFunc) g_free, NULL);
589 g_list_free (cave->objects);
590 /* free replays */
591 g_list_foreach(cave->replays, (GFunc) gd_replay_free, NULL);
592 g_list_free(cave->replays);
594 /* freeing main pointer */
595 g_free (cave);
598 static void
599 hash_copy_foreach(const char *key, const char *value, GHashTable *dest)
601 g_hash_table_insert(dest, g_strdup(key), g_strdup(value));
604 /* copy cave from src to destination, with duplicating dynamically allocated data */
605 void
606 gd_cave_copy(GdCave *dest, const GdCave *src)
608 int i;
610 /* copy entire data */
611 g_memmove(dest, src, sizeof(GdCave));
613 /* but duplicate dynamic data */
614 dest->tags=g_hash_table_new_full(gd_str_case_hash, gd_str_case_equal, g_free, g_free);
615 if (src->tags)
616 g_hash_table_foreach(src->tags, (GHFunc) hash_copy_foreach, dest->tags);
617 dest->map=gd_cave_map_dup(src, map);
618 dest->hammered_reappear=gd_cave_map_dup(src, hammered_reappear);
620 /* for longstrings */
621 for (i=0; gd_cave_properties[i].identifier!=NULL; i++)
622 if (gd_cave_properties[i].type==GD_TYPE_LONGSTRING)
623 G_STRUCT_MEMBER(GString *, dest, gd_cave_properties[i].offset)=g_string_new(G_STRUCT_MEMBER(GString *, src, gd_cave_properties[i].offset)->str);
625 /* no reason to copy this */
626 dest->objects_order=NULL;
628 /* copy objects list */
629 if (src->objects) {
630 GList *iter;
632 dest->objects=NULL; /* new empty list */
633 for (iter=src->objects; iter!=NULL; iter=iter->next) /* do a deep copy */
634 dest->objects=g_list_append(dest->objects, g_memdup (iter->data, sizeof (GdObject)));
637 /* copy replays */
638 if (src->replays) {
639 GList *iter;
641 dest->replays=NULL;
642 for (iter=src->replays; iter!=NULL; iter=iter->next) /* do a deep copy */
643 dest->replays=g_list_append(dest->replays, gd_replay_new_from_replay(iter->data));
646 /* copy random number generator */
647 if (src->random)
648 dest->random=g_rand_copy(src->random);
651 /* create new cave, which is a copy of the cave given. */
652 GdCave *
653 gd_cave_new_from_cave(const GdCave *orig)
655 GdCave *cave;
657 cave=gd_cave_new();
658 gd_cave_copy(cave, orig);
660 return cave;
665 Put an object to the specified position.
666 Performs range checking.
667 If wraparound objects are selected, wraps around x coordinates, with or without lineshift.
668 (The y coordinate is not wrapped, as it did not work like that on the c64)
669 order is a pointer to the GdObject describing this object. Thus the editor can identify which cell was created by which object.
671 void
672 gd_cave_store_rc(GdCave *cave, int x, int y, const GdElement element, const void* order)
674 /* if we do not need to draw, exit now */
675 if (element==O_NONE)
676 return;
678 /* check bounds */
679 if (cave->wraparound_objects) {
680 if (cave->lineshift) {
681 /* fit x coordinate within range, with correcting y at the same time */
682 while (x<0) {
683 x+=cave->w; /* out of bounds on the left... */
684 y--; /* previous row */
686 while (x>=cave->w) {
687 x-=cave->w;
688 y++;
690 /* lineshifting does not fix the y coordinates. if out of bounds, element will not be displayed. */
691 /* if such an object appeared in the c64 game, well, it was a buffer overrun. */
692 } else {
693 /* non lineshifting: changing x does not change y coordinate. */
694 while (x<0)
695 x+=cave->w;
696 while (x>=cave->w)
697 x-=cave->w;
698 /* after that, fix y coordinate */
699 while (y<0)
700 y+=cave->h;
701 while (y>=cave->h)
702 y-=cave->h;
706 /* if the above wraparound code fixed the coordinates, this will always be true. */
707 /* but see the above comment for lineshifting y coordinate */
708 if (x>=0 && x<cave->w && y>=0 && y<cave->h) {
709 cave->map[y][x]=element;
710 cave->objects_order[y][x]=(void *)order;
714 GdElement
715 gd_cave_get_rc(const GdCave *cave, int x, int y)
717 /* always fix coordinates as if cave was wraparound. */
719 /* fix x coordinate */
720 if (cave->lineshift) {
721 /* fit x coordinate within range, with correcting y at the same time */
722 while (x<0) {
723 x+=cave->w; /* out of bounds on the left... */
724 y--; /* previous row */
726 while (x>=cave->w) {
727 x-=cave->w;
728 y++;
730 } else {
731 /* non lineshifting: changing x does not change y coordinate. */
732 while (x<0)
733 x+=cave->w;
734 while (x>=cave->w)
735 x-=cave->w;
737 /* after that, fix y coordinate */
738 while (y<0)
739 y+=cave->h;
740 while (y>=cave->h)
741 y-=cave->h;
743 return cave->map[y][x];
753 unsigned int
754 gd_c64_random(GdC64RandomGenerator *rand)
756 unsigned int temp_rand_1, temp_rand_2, carry, result;
758 temp_rand_1=(rand->rand_seed_1&0x0001) << 7;
759 temp_rand_2=(rand->rand_seed_2 >> 1)&0x007F;
760 result=(rand->rand_seed_2)+((rand->rand_seed_2&0x0001) << 7);
761 carry=(result >> 8);
762 result=result&0x00FF;
763 result=result+carry+0x13;
764 carry=(result >> 8);
765 rand->rand_seed_2=result&0x00FF;
766 result=rand->rand_seed_1+carry+temp_rand_1;
767 carry=(result >> 8);
768 result=result&0x00FF;
769 result=result+carry+temp_rand_2;
770 rand->rand_seed_1=result&0x00FF;
772 return rand->rand_seed_1;
777 C64 BD predictable random number generator.
778 Used to load the original caves imported from c64 files.
779 Also by the predictable slime.
781 unsigned int
782 gd_cave_c64_random(GdCave *cave)
784 return gd_c64_random(&cave->c64_rand);
787 void
788 gd_c64_random_set_seed(GdC64RandomGenerator *rand, int seed1, int seed2)
790 rand->rand_seed_1=seed1;
791 rand->rand_seed_2=seed2;
794 void
795 gd_cave_c64_random_set_seed(GdCave *cave, int seed1, int seed2)
797 gd_c64_random_set_seed(&cave->c64_rand, seed1, seed2);
809 select random colors for a given cave.
810 this function will select colors so that they should look somewhat nice; for example
811 brick walls won't be the darkest color, for example.
813 static inline void
814 swap(int *i1, int *i2)
816 int t=*i1;
817 *i1=*i2;
818 *i2=t;
821 void
822 gd_cave_set_random_c64_colors(GdCave *cave)
824 const int bright_colors[]={1, 3, 7};
825 const int dark_colors[]={2, 6, 8, 9, 11};
827 /* always black */
828 cave->colorb=gd_c64_color(0);
829 cave->color0=gd_c64_color(0);
830 /* choose some bright color for brick */
831 cave->color3=gd_c64_color(bright_colors[g_random_int_range(0, G_N_ELEMENTS(bright_colors))]);
832 /* choose a dark color for dirt, but should not be == color of brick */
833 do {
834 cave->color1=gd_c64_color(dark_colors[g_random_int_range(0, G_N_ELEMENTS(dark_colors))]);
835 } while (cave->color1==cave->color3); /* so it is not the same as color 1 */
836 /* choose any but black for steel wall, but should not be == brick or dirt */
837 do {
838 /* between 1 and 15 - do not use black for this. */
839 cave->color2=gd_c64_color(g_random_int_range(1, 16));
840 } while (cave->color1==cave->color2 || cave->color2==cave->color3); /* so colors are not the same */
841 /* copy amoeba and slime color */
842 cave->color4=cave->color3;
843 cave->color5=cave->color1;
846 static void
847 cave_set_random_indexed_colors(GdCave *cave, GdColor (*color_indexer_func) (int, int))
849 int hue=g_random_int_range(0, 15);
850 int hue_spread=g_random_int_range(1, 6); /* 1..5 */
851 /* we only use 0..6, as saturation 15 is too bright (almost always white) */
852 /* also, saturation 0..1..2 is too dark. the color0=black is there for dark. */
853 int bri_spread=6-hue_spread; /* so this is also 1..5. when hue spread is low, brightness spread is high */
854 int bri1=8, bri2=8-bri_spread, bri3=8+bri_spread;
855 /* there are 15 valid choices for hue, so we do a %15 */
856 int col1=hue, col2=(hue+hue_spread+15)%15, col3=(hue-hue_spread+15)%15;
858 /* this makes up a random color, and selects a color triad by hue+5 and hue+10. */
859 /* also creates a random saturation. */
860 /* color of brick is 8+sat, so it is always a bright color. */
861 /* another two are 8-sat and 8. */
862 /* order of colors is also changed randomly. */
864 if (g_random_boolean())
865 swap(&bri1, &bri2);
866 /* we do not touch bri3 (8+sat), as it should be a bright color */
867 if (g_random_boolean())
868 swap(&col1, &col2);
869 if (g_random_boolean())
870 swap(&col2, &col3);
871 if (g_random_boolean())
872 swap(&col1, &col3);
874 cave->colorb=color_indexer_func(0, 0);
875 cave->color0=color_indexer_func(0, 0);
876 cave->color1=color_indexer_func(col1+1, bri1);
877 cave->color2=color_indexer_func(col2+1, bri2);
878 cave->color3=color_indexer_func(col3+1, bri3);
879 /* amoeba and slime are different */
880 cave->color4=color_indexer_func(g_random_int_range(11, 13), g_random_int_range(6, 12)); /* some green thing */
881 cave->color5=color_indexer_func(g_random_int_range(7, 10), g_random_int_range(0, 6)); /* some blueish thing */
884 void
885 gd_cave_set_random_atari_colors(GdCave *cave)
887 cave_set_random_indexed_colors(cave, gd_atari_color_huesat);
890 void
891 gd_cave_set_random_c64dtv_colors(GdCave *cave)
893 cave_set_random_indexed_colors(cave, gd_c64dtv_color_huesat);
896 static inline void
897 swapd(double *i1, double *i2)
899 double t=*i1;
900 *i1=*i2;
901 *i2=t;
904 void
905 gd_cave_set_random_rgb_colors(GdCave *cave)
907 const double hue_max=10.0/30.0;
908 double hue=g_random_double(); /* any hue allowed */
909 double hue_spread=g_random_double_range(2.0/30.0, hue_max); /* hue 360 degress=1. hue spread is min. 24 degrees, max 120 degrees (1/3) */
910 double h1=hue, h2=hue+hue_spread, h3=hue+2*hue_spread;
911 double v1, v2, v3;
912 double s1, s2, s3;
914 if (g_random_boolean()) {
915 /* when hue spread is low, brightness(saturation) spread is high */
916 /* 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 */
917 double spread=0.1+0.3*(1-hue_spread/hue_max);
918 v1=0.6; /* brightness variation, too */
919 v2=0.7;
920 v3=0.8;
921 s1=0.5; /* saturation is different */
922 s2=0.5-spread;
923 s3=0.5+spread;
924 } else {
925 /* when hue spread is low, brightness(saturation) spread is high */
926 /* 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 */
927 double spread=0.1+0.15*(1-hue_spread/hue_max);
928 v1=0.5; /* brightness is different */
929 v2=0.5+spread;
930 v3=0.5+2*spread;
931 s1=0.7; /* saturation is same - a not fully saturated one */
932 s2=0.8;
933 s3=0.9;
935 /* randomly change values, but do not touch v3, as cave->color3 should be a bright color */
936 if (g_random_boolean()) swapd(&v1, &v2);
937 /* randomly change hues and saturations */
938 if (g_random_boolean()) swapd(&h1, &h2);
939 if (g_random_boolean()) swapd(&h2, &h3);
940 if (g_random_boolean()) swapd(&h1, &h3);
941 if (g_random_boolean()) swapd(&s1, &s2);
942 if (g_random_boolean()) swapd(&s2, &s3);
943 if (g_random_boolean()) swapd(&s1, &s3);
945 h1=h1*360.0;
946 h2=h2*360.0;
947 h3=h3*360.0;
949 cave->colorb=gd_color_get_from_hsv(0,0,0);
950 cave->color0=gd_color_get_from_hsv(0,0,0); /* black for background */
951 cave->color1=gd_color_get_from_hsv(h1,s1,v1); /* dirt */
952 cave->color2=gd_color_get_from_hsv(h2,s2,v2); /* steel */
953 cave->color3=gd_color_get_from_hsv(h3,s3,v3); /* brick */
954 cave->color4=gd_color_get_from_hsv(g_random_int_range(100, 140),s2,v2); /* green(120+-20) with the saturation and brightness of brick */
955 cave->color5=gd_color_get_from_hsv(g_random_int_range(220, 260),s1,v1); /* blue(240+-20) with saturation and brightness of dirt */
959 void
960 gd_cave_set_random_colors(GdCave *cave, GdColorType type)
962 switch (type) {
963 case GD_COLOR_TYPE_RGB:
964 gd_cave_set_random_rgb_colors(cave);
965 break;
966 case GD_COLOR_TYPE_C64:
967 gd_cave_set_random_c64_colors(cave);
968 break;
969 case GD_COLOR_TYPE_C64DTV:
970 gd_cave_set_random_c64dtv_colors(cave);
971 break;
972 case GD_COLOR_TYPE_ATARI:
973 gd_cave_set_random_atari_colors(cave);
974 break;
976 default:
977 g_assert_not_reached();
984 shrink cave
985 if last line or last row is just steel wall (or (invisible) outbox).
986 used after loading a game for playing.
987 after this, ew and eh will contain the effective width and height.
989 void
990 gd_cave_auto_shrink(GdCave *cave)
993 int x, y;
994 enum {
995 STEEL_ONLY,
996 STEEL_OR_OTHER,
997 NO_SHRINK
998 } empty;
1000 /* set to maximum size, then try to shrink */
1001 cave->x1=0; cave->y1=0;
1002 cave->x2=cave->w-1; cave->y2=cave->h-1;
1004 /* search for empty, steel-wall-only last rows. */
1005 /* clear all lines, which are only steel wall.
1006 * and clear only one line, which is steel wall, but also has a player or an outbox. */
1007 empty=STEEL_ONLY;
1008 do {
1009 for (y=cave->y2-1; y <= cave->y2; y++)
1010 for (x=cave->x1; x <= cave->x2; x++)
1011 switch (gd_cave_get_rc (cave, x, y)) {
1012 case O_STEEL: /* if steels only, this is to be deleted. */
1013 break;
1014 case O_PRE_OUTBOX:
1015 case O_PRE_INVIS_OUTBOX:
1016 case O_INBOX:
1017 if (empty==STEEL_OR_OTHER)
1018 empty=NO_SHRINK;
1019 if (empty==STEEL_ONLY) /* if this, delete only this one, and exit. */
1020 empty=STEEL_OR_OTHER;
1021 break;
1022 default: /* anything else, that should be left in the cave. */
1023 empty=NO_SHRINK;
1024 break;
1026 if (empty!=NO_SHRINK) /* shrink if full steel or steel and player/outbox. */
1027 cave->y2--; /* one row shorter */
1029 while (empty==STEEL_ONLY); /* if found just steels, repeat. */
1031 /* search for empty, steel-wall-only first rows. */
1032 empty=STEEL_ONLY;
1033 do {
1034 for (y=cave->y1; y <= cave->y1+1; y++)
1035 for (x=cave->x1; x <= cave->x2; x++)
1036 switch (gd_cave_get_rc (cave, x, y)) {
1037 case O_STEEL:
1038 break;
1039 case O_PRE_OUTBOX:
1040 case O_PRE_INVIS_OUTBOX:
1041 case O_INBOX:
1042 /* shrink only lines, which have only ONE player or outbox. this is for bd4 intermission 2, for example. */
1043 if (empty==STEEL_OR_OTHER)
1044 empty=NO_SHRINK;
1045 if (empty==STEEL_ONLY)
1046 empty=STEEL_OR_OTHER;
1047 break;
1048 default:
1049 empty=NO_SHRINK;
1050 break;
1052 if (empty!=NO_SHRINK)
1053 cave->y1++;
1055 while (empty==STEEL_ONLY); /* if found one, repeat. */
1057 /* empty last columns. */
1058 empty=STEEL_ONLY;
1059 do {
1060 for (y=cave->y1; y <= cave->y2; y++)
1061 for (x=cave->x2-1; x <= cave->x2; x++)
1062 switch (gd_cave_get_rc (cave, x, y)) {
1063 case O_STEEL:
1064 break;
1065 case O_PRE_OUTBOX:
1066 case O_PRE_INVIS_OUTBOX:
1067 case O_INBOX:
1068 if (empty==STEEL_OR_OTHER)
1069 empty=NO_SHRINK;
1070 if (empty==STEEL_ONLY)
1071 empty=STEEL_OR_OTHER;
1072 break;
1073 default:
1074 empty=NO_SHRINK;
1075 break;
1077 if (empty!=NO_SHRINK)
1078 cave->x2--; /* just remember that one column shorter. g_free will know the size of memchunk, no need to realloc! */
1080 while (empty==STEEL_ONLY); /* if found one, repeat. */
1082 /* empty first columns. */
1083 empty=STEEL_ONLY;
1084 do {
1085 for (y=cave->y1; y <= cave->y2; y++)
1086 for (x=cave->x1; x <= cave->x1+1; x++)
1087 switch (gd_cave_get_rc (cave, x, y)) {
1088 case O_STEEL:
1089 break;
1090 case O_PRE_OUTBOX:
1091 case O_PRE_INVIS_OUTBOX:
1092 case O_INBOX:
1093 if (empty==STEEL_OR_OTHER)
1094 empty=NO_SHRINK;
1095 if (empty==STEEL_ONLY)
1096 empty=STEEL_OR_OTHER;
1097 break;
1098 default:
1099 empty=NO_SHRINK;
1100 break;
1102 if (empty!=NO_SHRINK)
1103 cave->x1++;
1105 while (empty==STEEL_ONLY); /* if found one, repeat. */
1108 /* check if cave visible part coordinates
1109 are outside cave sizes, or not in the right order.
1110 correct them if needed.
1112 void
1113 gd_cave_correct_visible_size(GdCave *cave)
1115 g_assert(cave!=NULL);
1117 /* change visible coordinates if they do not point to upperleft and lowerright */
1118 if (cave->x2<cave->x1) {
1119 int t=cave->x2;
1120 cave->x2=cave->x1;
1121 cave->x1=t;
1123 if (cave->y2<cave->y1) {
1124 int t=cave->y2;
1125 cave->y2=cave->y1;
1126 cave->y1=t;
1128 if (cave->x1<0)
1129 cave->x1=0;
1130 if (cave->y1<0)
1131 cave->y1=0;
1132 if (cave->x2>cave->w-1)
1133 cave->x2=cave->w-1;
1134 if (cave->y2>cave->h-1)
1135 cave->y2=cave->h-1;
1150 bd1 and similar engines had animation bits in cave data, to set which elements to animate (firefly, butterfly, amoeba).
1151 animating an element also caused some delay each frame; according to my measurements, around 2.6 ms/element.
1153 static void
1154 cave_set_ckdelay_extra_for_animation(GdCave *cave)
1156 int x, y;
1157 gboolean has_amoeba=FALSE, has_firefly=FALSE, has_butterfly=FALSE, has_slime=FALSE;
1158 g_assert(cave->map!=NULL);
1160 for (y=0; y<cave->h; y++)
1161 for (x=0; x<cave->w; x++) {
1162 switch (cave->map[y][x]&~SCANNED) {
1163 case O_FIREFLY_1:
1164 case O_FIREFLY_2:
1165 case O_FIREFLY_3:
1166 case O_FIREFLY_4:
1167 has_firefly=TRUE;
1168 break;
1169 case O_BUTTER_1:
1170 case O_BUTTER_2:
1171 case O_BUTTER_3:
1172 case O_BUTTER_4:
1173 has_butterfly=TRUE;
1174 break;
1175 case O_AMOEBA:
1176 has_amoeba=TRUE;
1177 break;
1178 case O_SLIME:
1179 has_slime=TRUE;
1180 break;
1183 cave->ckdelay_extra_for_animation=0;
1184 if (has_amoeba)
1185 cave->ckdelay_extra_for_animation+=2600;
1186 if (has_firefly)
1187 cave->ckdelay_extra_for_animation+=2600;
1188 if (has_butterfly)
1189 cave->ckdelay_extra_for_animation+=2600;
1190 if (has_amoeba)
1191 cave->ckdelay_extra_for_animation+=2600;
1195 /* do some init - setup some cave variables before the game. */
1196 void
1197 gd_cave_setup_for_game(GdCave *cave)
1199 int x, y;
1201 cave_set_ckdelay_extra_for_animation(cave);
1203 /* find the player which will be the one to scroll to at the beginning of the game (before the player's birth) */
1204 if (cave->active_is_first_found) {
1205 /* uppermost player is active */
1206 for (y=cave->h-1; y>=0; y--)
1207 for (x=cave->w-1; x>=0; x--)
1208 if (cave->map[y][x]==O_INBOX) {
1209 cave->player_x=x;
1210 cave->player_y=y;
1212 } else {
1213 /* lowermost player is active */
1214 for (y=0; y<cave->h; y++)
1215 for (x=0; x<cave->w; x++)
1216 if (cave->map[y][x]==O_INBOX) {
1217 cave->player_x=x;
1218 cave->player_y=y;
1222 /* select number of milliseconds (for pal and ntsc) */
1223 cave->timing_factor=cave->pal_timing?1200:1000;
1224 cave->time*=cave->timing_factor;
1225 cave->magic_wall_time*=cave->timing_factor;
1226 cave->amoeba_time*=cave->timing_factor;
1227 cave->amoeba_2_time*=cave->timing_factor;
1228 cave->hatching_delay_time*=cave->timing_factor;
1229 if (cave->hammered_walls_reappear)
1230 cave->hammered_reappear=gd_cave_map_new(cave, int);
1233 /* cave diamonds needed can be set to n<=0. */
1234 /* if so, count the diamonds at the time of the hatching, and decrement that value from */
1235 /* the number of diamonds found. */
1236 /* of course, this function is to be called from the cave engine, at the exact time of hatching. */
1237 void
1238 gd_cave_count_diamonds(GdCave *cave)
1240 int x, y;
1242 /* if automatically counting diamonds. if this was negative,
1243 * the sum will be this less than the number of all the diamonds in the cave */
1244 if (cave->diamonds_needed<=0) {
1245 for (y=0; y<cave->h; y++)
1246 for (x=0; x<cave->w; x++)
1247 if (cave->map[y][x]==O_DIAMOND)
1248 cave->diamonds_needed++;
1249 if (cave->diamonds_needed<0)
1250 /* if still below zero, let this be 0, so gate will be open immediately */
1251 cave->diamonds_needed=0;
1262 /* takes a cave and a gfx buffer, and fills the buffer with cell indexes.
1263 the indexes might change if bonus life flash is active (small lines in "SPACE" cells),
1264 for the paused state (which is used in gdash but not in sdash) - yellowish color.
1265 also one can select the animation frame (0..7) to draw the cave on. so the caller manages
1266 increasing that.
1268 if a cell is changed, it is flagged with GD_REDRAW; the flag can be cleared by the caller.
1270 void
1271 gd_drawcave_game(const GdCave *cave, int **gfx_buffer, gboolean bonus_life_flash, gboolean yellowish_color, int animcycle, gboolean hate_invisible_outbox)
1273 static int player_blinking=0;
1274 static int player_tapping=0;
1275 int elemdrawing[O_MAX];
1276 int x, y, draw;
1278 g_assert(cave!=NULL);
1279 g_assert(cave->map!=NULL);
1280 g_assert(gfx_buffer!=NULL);
1281 g_assert(animcycle>=0 && animcycle<=7);
1283 if (cave->last_direction) { /* he is moving, so stop blinking and tapping. */
1284 player_blinking=0;
1285 player_tapping=0;
1287 else { /* he is idle, so animations can be done. */
1288 if (animcycle==0) { /* blinking and tapping is started at the beginning of animation sequences. */
1289 player_blinking=g_random_int_range(0, 4)==0; /* 1/4 chance of blinking, every sequence. */
1290 if (g_random_int_range(0, 16)==0) /* 1/16 chance of starting or stopping tapping. */
1291 player_tapping=!player_tapping;
1295 for (x=0; x<O_MAX; x++)
1296 elemdrawing[x]=gd_elements[x].image_game;
1297 if (bonus_life_flash)
1298 elemdrawing[O_SPACE]=gd_elements[O_FAKE_BONUS].image_game;
1299 elemdrawing[O_MAGIC_WALL]=gd_elements[cave->magic_wall_state == GD_MW_ACTIVE ? O_MAGIC_WALL : O_BRICK].image_game;
1300 elemdrawing[O_CREATURE_SWITCH]=gd_elements[cave->creatures_backwards ? O_CREATURE_SWITCH_ON : O_CREATURE_SWITCH].image_game;
1301 elemdrawing[O_EXPANDING_WALL_SWITCH]=gd_elements[cave->expanding_wall_changed ? O_EXPANDING_WALL_SWITCH_VERT : O_EXPANDING_WALL_SWITCH_HORIZ].image_game;
1302 elemdrawing[O_GRAVITY_SWITCH]=gd_elements[cave->gravity_switch_active?O_GRAVITY_SWITCH_ACTIVE:O_GRAVITY_SWITCH].image_game;
1303 elemdrawing[O_REPLICATOR_SWITCH]=gd_elements[cave->replicators_active?O_REPLICATOR_SWITCH_ON:O_REPLICATOR_SWITCH_OFF].image_game;
1304 if (!cave->replicators_active)
1305 /* if the replicators are inactive, do not animate them. */
1306 elemdrawing[O_REPLICATOR]=ABS(elemdrawing[O_REPLICATOR]);
1307 elemdrawing[O_CONVEYOR_SWITCH]=gd_elements[cave->conveyor_belts_active?O_CONVEYOR_SWITCH_ON:O_CONVEYOR_SWITCH_OFF].image_game;
1308 if (cave->conveyor_belts_direction_changed) {
1309 /* if direction is changed, animation is changed. */
1310 int temp;
1312 temp=elemdrawing[O_CONVEYOR_LEFT];
1313 elemdrawing[O_CONVEYOR_LEFT]=elemdrawing[O_CONVEYOR_RIGHT];
1314 elemdrawing[O_CONVEYOR_RIGHT]=temp;
1316 elemdrawing[O_CONVEYOR_DIR_SWITCH]=gd_elements[O_CONVEYOR_DIR_CHANGED].image_game;
1318 else
1319 elemdrawing[O_CONVEYOR_DIR_SWITCH]=gd_elements[O_CONVEYOR_DIR_NORMAL].image_game;
1320 if (!cave->conveyor_belts_active) {
1321 /* if they are not running, do not animate them. */
1322 elemdrawing[O_CONVEYOR_LEFT]=ABS(elemdrawing[O_CONVEYOR_LEFT]);
1323 elemdrawing[O_CONVEYOR_RIGHT]=ABS(elemdrawing[O_CONVEYOR_RIGHT]);
1325 if (animcycle&2) {
1326 elemdrawing[O_PNEUMATIC_ACTIVE_LEFT]+=2; /* also a hack, like biter_switch */
1327 elemdrawing[O_PNEUMATIC_ACTIVE_RIGHT]+=2;
1328 elemdrawing[O_PLAYER_PNEUMATIC_LEFT]+=2;
1329 elemdrawing[O_PLAYER_PNEUMATIC_RIGHT]+=2;
1332 if ((cave->last_direction) == MV_STILL) { /* player is idle. */
1333 if (player_blinking && player_tapping)
1334 draw=gd_elements[O_PLAYER_TAP_BLINK].image_game;
1335 else if (player_blinking)
1336 draw=gd_elements[O_PLAYER_BLINK].image_game;
1337 else if (player_tapping)
1338 draw=gd_elements[O_PLAYER_TAP].image_game;
1339 else
1340 draw=gd_elements[O_PLAYER].image_game;
1342 else if (cave->last_horizontal_direction == MV_LEFT)
1343 draw=gd_elements[O_PLAYER_LEFT].image_game;
1344 else
1345 /* of course this is MV_RIGHT. */
1346 draw=gd_elements[O_PLAYER_RIGHT].image_game;
1347 elemdrawing[O_PLAYER]=draw;
1348 elemdrawing[O_PLAYER_GLUED]=draw;
1349 /* player with bomb does not blink or tap - no graphics drawn for that. running is drawn using w/o bomb cells */
1350 if (cave->last_direction!=MV_STILL)
1351 elemdrawing[O_PLAYER_BOMB]=draw;
1352 elemdrawing[O_INBOX]=gd_elements[cave->inbox_flash_toggle ? O_OUTBOX_OPEN : O_OUTBOX_CLOSED].image_game;
1353 elemdrawing[O_OUTBOX]=gd_elements[cave->inbox_flash_toggle ? O_OUTBOX_OPEN : O_OUTBOX_CLOSED].image_game;
1354 elemdrawing[O_BITER_SWITCH]=gd_elements[O_BITER_SWITCH].image_game+cave->biter_delay_frame; /* hack, not fit into gd_elements */
1355 /* visual effects */
1356 elemdrawing[O_DIRT]=elemdrawing[cave->dirt_looks_like];
1357 elemdrawing[O_EXPANDING_WALL]=elemdrawing[cave->expanding_wall_looks_like];
1358 elemdrawing[O_V_EXPANDING_WALL]=elemdrawing[cave->expanding_wall_looks_like];
1359 elemdrawing[O_H_EXPANDING_WALL]=elemdrawing[cave->expanding_wall_looks_like];
1360 elemdrawing[O_AMOEBA_2]=elemdrawing[cave->amoeba_2_looks_like];
1362 /* change only graphically */
1363 if (hate_invisible_outbox) {
1364 elemdrawing[O_PRE_INVIS_OUTBOX]=elemdrawing[O_PRE_OUTBOX];
1365 elemdrawing[O_INVIS_OUTBOX]=elemdrawing[O_OUTBOX];
1368 for (y=cave->y1; y<=cave->y2; y++) {
1369 for (x=cave->x1; x<=cave->x2; x++) {
1370 GdElement actual=cave->map[y][x];
1372 /* if covered, real element is not important */
1373 if (actual & COVERED)
1374 draw=gd_elements[O_COVERED].image_game;
1375 else
1376 draw=elemdrawing[actual];
1378 /* if negative, animated. */
1379 if (draw<0)
1380 draw=-draw+animcycle;
1381 /* flash */
1382 if (cave->gate_open_flash || yellowish_color)
1383 draw+=NUM_OF_CELLS;
1385 /* set to buffer, with caching */
1386 if (gfx_buffer[y][x]!=draw)
1387 gfx_buffer[y][x]=draw | GD_REDRAW;
1396 /* cave time is rounded _UP_ to seconds. so at the exact moment when it changes from
1397 2sec remaining to 1sec remaining, the player has exactly one second. when it changes
1398 to zero, it is the exact moment of timeout. */
1399 /* internal time is milliseconds (or 1200 milliseconds for pal timing). */
1401 gd_cave_time_show(const GdCave *cave, int internal_time)
1403 return (internal_time+cave->timing_factor-1)/cave->timing_factor;
1415 GdReplay *
1416 gd_replay_new()
1418 GdReplay *rep;
1420 rep=g_new0(GdReplay, 1);
1421 /* create dynamic objects */
1422 rep->comment=g_string_new(NULL);
1423 rep->movements=g_byte_array_new();
1424 return rep;
1427 GdReplay *
1428 gd_replay_new_from_replay(GdReplay *orig)
1430 GdReplay *rep;
1432 rep=g_memdup(orig, sizeof(GdReplay));
1433 /* replicate dynamic data */
1434 rep->comment=g_string_new(orig->comment->str);
1435 rep->movements=g_byte_array_new();
1436 g_byte_array_append(rep->movements, orig->movements->data, orig->movements->len);
1438 return rep;
1441 void
1442 gd_replay_free(GdReplay *replay)
1444 g_byte_array_free(replay->movements, TRUE);
1445 g_string_free(replay->comment, TRUE);
1446 g_free(replay);
1449 enum {
1450 REPLAY_MOVE_MASK=0x0f,
1451 REPLAY_FIRE_MASK=0x10,
1452 REPLAY_SUICIDE_MASK=0x20,
1455 /* store movement in a replay */
1456 void
1457 gd_replay_store_movement(GdReplay *replay, GdDirection player_move, gboolean player_fire, gboolean suicide)
1459 guint8 data[1];
1461 g_assert(player_move==(player_move & REPLAY_MOVE_MASK));
1462 data[0]=(player_move)|(player_fire?REPLAY_FIRE_MASK:0)|(suicide?REPLAY_SUICIDE_MASK:0);
1464 g_byte_array_append(replay->movements, data, 1);
1467 /* get next available movement from a replay; store variables to player_move, player_fire, suicide */
1468 /* return true if successful */
1469 gboolean
1470 gd_replay_get_next_movement(GdReplay *replay, GdDirection *player_move, gboolean *player_fire, gboolean *suicide)
1472 guint8 data;
1474 /* if no more available movements */
1475 if (replay->current_playing_pos>=replay->movements->len)
1476 return FALSE;
1478 data=replay->movements->data[replay->current_playing_pos++];
1479 *suicide=(data&REPLAY_SUICIDE_MASK)!=0;
1480 *player_fire=(data&REPLAY_FIRE_MASK)!=0;
1481 *player_move=(data&REPLAY_MOVE_MASK);
1483 return TRUE;
1486 void gd_replay_rewind(GdReplay *replay)
1488 replay->current_playing_pos=0;
1492 #define REPLAY_BDCFF_UP "u"
1493 #define REPLAY_BDCFF_UP_RIGHT "ur"
1494 #define REPLAY_BDCFF_RIGHT "r"
1495 #define REPLAY_BDCFF_DOWN_RIGHT "dr"
1496 #define REPLAY_BDCFF_DOWN "d"
1497 #define REPLAY_BDCFF_DOWN_LEFT "dl"
1498 #define REPLAY_BDCFF_LEFT "l"
1499 #define REPLAY_BDCFF_UP_LEFT "ul"
1500 /* when not moving */
1501 #define REPLAY_BDCFF_STILL "."
1502 /* when the fire is pressed */
1503 #define REPLAY_BDCFF_FIRE "F"
1504 #define REPLAY_BDCFF_SUICIDE "k"
1506 static char *
1507 direction_to_bdcff(GdDirection mov)
1509 switch (mov) {
1510 /* not moving */
1511 case MV_STILL: return REPLAY_BDCFF_STILL;
1512 /* directions */
1513 case MV_UP: return REPLAY_BDCFF_UP;
1514 case MV_UP_RIGHT: return REPLAY_BDCFF_UP_RIGHT;
1515 case MV_RIGHT: return REPLAY_BDCFF_RIGHT;
1516 case MV_DOWN_RIGHT: return REPLAY_BDCFF_DOWN_RIGHT;
1517 case MV_DOWN: return REPLAY_BDCFF_DOWN;
1518 case MV_DOWN_LEFT: return REPLAY_BDCFF_DOWN_LEFT;
1519 case MV_LEFT: return REPLAY_BDCFF_LEFT;
1520 case MV_UP_LEFT: return REPLAY_BDCFF_UP_LEFT;
1521 default:
1522 g_assert_not_reached(); /* programmer error */
1523 return REPLAY_BDCFF_STILL;
1527 /* same as above; pressing fire will be a capital letter. */
1528 static char *
1529 direction_fire_to_bdcff(GdDirection dir, gboolean fire)
1531 static char mov[10];
1533 strcpy(mov, direction_to_bdcff(dir));
1534 if (fire) {
1535 int i;
1537 for (i=0; mov[i]!=0; i++)
1538 mov[i]=g_ascii_toupper(mov[i]);
1541 return mov;
1544 char *
1545 gd_replay_movements_to_bdcff(GdReplay *replay)
1547 int pos;
1548 GString *str;
1550 str=g_string_new(NULL);
1552 for (pos=0; pos<replay->movements->len; pos++) {
1553 int num=1;
1554 guint8 data;
1556 /* if this is not the first movement, append a space. */
1557 if (str->len!=0)
1558 g_string_append_c(str, ' ');
1560 /* if same byte appears, count number of occurrences - something like an rle compression. */
1561 /* be sure not to cross the array boundaries */
1562 while (pos<replay->movements->len-1 && replay->movements->data[pos]==replay->movements->data[pos+1]) {
1563 pos++;
1564 num++;
1566 data=replay->movements->data[pos];
1567 if (data & REPLAY_SUICIDE_MASK)
1568 g_string_append(str, REPLAY_BDCFF_SUICIDE);
1569 g_string_append(str, direction_fire_to_bdcff(data & REPLAY_MOVE_MASK, data & REPLAY_FIRE_MASK));
1570 if (num!=1)
1571 g_string_append_printf(str, "%d", num);
1574 return g_string_free(str, FALSE);
1578 /* calculate adler checksum for a rendered cave; this can be used for more caves. */
1579 void
1580 gd_cave_adler_checksum_more(GdCave *cave, guint32 *a, guint32 *b)
1582 int x, y;
1584 g_assert(cave->rendered);
1586 for (y=0; y<cave->h; y++)
1587 for (x=0; x<cave->w; x++) {
1588 *a+=gd_elements[cave->map[y][x]].character;
1589 *b+=*a;
1591 *a %= 65521;
1592 *b %= 65521;
1596 /* calculate adler checksum for a single rendered cave. */
1597 guint32
1598 gd_cave_adler_checksum(GdCave *cave)
1600 guint32 a=1;
1601 guint32 b=0;
1603 gd_cave_adler_checksum_more(cave, &a, &b);
1604 return (b<<16)+a;