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