xref: /original-bsd/games/quiz/quiz.c (revision e59fb703)
1 /*-
2  * Copyright (c) 1991 The Regents of the University of California.
3  * All rights reserved.
4  *
5  * This code is derived from software contributed to Berkeley by
6  * Jim R. Oldroyd at The Instruction Set.
7  *
8  * %sccs.include.redist.c%
9  */
10 
11 #ifndef lint
12 char copyright[] =
13 "@(#) Copyright (c) 1991 The Regents of the University of California.\n\
14  All rights reserved.\n";
15 #endif /* not lint */
16 
17 #ifndef lint
18 static char sccsid[] = "@(#)quiz.c	5.1 (Berkeley) 11/10/91";
19 #endif /* not lint */
20 
21 #include <sys/types.h>
22 #include <errno.h>
23 #include <time.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <ctype.h>
28 #include "quiz.h"
29 #include "pathnames.h"
30 
31 static QE qlist;
32 static int catone, cattwo, tflag;
33 static u_int qsize;
34 
35 char	*appdstr __P((char *, char *));
36 void	 downcase __P((char *));
37 void	 err __P((const char *, ...));
38 void	 get_cats __P((char *, char *));
39 void	 get_file __P((char *));
40 char	*next_cat __P((char *));
41 void	 quiz __P((void));
42 void	 score __P((u_int, u_int, u_int));
43 void	 show_index __P((void));
44 void	 usage __P((void));
45 
46 int
47 main(argc, argv)
48 	int argc;
49 	char *argv[];
50 {
51 	register int ch;
52 	char *indexfile;
53 
54 	indexfile = _PATH_QUIZIDX;
55 	while ((ch = getopt(argc, argv, "i:t")) != EOF)
56 		switch(ch) {
57 		case 'i':
58 			indexfile = optarg;
59 			break;
60 		case 't':
61 			tflag = 1;
62 			break;
63 		case '?':
64 		default:
65 			usage();
66 		}
67 	argc -= optind;
68 	argv += optind;
69 
70 	switch(argc) {
71 	case 0:
72 		get_file(indexfile);
73 		show_index();
74 		break;
75 	case 2:
76 		get_file(indexfile);
77 		get_cats(argv[0], argv[1]);
78 		quiz();
79 		break;
80 	default:
81 		usage();
82 	}
83 	exit(0);
84 }
85 
86 void
87 get_file(file)
88 	char *file;
89 {
90 	register FILE *fp;
91 	register QE *qp;
92 	size_t len;
93 	char *lp;
94 
95 	if ((fp = fopen(file, "r")) == NULL)
96 		err("%s: %s", file, strerror(errno));
97 
98 	/*
99 	 * XXX
100 	 * Should really free up space from any earlier read list
101 	 * but there are no reverse pointers to do so with.
102 	 */
103 	qp = &qlist;
104 	qsize = 0;
105 	while ((lp = fgetline(fp, &len)) != NULL) {
106 		if (qp->q_text && qp->q_text[strlen(qp->q_text) - 1] == '\\')
107 			qp->q_text = appdstr(qp->q_text, lp);
108 		else {
109 			if ((qp->q_next = malloc(sizeof(QE))) == NULL)
110 				err("%s", strerror(errno));
111 			qp = qp->q_next;
112 			if ((qp->q_text = strdup(lp)) == NULL)
113 				err("%s", strerror(errno));
114 			qp->q_asked = qp->q_answered = FALSE;
115 			qp->q_next = NULL;
116 			++qsize;
117 		}
118 	}
119 	(void)fclose(fp);
120 }
121 
122 void
123 show_index()
124 {
125 	register QE *qp;
126 	register char *p, *s;
127 	FILE *pf;
128 
129 	if ((pf = popen(_PATH_PAGER, "w")) == NULL)
130 		err("%s: %s", _PATH_PAGER, strerror(errno));
131 	(void)fprintf(pf, "Subjects:\n\n");
132 	for (qp = qlist.q_next; qp; qp = qp->q_next) {
133 		for (s = next_cat(qp->q_text); s; s = next_cat(s)) {
134 			if (!rxp_compile(s))
135 				err("%s", rxperr);
136 			if (p = rxp_expand())
137 				(void)fprintf(pf, "%s ", p);
138 		}
139 		(void)fprintf(pf, "\n");
140 	}
141 	(void)fprintf(pf, "\n%s\n%s\n%s\n",
142 "For example, \"quiz victim killer\" prints a victim's name and you reply",
143 "with the killer, and \"quiz killer victim\" works the other way around.",
144 "Type an empty line to get the correct answer.");
145 	(void)pclose(pf);
146 }
147 
148 void
149 get_cats(cat1, cat2)
150 	char *cat1, *cat2;
151 {
152 	register QE *qp;
153 	int i;
154 	char *s;
155 
156 	downcase(cat1);
157 	downcase(cat2);
158 	for (qp = qlist.q_next; qp; qp = qp->q_next) {
159 		s = next_cat(qp->q_text);
160 		catone = cattwo = i = 0;
161 		while (s) {
162 			if (!rxp_compile(s))
163 				err("%s", rxperr);
164 			i++;
165 			if (rxp_match(cat1))
166 				catone = i;
167 			if (rxp_match(cat2))
168 				cattwo = i;
169 			s = next_cat(s);
170 		}
171 		if (catone && cattwo && catone != cattwo) {
172 			if (!rxp_compile(qp->q_text))
173 				err("%s", rxperr);
174 			get_file(rxp_expand());
175 			return;
176 		}
177 	}
178 	err("invalid categories");
179 }
180 
181 void
182 quiz()
183 {
184 	register QE *qp;
185 	register int i;
186 	u_int guesses, rights, wrongs;
187 	int next;
188 	char *s, *t, question[LINE_SZ];
189 	char *answer;
190 
191 	srandom(time(NULL));
192 	guesses = rights = wrongs = 0;
193 	for (;;) {
194 		if (qsize == 0)
195 			break;
196 		next = random() % qsize;
197 		qp = qlist.q_next;
198 		for (i = 0; i < next; i++)
199 			qp = qp->q_next;
200 		while (qp && qp->q_answered)
201 			qp = qp->q_next;
202 		if (!qp) {
203 			qsize = next;
204 			continue;
205 		}
206 		if (tflag && random() % 100 > 20) {
207 			/* repeat questions in tutorial mode */
208 			while (qp && (!qp->q_asked || qp->q_answered))
209 				qp = qp->q_next;
210 			if (!qp)
211 				continue;
212 		}
213 		s = qp->q_text;
214 		for (i = 0; i < catone - 1; i++)
215 			s = next_cat(s);
216 		if (!rxp_compile(s))
217 			err("%s", rxperr);
218 		t = rxp_expand();
219 		if (!t || *t == '\0') {
220 			qp->q_answered = TRUE;
221 			continue;
222 		}
223 		(void)strcpy(question, t);
224 		s = qp->q_text;
225 		for (i = 0; i < cattwo - 1; i++)
226 			s = next_cat(s);
227 		if (!rxp_compile(s))
228 			err("%s", rxperr);
229 		t = rxp_expand();
230 		if (!t || *t == '\0') {
231 			qp->q_answered = TRUE;
232 			continue;
233 		}
234 		qp->q_asked = TRUE;
235 		(void)printf("%s?\n", question);
236 		for (;; ++guesses) {
237 			if ((answer = fgetline(stdin, NULL)) == NULL) {
238 				score(rights, wrongs, guesses);
239 				exit(0);
240 			}
241 			downcase(answer);
242 			if (rxp_match(answer)) {
243 				(void)printf("Right!\n");
244 				++rights;
245 				qp->q_answered = TRUE;
246 				break;
247 			}
248 			if (*answer == '\0') {
249 				(void)printf("%s\n", t);
250 				++wrongs;
251 				if (!tflag)
252 					qp->q_answered = TRUE;
253 				break;
254 			}
255 			(void)printf("What?\n");
256 		}
257 	}
258 	score(rights, wrongs, guesses);
259 }
260 
261 char *
262 next_cat(s)
263 	register char *	s;
264 {
265 	for (;;)
266 		switch (*s++) {
267 		case '\0':
268 			return (NULL);
269 		case '\\':
270 			break;
271 		case ':':
272 			return (s);
273 		}
274 	/* NOTREACHED */
275 }
276 
277 char *
278 appdstr(s, tp)
279 	char *s;
280 	register char *tp;
281 {
282 	register char *mp, *sp;
283 	register int ch;
284 	char *m;
285 
286 	if ((m = malloc(strlen(sp) + strlen(tp) + 1)) == NULL)
287 		err("%s", strerror(errno));
288 	for (mp = m, sp = s; *mp++ = *sp++;);
289 
290 	if (*(mp - 1) == '\\')
291 		--mp;
292 	while ((ch = *mp++ = *tp++) && ch != '\n');
293 	*mp = '\0';
294 
295 	free(s);
296 	return (m);
297 }
298 
299 void
300 score(r, w, g)
301 	u_int r, w, g;
302 {
303 	(void)printf("Rights %d, wrongs %d,", r, w);
304 	if (g)
305 		(void)printf(" extra guesses %d,", g);
306 	(void)printf(" score %d%%\n", (r + w + g) ? r * 100 / (r + w + g) : 0);
307 }
308 
309 void
310 downcase(p)
311 	register char *p;
312 {
313 	register int ch;
314 
315 	for (; ch = *p; ++p)
316 		if (isascii(ch) && isupper(ch))
317 			*p = tolower(ch);
318 }
319 
320 void
321 usage()
322 {
323 	(void)fprintf(stderr, "quiz [-t] [-i file] category1 category2\n");
324 	exit(1);
325 }
326 
327 #if __STDC__
328 #include <stdarg.h>
329 #else
330 #include <varargs.h>
331 #endif
332 
333 void
334 #if __STDC__
335 err(const char *fmt, ...)
336 #else
337 err(fmt, va_alist)
338 	char *fmt;
339         va_dcl
340 #endif
341 {
342 	va_list ap;
343 #if __STDC__
344 	va_start(ap, fmt);
345 #else
346 	va_start(ap);
347 #endif
348 	(void)fprintf(stderr, "quiz: ");
349 	(void)vfprintf(stderr, fmt, ap);
350 	va_end(ap);
351 	(void)fprintf(stderr, "\n");
352 	exit(1);
353 }
354