xref: /netbsd-src/games/adventure/io.c (revision 1182a44c59cae4d586117d55eca24b4b8b173211)
1 /*	$NetBSD: io.c,v 1.23 2021/05/02 12:50:43 rillig Exp $	*/
2 
3 /*-
4  * Copyright (c) 1991, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * The game adventure was originally written in Fortran by Will Crowther
8  * and Don Woods.  It was later translated to C and enhanced by Jim
9  * Gillogly.  This code is derived from software contributed to Berkeley
10  * by Jim Gillogly at The Rand Corporation.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  * 3. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  */
36 
37 #include <sys/cdefs.h>
38 #ifndef lint
39 #if 0
40 static char sccsid[] = "@(#)io.c	8.1 (Berkeley) 5/31/93";
41 #else
42 __RCSID("$NetBSD: io.c,v 1.23 2021/05/02 12:50:43 rillig Exp $");
43 #endif
44 #endif /* not lint */
45 
46 /*      Re-coding of advent in C: file i/o and user i/o                 */
47 
48 #include <err.h>
49 #include <stdio.h>
50 #include <string.h>
51 #include <stdlib.h>
52 #include "hdr.h"
53 #include "extern.h"
54 
55 static int next(void);
56 static void rdesc(int);
57 static void rdefault(void);
58 static void rhints(void);
59 static void rliq(void);
60 static void rlocs(void);
61 static int rnum(void);
62 static void rtrav(void);
63 static void rvoc(void);
64 
65 /* get command from user        */
66 /* no prompt, usually           */
67 void
getin(char ** wrd1,char ** wrd2)68 getin(char **wrd1, char **wrd2)
69 {
70 	char   *s;
71 	static char wd1buf[MAXSTR], wd2buf[MAXSTR];
72 	int     first, numch, c;
73 
74 	*wrd1 = wd1buf;				/* return ptr to internal str */
75 	*wrd2 = wd2buf;
76 	wd2buf[0] = 0;				/* in case it isn't set here */
77 	for (s = wd1buf, first = 1, numch = 0;;) {
78 		c = getchar();
79 		if ((*s = (char)c) >= 'A' && *s <= 'Z')
80 			*s = *s - ('A' - 'a');
81 		/* convert to upper case */
82 		switch (c) {			/* start reading from user */
83 		case '\n':
84 			*s = 0;
85 			return;
86 		case ' ':
87 			if (s == wd1buf || s == wd2buf)	/* initial blank */
88 				continue;
89 			*s = 0;
90 			if (first) {		/* finished 1st wd; start 2nd */
91 				first = numch = 0;
92 				s = wd2buf;
93 				break;
94 			} else {		/* finished 2nd word */
95 				FLUSHLINE;
96 				*s = 0;
97 				return;
98 			}
99 		case EOF:
100 			printf("user closed input stream, quitting...\n");
101 			exit(0);
102 		default:
103 			if (++numch >= MAXSTR) {	/* string too long */
104 				printf("Give me a break!!\n");
105 				wd1buf[0] = wd2buf[0] = 0;
106 				FLUSHLINE;
107 				return;
108 			}
109 			s++;
110 		}
111 	}
112 }
113 
114 /* confirm with rspeak          */
115 int
yes(int x,int y,int z)116 yes(int x, int y, int z)
117 {
118 	int     result = TRUE;	/* pacify gcc */
119 	int    ch;
120 	for (;;) {
121 		rspeak(x);	/* tell him what we want */
122 		if ((ch = getchar()) == 'y')
123 			result = TRUE;
124 		else if (ch == 'n')
125 			result = FALSE;
126 		else if (ch == EOF) {
127 			printf("user closed input stream, quitting...\n");
128 			exit(0);
129 		}
130 		FLUSHLINE;
131 		if (ch == 'y' || ch == 'n')
132 			break;
133 		printf("Please answer the question.\n");
134 	}
135 	if (result == TRUE)
136 		rspeak(y);
137 	if (result == FALSE)
138 		rspeak(z);
139 	return (result);
140 }
141 
142 /* confirm with mspeak          */
143 int
yesm(int x,int y,int z)144 yesm(int x, int y, int z)
145 {
146 	int     result = TRUE;	/* pacify gcc */
147 	int    ch;
148 	for (;;) {
149 		mspeak(x);	/* tell him what we want */
150 		if ((ch = getchar()) == 'y')
151 			result = TRUE;
152 		else if (ch == 'n')
153 			result = FALSE;
154 		else if (ch == EOF) {
155 			printf("user closed input stream, quitting...\n");
156 			exit(0);
157 		}
158 		FLUSHLINE;
159 		if (ch == 'y' || ch == 'n')
160 			break;
161 		printf("Please answer the question.\n");
162 	}
163 	if (result == TRUE)
164 		mspeak(y);
165 	if (result == FALSE)
166 		mspeak(z);
167 	return (result);
168 }
169 /* FILE *inbuf,*outbuf; */
170 
171 static char *inptr;		/* Pointer into virtual disk    */
172 
173 static int outsw = 0;		/* putting stuff to data file?  */
174 
175 static const char    iotape[] = "Ax3F'\003tt$8h\315qer*h\017nGKrX\207:!l";
176 static const char   *tape = iotape;	/* pointer to encryption tape   */
177 
178 /* next virtual char, bump adr  */
179 static int
next(void)180 next(void)
181 {
182 	int     ch;
183 
184 	ch = (*inptr ^ random()) & 0xFF;	/* Decrypt input data  */
185 	if (outsw) {		/* putting data in tmp file     */
186 		if (*tape == 0)
187 			tape = iotape;	/* rewind encryption tape       */
188 		*inptr = ch ^ *tape++;	/* re-encrypt and replace value */
189 	}
190 	inptr++;
191 	return (ch);
192 }
193 
194 static char breakch;		/* tell which char ended rnum   */
195 
196 /* "read" data from virtual file */
197 void
rdata(void)198 rdata(void)
199 {
200 	int     sect;
201 	char    ch;
202 
203 	inptr = data_file;	/* Pointer to virtual data file */
204 	srandom(SEED);		/* which is lightly encrypted.  */
205 
206 	classes = 1;
207 	for (;;) {		/* read data sections           */
208 		sect = next() - '0';	/* 1st digit of section number  */
209 #ifdef VERBOSE
210 		printf("Section %c", sect + '0');
211 #endif
212 		if ((ch = next()) != LF) {	/* is there a second digit?     */
213 			FLUSHLF;
214 #ifdef VERBOSE
215 			putchar(ch);
216 #endif
217 			sect = 10 * sect + ch - '0';
218 		}
219 #ifdef VERBOSE
220 		putchar('\n');
221 #endif
222 		switch (sect) {
223 		case 0:	/* finished reading database    */
224 			return;
225 		case 1:	/* long form descriptions       */
226 			rdesc(1);
227 			break;
228 		case 2:	/* short form descriptions      */
229 			rdesc(2);
230 			break;
231 		case 3:	/* travel table                 */
232 			rtrav();
233 			break;
234 		case 4:	/* vocabulary                   */
235 			rvoc();
236 			break;
237 		case 5:	/* object descriptions          */
238 			rdesc(5);
239 			break;
240 		case 6:	/* arbitrary messages           */
241 			rdesc(6);
242 			break;
243 		case 7:	/* object locations             */
244 			rlocs();
245 			break;
246 		case 8:	/* action defaults              */
247 			rdefault();
248 			break;
249 		case 9:	/* liquid assets                */
250 			rliq();
251 			break;
252 		case 10:	/* class messages               */
253 			rdesc(10);
254 			break;
255 		case 11:	/* hints                        */
256 			rhints();
257 			break;
258 		case 12:	/* magic messages               */
259 			rdesc(12);
260 			break;
261 		default:
262 			printf("Invalid data section number: %d\n", sect);
263 			for (;;)
264 				putchar(next());
265 		}
266 		if (breakch != LF)	/* routines return after "-1"   */
267 			FLUSHLF;
268 	}
269 }
270 
271 static char nbf[12];
272 
273 /* read initial location num    */
274 static int
rnum(void)275 rnum(void)
276 {
277 	char   *s;
278 	tape = iotape;		/* restart encryption tape      */
279 	for (s = nbf, *s = 0;; s++)
280 		if ((*s = next()) == TAB || *s == '\n' || *s == LF)
281 			break;
282 	breakch = *s;		/* save char for rtrav()        */
283 	*s = 0;			/* got the number as ascii      */
284 	if (nbf[0] == '-')
285 		return (-1);	/* end of data                  */
286 	return (atoi(nbf));	/* convert it to integer        */
287 }
288 
289 static char *seekhere;
290 
291 /* read description-format msgs */
292 static void
rdesc(int sect)293 rdesc(int sect)
294 {
295 	int     locc;
296 	char   *seekstart, *maystart;
297 
298 	seekhere = inptr;	/* Where are we in virtual file? */
299 	outsw = 1;		/* these msgs go into tmp file  */
300 	for (oldloc = -1, seekstart = seekhere;;) {
301 		maystart = inptr;	/* maybe starting new entry     */
302 		if ((locc = rnum()) != oldloc && oldloc >= 0 /* finished msg */
303   		    /* unless sect 5 */
304 		    && !(sect == 5 && (locc == 0 || locc >= 100))) {
305 			switch (sect) {	/* now put it into right table  */
306 			case 1:/* long descriptions            */
307 				ltext[oldloc].seekadr = seekhere;
308 				ltext[oldloc].txtlen = maystart - seekstart;
309 				break;
310 			case 2:/* short descriptions           */
311 				stext[oldloc].seekadr = seekhere;
312 				stext[oldloc].txtlen = maystart - seekstart;
313 				break;
314 			case 5:/* object descriptions          */
315 				ptext[oldloc].seekadr = seekhere;
316 				ptext[oldloc].txtlen = maystart - seekstart;
317 				break;
318 			case 6:/* random messages              */
319 				if (oldloc >= RTXSIZE)
320 					errx(1,"Too many random msgs");
321 				rtext[oldloc].seekadr = seekhere;
322 				rtext[oldloc].txtlen = maystart - seekstart;
323 				break;
324 			case 10:	/* class messages               */
325 				ctext[classes].seekadr = seekhere;
326 				ctext[classes].txtlen = maystart - seekstart;
327 				cval[classes++] = oldloc;
328 				break;
329 			case 12:	/* magic messages               */
330 				if (oldloc >= MAGSIZE)
331 					errx(1,"Too many magic msgs");
332 				mtext[oldloc].seekadr = seekhere;
333 				mtext[oldloc].txtlen = maystart - seekstart;
334 				break;
335 			default:
336 				errx(1,"rdesc called with bad section");
337 			}
338 			seekhere += maystart - seekstart;
339 		}
340 		if (locc < 0) {
341 			outsw = 0;	/* turn off output              */
342 			seekhere += 3;	/* -1<delimiter>                */
343 			return;
344 		}
345 		if (sect != 5 || (locc > 0 && locc < 100)) {
346 			if (oldloc != locc)	/* starting a new message */
347 				seekstart = maystart;
348 			oldloc = locc;
349 		}
350 		FLUSHLF;	/* scan the line                */
351 	}
352 }
353 
354 /* read travel table            */
355 static void
rtrav(void)356 rtrav(void)
357 {
358 	int     locc;
359 	struct travlist *t = NULL;
360 	char   *s;
361 	char    buf[12];
362 	int     len, m, n, entries = 0;
363 
364 	for (oldloc = -1;;) {	/* get another line             */
365 		/* end of entry */
366 		if ((locc = rnum()) != oldloc && oldloc >= 0 && t) {
367 			t->next = 0;	/* terminate the old entry      */
368 			/* printf("%d:%d entries\n",oldloc,entries);       */
369 			/* twrite(oldloc);                                 */
370 		}
371 		if (locc == -1)
372 			return;
373 		if (locc != oldloc) {	/* getting a new entry         */
374 			t = travel[locc] = calloc(1, sizeof(*t));
375 			if (t == NULL)
376 				err(1, NULL);
377 			/* printf("New travel list for %d\n",locc);        */
378 			entries = 0;
379 			oldloc = locc;
380 		}
381 		for (s = buf;; s++)	/* get the newloc number /ASCII */
382 			if ((*s = next()) == TAB || *s == LF)
383 				break;
384 		*s = 0;
385 		len = length(buf) - 1;	/* quad long number handling    */
386 		/* printf("Newloc: %s (%d chars)\n",buf,len);              */
387 		if (len < 4) {	/* no "m" conditions            */
388 			m = 0;
389 			n = atoi(buf);	/* newloc mod 1000 = newloc     */
390 		} else {	/* a long integer               */
391 			n = atoi(buf + len - 3);
392 			buf[len - 3] = 0;	/* terminate newloc/1000  */
393 			m = atoi(buf);
394 		}
395 		while (breakch != LF) {	/* only do one line at a time   */
396 			if (t == NULL)
397 				abort();
398 			if (entries++) {
399 				t->next = calloc(1, sizeof(*t));
400 				if (t->next == NULL)
401 					err(1, NULL);
402 				t = t->next;
403 			}
404 			t->tverb = rnum();	/* get verb from the file */
405 			t->tloc = n;	/* table entry mod 1000         */
406 			t->conditions = m;	/* table entry / 1000   */
407 			/* printf("entry %d for %d\n",entries,locc);    */
408 		}
409 	}
410 }
411 #ifdef DEBUG
412 
413 /* travel options from this loc */
414 void
twrite(int loq)415 twrite(int loq)
416 {
417 	struct travlist *t;
418 	printf("If");
419 	speak(&ltext[loq]);
420 	printf("then\n");
421 	for (t = travel[loq]; t != 0; t = t->next) {
422 		printf("verb %d takes you to ", t->tverb);
423 		if (t->tloc <= 300)
424 			speak(&ltext[t->tloc]);
425 		else
426 			if (t->tloc <= 500)
427 				printf("special code %d\n", t->tloc - 300);
428 			else
429 				rspeak(t->tloc - 500);
430 		printf("under conditions %d\n", t->conditions);
431 	}
432 }
433 #endif				/* DEBUG */
434 
435 /* read the vocabulary          */
436 static void
rvoc(void)437 rvoc(void)
438 {
439 	char   *s;
440 	int     idx;
441 	char    buf[6];
442 	for (;;) {
443 		idx = rnum();
444 		if (idx < 0)
445 			break;
446 		for (s = buf, *s = 0;; s++)	/* get the word  */
447 			if ((*s = next()) == TAB || *s == '\n' || *s == LF
448 			    || *s == ' ')
449 				break;
450 		/* terminate word with newline, LF, tab, blank  */
451 		if (*s != '\n' && *s != LF)
452 			FLUSHLF;/* can be comments    */
453 		*s = 0;
454 		/* printf("\"%s\"=%d\n",buf,idx); */
455 		vocab(buf, -2, idx);
456 	}
457 /*	prht();	*/
458 }
459 
460 /* initial object locations     */
461 static void
rlocs(void)462 rlocs(void)
463 {
464 	for (;;) {
465 		if ((obj = rnum()) < 0)
466 			break;
467 		plac[obj] = rnum();	/* initial loc for this obj     */
468 		if (breakch == TAB)	/* there's another entry        */
469 			fixd[obj] = rnum();
470 		else
471 			fixd[obj] = 0;
472 	}
473 }
474 
475 /* default verb messages        */
476 static void
rdefault(void)477 rdefault(void)
478 {
479 	for (;;) {
480 		if ((verb = rnum()) < 0)
481 			break;
482 		actspeak[verb] = rnum();
483 	}
484 }
485 
486 /* liquid assets &c: cond bits  */
487 static void
rliq(void)488 rliq(void)
489 {
490 	int     bitnum;
491 	for (;;) {		/* read new bit list            */
492 		if ((bitnum = rnum()) < 0)
493 			break;
494 		for (;;) {	/* read locs for bits           */
495 			int n = rnum();
496 			if (n < 0)
497 				break;
498 			cond[n] |= setbit[bitnum];
499 			if (breakch == LF)
500 				break;
501 		}
502 	}
503 }
504 
505 static void
rhints(void)506 rhints(void)
507 {
508 	int     hintnum, i;
509 	hintmax = 0;
510 	for (;;) {
511 		if ((hintnum = rnum()) < 0)
512 			break;
513 		for (i = 1; i < 5; i++)
514 			hints[hintnum][i] = rnum();
515 		if (hintnum > hintmax)
516 			hintmax = hintnum;
517 	}
518 }
519 
520 
521 void
rspeak(int msg)522 rspeak(int msg)
523 {
524 	if (msg != 0)
525 		speak(&rtext[msg]);
526 }
527 
528 
529 void
mspeak(int msg)530 mspeak(int msg)
531 {
532 	if (msg != 0)
533 		speak(&mtext[msg]);
534 }
535 
536 
537 /* read, decrypt, and print a message (not ptext)      */
538 /* msg is a pointer to seek address and length of mess */
539 void
speak(const struct text * msg)540 speak(const struct text *msg)
541 {
542 	char   *s, nonfirst;
543 
544 	s = msg->seekadr;
545 	nonfirst = 0;
546 	while (s - msg->seekadr < msg->txtlen) { /* read a line at a time */
547 		tape = iotape;	/* restart decryption tape      */
548 		while ((*s++ ^ *tape++) != TAB); /* read past loc num       */
549 		/* assume tape is longer than location number           */
550 		/* plus the lookahead put together                    */
551 		if ((*s ^ *tape) == '>' &&
552 		    (*(s + 1) ^ *(tape + 1)) == '$' &&
553 		    (*(s + 2) ^ *(tape + 2)) == '<')
554 			break;
555 		if (blklin && !nonfirst++)
556 			putchar('\n');
557 		do {
558 			if (*tape == 0)
559 				tape = iotape;	/* rewind decryp tape */
560 			putchar(*s ^ *tape);
561 		} while ((*s++ ^ *tape++) != LF); /* better end with LF   */
562 	}
563 }
564 
565 /* read, decrypt and print a ptext message  */
566 /* msg is the number of all the p msgs for this place  */
567 /* assumes object 1 doesn't have prop 1, obj 2 no prop 2 &c */
568 void
pspeak(int m,int skip)569 pspeak(int m, int skip)
570 {
571 	char   *s, nonfirst;
572 	char   *numst;
573 	struct text *msg;
574 	char   *tbuf;
575 
576 	msg = &ptext[m];
577 	if ((tbuf = (char *) malloc(msg->txtlen + 1)) == NULL)
578 		err(1, NULL);
579 	memcpy(tbuf, msg->seekadr, msg->txtlen + 1);	/* Room to null */
580 	s = tbuf;
581 
582 	nonfirst = 0;
583 	while (s - tbuf < msg->txtlen) {	/* read line at a time */
584 		tape = iotape;	/* restart decryption tape      */
585 		for (numst = s; (*s ^= *tape++) != TAB; s++); /* get number  */
586 
587 				/* Temporarily trash the string (cringe) */
588 		*s++ = 0;	/* decrypting number within the string   */
589 
590 		if (atoi(numst) != 100 * skip && skip >= 0) {
591 			while ((*s++ ^ *tape++) != LF)	/* flush the line    */
592 				if (*tape == 0)
593 					tape = iotape;
594 			continue;
595 		}
596 		if ((*s ^ *tape) == '>' && (*(s + 1) ^ *(tape + 1)) == '$' &&
597 		    (*(s + 2) ^ *(tape + 2)) == '<')
598 			break;
599 		if (blklin && !nonfirst++)
600 			putchar('\n');
601 		do {
602 			if (*tape == 0)
603 				tape = iotape;
604 			putchar(*s ^ *tape);
605 		} while ((*s++ ^ *tape++) != LF); /* better end with LF   */
606 		if (skip < 0)
607 			break;
608 	}
609 	free(tbuf);
610 }
611