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