Home | History | Annotate | Line # | Download | only in quiz
      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