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