#include <stdlib.h>
#include <stdio.h>
#include <string.h>
+#include <stdarg.h>
#include <sys/time.h>
#include <ctype.h>
void* xmalloc(size_t size)
{
- void* ptr = malloc(size);
- if (ptr == NULL)
- {
- fprintf(stderr, "Out of memory!\n");
- exit(EXIT_FAILURE);
- }
- return(ptr);
-}
-
-char* xstrdup(const char* s)
-{
- char* ptr = strdup(s);
+ void* ptr = malloc(size);
if (ptr == NULL) {
fprintf(stderr, "Out of memory!\n");
exit(EXIT_FAILURE);
}
}
-/* I/O routines (SPEAK, PSPEAK, RSPEAK, SETPRM, GETIN, YES) */
+void token_to_packed(char token[6], long* packed)
+{
+ *packed = 0;
+ for (size_t i = 0; i < 5; ++i)
+ {
+ if (token[4 - i] == '\0')
+ continue;
+ char mapped = ascii_to_advent[(int) token[4 - i]];
+ *packed |= (mapped << (6 * i));
+ }
+}
+
+/* Hide the fact that wods are corrently packed longs */
+
+bool wordeq(token_t a, token_t b)
+{
+ return a == b;
+}
+
+bool wordempty(token_t a)
+{
+ return a == 0;
+}
-void speak(const char* msg)
+void wordclear(token_t *v)
+{
+ *v = 0;
+}
+
+/* I/O routines (speak, pspeak, rspeak, GETIN, YES) */
+
+void vspeak(const char* msg, va_list ap)
{
// Do nothing if we got a null pointer.
if (msg == NULL)
if (game.blklin == true)
printf("\n");
- // Create a copy of our string, so we can edit it.
- char* copy = xstrdup(msg);
+ int msglen = strlen(msg);
- // Staging area for stringified parameters.
- char parameters[5][100]; // FIXME: to be replaced with dynamic allocation
+ // Rendered string
+ ssize_t size = 2000; /* msglen > 50 ? msglen*2 : 100; */
+ char* rendered = xmalloc(size);
+ char* renderp = rendered;
// Handle format specifiers (including the custom %C, %L, %S) by adjusting the parameter accordingly, and replacing the specifier with %s.
- int pi = 0; // parameter index
- for (int i = 0; i < (int)strlen(msg); ++i) {
- if (msg[i] == '%') {
- ++pi;
-
+ long previous_arg = 0;
+ for (int i = 0; i < msglen; i++) {
+ if (msg[i] != '%') {
+ *renderp++ = msg[i];
+ size--;
+ } else {
+ long arg = va_arg(ap, long);
+ if (arg == -1)
+ arg = 0;
+ i++;
// Integer specifier. In order to accommodate the fact that PARMS can have both legitimate integers *and* packed tokens, stringify everything. Future work may eliminate the need for this.
- if (msg[i + 1] == 'd') {
- copy[i + 1] = 's';
- sprintf(parameters[pi], "%ld", PARMS[pi]);
+ if (msg[i] == 'd') {
+ int ret = snprintf(renderp, size, "%ld", arg);
+ if (ret < size) {
+ renderp += ret;
+ size -= ret;
+ }
}
// Unmodified string specifier.
- if (msg[i + 1] == 's') {
- packed_to_token(PARMS[pi], parameters[pi]);
+ if (msg[i] == 's') {
+ packed_to_token(arg, renderp); /* unpack directly to destination */
+ size_t len = strlen(renderp);
+ renderp += len;
+ size -= len;
}
// Singular/plural specifier.
- if (msg[i + 1] == 'S') {
- copy[i + 1] = 's';
- if (PARMS[pi - 1] > 1) { // look at the *previous* parameter (which by necessity must be numeric)
- sprintf(parameters[pi], "%s", "s");
- } else {
- sprintf(parameters[pi], "%s", "");
+ if (msg[i] == 'S') {
+ if (previous_arg > 1) { // look at the *previous* parameter (which by necessity must be numeric)
+ *renderp++ = 's';
+ size--;
}
}
// All-lowercase specifier.
- if (msg[i + 1] == 'L') {
- copy[i + 1] = 's';
- packed_to_token(PARMS[pi], parameters[pi]);
- for (int j = 0; j < (int)strlen(parameters[pi]); ++j) {
- parameters[pi][j] = tolower(parameters[pi][j]);
+ if (msg[i] == 'L' || msg[i] == 'C') {
+ packed_to_token(arg, renderp); /* unpack directly to destination */
+ int len = strlen(renderp);
+ for (int j = 0; j < len; ++j) {
+ renderp[j] = tolower(renderp[j]);
}
+ if (msg[i] == 'C') // First char uppercase, rest lowercase.
+ renderp[0] = toupper(renderp[0]);
+ renderp += len;
+ size -= len;
}
- // First char uppercase, rest lowercase.
- if (msg[i + 1] == 'C') {
- copy[i + 1] = 's';
- packed_to_token(PARMS[pi], parameters[pi]);
- for (int j = 0; j < (int)strlen(parameters[pi]); ++j) {
- parameters[pi][j] = tolower(parameters[pi][j]);
- }
- parameters[pi][0] = toupper(parameters[pi][0]);
- }
+ previous_arg = arg;
}
}
-
- // Render the final string.
- char rendered[2000]; // FIXME: to be replaced with dynamic allocation
- sprintf(rendered, copy, parameters[1], parameters[2], parameters[3], parameters[4]); // FIXME: to be replaced with vsprintf()
+ *renderp = 0;
// Print the message.
printf("%s\n", rendered);
- free(copy);
+ free(rendered);
}
-void PSPEAK(vocab_t msg, int skip)
+void speak(const char* msg, ...)
+{
+ va_list ap;
+ va_start(ap, msg);
+ vspeak(msg, ap);
+ va_end(ap);
+}
+
+void pspeak(vocab_t msg, int skip, ...)
/* Find the skip+1st message from msg and print it. msg should be
* the index of the inventory message for object. (INVEN+N+1 message
* is game.prop=N message). */
{
+ va_list ap;
+ va_start(ap, skip);
if (skip >= 0)
- speak(object_descriptions[msg].longs[skip]);
+ vspeak(object_descriptions[msg].longs[skip], ap);
else
- speak(object_descriptions[msg].inventory);
+ vspeak(object_descriptions[msg].inventory, ap);
+ va_end(ap);
}
-void RSPEAK(vocab_t i)
+void rspeak(vocab_t i, ...)
/* Print the i-th "random" message (section 6 of database). */
{
- speak(arbitrary_messages[i]);
-}
-
-void SETPRM(long first, long p1, long p2)
-/* Stores parameters into the PRMCOM parms array for use by speak. P1 and P2
- * are stored into PARMS(first) and PARMS(first+1). */
-{
- if (first >= MAXPARMS)
- BUG(29);
- else {
- PARMS[first] = p1;
- PARMS[first + 1] = p2;
- }
+ va_list ap;
+ va_start(ap, i);
+ vspeak(arbitrary_messages[i], ap);
+ va_end(ap);
}
bool GETIN(FILE *input,
(junk > 0);
if (GETTXT(true, true, true) <= 0)
return true;
- RSPEAK(TWO_WORDS);
+ rspeak(TWO_WORDS);
}
}
void echo_input(FILE* destination, char* input_prompt, char* input)
{
- size_t len = strlen(input_prompt) + strlen(input) + 1;
- char* prompt_and_input = (char*) xmalloc(len);
- strcpy(prompt_and_input, input_prompt);
- strcat(prompt_and_input, input);
- fprintf(destination, "%s\n", prompt_and_input);
- free(prompt_and_input);
+ size_t len = strlen(input_prompt) + strlen(input) + 1;
+ char* prompt_and_input = (char*) xmalloc(len);
+ strcpy(prompt_and_input, input_prompt);
+ strcat(prompt_and_input, input);
+ fprintf(destination, "%s\n", prompt_and_input);
+ free(prompt_and_input);
}
char* get_input()
-{
- // Set up the prompt
- char input_prompt[] = "> ";
- if (!prompt)
- input_prompt[0] = '\0';
-
- // Print a blank line if game.blklin tells us to.
- if (game.blklin == true)
- printf("\n");
-
- char* input;
- while (true)
- {
- if (editline)
- input = linenoise(input_prompt);
- else
- {
- input = NULL;
- size_t n = 0;
- if (isatty(0))
- printf("%s", input_prompt);
- getline(&input, &n, stdin);
- }
-
- if (input == NULL) // Got EOF; quit.
- exit(EXIT_SUCCESS);
- else if (input[0] == '#') // Ignore comments.
- continue;
- else // We have a 'normal' line; leave the loop.
- break;
+{
+ // Set up the prompt
+ char input_prompt[] = "> ";
+ if (!prompt)
+ input_prompt[0] = '\0';
+
+ // Print a blank line if game.blklin tells us to.
+ if (game.blklin == true)
+ printf("\n");
+
+ char* input;
+ while (true) {
+ if (editline)
+ input = linenoise(input_prompt);
+ else {
+ input = NULL;
+ size_t n = 0;
+ if (isatty(0))
+ printf("%s", input_prompt);
+ IGNORE(getline(&input, &n, stdin));
+ }
+
+ if (input == NULL) // Got EOF; return with it.
+ return(input);
+ else if (input[0] == '#') // Ignore comments.
+ continue;
+ else // We have a 'normal' line; leave the loop.
+ break;
}
- // Strip trailing newlines from the input
- input[strcspn(input, "\n")] = 0;
+ // Strip trailing newlines from the input
+ input[strcspn(input, "\n")] = 0;
- linenoiseHistoryAdd(input);
+ linenoiseHistoryAdd(input);
- if (!isatty(0))
- echo_input(stdout, input_prompt, input);
+ if (!isatty(0))
+ echo_input(stdout, input_prompt, input);
- if (logfp)
- echo_input(logfp, input_prompt, input);
-
- return(input);
+ if (logfp)
+ echo_input(logfp, input_prompt, input);
+
+ return (input);
}
-bool YES(vocab_t question, vocab_t yes_response, vocab_t no_response)
+bool YES(const char* question, const char* yes_response, const char* no_response)
/* Print message X, wait for yes/no answer. If yes, print Y and return true;
* if no, print Z and return false. */
{
char* reply;
bool outcome;
-
+
for (;;) {
- RSPEAK(question);
-
- reply = get_input();
-
- char* firstword = (char*) xmalloc(strlen(reply));
- sscanf(reply, "%s", firstword);
-
- for (int i = 0; i < strlen(firstword); ++i)
- firstword[i] = tolower(firstword[i]);
-
- int yes = strncmp("yes", firstword, sizeof("yes") - 1);
- int y = strncmp("y", firstword, sizeof("y") - 1);
- int no = strncmp("no", firstword, sizeof("no") - 1);
- int n = strncmp("n", firstword, sizeof("n") - 1);
-
- free(firstword);
-
- if (yes == 0 || y == 0) {
- RSPEAK(yes_response);
- outcome = true;
- break;
- }
- else if (no == 0 || n == 0) {
- RSPEAK(no_response);
- outcome = false;
- break;
+ speak(question);
+
+ reply = get_input();
+ if (reply == NULL) {
+ linenoiseFree(reply);
+ exit(EXIT_SUCCESS);
}
- else
- RSPEAK(PLEASE_ANSWER);
+
+ char* firstword = (char*) xmalloc(strlen(reply)+1);
+ sscanf(reply, "%s", firstword);
+
+ for (int i = 0; i < (int)strlen(firstword); ++i)
+ firstword[i] = tolower(firstword[i]);
+
+ int yes = strncmp("yes", firstword, sizeof("yes") - 1);
+ int y = strncmp("y", firstword, sizeof("y") - 1);
+ int no = strncmp("no", firstword, sizeof("no") - 1);
+ int n = strncmp("n", firstword, sizeof("n") - 1);
+
+ free(firstword);
+
+ if (yes == 0 || y == 0) {
+ speak(yes_response);
+ outcome = true;
+ break;
+ } else if (no == 0 || n == 0) {
+ speak(no_response);
+ outcome = false;
+ break;
+ } else
+ rspeak(PLEASE_ANSWER);
}
linenoiseFree(reply);
- return(outcome);
+ return (outcome);
}
/* Line-parsing routines (GETTXT, MAKEWD, PUTTXT, SHFTXT) */
lexeme = -1;
if (init < 0)
return (lexeme);
- BUG(5);
+ BUG(REQUIRED_VOCABULARY_WORD_NOT_FOUND);
}
if (init >= 0 && KTAB[i] / 1000 != init)
continue;
return (lexeme);
}
}
- BUG(21);
+ BUG(RAN_OFF_END_OF_VOCABULARY_TABLE);
}
void JUGGLE(long object)
from = game.fixed[object - NOBJECTS];
else
from = game.place[object];
- if (from != NOWHERE && from != CARRIED && !SPECIAL(from))
+ if (from != LOC_NOWHERE && from != CARRIED && !SPECIAL(from))
CARRY(object, from);
DROP(object, where);
}
}
/* Utility routines (SETBIT, TSTBIT, set_seed, get_next_lcg_value,
- * randrange, RNDVOC, BUG) */
+ * randrange, RNDVOC) */
long SETBIT(long bit)
/* Returns 2**bit for use in constructing bit-masks. */
return rnd;
}
-void BUG(long num)
-/* The following conditions are currently considered fatal bugs. Numbers < 20
- * are detected while reading the database; the others occur at "run time".
- * 0 Message line > 70 characters
- * 1 Null line in message
- * 2 Too many words of messages
- * 3 Too many travel options
- * 4 Too many vocabulary words
- * 5 Required vocabulary word not found
- * 6 Too many RTEXT messages
- * 7 Too many hints
- * 8 Location has cond bit being set twice
- * 9 Invalid section number in database
- * 10 Too many locations
- * 11 Too many class or turn messages
- * 20 Special travel (500>L>300) exceeds goto list
- * 21 Ran off end of vocabulary table
- * 22 Vocabulary type (N/1000) not between 0 and 3
- * 23 Intransitive action verb exceeds goto list
- * 24 Transitive action verb exceeds goto list
- * 25 Conditional travel entry with no alternative
- * 26 Location has no travel entries
- * 27 Hint number exceeds goto list
- * 28 Invalid month returned by date function
- * 29 Too many parameters given to SETPRM */
-{
-
- printf("Fatal error %ld. See source code for interpretation.\n", num);
- exit(0);
-}
/* Machine dependent routines (MAPLIN, SAVEIO) */
* If the data file uses a character other than space (e.g., tab) to
* separate numbers, that character should also translate to 0.
*
- * This procedure may use the map1,map2 arrays to maintain static data for
- * the mapping. MAP2(1) is set to 0 when the program starts
- * and is not changed thereafter unless the routines on this page choose
- * to do so. */
+ * This procedure may use the map1,map2 arrays to maintain
+ * static data for he mapping. MAP2(1) is set to 0 when the
+ * program starts and is not changed thereafter unless the
+ * routines in this module choose to do so. */
LNLENG = 0;
for (long i = 1; i <= (long)sizeof(INLINE) && INLINE[i] != 0; i++) {
long val = INLINE[i];
*t = (long) tv.tv_usec;
}
+void bug(enum bugtype num, const char *error_string)
+{
+ fprintf(stderr, "Fatal error %d, %s.\n", num, error_string);
+ exit(EXIT_FAILURE);
+}
+
/* end */