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