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