-#include "main.h"
-#include "share.h" /* for SETUP */
-#include "misc.h"
-#include "funcs.h"
-
-/* hack to ignore GCC Unused Result */
-#define IGNORE(r) do{if(r){}}while(0)
-
-/* I/O routines (SPEAK, PSPEAK, RSPEAK, SETPRM, GETIN, YES) */
-
-#undef SPEAK
-void fSPEAK(long N) {
-long BLANK, CASE, I, K, L, NEG, NPARMS, PARM, PRMTYP, STATE;
-
-/* Print the message which starts at LINES(N). Precede it with a blank line
- * unless BLKLIN is false. */
-
-
- if(N == 0)return;
- BLANK=BLKLIN;
- K=N;
- NPARMS=1;
-L10: L=IABS(LINES[K])-1;
- K=K+1;
- LNLENG=0;
- LNPOSN=1;
- STATE=0;
- for (I=K; I<=L; I++) {
- PUTTXT(LINES[I],STATE,2,I);
- } /* end loop */
- LNPOSN=0;
-L30: LNPOSN=LNPOSN+1;
-L32: if(LNPOSN > LNLENG) goto L40;
- if(INLINE[LNPOSN] != 63) goto L30;
- {long x = LNPOSN+1; PRMTYP=INLINE[x];}
-/* 63 is a "%"; the next character determine the type of parameter: 1 (!) =
- * suppress message completely, 29 (S) = NULL If PARM=1, else 'S' (optional
- * plural ending), 33 (W) = word (two 30-bit values) with trailing spaces
- * suppressed, 22 (L) or 31 (U) = word but map to lower/upper case, 13 (C) =
- * word in lower case with first letter capitalised, 30 (T) = text ending
- * with a word of -1, 65-73 (1-9) = number using that many characters,
- * 12 (B) = variable number of blanks. */
- if(PRMTYP == 1)return;
- if(PRMTYP == 29) goto L320;
- if(PRMTYP == 30) goto L340;
- if(PRMTYP == 12) goto L360;
- if(PRMTYP == 33 || PRMTYP == 22 || PRMTYP == 31 || PRMTYP == 13) goto
- L380;
- PRMTYP=PRMTYP-64;
- if(PRMTYP < 1 || PRMTYP > 9) goto L30;
- SHFTXT(LNPOSN+2,PRMTYP-2);
- LNPOSN=LNPOSN+PRMTYP;
- PARM=IABS(PARMS[NPARMS]);
- NEG=0;
- if(PARMS[NPARMS] < 0)NEG=9;
- /* 390 */ for (I=1; I<=PRMTYP; I++) {
- LNPOSN=LNPOSN-1;
- INLINE[LNPOSN]=MOD(PARM,10)+64;
- if(I == 1 || PARM != 0) goto L390;
- INLINE[LNPOSN]=NEG;
- NEG=0;
-L390: PARM=PARM/10;
- } /* end loop */
- LNPOSN=LNPOSN+PRMTYP;
-L395: NPARMS=NPARMS+1;
- goto L32;
-
-L320: SHFTXT(LNPOSN+2,-1);
- INLINE[LNPOSN]=55;
- if(PARMS[NPARMS] == 1)SHFTXT(LNPOSN+1,-1);
- goto L395;
-
-L340: SHFTXT(LNPOSN+2,-2);
- STATE=0;
- CASE=2;
-L345: if(PARMS[NPARMS] < 0) goto L395;
- {long x = NPARMS+1; if(PARMS[x] < 0)CASE=0;}
- PUTTXT(PARMS[NPARMS],STATE,CASE,0);
- NPARMS=NPARMS+1;
- goto L345;
-
-L360: PRMTYP=PARMS[NPARMS];
- SHFTXT(LNPOSN+2,PRMTYP-2);
- if(PRMTYP == 0) goto L395;
- for (I=1; I<=PRMTYP; I++) {
- INLINE[LNPOSN]=0;
- LNPOSN=LNPOSN+1;
- } /* end loop */
- goto L395;
-
-L380: SHFTXT(LNPOSN+2,-2);
- STATE=0;
- CASE= -1;
- if(PRMTYP == 31)CASE=1;
- if(PRMTYP == 33)CASE=0;
- I=LNPOSN;
- PUTTXT(PARMS[NPARMS],STATE,CASE,0);
- {long x = NPARMS+1; PUTTXT(PARMS[x],STATE,CASE,0);}
- if(PRMTYP == 13 && INLINE[I] >= 37 && INLINE[I] <=
- 62)INLINE[I]=INLINE[I]-26;
- NPARMS=NPARMS+2;
- goto L32;
-
-L40: if(BLANK)TYPE0();
- BLANK=false;
- TYPE();
- K=L+1;
- if(LINES[K] >= 0) goto L10;
- return;
-}
-
-
-
-#define SPEAK(N) fSPEAK(N)
-#undef PSPEAK
-void fPSPEAK(long MSG,long SKIP) {
-long I, M;
-
-/* 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 PROP=N message). */
-
-
- M=PTEXT[MSG];
- if(SKIP < 0) goto L9;
- for (I=0; I<=SKIP; I++) {
-L1: M=IABS(LINES[M]);
- if(LINES[M] >= 0) goto L1;
- /*etc*/ ;
- } /* end loop */
-L9: SPEAK(M);
- return;
-}
-
-
-
-#define PSPEAK(MSG,SKIP) fPSPEAK(MSG,SKIP)
-#undef RSPEAK
-void fRSPEAK(long I) {
-;
-
-/* Print the I-TH "random" message (section 6 of database). */
-
-
- if(I != 0)SPEAK(RTEXT[I]);
- return;
-}
-
-
-
-#define RSPEAK(I) fRSPEAK(I)
-#undef SETPRM
-void fSETPRM(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 >= 25)BUG(29);
- PARMS[FIRST]=P1;
- {long x = FIRST+1; PARMS[x]=P2;}
- return;
-}
-
-
-
-#define SETPRM(FIRST,P1,P2) fSETPRM(FIRST,P1,P2)
-#undef GETIN
-#define WORD1 (*wORD1)
-#define WORD1X (*wORD1X)
-#define WORD2 (*wORD2)
-#define WORD2X (*wORD2X)
-void fGETIN(FILE *input, long *wORD1, long *wORD1X, long *wORD2, long *wORD2X) {
-long JUNK;
-
-/* Get a command from the adventurer. Snarf out the first word, pad it with
- * blanks, and return it in WORD1. Chars 6 thru 10 are returned in WORD1X, in
- * case we need to print out the whole word in an error message. Any number of
- * blanks may follow the word. If a second word appears, it is returned in
- * WORD2 (chars 6 thru 10 in WORD2X), else WORD2 is -1. */
-
-
-L10: if(BLKLIN)TYPE0();
- MAPLIN(input);
- if(input == stdin && feof(stdin)) score(1);
- WORD1=GETTXT(true,true,true,0);
- if(BLKLIN && WORD1 < 0) goto L10;
- WORD1X=GETTXT(false,true,true,0);
-L12: JUNK=GETTXT(false,true,true,0);
- if(JUNK > 0) goto L12;
- WORD2=GETTXT(true,true,true,0);
- WORD2X=GETTXT(false,true,true,0);
-L22: JUNK=GETTXT(false,true,true,0);
- if(JUNK > 0) goto L22;
- if(GETTXT(true,true,true,0) <= 0)return;
- RSPEAK(53);
- goto L10;
-}
-
-
-
-#undef WORD1
-#undef WORD1X
-#undef WORD2
-#undef WORD2X
-#define GETIN(SRC,WORD1,WORD1X,WORD2,WORD2X) fGETIN(SRC,&WORD1,&WORD1X,&WORD2,&WORD2X)
-#undef YES
-long fYES(FILE *input, long X, long Y, long Z) {
-
-long YES, REPLY, JUNK1, JUNK2, JUNK3;
-
+#include <string.h>
+#include <stdarg.h>
+#include <sys/time.h>
+#include <ctype.h>
+#include <editline/readline.h>
+
+#include "advent.h"
+#include "dungeon.h"
+
+static void* xmalloc(size_t size)
+{
+ void* ptr = malloc(size);
+ 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);
+ // LCOV_EXCL_STOP
+ }
+ return (ptr);
+}
+
+void packed_to_token(long packed, char token[TOKLEN + 1])
+{
+ // The advent->ascii mapping.
+ const char advent_to_ascii[] = {
+ ' ', '!', '"', '#', '$', '%', '&', '\'',
+ '(', ')', '*', '+', ',', '-', '.', '/',
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', ':', ';', '<', '=', '>', '?',
+ '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
+ 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
+ 'X', 'Y', 'Z', '\0', '\0', '\0', '\0', '\0',
+ };
+
+ // Unpack and map back to ASCII.
+ for (int i = 0; i < 5; ++i) {
+ char advent = (packed >> i * 6) & 63;
+ token[i] = advent_to_ascii[(int) advent];
+ }
+
+ // Ensure the last character is \0.
+ token[5] = '\0';
+
+ // Replace trailing whitespace with \0.
+ for (int i = 4; i >= 0; --i) {
+ if (token[i] == ' ' ||
+ token[i] == '\t')
+ token[i] = '\0';
+ else
+ break;
+ }
+}
+
+long token_to_packed(const char token[])
+{
+ const char ascii_to_advent[] = {
+ 63, 63, 63, 63, 63, 63, 63, 63,
+ 63, 63, 63, 63, 63, 63, 63, 63,
+ 63, 63, 63, 63, 63, 63, 63, 63,
+ 63, 63, 63, 63, 63, 63, 63, 63,
+
+ 0, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 34, 35, 36, 37, 38, 39,
+ 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55,
+ 56, 57, 58, 59, 60, 61, 62, 63,
+
+ 63, 63, 63, 63, 63, 63, 63, 63,
+ 63, 63, 63, 63, 63, 63, 63, 63,
+ 63, 63, 63, 63, 63, 63, 63, 63,
+ 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) toupper(token[i])];
+ packed |= (mapped << (6 * i));
+ }
+ return (packed);
+}
+
+void tokenize(char* raw, struct command_t *cmd)
+{
+ memset(cmd, '\0', sizeof(struct command_t));
+
+ /* 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(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;
+}
+
+void wordclear(token_t *v)
+{
+ *v = 0;
+}
+
+/* I/O routines (speak, pspeak, rspeak, sspeak, get_input, yes) */
+
+void vspeak(const char* msg, bool blank, va_list ap)
+{
+ // Do nothing if we got a null pointer.
+ if (msg == NULL)
+ return;
+
+ // Do nothing if we got an empty string.
+ if (strlen(msg) == 0)
+ return;
+
+ if (blank == true)
+ printf("\n");
+
+ int msglen = strlen(msg);
+
+ // 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.
+ 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; // LCOV_EXCL_LINE - don't think we can get here.
+ 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] == 'd') {
+ int ret = snprintf(renderp, size, "%ld", arg);
+ if (ret < size) {
+ renderp += ret;
+ size -= ret;
+ }
+ }
+
+ // Unmodified string specifier.
+ 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] == 'S') {
+ if (previous_arg > 1) { // look at the *previous* parameter (which by necessity must be numeric)
+ *renderp++ = 's';
+ size--;
+ }
+ }
+
+ /* Version specifier */
+ if (msg[i] == 'V') {
+ strcpy(renderp, VERSION);
+ size_t len = strlen(VERSION);
+ renderp += len;
+ size -= len;
+ }
+
+ previous_arg = arg;
+ }
+ }
+ *renderp = 0;
+
+ // Print the message.
+ printf("%s\n", rendered);
+
+ free(rendered);
+}
+
+void speak(const char* msg, ...)
+{
+ va_list ap;
+ va_start(ap, msg);
+ vspeak(msg, true, ap);
+ va_end(ap);
+}
+
+void sspeak(const long msg, ...)
+{
+ va_list ap;
+ va_start(ap, msg);
+ fputc('\n', stdout);
+ vprintf(arbitrary_messages[msg], ap);
+ fputc('\n', stdout);
+ va_end(ap);
+}
+
+void pspeak(vocab_t msg, enum speaktype mode, int skip, bool blank, ...)
+/* Find the skip+1st message from msg and print it. Modes are:
+ * feel = for inventory, what you can touch
+ * look = the long description for the state the object is in
+ * listen = the sound for the state the object is in
+ * study = text on the object. */
+{
+ va_list ap;
+ va_start(ap, blank);
+ switch (mode) {
+ case touch:
+ vspeak(objects[msg].inventory, blank, ap);
+ break;
+ case look:
+ vspeak(objects[msg].descriptions[skip], blank, ap);
+ break;
+ case hear:
+ vspeak(objects[msg].sounds[skip], blank, ap);
+ break;
+ case study:
+ vspeak(objects[msg].texts[skip], blank, ap);
+ break;
+ case change:
+ vspeak(objects[msg].changes[skip], blank, ap);
+ break;
+ }
+ va_end(ap);
+}
+
+void rspeak(vocab_t i, ...)
+/* Print the i-th "random" message (section 6 of database). */
+{
+ va_list ap;
+ va_start(ap, i);
+ vspeak(arbitrary_messages[i], true, ap);
+ va_end(ap);
+}
+
+void echo_input(FILE* destination, const char* input_prompt, const 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);
+}
+
+int word_count(char* str)
+{
+ char delims[] = " \t";
+ int count = 0;
+ 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;
+ }
+ }
+
+ return (count);
+}
+
+char* get_input()
+{
+ // Set up the prompt
+ char input_prompt[] = "> ";
+ if (!settings.prompt)
+ input_prompt[0] = '\0';
+
+ // Print a blank line
+ printf("\n");
+
+ char* input;
+ while (true) {
+ input = readline(input_prompt);
+
+ if (input == NULL) // Got EOF; return with it.
+ return (input);
+ if (input[0] == '#') { // Ignore comments.
+ free(input);
+ continue;
+ }
+ // We have a 'normal' line; leave the loop.
+ break;
+ }
+
+ // Strip trailing newlines from the input
+ input[strcspn(input, "\n")] = 0;
+
+ add_history(input);
+
+ if (!isatty(0))
+ echo_input(stdout, input_prompt, input);
+
+ if (settings.logfp)
+ echo_input(settings.logfp, "", input);
+
+ return (input);
+}
+
+bool silent_yes()
+{
+ bool outcome = false;
+
+ for (;;) {
+ char* reply = get_input();
+ if (reply == NULL) {
+ // LCOV_EXCL_START
+ // Should be unreachable. Reply should never be NULL
+ free(reply);
+ 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);
+
+ free(reply);
+
+ 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) {
+ outcome = true;
+ break;
+ } else if (no == 0 ||
+ n == 0) {
+ outcome = false;
+ break;
+ } else
+ rspeak(PLEASE_ANSWER);
+ }
+ return (outcome);
+}
+
+
+bool yes(const char* question, const char* yes_response, const char* no_response)