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