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