139101Sbostic /*
239101Sbostic  * Copyright (c) 1989 The Regents of the University of California.
339101Sbostic  * All rights reserved.
439101Sbostic  *
539101Sbostic  * This code is derived from software contributed to Berkeley by
639101Sbostic  * Eamonn McManus of Trinity College Dublin.
739101Sbostic  *
839101Sbostic  * Redistribution and use in source and binary forms are permitted
939101Sbostic  * provided that the above copyright notice and this paragraph are
1039101Sbostic  * duplicated in all such forms and that any documentation,
1139101Sbostic  * advertising materials, and other materials related to such
1239101Sbostic  * distribution and use acknowledge that the software was developed
1339101Sbostic  * by the University of California, Berkeley.  The name of the
1439101Sbostic  * University may not be used to endorse or promote products derived
1539101Sbostic  * from this software without specific prior written permission.
1639101Sbostic  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
1739101Sbostic  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
1839101Sbostic  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
1939101Sbostic  */
208852Smckusick 
2139101Sbostic #ifndef lint
2239101Sbostic char copyright[] =
2339101Sbostic "@(#) Copyright (c) 1989 The Regents of the University of California.\n\
2439101Sbostic  All rights reserved.\n";
2539101Sbostic #endif /* not lint */
268852Smckusick 
2739101Sbostic #ifndef lint
28*42017Sbostic static char sccsid[] = "@(#)arithmetic.c	5.3 (Berkeley) 05/15/90";
2939101Sbostic #endif /* not lint */
3039101Sbostic 
3139101Sbostic /*
3239101Sbostic  * By Eamonn McManus, Trinity College Dublin <emcmanus@cs.tcd.ie>.
3339101Sbostic  *
3439101Sbostic  * The operation of this program mimics that of the standard Unix game
3539101Sbostic  * `arithmetic'.  I've made it as close as I could manage without examining
3639101Sbostic  * the source code.  The principal differences are:
3739101Sbostic  *
3839101Sbostic  * The method of biasing towards numbers that had wrong answers in the past
3939101Sbostic  * is different; original `arithmetic' seems to retain the bias forever,
4039101Sbostic  * whereas this program lets the bias gradually decay as it is used.
4139101Sbostic  *
4239101Sbostic  * Original `arithmetic' delays for some period (3 seconds?) after printing
4339101Sbostic  * the score.  I saw no reason for this delay, so I scrapped it.
4439101Sbostic  *
4539101Sbostic  * There is no longer a limitation on the maximum range that can be supplied
4639101Sbostic  * to the program.  The original program required it to be less than 100.
4739101Sbostic  * Anomalous results may occur with this program if ranges big enough to
4839101Sbostic  * allow overflow are given.
4939101Sbostic  *
5039101Sbostic  * I have obviously not attempted to duplicate bugs in the original.  It
5139101Sbostic  * would go into an infinite loop if invoked as `arithmetic / 0'.  It also
5239101Sbostic  * did not recognise an EOF in its input, and would continue trying to read
5339101Sbostic  * after it.  It did not check that the input was a valid number, treating any
5439101Sbostic  * garbage as 0.  Finally, it did not flush stdout after printing its prompt,
5539101Sbostic  * so in the unlikely event that stdout was not a terminal, it would not work
5639101Sbostic  * properly.
5739101Sbostic  */
5839101Sbostic 
5939101Sbostic #include <sys/types.h>
6039101Sbostic #include <sys/signal.h>
6139101Sbostic #include <ctype.h>
628852Smckusick #include <stdio.h>
63*42017Sbostic #include <string.h>
648852Smckusick 
6539101Sbostic char keylist[] = "+-x/";
6639101Sbostic char defaultkeys[] = "+-";
6739101Sbostic char *keys = defaultkeys;
6839101Sbostic int nkeys = sizeof(defaultkeys) - 1;
6939101Sbostic int rangemax = 10;
7039101Sbostic int nright, nwrong;
7139101Sbostic time_t qtime;
7239101Sbostic #define	NQUESTS	20
738852Smckusick 
7439101Sbostic /*
7539101Sbostic  * Select keys from +-x/ to be asked addition, subtraction, multiplication,
7639101Sbostic  * and division problems.  More than one key may be given.  The default is
7739101Sbostic  * +-.  Specify a range to confine the operands to 0 - range.  Default upper
7839101Sbostic  * bound is 10.  After every NQUESTS questions, statistics on the performance
7939101Sbostic  * so far are printed.
8039101Sbostic  */
8139101Sbostic void
8239101Sbostic main(argc, argv)
8339101Sbostic 	int argc;
8439101Sbostic 	char **argv;
858852Smckusick {
8639101Sbostic 	extern char *optarg;
8739101Sbostic 	extern int optind;
8839101Sbostic 	int ch, cnt;
8939101Sbostic 	time_t time();
9039101Sbostic 	sig_t intr();
918852Smckusick 
9239101Sbostic 	while ((ch = getopt(argc, argv, "r:o:")) != EOF)
9339101Sbostic 		switch(ch) {
9439101Sbostic 		case 'o': {
9539101Sbostic 			register char *p;
968852Smckusick 
9739101Sbostic 			for (p = keys = optarg; *p; ++p)
9839101Sbostic 				if (!index(keylist, *p)) {
9939101Sbostic 					(void)fprintf(stderr,
10039101Sbostic 					    "arithmetic: unknown key.\n");
10139101Sbostic 					exit(1);
10239101Sbostic 				}
10339101Sbostic 			nkeys = p - optarg;
1048852Smckusick 			break;
10539101Sbostic 		}
10639101Sbostic 		case 'r':
10739101Sbostic 			if ((rangemax = atoi(optarg)) <= 0) {
10839101Sbostic 				(void)fprintf(stderr,
10939101Sbostic 				    "arithmetic: invalid range.\n");
11039101Sbostic 				exit(1);
11139101Sbostic 			}
11239101Sbostic 			break;
11339101Sbostic 		case '?':
1148852Smckusick 		default:
11539101Sbostic 			usage();
1168852Smckusick 		}
11739101Sbostic 	if (argc -= optind)
11839101Sbostic 		usage();
1198852Smckusick 
12039101Sbostic 	/* Seed the random-number generator. */
12139101Sbostic 	srandom((int)time((time_t *)NULL));
1228852Smckusick 
12339101Sbostic 	(void)signal(SIGINT, intr);
1248852Smckusick 
12539101Sbostic 	/* Now ask the questions. */
12639101Sbostic 	for (;;) {
12739101Sbostic 		for (cnt = NQUESTS; cnt--;)
12839101Sbostic 			if (problem() == EOF)
12939101Sbostic 				exit(0);
13039101Sbostic 		showstats();
1318852Smckusick 	}
13239101Sbostic 	/* NOTREACHED */
13339101Sbostic }
1348852Smckusick 
13539101Sbostic /* Handle interrupt character.  Print score and exit. */
13639101Sbostic sig_t
13739101Sbostic intr()
13839101Sbostic {
13939101Sbostic 	showstats();
14039101Sbostic 	exit(0);
14139101Sbostic }
1428852Smckusick 
14339101Sbostic /* Print score.  Original `arithmetic' had a delay after printing it. */
14439101Sbostic showstats()
14539101Sbostic {
14639101Sbostic 	if (nright + nwrong > 0) {
14739101Sbostic 		(void)printf("\n\nRights %d; Wrongs %d; Score %d%%",
14839101Sbostic 		    nright, nwrong, (int)(100L * nright / (nright + nwrong)));
14939101Sbostic 		if (nright > 0)
15039101Sbostic 	(void)printf("\nTotal time %ld seconds; %.1f seconds per problem\n\n",
15139101Sbostic 			    (long)qtime, (float)qtime / nright);
1528852Smckusick 	}
15339101Sbostic 	(void)printf("\n");
1548852Smckusick }
1558852Smckusick 
15639101Sbostic /*
15739101Sbostic  * Pick a problem and ask it.  Keeps asking the same problem until supplied
15839101Sbostic  * with the correct answer, or until EOF or interrupt is typed.  Problems are
15939101Sbostic  * selected such that the right operand and either the left operand (for +, x)
16039101Sbostic  * or the correct result (for -, /) are in the range 0 to rangemax.  Each wrong
16139101Sbostic  * answer causes the numbers in the problem to be penalised, so that they are
16239101Sbostic  * more likely to appear in subsequent problems.
16339101Sbostic  */
16439101Sbostic problem()
1658852Smckusick {
16639101Sbostic 	register char *p;
16739101Sbostic 	time_t start, finish;
16839101Sbostic 	int left, op, right, result;
16939101Sbostic 	char line[80];
1708852Smckusick 
17139101Sbostic 	op = keys[random() % nkeys];
17239101Sbostic 	if (op != '/')
17339101Sbostic 		right = getrandom(rangemax + 1, op, 1);
17439101Sbostic retry:
17539101Sbostic 	/* Get the operands. */
17639101Sbostic 	switch (op) {
17739101Sbostic 	case '+':
17839101Sbostic 		left = getrandom(rangemax + 1, op, 0);
17939101Sbostic 		result = left + right;
18039101Sbostic 		break;
18139101Sbostic 	case '-':
18239101Sbostic 		result = getrandom(rangemax + 1, op, 0);
18339101Sbostic 		left = right + result;
18439101Sbostic 		break;
18539101Sbostic 	case 'x':
18639101Sbostic 		left = getrandom(rangemax + 1, op, 0);
18739101Sbostic 		result = left * right;
18839101Sbostic 		break;
18939101Sbostic 	case '/':
19039101Sbostic 		right = getrandom(rangemax, op, 1) + 1;
19139101Sbostic 		result = getrandom(rangemax + 1, op, 0);
19239101Sbostic 		left = right * result + random() % right;
19339101Sbostic 		break;
19439101Sbostic 	}
1958852Smckusick 
19639101Sbostic 	/*
19739101Sbostic 	 * A very big maxrange could cause negative values to pop
19839101Sbostic 	 * up, owing to overflow.
19939101Sbostic 	 */
20039101Sbostic 	if (result < 0 || left < 0)
20139101Sbostic 		goto retry;
20239101Sbostic 
20339101Sbostic 	(void)printf("%d %c %d =   ", left, op, right);
20439101Sbostic 	(void)fflush(stdout);
20539101Sbostic 	(void)time(&start);
20639101Sbostic 
20739101Sbostic 	/*
20839101Sbostic 	 * Keep looping until the correct answer is given, or until EOF or
20939101Sbostic 	 * interrupt is typed.
21039101Sbostic 	 */
21139101Sbostic 	for (;;) {
21239101Sbostic 		if (!fgets(line, sizeof(line), stdin)) {
21339101Sbostic 			(void)printf("\n");
21439101Sbostic 			return(EOF);
2158852Smckusick 		}
21639101Sbostic 		for (p = line; *p && isspace(*p); ++p);
21739101Sbostic 		if (!isdigit(*p)) {
21839101Sbostic 			(void)printf("Please type a number.\n");
21939101Sbostic 			continue;
22039101Sbostic 		}
22139101Sbostic 		if (atoi(p) == result) {
22239101Sbostic 			(void)printf("Right!\n");
22339101Sbostic 			++nright;
22439101Sbostic 			break;
22539101Sbostic 		}
22639101Sbostic 		/* Wrong answer; penalise and ask again. */
22739101Sbostic 		(void)printf("What?\n");
22839101Sbostic 		++nwrong;
22939101Sbostic 		penalise(right, op, 1);
23039101Sbostic 		if (op == 'x' || op == '+')
23139101Sbostic 			penalise(left, op, 0);
2328852Smckusick 		else
23339101Sbostic 			penalise(result, op, 0);
23439101Sbostic 	}
23539101Sbostic 
23639101Sbostic 	/*
23739101Sbostic 	 * Accumulate the time taken.  Obviously rounding errors happen here;
23839101Sbostic 	 * however they should cancel out, because some of the time you are
23939101Sbostic 	 * charged for a partially elapsed second at the start, and some of
24039101Sbostic 	 * the time you are not charged for a partially elapsed second at the
24139101Sbostic 	 * end.
24239101Sbostic 	 */
24339101Sbostic 	(void)time(&finish);
24439101Sbostic 	qtime += finish - start;
24539101Sbostic 	return(0);
2468852Smckusick }
2478852Smckusick 
24839101Sbostic /*
24939101Sbostic  * Here is the code for accumulating penalties against the numbers for which
25039101Sbostic  * a wrong answer was given.  The right operand and either the left operand
25139101Sbostic  * (for +, x) or the result (for -, /) are stored in a list for the particular
25239101Sbostic  * operation, and each becomes more likely to appear again in that operation.
25339101Sbostic  * Initially, each number is charged a penalty of WRONGPENALTY, giving it that
25439101Sbostic  * many extra chances of appearing.  Each time it is selected because of this,
25539101Sbostic  * its penalty is decreased by one; it is removed when it reaches 0.
25639101Sbostic  *
25739101Sbostic  * The penalty[] array gives the sum of all penalties in the list for
25839101Sbostic  * each operation and each operand.  The penlist[] array has the lists of
25939101Sbostic  * penalties themselves.
26039101Sbostic  */
2618852Smckusick 
26239101Sbostic int penalty[sizeof(keylist) - 1][2];
26339101Sbostic struct penalty {
26439101Sbostic 	int value, penalty;	/* Penalised value and its penalty. */
26539101Sbostic 	struct penalty *next;
26639101Sbostic } *penlist[sizeof(keylist) - 1][2];
2678852Smckusick 
26839101Sbostic #define	WRONGPENALTY	5	/* Perhaps this should depend on maxrange. */
2698852Smckusick 
27039101Sbostic /*
27139101Sbostic  * Add a penalty for the number `value' to the list for operation `op',
27239101Sbostic  * operand number `operand' (0 or 1).  If we run out of memory, we just
27339101Sbostic  * forget about the penalty (how likely is this, anyway?).
27439101Sbostic  */
27539101Sbostic penalise(value, op, operand)
27639101Sbostic 	int value, op, operand;
2778852Smckusick {
27839101Sbostic 	struct penalty *p;
27939101Sbostic 	char *malloc();
28039101Sbostic 
28139101Sbostic 	op = opnum(op);
28239101Sbostic 	if ((p = (struct penalty *)malloc((u_int)sizeof(*p))) == NULL)
28339101Sbostic 		return;
28439101Sbostic 	p->next = penlist[op][operand];
28539101Sbostic 	penlist[op][operand] = p;
28639101Sbostic 	penalty[op][operand] += p->penalty = WRONGPENALTY;
28739101Sbostic 	p->value = value;
2888852Smckusick }
2898852Smckusick 
29039101Sbostic /*
29139101Sbostic  * Select a random value from 0 to maxval - 1 for operand `operand' (0 or 1)
29239101Sbostic  * of operation `op'.  The random number we generate is either used directly
29339101Sbostic  * as a value, or represents a position in the penalty list.  If the latter,
29439101Sbostic  * we find the corresponding value and return that, decreasing its penalty.
29539101Sbostic  */
29639101Sbostic getrandom(maxval, op, operand)
29739101Sbostic 	int maxval, op, operand;
2988852Smckusick {
29939101Sbostic 	int value;
30039101Sbostic 	register struct penalty **pp, *p;
3018852Smckusick 
30239101Sbostic 	op = opnum(op);
30339101Sbostic 	value = random() % (maxval + penalty[op][operand]);
3048852Smckusick 
30539101Sbostic 	/*
30639101Sbostic 	 * 0 to maxval - 1 is a number to be used directly; bigger values
30739101Sbostic 	 * are positions to be located in the penalty list.
30839101Sbostic 	 */
30939101Sbostic 	if (value < maxval)
31039101Sbostic 		return(value);
31139101Sbostic 	value -= maxval;
3128852Smckusick 
31339101Sbostic 	/*
31439101Sbostic 	 * Find the penalty at position `value'; decrement its penalty and
31539101Sbostic 	 * delete it if it reaches 0; return the corresponding value.
31639101Sbostic 	 */
31739101Sbostic 	for (pp = &penlist[op][operand]; (p = *pp) != NULL; pp = &p->next) {
31839101Sbostic 		if (p->penalty > value) {
31939101Sbostic 			value = p->value;
32039101Sbostic 			penalty[op][operand]--;
32139101Sbostic 			if (--(p->penalty) <= 0) {
32239101Sbostic 				p = p->next;
32339101Sbostic 				(void)free((char *)*pp);
32439101Sbostic 				*pp = p;
32539101Sbostic 			}
32639101Sbostic 			return(value);
32739101Sbostic 		}
32839101Sbostic 		value -= p->penalty;
3298852Smckusick 	}
33039101Sbostic 	/*
33139101Sbostic 	 * We can only get here if the value from the penalty[] array doesn't
33239101Sbostic 	 * correspond to the actual sum of penalties in the list.  Provide an
33339101Sbostic 	 * obscure message.
33439101Sbostic 	 */
33539101Sbostic 	(void)fprintf(stderr, "arithmetic: bug: inconsistent penalties\n");
33639101Sbostic 	exit(1);
33739101Sbostic 	/* NOTREACHED */
33839101Sbostic }
3398852Smckusick 
34039101Sbostic /* Return an index for the character op, which is one of [+-x/]. */
34139101Sbostic opnum(op)
34239101Sbostic 	int op;
3438852Smckusick {
34439101Sbostic 	char *p;
3458852Smckusick 
34639101Sbostic 	if (op == 0 || (p = index(keylist, op)) == NULL) {
34739101Sbostic 		(void)fprintf(stderr,
34839101Sbostic 		    "arithmetic: bug: op %c not in keylist %s\n", op, keylist);
34939101Sbostic 		exit(1);
35039101Sbostic 	}
35139101Sbostic 	return(p - keylist);
3528852Smckusick }
3538852Smckusick 
35439101Sbostic /* Print usage message and quit. */
35539101Sbostic usage()
3568852Smckusick {
35739134Sbostic 	(void)fprintf(stderr, "usage: arithmetic [-o +-x/] [-r range]\n");
35839101Sbostic 	exit(1);
3598852Smckusick }
360