xref: /netbsd-src/games/quiz/quiz.c (revision fdecd6a253f999ae92b139670d9e15cc9df4497c)
1 /*	$NetBSD: quiz.c,v 1.11 1997/07/06 11:19:16 mycroft Exp $	*/
2 
3 /*-
4  * Copyright (c) 1991, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Jim R. Oldroyd at The Instruction Set and Keith Gabryelski at
9  * Commodore Business Machines.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. All advertising materials mentioning features or use of this software
20  *    must display the following acknowledgement:
21  *	This product includes software developed by the University of
22  *	California, Berkeley and its contributors.
23  * 4. Neither the name of the University nor the names of its contributors
24  *    may be used to endorse or promote products derived from this software
25  *    without specific prior written permission.
26  *
27  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37  * SUCH DAMAGE.
38  */
39 
40 #ifndef lint
41 static char copyright[] =
42 "@(#) Copyright (c) 1991, 1993\n\
43 	The Regents of the University of California.  All rights reserved.\n";
44 #endif /* not lint */
45 
46 #ifndef lint
47 #if 0
48 static char sccsid[] = "@(#)quiz.c	8.3 (Berkeley) 5/4/95";
49 #else
50 static char rcsid[] = "$NetBSD: quiz.c,v 1.11 1997/07/06 11:19:16 mycroft Exp $";
51 #endif
52 #endif /* not lint */
53 
54 #include <sys/types.h>
55 
56 #include <ctype.h>
57 #include <errno.h>
58 #include <stdio.h>
59 #include <stdlib.h>
60 #include <string.h>
61 #include <ctype.h>
62 #include <err.h>
63 #include <time.h>
64 #include <unistd.h>
65 #include "quiz.h"
66 #include "pathnames.h"
67 
68 static QE qlist;
69 static int catone, cattwo, tflag;
70 static u_int qsize;
71 
72 char	*appdstr __P((char *, char *, size_t));
73 void	 downcase __P((char *));
74 void	 get_cats __P((char *, char *));
75 void	 get_file __P((char *));
76 char	*next_cat __P((char *));
77 void	 quiz __P((void));
78 void	 score __P((u_int, u_int, u_int));
79 void	 show_index __P((void));
80 void	 usage __P((void));
81 
82 int
83 main(argc, argv)
84 	int argc;
85 	char *argv[];
86 {
87 	register int ch;
88 	char *indexfile;
89 
90 	indexfile = _PATH_QUIZIDX;
91 	while ((ch = getopt(argc, argv, "i:t")) != EOF)
92 		switch(ch) {
93 		case 'i':
94 			indexfile = optarg;
95 			break;
96 		case 't':
97 			tflag = 1;
98 			break;
99 		case '?':
100 		default:
101 			usage();
102 		}
103 	argc -= optind;
104 	argv += optind;
105 
106 	switch(argc) {
107 	case 0:
108 		get_file(indexfile);
109 		show_index();
110 		break;
111 	case 2:
112 		get_file(indexfile);
113 		get_cats(argv[0], argv[1]);
114 		quiz();
115 		break;
116 	default:
117 		usage();
118 	}
119 	exit(0);
120 }
121 
122 void
123 get_file(file)
124 	char *file;
125 {
126 	register FILE *fp;
127 	register QE *qp;
128 	size_t len;
129 	char *lp;
130 
131 	if ((fp = fopen(file, "r")) == NULL)
132 		err(1, "%s", file);
133 
134 	/*
135 	 * XXX
136 	 * Should really free up space from any earlier read list
137 	 * but there are no reverse pointers to do so with.
138 	 */
139 	qp = &qlist;
140 	qsize = 0;
141 	while ((lp = fgetln(fp, &len)) != NULL) {
142 		lp[--len] = '\0';
143 		if (qp->q_text && qp->q_text[strlen(qp->q_text) - 1] == '\\')
144 			qp->q_text = appdstr(qp->q_text, lp, len);
145 		else {
146 			if ((qp->q_next = malloc(sizeof(QE))) == NULL)
147 				err(1, NULL);
148 			qp = qp->q_next;
149 			if ((qp->q_text = strdup(lp)) == NULL)
150 				err(1, NULL);
151 			qp->q_asked = qp->q_answered = FALSE;
152 			qp->q_next = NULL;
153 			++qsize;
154 		}
155 	}
156 	(void)fclose(fp);
157 }
158 
159 void
160 show_index()
161 {
162 	register QE *qp;
163 	register char *p, *s;
164 	FILE *pf;
165 
166 	if ((pf = popen(_PATH_PAGER, "w")) == NULL)
167 		err(1, "%s", _PATH_PAGER);
168 	(void)fprintf(pf, "Subjects:\n\n");
169 	for (qp = qlist.q_next; qp; qp = qp->q_next) {
170 		for (s = next_cat(qp->q_text); s; s = next_cat(s)) {
171 			if (!rxp_compile(s))
172 				errx(1, "%s", rxperr);
173 			if (p = rxp_expand())
174 				(void)fprintf(pf, "%s ", p);
175 		}
176 		(void)fprintf(pf, "\n");
177 	}
178 	(void)fprintf(pf, "\n%s\n%s\n%s\n",
179 "For example, \"quiz victim killer\" prints a victim's name and you reply",
180 "with the killer, and \"quiz killer victim\" works the other way around.",
181 "Type an empty line to get the correct answer.");
182 	(void)pclose(pf);
183 }
184 
185 void
186 get_cats(cat1, cat2)
187 	char *cat1, *cat2;
188 {
189 	register QE *qp;
190 	int i;
191 	char *s;
192 
193 	downcase(cat1);
194 	downcase(cat2);
195 	for (qp = qlist.q_next; qp; qp = qp->q_next) {
196 		s = next_cat(qp->q_text);
197 		catone = cattwo = i = 0;
198 		while (s) {
199 			if (!rxp_compile(s))
200 				errx(1, "%s", rxperr);
201 			i++;
202 			if (rxp_match(cat1))
203 				catone = i;
204 			if (rxp_match(cat2))
205 				cattwo = i;
206 			s = next_cat(s);
207 		}
208 		if (catone && cattwo && catone != cattwo) {
209 			if (!rxp_compile(qp->q_text))
210 				errx(1, "%s", rxperr);
211 			get_file(rxp_expand());
212 			return;
213 		}
214 	}
215 	errx(1, "invalid categories");
216 }
217 
218 void
219 quiz()
220 {
221 	register QE *qp;
222 	register int i;
223 	size_t len;
224 	u_int guesses, rights, wrongs;
225 	int next;
226 	char *answer, *s, *t, question[LINE_SZ];
227 
228 	srandom(time(NULL));
229 	guesses = rights = wrongs = 0;
230 	for (;;) {
231 		if (qsize == 0)
232 			break;
233 		next = random() % qsize;
234 		qp = qlist.q_next;
235 		for (i = 0; i < next; i++)
236 			qp = qp->q_next;
237 		while (qp && qp->q_answered)
238 			qp = qp->q_next;
239 		if (!qp) {
240 			qsize = next;
241 			continue;
242 		}
243 		if (tflag && random() % 100 > 20) {
244 			/* repeat questions in tutorial mode */
245 			while (qp && (!qp->q_asked || qp->q_answered))
246 				qp = qp->q_next;
247 			if (!qp)
248 				continue;
249 		}
250 		s = qp->q_text;
251 		for (i = 0; i < catone - 1; i++)
252 			s = next_cat(s);
253 		if (!rxp_compile(s))
254 			errx(1, "%s", rxperr);
255 		t = rxp_expand();
256 		if (!t || *t == '\0') {
257 			qp->q_answered = TRUE;
258 			continue;
259 		}
260 		(void)strcpy(question, t);
261 		s = qp->q_text;
262 		for (i = 0; i < cattwo - 1; i++)
263 			s = next_cat(s);
264 		if (!rxp_compile(s))
265 			errx(1, "%s", rxperr);
266 		t = rxp_expand();
267 		if (!t || *t == '\0') {
268 			qp->q_answered = TRUE;
269 			continue;
270 		}
271 		qp->q_asked = TRUE;
272 		(void)printf("%s?\n", question);
273 		for (;; ++guesses) {
274 			if ((answer = fgetln(stdin, &len)) == NULL) {
275 				score(rights, wrongs, guesses);
276 				exit(0);
277 			}
278 			answer[len - 1] = '\0';
279 			downcase(answer);
280 			if (rxp_match(answer)) {
281 				(void)printf("Right!\n");
282 				++rights;
283 				qp->q_answered = TRUE;
284 				break;
285 			}
286 			if (*answer == '\0') {
287 				(void)printf("%s\n", t);
288 				++wrongs;
289 				if (!tflag)
290 					qp->q_answered = TRUE;
291 				break;
292 			}
293 			(void)printf("What?\n");
294 		}
295 	}
296 	score(rights, wrongs, guesses);
297 }
298 
299 char *
300 next_cat(s)
301 	register char *	s;
302 {
303 	int esc;
304 
305 	esc = 0;
306 	for (;;)
307 		switch (*s++) {
308 		case '\0':
309 			return (NULL);
310 		case '\\':
311 			esc = 1;
312 			break;
313 		case ':':
314 			if (!esc)
315 				return (s);
316 		default:
317 			esc = 0;
318 			break;
319 		}
320 	/* NOTREACHED */
321 }
322 
323 char *
324 appdstr(s, tp, len)
325 	char *s;
326 	register char *tp;
327 	size_t len;
328 {
329 	register char *mp, *sp;
330 	register int ch;
331 	char *m;
332 
333 	if ((m = malloc(strlen(s) + len + 1)) == NULL)
334 		err(1, NULL);
335 	for (mp = m, sp = s; *mp++ = *sp++;);
336 	--mp;
337 	if (*(mp - 1) == '\\')
338 		--mp;
339 
340 	while ((ch = *mp++ = *tp++) && ch != '\n');
341 	*mp = '\0';
342 
343 	free(s);
344 	return (m);
345 }
346 
347 void
348 score(r, w, g)
349 	u_int r, w, g;
350 {
351 	(void)printf("Rights %d, wrongs %d,", r, w);
352 	if (g)
353 		(void)printf(" extra guesses %d,", g);
354 	(void)printf(" score %d%%\n", (r + w + g) ? r * 100 / (r + w + g) : 0);
355 }
356 
357 void
358 downcase(p)
359 	register char *p;
360 {
361 	register int ch;
362 
363 	for (; ch = *p; ++p)
364 		if (isascii(ch) && isupper(ch))
365 			*p = tolower(ch);
366 }
367 
368 void
369 usage()
370 {
371 	(void)fprintf(stderr, "quiz [-t] [-i file] category1 category2\n");
372 	exit(1);
373 }
374