xref: /openbsd-src/games/quiz/quiz.c (revision 9e6678dc8fc3c990b80a99231a6cfa6e47eddd2f)
1*9e6678dcSop /*	$OpenBSD: quiz.c,v 1.32 2022/08/08 17:54:08 op Exp $	*/
2df930be7Sderaadt /*	$NetBSD: quiz.c,v 1.9 1995/04/22 10:16:58 cgd Exp $	*/
3df930be7Sderaadt 
4df930be7Sderaadt /*-
5df930be7Sderaadt  * Copyright (c) 1991, 1993
6df930be7Sderaadt  *	The Regents of the University of California.  All rights reserved.
7df930be7Sderaadt  *
8df930be7Sderaadt  * This code is derived from software contributed to Berkeley by
9df930be7Sderaadt  * Jim R. Oldroyd at The Instruction Set and Keith Gabryelski at
10df930be7Sderaadt  * Commodore Business Machines.
11df930be7Sderaadt  *
12df930be7Sderaadt  * Redistribution and use in source and binary forms, with or without
13df930be7Sderaadt  * modification, are permitted provided that the following conditions
14df930be7Sderaadt  * are met:
15df930be7Sderaadt  * 1. Redistributions of source code must retain the above copyright
16df930be7Sderaadt  *    notice, this list of conditions and the following disclaimer.
17df930be7Sderaadt  * 2. Redistributions in binary form must reproduce the above copyright
18df930be7Sderaadt  *    notice, this list of conditions and the following disclaimer in the
19df930be7Sderaadt  *    documentation and/or other materials provided with the distribution.
207a09557bSmillert  * 3. Neither the name of the University nor the names of its contributors
21df930be7Sderaadt  *    may be used to endorse or promote products derived from this software
22df930be7Sderaadt  *    without specific prior written permission.
23df930be7Sderaadt  *
24df930be7Sderaadt  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25df930be7Sderaadt  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26df930be7Sderaadt  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27df930be7Sderaadt  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28df930be7Sderaadt  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29df930be7Sderaadt  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30df930be7Sderaadt  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31df930be7Sderaadt  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32df930be7Sderaadt  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33df930be7Sderaadt  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34df930be7Sderaadt  * SUCH DAMAGE.
35df930be7Sderaadt  */
36df930be7Sderaadt 
379ea8ec70Smestre #include <ctype.h>
389ea8ec70Smestre #include <err.h>
39df930be7Sderaadt #include <stdio.h>
40df930be7Sderaadt #include <stdlib.h>
41df930be7Sderaadt #include <string.h>
421ed0e75dSpjanzen #include <unistd.h>
439ea8ec70Smestre 
44df930be7Sderaadt #include "pathnames.h"
459ea8ec70Smestre #include "quiz.h"
46df930be7Sderaadt 
47df930be7Sderaadt static QE qlist;
48df930be7Sderaadt static int catone, cattwo, tflag;
49df930be7Sderaadt static u_int qsize;
50df930be7Sderaadt 
51c72b5b24Smillert void	 downcase(char *);
52c72b5b24Smillert void	 get_cats(char *, char *);
53c72b5b24Smillert void	 get_file(const char *);
54c72b5b24Smillert const char	*next_cat(const char *);
55c72b5b24Smillert void	 quiz(void);
56c72b5b24Smillert void	 score(u_int, u_int, u_int);
57c72b5b24Smillert void	 show_index(void);
58f0628b46Smestre __dead void	usage(void);
59df930be7Sderaadt 
60df930be7Sderaadt int
main(int argc,char * argv[])61ff8320a7Sderaadt main(int argc, char *argv[])
62df930be7Sderaadt {
631ed0e75dSpjanzen 	int ch;
645b587426Spjanzen 	const char *indexfile;
65df930be7Sderaadt 
66da74d77aStb 	if (pledge("stdio rpath proc exec", NULL) == -1)
675e0221f5Stedu 		err(1, "pledge");
685e0221f5Stedu 
69df930be7Sderaadt 	indexfile = _PATH_QUIZIDX;
706fa5e1daSmestre 	while ((ch = getopt(argc, argv, "hi:t")) != -1)
71df930be7Sderaadt 		switch(ch) {
72df930be7Sderaadt 		case 'i':
73df930be7Sderaadt 			indexfile = optarg;
74df930be7Sderaadt 			break;
75df930be7Sderaadt 		case 't':
76df930be7Sderaadt 			tflag = 1;
77df930be7Sderaadt 			break;
786fa5e1daSmestre 		case 'h':
79df930be7Sderaadt 		default:
80df930be7Sderaadt 			usage();
81df930be7Sderaadt 		}
82df930be7Sderaadt 	argc -= optind;
83df930be7Sderaadt 	argv += optind;
84df930be7Sderaadt 
85df930be7Sderaadt 	switch(argc) {
86df930be7Sderaadt 	case 0:
87df930be7Sderaadt 		get_file(indexfile);
88df930be7Sderaadt 		show_index();
89df930be7Sderaadt 		break;
90df930be7Sderaadt 	case 2:
91da74d77aStb 		if (pledge("stdio rpath", NULL) == -1)
92da74d77aStb 			err(1, "pledge");
93df930be7Sderaadt 		get_file(indexfile);
94df930be7Sderaadt 		get_cats(argv[0], argv[1]);
95b9f12921Smestre 
96b9f12921Smestre 		if (pledge("stdio", NULL) == -1)
97b9f12921Smestre 			err(1, "pledge");
98b9f12921Smestre 
99df930be7Sderaadt 		quiz();
100df930be7Sderaadt 		break;
101df930be7Sderaadt 	default:
102df930be7Sderaadt 		usage();
103df930be7Sderaadt 	}
10417641e31Stb 	return 0;
105df930be7Sderaadt }
106df930be7Sderaadt 
107df930be7Sderaadt void
get_file(const char * file)108ff8320a7Sderaadt get_file(const char *file)
109df930be7Sderaadt {
1101ed0e75dSpjanzen 	FILE *fp;
1111ed0e75dSpjanzen 	QE *qp;
1125a0076c3Snaddy 	ssize_t len;
1135a0076c3Snaddy 	size_t qlen, size;
114df930be7Sderaadt 	char *lp;
115df930be7Sderaadt 
116df930be7Sderaadt 	if ((fp = fopen(file, "r")) == NULL)
117df930be7Sderaadt 		err(1, "%s", file);
118df930be7Sderaadt 
119df930be7Sderaadt 	/*
120df930be7Sderaadt 	 * XXX
121df930be7Sderaadt 	 * Should really free up space from any earlier read list
122df930be7Sderaadt 	 * but there are no reverse pointers to do so with.
123df930be7Sderaadt 	 */
124df930be7Sderaadt 	qp = &qlist;
125df930be7Sderaadt 	qsize = 0;
1265a0076c3Snaddy 	qlen = 0;
1275a0076c3Snaddy 	lp = NULL;
1285a0076c3Snaddy 	size = 0;
1295a0076c3Snaddy 	while ((len = getline(&lp, &size, fp)) != -1) {
1301ed0e75dSpjanzen 		if (lp[len - 1] == '\n')
1315a0076c3Snaddy 			lp[--len] = '\0';
1325a0076c3Snaddy 		if (qp->q_text)
1335a0076c3Snaddy 			qlen = strlen(qp->q_text);
1345a0076c3Snaddy 		if (qlen > 0 && qp->q_text[qlen - 1] == '\\') {
1355a0076c3Snaddy 			qp->q_text[--qlen] = '\0';
1365a0076c3Snaddy 			qlen += len;
1375a0076c3Snaddy 			qp->q_text = realloc(qp->q_text, qlen + 1);
1385a0076c3Snaddy 			if (qp->q_text == NULL)
1395a0076c3Snaddy 				errx(1, "realloc");
1405a0076c3Snaddy 			strlcat(qp->q_text, lp, qlen + 1);
1415a0076c3Snaddy 		} else {
142df930be7Sderaadt 			if ((qp->q_next = malloc(sizeof(QE))) == NULL)
1431ed0e75dSpjanzen 				errx(1, "malloc");
144df930be7Sderaadt 			qp = qp->q_next;
1455a0076c3Snaddy 			qp->q_text = strdup(lp);
1465a0076c3Snaddy 			if (qp->q_text == NULL)
1475a0076c3Snaddy 				errx(1, "strdup");
148df930be7Sderaadt 			qp->q_asked = qp->q_answered = FALSE;
149df930be7Sderaadt 			qp->q_next = NULL;
150df930be7Sderaadt 			++qsize;
151df930be7Sderaadt 		}
152df930be7Sderaadt 	}
1535a0076c3Snaddy 	free(lp);
1545a0076c3Snaddy 	if (ferror(fp))
1555a0076c3Snaddy 		err(1, "getline");
156df930be7Sderaadt 	(void)fclose(fp);
157df930be7Sderaadt }
158df930be7Sderaadt 
159df930be7Sderaadt void
show_index(void)160ff8320a7Sderaadt show_index(void)
161df930be7Sderaadt {
1621ed0e75dSpjanzen 	QE *qp;
1635b587426Spjanzen 	const char *p, *s;
164da74d77aStb 	FILE *pf;
165da74d77aStb 	const char *pager;
166df930be7Sderaadt 
167da74d77aStb 	if (!isatty(1))
168da74d77aStb 		pager = "/bin/cat";
169da74d77aStb 	else if (!(pager = getenv("PAGER")) || (*pager == 0))
170da74d77aStb 			pager = _PATH_PAGER;
171da74d77aStb 	if ((pf = popen(pager, "w")) == NULL)
172da74d77aStb 		err(1, "%s", pager);
173da74d77aStb 	(void)fprintf(pf, "Subjects:\n\n");
174df930be7Sderaadt 	for (qp = qlist.q_next; qp; qp = qp->q_next) {
175df930be7Sderaadt 		for (s = next_cat(qp->q_text); s; s = next_cat(s)) {
176df930be7Sderaadt 			if (!rxp_compile(s))
177df930be7Sderaadt 				errx(1, "%s", rxperr);
1781ed0e75dSpjanzen 			if ((p = rxp_expand()))
179da74d77aStb 				(void)fprintf(pf, "%s ", p);
180df930be7Sderaadt 		}
181da74d77aStb 		(void)fprintf(pf, "\n");
182df930be7Sderaadt 	}
183da74d77aStb 	(void)fprintf(pf, "\n%s\n%s\n%s\n",
184df930be7Sderaadt "For example, \"quiz victim killer\" prints a victim's name and you reply",
185df930be7Sderaadt "with the killer, and \"quiz killer victim\" works the other way around.",
186df930be7Sderaadt "Type an empty line to get the correct answer.");
187da74d77aStb 	(void)pclose(pf);
188df930be7Sderaadt }
189df930be7Sderaadt 
190df930be7Sderaadt void
get_cats(char * cat1,char * cat2)191ff8320a7Sderaadt get_cats(char *cat1, char *cat2)
192df930be7Sderaadt {
1931ed0e75dSpjanzen 	QE *qp;
194df930be7Sderaadt 	int i;
1955b587426Spjanzen 	const char *s;
196df930be7Sderaadt 
197df930be7Sderaadt 	downcase(cat1);
198df930be7Sderaadt 	downcase(cat2);
199df930be7Sderaadt 	for (qp = qlist.q_next; qp; qp = qp->q_next) {
200df930be7Sderaadt 		s = next_cat(qp->q_text);
201df930be7Sderaadt 		catone = cattwo = i = 0;
202df930be7Sderaadt 		while (s) {
203df930be7Sderaadt 			if (!rxp_compile(s))
204df930be7Sderaadt 				errx(1, "%s", rxperr);
205df930be7Sderaadt 			i++;
206df930be7Sderaadt 			if (rxp_match(cat1))
207df930be7Sderaadt 				catone = i;
208df930be7Sderaadt 			if (rxp_match(cat2))
209df930be7Sderaadt 				cattwo = i;
210df930be7Sderaadt 			s = next_cat(s);
211df930be7Sderaadt 		}
212df930be7Sderaadt 		if (catone && cattwo && catone != cattwo) {
213df930be7Sderaadt 			if (!rxp_compile(qp->q_text))
214df930be7Sderaadt 				errx(1, "%s", rxperr);
215df930be7Sderaadt 			get_file(rxp_expand());
216df930be7Sderaadt 			return;
217df930be7Sderaadt 		}
218df930be7Sderaadt 	}
219df930be7Sderaadt 	errx(1, "invalid categories");
220df930be7Sderaadt }
221df930be7Sderaadt 
222df930be7Sderaadt void
quiz(void)223ff8320a7Sderaadt quiz(void)
224df930be7Sderaadt {
2251ed0e75dSpjanzen 	QE *qp;
2261ed0e75dSpjanzen 	int i;
227*9e6678dcSop 	size_t size;
228*9e6678dcSop 	ssize_t len;
229df930be7Sderaadt 	u_int guesses, rights, wrongs;
230df930be7Sderaadt 	int next;
2315b587426Spjanzen 	char *answer, *t, question[LINE_SZ];
2325b587426Spjanzen 	const char *s;
233df930be7Sderaadt 
234*9e6678dcSop 	size = 0;
235*9e6678dcSop 	answer = NULL;
236*9e6678dcSop 
237df930be7Sderaadt 	guesses = rights = wrongs = 0;
238df930be7Sderaadt 	for (;;) {
239df930be7Sderaadt 		if (qsize == 0)
240df930be7Sderaadt 			break;
24166e49541Snaddy 		next = arc4random_uniform(qsize);
242df930be7Sderaadt 		qp = qlist.q_next;
243df930be7Sderaadt 		for (i = 0; i < next; i++)
244df930be7Sderaadt 			qp = qp->q_next;
245df930be7Sderaadt 		while (qp && qp->q_answered)
246df930be7Sderaadt 			qp = qp->q_next;
247df930be7Sderaadt 		if (!qp) {
248df930be7Sderaadt 			qsize = next;
249df930be7Sderaadt 			continue;
250df930be7Sderaadt 		}
25166e49541Snaddy 		if (tflag && arc4random_uniform(100) > 20) {
252df930be7Sderaadt 			/* repeat questions in tutorial mode */
253df930be7Sderaadt 			while (qp && (!qp->q_asked || qp->q_answered))
254df930be7Sderaadt 				qp = qp->q_next;
255df930be7Sderaadt 			if (!qp)
256df930be7Sderaadt 				continue;
257df930be7Sderaadt 		}
258df930be7Sderaadt 		s = qp->q_text;
259df930be7Sderaadt 		for (i = 0; i < catone - 1; i++)
260df930be7Sderaadt 			s = next_cat(s);
261df930be7Sderaadt 		if (!rxp_compile(s))
262df930be7Sderaadt 			errx(1, "%s", rxperr);
263df930be7Sderaadt 		t = rxp_expand();
264df930be7Sderaadt 		if (!t || *t == '\0') {
265df930be7Sderaadt 			qp->q_answered = TRUE;
266df930be7Sderaadt 			continue;
267df930be7Sderaadt 		}
26842ceebb3Sderaadt 		(void)strlcpy(question, t, sizeof question);
269df930be7Sderaadt 		s = qp->q_text;
270df930be7Sderaadt 		for (i = 0; i < cattwo - 1; i++)
271df930be7Sderaadt 			s = next_cat(s);
27290b6e068Spjanzen 		if (s == NULL)
27390b6e068Spjanzen 			errx(1, "too few fields in data file, line \"%s\"",
27490b6e068Spjanzen 			    qp->q_text);
275df930be7Sderaadt 		if (!rxp_compile(s))
276df930be7Sderaadt 			errx(1, "%s", rxperr);
277df930be7Sderaadt 		t = rxp_expand();
278df930be7Sderaadt 		if (!t || *t == '\0') {
279df930be7Sderaadt 			qp->q_answered = TRUE;
280df930be7Sderaadt 			continue;
281df930be7Sderaadt 		}
282df930be7Sderaadt 		qp->q_asked = TRUE;
283df930be7Sderaadt 		(void)printf("%s?\n", question);
284df930be7Sderaadt 		for (;; ++guesses) {
285*9e6678dcSop 			if ((len = getline(&answer, &size, stdin)) == -1 ||
2861ed0e75dSpjanzen 			    answer[len - 1] != '\n') {
287df930be7Sderaadt 				score(rights, wrongs, guesses);
288df930be7Sderaadt 				exit(0);
289df930be7Sderaadt 			}
290df930be7Sderaadt 			answer[len - 1] = '\0';
291df930be7Sderaadt 			downcase(answer);
292df930be7Sderaadt 			if (rxp_match(answer)) {
293df930be7Sderaadt 				(void)printf("Right!\n");
294df930be7Sderaadt 				++rights;
295df930be7Sderaadt 				qp->q_answered = TRUE;
296df930be7Sderaadt 				break;
297df930be7Sderaadt 			}
298df930be7Sderaadt 			if (*answer == '\0') {
299df930be7Sderaadt 				(void)printf("%s\n", t);
300df930be7Sderaadt 				++wrongs;
301df930be7Sderaadt 				if (!tflag)
302df930be7Sderaadt 					qp->q_answered = TRUE;
303df930be7Sderaadt 				break;
304df930be7Sderaadt 			}
305df930be7Sderaadt 			(void)printf("What?\n");
306df930be7Sderaadt 		}
307df930be7Sderaadt 	}
308df930be7Sderaadt 	score(rights, wrongs, guesses);
309*9e6678dcSop 	free(answer);
310df930be7Sderaadt }
311df930be7Sderaadt 
3125b587426Spjanzen const char *
next_cat(const char * s)313ff8320a7Sderaadt next_cat(const char *s)
314df930be7Sderaadt {
3151ed0e75dSpjanzen 	int esc;
31609634613Sderaadt 
31790b6e068Spjanzen 	if (s == NULL)
31890b6e068Spjanzen 		return (NULL);
3191ed0e75dSpjanzen 	esc = 0;
320df930be7Sderaadt 	for (;;)
321df930be7Sderaadt 		switch (*s++) {
322df930be7Sderaadt 		case '\0':
323df930be7Sderaadt 			return (NULL);
324df930be7Sderaadt 		case '\\':
32509634613Sderaadt 			esc = 1;
326df930be7Sderaadt 			break;
327df930be7Sderaadt 		case ':':
32809634613Sderaadt 			if (!esc)
329df930be7Sderaadt 				return (s);
33009634613Sderaadt 		default:
33109634613Sderaadt 			esc = 0;
33209634613Sderaadt 			break;
333df930be7Sderaadt 		}
334df930be7Sderaadt }
335df930be7Sderaadt 
336df930be7Sderaadt void
score(u_int r,u_int w,u_int g)337ff8320a7Sderaadt score(u_int r, u_int w, u_int g)
338df930be7Sderaadt {
339df930be7Sderaadt 	(void)printf("Rights %d, wrongs %d,", r, w);
340df930be7Sderaadt 	if (g)
341df930be7Sderaadt 		(void)printf(" extra guesses %d,", g);
342df930be7Sderaadt 	(void)printf(" score %d%%\n", (r + w + g) ? r * 100 / (r + w + g) : 0);
343df930be7Sderaadt }
344df930be7Sderaadt 
345df930be7Sderaadt void
downcase(char * p)346ff8320a7Sderaadt downcase(char *p)
347df930be7Sderaadt {
3481ed0e75dSpjanzen 	int ch;
349df930be7Sderaadt 
3501ed0e75dSpjanzen 	for (; (ch = *p) != '\0'; ++p)
351df930be7Sderaadt 		if (isascii(ch) && isupper(ch))
352df930be7Sderaadt 			*p = tolower(ch);
353df930be7Sderaadt }
354df930be7Sderaadt 
355df930be7Sderaadt void
usage(void)356ff8320a7Sderaadt usage(void)
357df930be7Sderaadt {
358a307e700Ssobrado 	(void)fprintf(stderr,
3596fa5e1daSmestre 	    "usage: %s [-t] [-i file] category1 category2\n", getprogname());
360df930be7Sderaadt 	exit(1);
361df930be7Sderaadt }
362