#include "advent.h"
#include "dungeon.h"
-char* xstrdup(const char* s)
-{
- char* ptr = strdup(s);
- if (ptr == NULL) {
- // LCOV_EXCL_START
- // exclude from coverage analysis because we can't simulate an out of memory error in testing
- fprintf(stderr, "Out of memory!\n");
- exit(EXIT_FAILURE);
- }
- return (ptr);
-}
-
-void* xmalloc(size_t size)
+static void* xmalloc(size_t size)
{
void* ptr = malloc(size);
if (ptr == NULL) {
return (ptr);
}
-void packed_to_token(long packed, char token[TOKLEN+1])
+void packed_to_token(long packed, char token[TOKLEN + 1])
{
// The advent->ascii mapping.
const char advent_to_ascii[] = {
}
}
-long token_to_packed(const char token[TOKLEN+1])
+long token_to_packed(const char token[])
{
const char ascii_to_advent[] = {
63, 63, 63, 63, 63, 63, 63, 63,
};
size_t t_len = strlen(token);
+ if (t_len > TOKLEN)
+ t_len = TOKLEN;
long packed = 0;
for (size_t i = 0; i < t_len; ++i) {
- char mapped = ascii_to_advent[(int) token[i]];
+ char mapped = ascii_to_advent[(int) toupper(token[i])];
packed |= (mapped << (6 * i));
}
return (packed);
{
memset(cmd, '\0', sizeof(struct command_t));
- /* FIXME: put a bound prefix on the %s to prevent buffer overflow */
- int word_count = sscanf(raw, "%s%s", cmd->raw1, cmd->raw2);
-
- // make space for substrings and zero it out
- char chunk_data[][TOKLEN+1] = {
- {"\0\0\0\0\0"},
- {"\0\0\0\0\0"},
- {"\0\0\0\0\0"},
- {"\0\0\0\0\0"},
- };
-
- // break the words into up to 4 5-char substrings
- sscanf(cmd->raw1, "%5s%5s", chunk_data[0], chunk_data[1]);
- if (word_count == 2)
- sscanf(cmd->raw2, "%5s%5s", chunk_data[2], chunk_data[3]);
-
- // uppercase all the substrings
- for (int i = 0; i < 4; ++i)
- for (unsigned int j = 0; j < strlen(chunk_data[i]); ++j)
- chunk_data[i][j] = (char) toupper(chunk_data[i][j]);
+ /* Bound prefix on the %s would be needed to prevent buffer
+ * overflow. but we shortstop this more simply by making each
+ * raw-input buffer as long as the enrire inout buffer. */
+ sscanf(raw, "%s%s", cmd->raw1, cmd->raw2);
// pack the substrings
- cmd->wd1 = token_to_packed(chunk_data[0]);
- cmd->wd2 = token_to_packed(chunk_data[2]);
+ cmd->wd1 = token_to_packed(cmd->raw1);
+ cmd->wd2 = token_to_packed(cmd->raw2);
+
+ /* (ESR) In oldstyle mode, simulate the uppercasing and truncating
+ * effect on raw tokens of packing them into sixbit characters, 5
+ * to a 32-bit word. This is something the FORTRAN version did
+ * becuse archaic FORTRAN had no string types. Don Wood's
+ * mechanical translation of 2.5 to C retained the packing and
+ * thus this misfeature.
+ *
+ * It's philosophically questionable whether this is the right
+ * thing to do even in oldstyle mode. On one hand, the text
+ * mangling was not authorial intent, but a result of limitations
+ * in their tools. On the other, not simulating this misbehavior
+ * goes against the goal of making oldstyle as accurate as
+ * possible an emulation of the original UI.
+ */
+ if (settings.oldstyle) {
+ cmd->raw1[TOKLEN + TOKLEN] = cmd->raw2[TOKLEN + TOKLEN] = '\0';
+ for (size_t i = 0; i < strlen(cmd->raw1); i++)
+ cmd->raw1[i] = toupper(cmd->raw1[i]);
+ for (size_t i = 0; i < strlen(cmd->raw2); i++)
+ cmd->raw2[i] = toupper(cmd->raw2[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;
long previous_arg = 0;
for (int i = 0; i < msglen; i++) {
if (msg[i] != '%') {
- *renderp++ = msg[i];
- size--;
+ /* Ugh. Least obtrusive way to deal with artifacts "on the floor"
+ * being dropped outside of both cave and building. */
+ if (strncmp(msg + i, "floor", 5) == 0 && strchr(" .", msg[i + 5]) && !INSIDE(game.loc)) {
+ strcpy(renderp, "ground");
+ renderp += 6;
+ i += 4;
+ size -= 5;
+ } else {
+ *renderp++ = msg[i];
+ size--;
+ }
} else {
long arg = va_arg(ap, long);
if (arg == -1)
size -= len;
}
- // All-lowercase specifier.
- 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;
- }
-
previous_arg = arg;
}
}
free(prompt_and_input);
}
-int word_count(char* s)
+int word_count(char* str)
{
- char* copy = xstrdup(s);
char delims[] = " \t";
int count = 0;
- char* word;
+ int inblanks = true;
+
+ for (char *s = str; *s; s++)
+ if (inblanks) {
+ if (strchr(delims, *s) == 0) {
+ ++count;
+ inblanks = false;
+ }
+ } else {
+ if (strchr(delims, *s) != 0) {
+ inblanks = true;
+ }
+ }
- word = strtok(copy, delims);
- while (word != NULL) {
- word = strtok(NULL, delims);
- ++count;
- }
- free(copy);
return (count);
}
if (input == NULL) // Got EOF; return with it.
return (input);
- else if (input[0] == '#') { // Ignore comments.
+ if (input[0] == '#') { // Ignore comments.
free(input);
continue;
- } else // We have a 'normal' line; leave the loop.
- break;
+ }
+ // We have a 'normal' line; leave the loop.
+ break;
}
// Strip trailing newlines from the input
bool silent_yes()
{
- char* reply;
- bool outcome;
+ bool outcome = false;
for (;;) {
- reply = get_input();
+ char* reply = get_input();
if (reply == NULL) {
// LCOV_EXCL_START
// Should be unreachable. Reply should never be NULL
exit(EXIT_SUCCESS);
// LCOV_EXCL_STOP
}
+ if (strlen(reply) == 0) {
+ free(reply);
+ rspeak(PLEASE_ANSWER);
+ continue;
+ }
char* firstword = (char*) xmalloc(strlen(reply) + 1);
sscanf(reply, "%s", firstword);
/* 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;
+ bool outcome = false;
for (;;) {
speak(question);
- reply = get_input();
+ char* reply = get_input();
if (reply == NULL) {
// LCOV_EXCL_START
// Should be unreachable. Reply should never be NULL
// LCOV_EXCL_STOP
}
+ if (strlen(reply) == 0) {
+ free(reply);
+ rspeak(PLEASE_ANSWER);
+ continue;
+ }
+
char* firstword = (char*) xmalloc(strlen(reply) + 1);
sscanf(reply, "%s", firstword);
return (WORD_NOT_FOUND);
}
-long get_vocab_id(const char* word)
-// Search the vocab categories in order for the supplied word.
+void get_vocab_metadata(const char* word, long* id, enum wordtype* type)
{
+ /* Check for an empty string */
+ if (strncmp(word, "", sizeof("")) == 0) {
+ *id = WORD_EMPTY;
+ *type = NO_WORD_TYPE;
+ return;
+ }
+
long ref_num;
- /* FIXME: Magic numbers related to vocabulary */
ref_num = get_motion_vocab_id(word);
- if (ref_num != WORD_NOT_FOUND)
- return (ref_num + 0); // FIXME: replace with a proper hash
+ if (ref_num != WORD_NOT_FOUND) {
+ *id = ref_num;
+ *type = MOTION;
+ return;
+ }
ref_num = get_object_vocab_id(word);
- if (ref_num != WORD_NOT_FOUND)
- return (ref_num + 1000); // FIXME: replace with a proper hash
+ if (ref_num != WORD_NOT_FOUND) {
+ *id = ref_num;
+ *type = OBJECT;
+ return;
+ }
ref_num = get_action_vocab_id(word);
- if (ref_num != WORD_NOT_FOUND)
- return (ref_num + 2000); // FIXME: replace with a proper hash
+ if (ref_num != WORD_NOT_FOUND) {
+ *id = ref_num;
+ *type = ACTION;
+ return;
+ }
ref_num = get_special_vocab_id(word);
- if (ref_num != WORD_NOT_FOUND)
- return (ref_num + 3000); // FIXME: replace with a proper hash
+ if (ref_num != WORD_NOT_FOUND) {
+ *id = ref_num;
+ *type = SPECIAL;
+ return;
+ }
// Check for the reservoir magic word.
- if (strcasecmp(word, game.zzword) == 0)
- return (PART + 2000); // FIXME: replace with a proper hash
+ if (strcasecmp(word, game.zzword) == 0) {
+ *id = PART;
+ *type = ACTION;
+ return;
+ }
- return (WORD_NOT_FOUND);
+ *id = WORD_NOT_FOUND;
+ *type = NO_WORD_TYPE;
+ return;
}
-void juggle(long object)
+void juggle(obj_t object)
/* Juggle an object by picking it up and putting it down again, the purpose
* being to get the object to the front of the chain of things at its loc. */
{
- long i, j;
+ loc_t i, j;
i = game.place[object];
j = game.fixed[object];
move(object + NOBJECTS, j);
}
-void move(long object, long where)
+void move(obj_t object, loc_t where)
/* Place any object anywhere by picking it up and dropping it. May
* already be toting, in which case the carry is a no-op. Mustn't
* pick up objects which are not at any loc, since carry wants to
from = game.fixed[object - NOBJECTS];
else
from = game.place[object];
- if (from != LOC_NOWHERE && from != CARRIED && !SPECIAL(from))
+ /* (ESR) Used to check for !SPECIAL(from). I *think* that was wrong... */
+ if (from != LOC_NOWHERE && from != CARRIED)
carry(object, from);
drop(object, where);
}
-long put(long object, long where, long pval)
-/* PUT is the same as MOVE, except it returns a value used to set up the
+long put(obj_t object, loc_t where, long pval)
+/* put() is the same as move(), except it returns a value used to set up the
* negated game.prop values for the repository objects. */
{
move(object, where);
- return (-1) - pval;;
+ return STASHED(pval);
}
-void carry(long object, long where)
+void carry(obj_t object, loc_t where)
/* Start toting an object, removing it from the list of things at its former
* location. Incr holdng unless it was already being toted. If object>NOBJECTS
* (moving "fixed" second loc), don't change game.place or game.holdng. */
game.link[temp] = game.link[object];
}
-void drop(long object, long where)
+void drop(obj_t object, loc_t where)
/* Place an object at a given loc, prefixing it onto the game.atloc list. Decr
* game.holdng if the object was being toted. */
{
--game.holdng;
game.place[object] = where;
}
- if (where <= 0)
+ if (where == LOC_NOWHERE ||
+ where == CARRIED)
return;
game.link[object] = game.atloc[where];
game.atloc[where] = object;
}
-long atdwrf(long where)
+long atdwrf(loc_t where)
/* Return the index of first dwarf at the given location, zero if no dwarf is
* there (or if dwarves not active yet), -1 if all dwarves are dead. Ignore
* the pirate (6th dwarf). */
return range * get_next_lcg_value() / game.lcg_m;
}
-void make_zzword(char zzword[TOKLEN+1])
+void make_zzword(char zzword[TOKLEN + 1])
{
for (int i = 0; i < 5; ++i) {
zzword[i] = 'A' + randrange(26);
// LCOV_EXCL_STOP
/* end */
+
+void state_change(obj_t obj, long state)
+/* Object must have a change-message list for this to be useful; only some do */
+{
+ game.prop[obj] = state;
+ pspeak(obj, change, state, true);
+}
+
+/* end */