1
0
mirror of https://git.zap.org.au/git/trader.git synced 2024-12-04 14:46:45 -05:00
trader/src/utils.c
John Zaitseff bf76fa312c Assume MOD_POSIX_CURRENCY_SYMBOL contains only ASCII characters
The GNU library now has "C.UTF-8" as a locale (which is NOT a single-byte
locale!), but we assume the currency symbol in MOD_POSIX_CURRENCY_SYMBOL
contains only ASCII characters.
2011-08-16 20:55:42 +10:00

483 lines
13 KiB
C

/************************************************************************
* *
* Star Traders: A Game of Interstellar Trading *
* Copyright (C) 1990-2011, John Zaitseff *
* *
************************************************************************/
/*
Author: John Zaitseff <J.Zaitseff@zap.org.au>
$Id$
This file, utils.c, contains the implementation of various utility
functions used in Star Traders.
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 3 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, see http://www.gnu.org/licenses/.
*/
#include "trader.h"
/************************************************************************
* Global variable definitions *
************************************************************************/
// Global copy, suitably modified, of localeconv() information
struct lconv lconvinfo;
/************************************************************************
* Module-specific constants and variable definitions *
************************************************************************/
#define GAME_FILENAME_PROTO "game%d"
#define GAME_FILENAME_BUFSIZE 16
// Default values used to override POSIX locale
#define MOD_POSIX_CURRENCY_SYMBOL "$"
#define MOD_POSIX_FRAC_DIGITS 2
#define MOD_POSIX_P_CS_PRECEDES 1
#define MOD_POSIX_P_SEP_BY_SPACE 0
/************************************************************************
* Module-specific variables *
************************************************************************/
static char *program_name_str = NULL; // Canonical program name
static char *home_directory_str = NULL; // Full pathname to home
static char *data_directory_str = NULL; // Writable data dir pathname
static char *current_mon_locale; // As returned by setlocale()
static bool add_currency_symbol = false; // Do we need to add "$"?
/************************************************************************
* Initialisation and environment function definitions *
************************************************************************/
// These functions are documented in the file "utils.h"
/***********************************************************************/
// init_program_name: Make the program name "canonical"
void init_program_name (char *argv[])
{
if (argv == NULL || argv[0] == NULL || *argv[0] == '\0') {
program_name_str = PACKAGE;
} else {
char *p = strrchr(argv[0], '/');
if (p != NULL && *++p != '\0') {
argv[0] = p;
}
program_name_str = argv[0];
}
}
/***********************************************************************/
// program_name: Return the canonical program name
const char *program_name (void)
{
if (program_name_str == NULL) {
init_program_name(NULL);
}
return program_name_str;
}
/***********************************************************************/
// home_directory: Return home directory pathname
const char *home_directory (void)
{
if (home_directory_str == NULL) {
// Use the HOME environment variable where possible
char *home = getenv("HOME");
if (home != NULL && *home != '\0') {
home_directory_str = xstrdup(home);
}
}
return home_directory_str;
}
/***********************************************************************/
// data_directory: Return writable data directory pathname
const char *data_directory (void)
{
/* This implementation assumes a POSIX environment by using "/" as
the directory separator. It also assumes a dot-starting directory
name is permissible (again, true on POSIX systems) */
if (data_directory_str == NULL) {
const char *name = program_name();
const char *home = home_directory();
if (name != NULL && home != NULL) {
char *p = xmalloc(strlen(home) + strlen(name) + 3);
strcpy(p, home);
strcat(p, "/.");
strcat(p, name);
data_directory_str = p;
}
}
return data_directory_str;
}
/***********************************************************************/
// game_filename: Convert an integer to a game filename
char *game_filename (int gamenum)
{
/* This implementation assumes a POSIX environment by using "/" as
the directory separator */
char buf[GAME_FILENAME_BUFSIZE]; // Buffer for part of filename
const char *dd; // Data directory
if (gamenum < 1 || gamenum > 9) {
return NULL;
}
dd = data_directory();
snprintf(buf, GAME_FILENAME_BUFSIZE, GAME_FILENAME_PROTO, gamenum);
if (dd == NULL) {
return xstrdup(buf);
} else {
char *p = xmalloc(strlen(dd) + strlen(buf) + 2);
strcpy(p, dd);
strcat(p, "/");
strcat(p, buf);
return p;
}
}
/************************************************************************
* Error-reporting function definitions *
************************************************************************/
// These functions are documented in the file "utils.h"
/***********************************************************************/
// err_exit: Print an error and exit
void err_exit (const char *restrict format, ...)
{
va_list args;
end_screen();
fprintf(stderr, _("%s: "), program_name());
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
fputs("\n", stderr);
exit(EXIT_FAILURE);
}
/***********************************************************************/
// errno_exit: Print an error message (using errno) and exit
void errno_exit (const char *restrict format, ...)
{
va_list args;
int saved_errno = errno;
end_screen();
fprintf(stderr, _("%s: "), program_name());
if (format != NULL) {
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
fputs(_(": "), stderr);
}
fprintf(stderr, "%s\n", strerror(saved_errno));
exit(EXIT_FAILURE);
}
/***********************************************************************/
// err_exit_nomem: Print an "out of memory" error and exit
void err_exit_nomem (void)
{
err_exit(_("out of memory"));
}
/************************************************************************
* Random-number function definitions *
************************************************************************/
// These functions are documented in the file "utils.h"
/***********************************************************************/
// init_rand: Initialise the random number generator
void init_rand (void)
{
/* Ideally, initialisation of the random number generator should be
made using seed48() and lcong48(). However, since this is "only a
game", 32 bits of "randomness" as returned by gettimeofday() is
probably more than enough... */
struct timeval tv;
unsigned long int seed;
gettimeofday(&tv, NULL); // If this fails, tv is random enough!
seed = tv.tv_sec + tv.tv_usec;
srand48(seed);
}
/***********************************************************************/
// randf: Return a random number between 0.0 and 1.0
extern double randf (void)
{
return drand48();
}
/***********************************************************************/
// randi: Return a random number between 0 and limit
extern int randi (int limit)
{
return drand48() * (double) limit;
}
/************************************************************************
* Locale-aware function definitions *
************************************************************************/
// These functions are documented in the file "utils.h"
/***********************************************************************/
// init_locale: Initialise locale-specific variables
void init_locale (void)
{
struct lconv *lc;
current_mon_locale = setlocale(LC_MONETARY, NULL);
lc = localeconv();
assert(current_mon_locale != NULL);
assert(lc != NULL);
lconvinfo = *lc;
/* Are we in the POSIX locale? This test may not be portable as the
string returned by setlocale() is supposed to be opaque. */
add_currency_symbol = false;
if ( strcmp(current_mon_locale, "POSIX") == 0
|| strcmp(current_mon_locale, "C") == 0
|| strcmp(current_mon_locale, "C.UTF-8") == 0
|| strcmp(current_mon_locale, "C.utf8") == 0) {
add_currency_symbol = true;
lconvinfo.currency_symbol = MOD_POSIX_CURRENCY_SYMBOL;
lconvinfo.frac_digits = MOD_POSIX_FRAC_DIGITS;
lconvinfo.p_cs_precedes = MOD_POSIX_P_CS_PRECEDES;
lconvinfo.p_sep_by_space = MOD_POSIX_P_SEP_BY_SPACE;
}
}
/***********************************************************************/
// l_strfmon: Convert monetary value to a string
ssize_t l_strfmon (char *restrict s, size_t maxsize,
const char *restrict format, double val)
{
/* The current implementation assumes MOD_POSIX_P_CS_PRECEDES is 1
(currency symbol precedes value) and that MOD_POSIX_P_SEP_BY_SPACE
is 0 (no space separates currency symbol and value). It does,
however, handle currency symbols of length > 1 */
assert(MOD_POSIX_P_CS_PRECEDES == 1);
assert(MOD_POSIX_P_SEP_BY_SPACE == 0);
ssize_t ret = strfmon(s, maxsize, format, val);
if (ret > 0 && add_currency_symbol) {
if (strstr(format, "!") == NULL) {
/* Insert lconvinfo.currency_symbol to s.
NB: add_currecy_symbol == true assumes a POSIX locale and
that MOD_POSIX_CURRENCY_SYMBOL contains only ASCII-safe
characters that work with strlen(), etc. */
const char *sym = lconvinfo.currency_symbol;
int symlen = strlen(sym);
char *p;
int spc;
assert(maxsize > (unsigned int) symlen);
// Count number of leading spaces
for (p = s, spc = 0; *p == ' '; p++, spc++)
;
if (symlen <= spc) {
/* Enough space for currency symbol: copy it WITHOUT
copying terminating NUL character */
for (p -= symlen; *sym != '\0'; p++, sym++) {
*p = *sym;
}
} else {
// Make space for currency symbol, then copy it
memmove(s + symlen - spc, s, maxsize - (symlen - spc));
s[maxsize - 1] = '\0';
for ( ; *sym != '\0'; sym++, s++) {
// Make sure terminating NUL character is NOT copied!
*s = *sym;
}
ret = MIN((unsigned int) ret + symlen - spc, maxsize - 1);
}
}
}
return ret;
}
/************************************************************************
* Encryption function definitions *
************************************************************************/
// These functions are documented in the file "utils.h"
/***********************************************************************/
// scramble: Scramble (encrypt) the buffer
char *scramble (int key, char *restrict buf, int bufsize)
{
/* The algorithm used here is reversable: scramble(scramble(...))
will (or, at least, should!) return the same as the original
buffer. Problematic characters are ignored; however, this
function assumes all other characters are permitted in files.
This is true on all POSIX systems. */
if (buf != NULL && key != 0) {
char *p = buf;
unsigned char k = ~key;
for (int i = 0; i < bufsize && *p != '\0'; i++, k++, p++) {
char c = *p;
char r = c ^ k; // Simple encryption: XOR on a moving key
if (c != '\r' && c != '\n'
&& r != '\r' && r != '\n' && r != '\0') {
*p = r;
}
}
}
return buf;
}
/***********************************************************************/
// unscramble: Unscramble (decrypt) the buffer
char *unscramble (int key, char *restrict buf, int bufsize)
{
return scramble(key, buf, bufsize);
}
/************************************************************************
* Miscellaneous function definitions *
************************************************************************/
// These functions are documented in the file "utils.h"
/***********************************************************************/
// xmalloc: Allocate a new block of memory, with checking
void *xmalloc (size_t size)
{
void *p;
if (size < 1)
size = 1;
p = malloc(size);
if (p == NULL) {
err_exit_nomem();
}
return p;
}
/***********************************************************************/
// xstrdup: Duplicate a string, with checking
char *xstrdup (const char *str)
{
char *s;
if (str == NULL)
str = "";
s = strdup(str);
if (s == NULL) {
err_exit_nomem();
}
return s;
}
/***********************************************************************/
// End of file