/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "glk/adrift/scare.h" #include "glk/adrift/scprotos.h" #include "glk/glk.h" #include "glk/events.h" #include "common/debug.h" #include "common/str.h" #include "common/textconsole.h" namespace Glk { namespace Adrift { /* * Module notes: * * o Implement smarter selective module tracing. */ /* * sc_trace() * * Debugging trace function; printf wrapper that writes to stderr. */ void sc_trace(const sc_char *format, ...) { va_list ap; assert(format); va_start(ap, format); Common::String s = Common::String::format(format, ap); va_end(ap); debug("%s", s.c_str()); } /* * sc_error() * sc_fatal() * * Error reporting functions. sc_error() prints a message and continues. * sc_fatal() prints a message, then calls abort(). */ void sc_error(const sc_char *format, ...) { va_list ap; assert(format); va_start(ap, format); Common::String s = Common::String::vformat(format, ap); va_end(ap); warning("%s", s.c_str()); } void sc_fatal(const sc_char *format, ...) { va_list ap; assert(format); va_start(ap, format); Common::String s = Common::String::format(format, ap); va_end(ap); error("%s", s.c_str()); } /* Unique non-heap address for zero size malloc() and realloc() requests. */ static void *sc_zero_allocation = &sc_zero_allocation; /* * sc_malloc() * sc_realloc() * sc_free() * * Non-failing wrappers around malloc functions. Newly allocated memory is * cleared to zero. In ANSI/ISO C, zero byte allocations are implementation- * defined, so we have to take special care to get predictable behavior. */ void *sc_malloc(size_t size) { void *allocated; if (size == 0) return sc_zero_allocation; allocated = malloc(size); if (!allocated) sc_fatal("sc_malloc: requested %lu bytes\n", (sc_uint) size); else if (allocated == sc_zero_allocation) sc_fatal("sc_malloc: zero-byte allocation address returned\n"); memset(allocated, 0, size); return allocated; } void *sc_realloc(void *pointer, size_t size) { void *allocated; if (size == 0) { sc_free(pointer); return sc_zero_allocation; } if (pointer == sc_zero_allocation) pointer = NULL; allocated = realloc(pointer, size); if (!allocated) sc_fatal("sc_realloc: requested %lu bytes\n", (sc_uint) size); else if (allocated == sc_zero_allocation) sc_fatal("sc_realloc: zero-byte allocation address returned\n"); if (!pointer) memset(allocated, 0, size); return allocated; } void sc_free(void *pointer) { if (sc_zero_allocation != &sc_zero_allocation) sc_fatal("sc_free: write to zero-byte allocation address detected\n"); if (pointer && pointer != sc_zero_allocation) free(pointer); } /* * sc_strncasecmp() * sc_strcasecmp() * * Strncasecmp and strcasecmp are not ANSI functions, so here are local * definitions to do the same jobs. */ sc_int sc_strncasecmp(const sc_char *s1, const sc_char *s2, sc_int n) { sc_int index_; assert(s1 && s2); for (index_ = 0; index_ < n; index_++) { sc_int diff; diff = sc_tolower(s1[index_]) - sc_tolower(s2[index_]); if (diff < 0 || diff > 0) return diff < 0 ? -1 : 1; } return 0; } sc_int sc_strcasecmp(const sc_char *s1, const sc_char *s2) { sc_int s1len, s2len, result; assert(s1 && s2); s1len = strlen(s1); s2len = strlen(s2); result = sc_strncasecmp(s1, s2, s1len < s2len ? s1len : s2len); if (result < 0 || result > 0) return result; else return s1len < s2len ? -1 : s1len > s2len ? 1 : 0; } /* * sc_platform_rand() * sc_congruential_rand() * sc_set_random_handler() * * Internal random number generation functions. We offer two: one is a self- * seeding wrapper around the platform's rand(), which should generate good * random numbers but with a sequence that is platform-dependent; the other * is a linear congruential generator with a long period that is guaranteed * to return the same sequence for all platforms. The default is the first, * with the latter intended for predictability of game actions. */ static sc_int sc_platform_rand(sc_uint new_seed) { static sc_bool is_seeded = FALSE; /* If reseeding, seed with the value supplied, note seeded, and return 0. */ if (new_seed > 0) { g_vm->setRandomNumberSeed(new_seed); is_seeded = TRUE; return 0; } else { /* If not explicitly seeded yet, generate a seed from time(). */ if (!is_seeded) { //srand ((sc_uint) time (NULL)); is_seeded = TRUE; } /* Return the next rand() number in the sequence. */ return g_vm->getRandomNumber(0xffffff); } } static sc_int sc_congruential_rand(sc_uint new_seed) { static sc_bool is_seeded = FALSE; static sc_uint rand_state = 1; /* If reseeding, seed with the value supplied, and note seeded. */ if (new_seed > 0) { rand_state = new_seed; is_seeded = TRUE; return 0; } else { /* If not explicitly seeded yet, generate a seed from time(). */ if (!is_seeded) { rand_state = (sc_uint)g_vm->_events->getTotalPlayTicks(); is_seeded = TRUE; } /* * Advance random state, using constants from Park & Miller (1988). * To keep the values the same for both 32 and 64 bit longs, mask out * any bits above the bottom 32. */ rand_state = (rand_state * 16807 + 2147483647) & 0xffffffff; /* * Discard the lowest bit as a way to map 32-bits unsigned to a 32-bit * positive signed. */ return rand_state >> 1; } } /* Function pointer for the actual random number generator in use. */ static sc_int(*sc_rand_function)(sc_uint) = sc_platform_rand; /* * sc_set_congruential_random() * sc_set_platform_random() * sc_is_congruential_random() * sc_seed_random() * sc_rand() * sc_randomint() * * Public interface to random functions; control and reseed the random * handler in use, generate a random number, and a convenience function to * generate a random value within a given range. */ void sc_set_congruential_random(void) { sc_rand_function = sc_congruential_rand; } void sc_set_platform_random(void) { sc_rand_function = sc_platform_rand; } sc_bool sc_is_congruential_random(void) { return sc_rand_function == sc_congruential_rand; } void sc_seed_random(sc_uint new_seed) { /* Ignore zero values of new_seed by simply using 1 instead. */ sc_rand_function(new_seed > 0 ? new_seed : 1); } sc_int sc_rand(void) { sc_int retval; /* Passing zero indicates this is not a seed operation. */ retval = sc_rand_function(0); return retval; } sc_int sc_randomint(sc_int low, sc_int high) { /* * If the range is invalid, just return the low value given. This mimics * Adrift under the same conditions, and also guards against division by * zero in the mod operation. */ return (high < low) ? low : low + sc_rand() % (high - low + 1); } /* Miscellaneous general ascii constants. */ static const sc_char NUL = '\0'; static const sc_char SPACE = ' '; /* * sc_strempty() * * Return TRUE if a string is either zero-length or contains only whitespace. */ sc_bool sc_strempty(const sc_char *string) { sc_int index_; assert(string); /* Scan for any non-space character. */ for (index_ = 0; string[index_] != NUL; index_++) { if (!sc_isspace(string[index_])) return FALSE; } /* None found, so string is empty. */ return TRUE; } /* * sc_trim_string() * * Trim leading and trailing whitespace from a string. Modifies the string * in place, and returns the string address for convenience. */ sc_char *sc_trim_string(sc_char *string) { sc_int index_; assert(string); for (index_ = strlen(string) - 1; index_ >= 0 && sc_isspace(string[index_]); index_--) string[index_] = NUL; for (index_ = 0; sc_isspace(string[index_]);) index_++; memmove(string, string + index_, strlen(string) - index_ + 1); return string; } /* * sc_normalize_string() * * Trim a string, and set all runs of whitespace to a single space character. * Modifies the string in place, and returns the string address for * convenience. */ sc_char *sc_normalize_string(sc_char *string) { sc_int index_; assert(string); /* Trim all leading and trailing spaces. */ string = sc_trim_string(string); /* Compress multiple whitespace runs into a single space character. */ for (index_ = 0; string[index_] != NUL; index_++) { if (sc_isspace(string[index_])) { sc_int cursor; string[index_] = SPACE; for (cursor = index_ + 1; sc_isspace(string[cursor]);) cursor++; memmove(string + index_ + 1, string + cursor, strlen(string + cursor) + 1); } } return string; } /* * sc_compare_word() * * Return TRUE if the first word in the string is word, case insensitive. */ sc_bool sc_compare_word(const sc_char *string, const sc_char *word, sc_int length) { assert(string && word); /* Return TRUE if string starts with word, then space or string end. */ return sc_strncasecmp(string, word, length) == 0 && (string[length] == NUL || sc_isspace(string[length])); } /* * sc_hash() * * Hash a string, hashpjw algorithm, from 'Compilers, principles, techniques, * and tools', page 436, unmodulo'ed and somewhat restyled. */ sc_uint sc_hash(const sc_char *string) { sc_int index_; sc_uint hash; assert(string); hash = 0; for (index_ = 0; string[index_] != NUL; index_++) { sc_uint temp; hash = (hash << 4) + string[index_]; temp = hash & 0xf0000000; if (temp != 0) { hash = hash ^ (temp >> 24); hash = hash ^ temp; } } return hash; } } // End of namespace Adrift } // End of namespace Glk