139101Sbostic /*
2*60737Sbostic * Copyright (c) 1989, 1993
3*60737Sbostic * The Regents of the University of California. All rights reserved.
439101Sbostic *
539101Sbostic * This code is derived from software contributed to Berkeley by
639101Sbostic * Eamonn McManus of Trinity College Dublin.
739101Sbostic *
842556Sbostic * %sccs.include.redist.c%
939101Sbostic */
108852Smckusick
1139101Sbostic #ifndef lint
12*60737Sbostic static char copyright[] =
13*60737Sbostic "@(#) Copyright (c) 1989, 1993\n\
14*60737Sbostic The Regents of the University of California. All rights reserved.\n";
1539101Sbostic #endif /* not lint */
168852Smckusick
1739101Sbostic #ifndef lint
18*60737Sbostic static char sccsid[] = "@(#)arithmetic.c 8.1 (Berkeley) 05/31/93";
1939101Sbostic #endif /* not lint */
2039101Sbostic
2139101Sbostic /*
2239101Sbostic * By Eamonn McManus, Trinity College Dublin <emcmanus@cs.tcd.ie>.
2339101Sbostic *
2439101Sbostic * The operation of this program mimics that of the standard Unix game
2539101Sbostic * `arithmetic'. I've made it as close as I could manage without examining
2639101Sbostic * the source code. The principal differences are:
2739101Sbostic *
2839101Sbostic * The method of biasing towards numbers that had wrong answers in the past
2939101Sbostic * is different; original `arithmetic' seems to retain the bias forever,
3039101Sbostic * whereas this program lets the bias gradually decay as it is used.
3139101Sbostic *
3239101Sbostic * Original `arithmetic' delays for some period (3 seconds?) after printing
3339101Sbostic * the score. I saw no reason for this delay, so I scrapped it.
3439101Sbostic *
3539101Sbostic * There is no longer a limitation on the maximum range that can be supplied
3639101Sbostic * to the program. The original program required it to be less than 100.
3739101Sbostic * Anomalous results may occur with this program if ranges big enough to
3839101Sbostic * allow overflow are given.
3939101Sbostic *
4039101Sbostic * I have obviously not attempted to duplicate bugs in the original. It
4139101Sbostic * would go into an infinite loop if invoked as `arithmetic / 0'. It also
4239101Sbostic * did not recognise an EOF in its input, and would continue trying to read
4339101Sbostic * after it. It did not check that the input was a valid number, treating any
4439101Sbostic * garbage as 0. Finally, it did not flush stdout after printing its prompt,
4539101Sbostic * so in the unlikely event that stdout was not a terminal, it would not work
4639101Sbostic * properly.
4739101Sbostic */
4839101Sbostic
4939101Sbostic #include <sys/types.h>
5039101Sbostic #include <sys/signal.h>
5139101Sbostic #include <ctype.h>
528852Smckusick #include <stdio.h>
5342017Sbostic #include <string.h>
548852Smckusick
5539101Sbostic char keylist[] = "+-x/";
5639101Sbostic char defaultkeys[] = "+-";
5739101Sbostic char *keys = defaultkeys;
5839101Sbostic int nkeys = sizeof(defaultkeys) - 1;
5939101Sbostic int rangemax = 10;
6039101Sbostic int nright, nwrong;
6139101Sbostic time_t qtime;
6239101Sbostic #define NQUESTS 20
638852Smckusick
6439101Sbostic /*
6539101Sbostic * Select keys from +-x/ to be asked addition, subtraction, multiplication,
6639101Sbostic * and division problems. More than one key may be given. The default is
6739101Sbostic * +-. Specify a range to confine the operands to 0 - range. Default upper
6839101Sbostic * bound is 10. After every NQUESTS questions, statistics on the performance
6939101Sbostic * so far are printed.
7039101Sbostic */
7139101Sbostic void
main(argc,argv)7239101Sbostic main(argc, argv)
7339101Sbostic int argc;
7439101Sbostic char **argv;
758852Smckusick {
7639101Sbostic extern char *optarg;
7739101Sbostic extern int optind;
7839101Sbostic int ch, cnt;
7946726Sbostic void intr();
808852Smckusick
8139101Sbostic while ((ch = getopt(argc, argv, "r:o:")) != EOF)
8239101Sbostic switch(ch) {
8339101Sbostic case 'o': {
8439101Sbostic register char *p;
858852Smckusick
8639101Sbostic for (p = keys = optarg; *p; ++p)
8739101Sbostic if (!index(keylist, *p)) {
8839101Sbostic (void)fprintf(stderr,
8939101Sbostic "arithmetic: unknown key.\n");
9039101Sbostic exit(1);
9139101Sbostic }
9239101Sbostic nkeys = p - optarg;
938852Smckusick break;
9439101Sbostic }
9539101Sbostic case 'r':
9639101Sbostic if ((rangemax = atoi(optarg)) <= 0) {
9739101Sbostic (void)fprintf(stderr,
9839101Sbostic "arithmetic: invalid range.\n");
9939101Sbostic exit(1);
10039101Sbostic }
10139101Sbostic break;
10239101Sbostic case '?':
1038852Smckusick default:
10439101Sbostic usage();
1058852Smckusick }
10639101Sbostic if (argc -= optind)
10739101Sbostic usage();
1088852Smckusick
10939101Sbostic /* Seed the random-number generator. */
11039101Sbostic srandom((int)time((time_t *)NULL));
1118852Smckusick
11239101Sbostic (void)signal(SIGINT, intr);
1138852Smckusick
11439101Sbostic /* Now ask the questions. */
11539101Sbostic for (;;) {
11639101Sbostic for (cnt = NQUESTS; cnt--;)
11739101Sbostic if (problem() == EOF)
11839101Sbostic exit(0);
11939101Sbostic showstats();
1208852Smckusick }
12139101Sbostic /* NOTREACHED */
12239101Sbostic }
1238852Smckusick
12439101Sbostic /* Handle interrupt character. Print score and exit. */
12546726Sbostic void
intr()12639101Sbostic intr()
12739101Sbostic {
12839101Sbostic showstats();
12939101Sbostic exit(0);
13039101Sbostic }
1318852Smckusick
13239101Sbostic /* Print score. Original `arithmetic' had a delay after printing it. */
showstats()13339101Sbostic showstats()
13439101Sbostic {
13539101Sbostic if (nright + nwrong > 0) {
13639101Sbostic (void)printf("\n\nRights %d; Wrongs %d; Score %d%%",
13739101Sbostic nright, nwrong, (int)(100L * nright / (nright + nwrong)));
13839101Sbostic if (nright > 0)
13939101Sbostic (void)printf("\nTotal time %ld seconds; %.1f seconds per problem\n\n",
14039101Sbostic (long)qtime, (float)qtime / nright);
1418852Smckusick }
14239101Sbostic (void)printf("\n");
1438852Smckusick }
1448852Smckusick
14539101Sbostic /*
14639101Sbostic * Pick a problem and ask it. Keeps asking the same problem until supplied
14739101Sbostic * with the correct answer, or until EOF or interrupt is typed. Problems are
14839101Sbostic * selected such that the right operand and either the left operand (for +, x)
14939101Sbostic * or the correct result (for -, /) are in the range 0 to rangemax. Each wrong
15039101Sbostic * answer causes the numbers in the problem to be penalised, so that they are
15139101Sbostic * more likely to appear in subsequent problems.
15239101Sbostic */
problem()15339101Sbostic problem()
1548852Smckusick {
15539101Sbostic register char *p;
15639101Sbostic time_t start, finish;
15739101Sbostic int left, op, right, result;
15839101Sbostic char line[80];
1598852Smckusick
16039101Sbostic op = keys[random() % nkeys];
16139101Sbostic if (op != '/')
16239101Sbostic right = getrandom(rangemax + 1, op, 1);
16339101Sbostic retry:
16439101Sbostic /* Get the operands. */
16539101Sbostic switch (op) {
16639101Sbostic case '+':
16739101Sbostic left = getrandom(rangemax + 1, op, 0);
16839101Sbostic result = left + right;
16939101Sbostic break;
17039101Sbostic case '-':
17139101Sbostic result = getrandom(rangemax + 1, op, 0);
17239101Sbostic left = right + result;
17339101Sbostic break;
17439101Sbostic case 'x':
17539101Sbostic left = getrandom(rangemax + 1, op, 0);
17639101Sbostic result = left * right;
17739101Sbostic break;
17839101Sbostic case '/':
17939101Sbostic right = getrandom(rangemax, op, 1) + 1;
18039101Sbostic result = getrandom(rangemax + 1, op, 0);
18139101Sbostic left = right * result + random() % right;
18239101Sbostic break;
18339101Sbostic }
1848852Smckusick
18539101Sbostic /*
18639101Sbostic * A very big maxrange could cause negative values to pop
18739101Sbostic * up, owing to overflow.
18839101Sbostic */
18939101Sbostic if (result < 0 || left < 0)
19039101Sbostic goto retry;
19139101Sbostic
19239101Sbostic (void)printf("%d %c %d = ", left, op, right);
19339101Sbostic (void)fflush(stdout);
19439101Sbostic (void)time(&start);
19539101Sbostic
19639101Sbostic /*
19739101Sbostic * Keep looping until the correct answer is given, or until EOF or
19839101Sbostic * interrupt is typed.
19939101Sbostic */
20039101Sbostic for (;;) {
20139101Sbostic if (!fgets(line, sizeof(line), stdin)) {
20239101Sbostic (void)printf("\n");
20339101Sbostic return(EOF);
2048852Smckusick }
20539101Sbostic for (p = line; *p && isspace(*p); ++p);
20639101Sbostic if (!isdigit(*p)) {
20739101Sbostic (void)printf("Please type a number.\n");
20839101Sbostic continue;
20939101Sbostic }
21039101Sbostic if (atoi(p) == result) {
21139101Sbostic (void)printf("Right!\n");
21239101Sbostic ++nright;
21339101Sbostic break;
21439101Sbostic }
21539101Sbostic /* Wrong answer; penalise and ask again. */
21639101Sbostic (void)printf("What?\n");
21739101Sbostic ++nwrong;
21839101Sbostic penalise(right, op, 1);
21939101Sbostic if (op == 'x' || op == '+')
22039101Sbostic penalise(left, op, 0);
2218852Smckusick else
22239101Sbostic penalise(result, op, 0);
22339101Sbostic }
22439101Sbostic
22539101Sbostic /*
22639101Sbostic * Accumulate the time taken. Obviously rounding errors happen here;
22739101Sbostic * however they should cancel out, because some of the time you are
22839101Sbostic * charged for a partially elapsed second at the start, and some of
22939101Sbostic * the time you are not charged for a partially elapsed second at the
23039101Sbostic * end.
23139101Sbostic */
23239101Sbostic (void)time(&finish);
23339101Sbostic qtime += finish - start;
23439101Sbostic return(0);
2358852Smckusick }
2368852Smckusick
23739101Sbostic /*
23839101Sbostic * Here is the code for accumulating penalties against the numbers for which
23939101Sbostic * a wrong answer was given. The right operand and either the left operand
24039101Sbostic * (for +, x) or the result (for -, /) are stored in a list for the particular
24139101Sbostic * operation, and each becomes more likely to appear again in that operation.
24239101Sbostic * Initially, each number is charged a penalty of WRONGPENALTY, giving it that
24339101Sbostic * many extra chances of appearing. Each time it is selected because of this,
24439101Sbostic * its penalty is decreased by one; it is removed when it reaches 0.
24539101Sbostic *
24639101Sbostic * The penalty[] array gives the sum of all penalties in the list for
24739101Sbostic * each operation and each operand. The penlist[] array has the lists of
24839101Sbostic * penalties themselves.
24939101Sbostic */
2508852Smckusick
25139101Sbostic int penalty[sizeof(keylist) - 1][2];
25239101Sbostic struct penalty {
25339101Sbostic int value, penalty; /* Penalised value and its penalty. */
25439101Sbostic struct penalty *next;
25539101Sbostic } *penlist[sizeof(keylist) - 1][2];
2568852Smckusick
25739101Sbostic #define WRONGPENALTY 5 /* Perhaps this should depend on maxrange. */
2588852Smckusick
25939101Sbostic /*
26039101Sbostic * Add a penalty for the number `value' to the list for operation `op',
26139101Sbostic * operand number `operand' (0 or 1). If we run out of memory, we just
26239101Sbostic * forget about the penalty (how likely is this, anyway?).
26339101Sbostic */
penalise(value,op,operand)26439101Sbostic penalise(value, op, operand)
26539101Sbostic int value, op, operand;
2668852Smckusick {
26739101Sbostic struct penalty *p;
26839101Sbostic char *malloc();
26939101Sbostic
27039101Sbostic op = opnum(op);
27139101Sbostic if ((p = (struct penalty *)malloc((u_int)sizeof(*p))) == NULL)
27239101Sbostic return;
27339101Sbostic p->next = penlist[op][operand];
27439101Sbostic penlist[op][operand] = p;
27539101Sbostic penalty[op][operand] += p->penalty = WRONGPENALTY;
27639101Sbostic p->value = value;
2778852Smckusick }
2788852Smckusick
27939101Sbostic /*
28039101Sbostic * Select a random value from 0 to maxval - 1 for operand `operand' (0 or 1)
28139101Sbostic * of operation `op'. The random number we generate is either used directly
28239101Sbostic * as a value, or represents a position in the penalty list. If the latter,
28339101Sbostic * we find the corresponding value and return that, decreasing its penalty.
28439101Sbostic */
getrandom(maxval,op,operand)28539101Sbostic getrandom(maxval, op, operand)
28639101Sbostic int maxval, op, operand;
2878852Smckusick {
28839101Sbostic int value;
28939101Sbostic register struct penalty **pp, *p;
2908852Smckusick
29139101Sbostic op = opnum(op);
29239101Sbostic value = random() % (maxval + penalty[op][operand]);
2938852Smckusick
29439101Sbostic /*
29539101Sbostic * 0 to maxval - 1 is a number to be used directly; bigger values
29639101Sbostic * are positions to be located in the penalty list.
29739101Sbostic */
29839101Sbostic if (value < maxval)
29939101Sbostic return(value);
30039101Sbostic value -= maxval;
3018852Smckusick
30239101Sbostic /*
30339101Sbostic * Find the penalty at position `value'; decrement its penalty and
30439101Sbostic * delete it if it reaches 0; return the corresponding value.
30539101Sbostic */
30639101Sbostic for (pp = &penlist[op][operand]; (p = *pp) != NULL; pp = &p->next) {
30739101Sbostic if (p->penalty > value) {
30839101Sbostic value = p->value;
30939101Sbostic penalty[op][operand]--;
31039101Sbostic if (--(p->penalty) <= 0) {
31139101Sbostic p = p->next;
31239101Sbostic (void)free((char *)*pp);
31339101Sbostic *pp = p;
31439101Sbostic }
31539101Sbostic return(value);
31639101Sbostic }
31739101Sbostic value -= p->penalty;
3188852Smckusick }
31939101Sbostic /*
32039101Sbostic * We can only get here if the value from the penalty[] array doesn't
32139101Sbostic * correspond to the actual sum of penalties in the list. Provide an
32239101Sbostic * obscure message.
32339101Sbostic */
32439101Sbostic (void)fprintf(stderr, "arithmetic: bug: inconsistent penalties\n");
32539101Sbostic exit(1);
32639101Sbostic /* NOTREACHED */
32739101Sbostic }
3288852Smckusick
32939101Sbostic /* Return an index for the character op, which is one of [+-x/]. */
opnum(op)33039101Sbostic opnum(op)
33139101Sbostic int op;
3328852Smckusick {
33339101Sbostic char *p;
3348852Smckusick
33539101Sbostic if (op == 0 || (p = index(keylist, op)) == NULL) {
33639101Sbostic (void)fprintf(stderr,
33739101Sbostic "arithmetic: bug: op %c not in keylist %s\n", op, keylist);
33839101Sbostic exit(1);
33939101Sbostic }
34039101Sbostic return(p - keylist);
3418852Smckusick }
3428852Smckusick
34339101Sbostic /* Print usage message and quit. */
usage()34439101Sbostic usage()
3458852Smckusick {
34639134Sbostic (void)fprintf(stderr, "usage: arithmetic [-o +-x/] [-r range]\n");
34739101Sbostic exit(1);
3488852Smckusick }
349