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