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