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