
1: /* $NetBSD: arithmetic.c,v 1.21 2004/11/05 21:30:31 dsl Exp $ */ 2: 3: /* 4: * Copyright (c) 1989, 1993 5: * The Regents of the University of California. All rights reserved. 6: * 7: * This code is derived from software contributed to Berkeley by 8: * Eamonn McManus of Trinity College Dublin. 9: * 10: * Redistribution and use in source and binary forms, with or without 11: * modification, are permitted provided that the following conditions 12: * are met: 13: * 1. Redistributions of source code must retain the above copyright 14: * notice, this list of conditions and the following disclaimer. 15: * 2. Redistributions in binary form must reproduce the above copyright 16: * notice, this list of conditions and the following disclaimer in the 17: * documentation and/or other materials provided with the distribution. 18: * 3. Neither the name of the University nor the names of its contributors 19: * may be used to endorse or promote products derived from this software 20: * without specific prior written permission. 21: * 22: * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 23: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25: * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 26: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32: * SUCH DAMAGE. 33: */ 34: 35: #include <sys/cdefs.h> 36: #ifndef lint 37: __COPYRIGHT("@(#) Copyright (c) 1989, 1993\n\ 38: The Regents of the University of California. All rights reserved.\n"); 39: #endif /* not lint */ 40: 41: #ifndef lint 42: #if 0 43: static char sccsid[] = "@(#)arithmetic.c 8.1 (Berkeley) 5/31/93"; 44: #else 45: __RCSID("$NetBSD: arithmetic.c,v 1.21 2004/11/05 21:30:31 dsl Exp $"); 46: #endif 47: #endif /* not lint */ 48: 49: /* 50: * By Eamonn McManus, Trinity College Dublin <emcmanus@cs.tcd.ie>. 51: * 52: * The operation of this program mimics that of the standard Unix game 53: * `arithmetic'. I've made it as close as I could manage without examining 54: * the source code. The principal differences are: 55: * 56: * The method of biasing towards numbers that had wrong answers in the past 57: * is different; original `arithmetic' seems to retain the bias forever, 58: * whereas this program lets the bias gradually decay as it is used. 59: * 60: * Original `arithmetic' delays for some period (3 seconds?) after printing 61: * the score. I saw no reason for this delay, so I scrapped it. 62: * 63: * There is no longer a limitation on the maximum range that can be supplied 64: * to the program. The original program required it to be less than 100. 65: * Anomalous results may occur with this program if ranges big enough to 66: * allow overflow are given. 67: * 68: * I have obviously not attempted to duplicate bugs in the original. It 69: * would go into an infinite loop if invoked as `arithmetic / 0'. It also 70: * did not recognise an EOF in its input, and would continue trying to read 71: * after it. It did not check that the input was a valid number, treating any 72: * garbage as 0. Finally, it did not flush stdout after printing its prompt, 73: * so in the unlikely event that stdout was not a terminal, it would not work 74: * properly. 75: */ 76: 77: #include <sys/types.h> 78: #include <err.h> 79: #include <ctype.h> 80: #include <signal.h> 81: #include <stdio.h> 82: #include <stdlib.h> 83: #include <string.h> 84: #include <time.h> 85: #include <unistd.h> 86: 87: int getrandom(int, int, int); 88: void intr(int) __attribute__((__noreturn__)); 89: int main(int, char *[]); 90: int opnum(int); 91: void penalise(int, int, int); 92: int problem(void); 93: void showstats(int); 94: void usage(void) __attribute__((__noreturn__)); 95: 96: const char keylist[] = "+-x/"; 97: const char defaultkeys[] = "+-"; 98: const char *keys = defaultkeys; 99: int nkeys = sizeof(defaultkeys) - 1; 100: int rangemax = 10; 101: int nright, nwrong; 102: time_t qtime; 103: #define NQUESTS 20 104: 105: /* 106: * Select keys from +-x/ to be asked addition, subtraction, multiplication, 107: * and division problems. More than one key may be given. The default is 108: * +-. Specify a range to confine the operands to 0 - range. Default upper 109: * bound is 10. After every NQUESTS questions, statistics on the performance 110: * so far are printed. 111: */ 112: int 113: main(argc, argv) 114: int argc; 115: char **argv; 116: { 117: int ch, cnt; 118: 119: /* Revoke setgid privileges */ 120: setregid(getgid(), getgid()); 121: 122: while ((ch = getopt(argc, argv, "r:o:")) != -1) 123: switch(ch) { 124: case 'o': { 125: const char *p; 126: 127: for (p = keys = optarg; *p; ++p) 128: if (!strchr(keylist, *p)) 129: errx(1, "arithmetic: unknown key."); 130: nkeys = p - optarg; 131: break; 132: } 133: case 'r': 134: if ((rangemax = atoi(optarg)) <= 0) 135: errx(1, "arithmetic: invalid range."); 136: break; 137: case '?': 138: default: 139: usage(); 140: } 141: if (argc -= optind) 142: usage(); 143: 144: /* Seed the random-number generator. */ 145: srandom((int)time((time_t *)NULL)); 146: 147: (void)signal(SIGINT, intr); 148: 149: /* Now ask the questions. */ 150: for (;;) { 151: for (cnt = NQUESTS; cnt--;) 152: if (problem() == EOF) 153: exit(0); 154: showstats(0); 155: } 156: /* NOTREACHED */ 157: } 158: 159: /* Handle interrupt character. Print score and exit. */ 160: void 161: intr(dummy) 162: int dummy __attribute__((__unused__)); 163: { 164: showstats(1); 165: exit(0); 166: } 167: 168: /* Print score. Original `arithmetic' had a delay after printing it. */ 169: void 170: showstats(bool_sigint) 171: int bool_sigint; 172: { 173: if (nright + nwrong > 0) { 174: (void)printf("\n\nRights %d; Wrongs %d; Score %d%%", 175: nright, nwrong, (int)(100L * nright / (nright + nwrong))); 176: if (nright > 0) 177: (void)printf("\nTotal time %ld seconds; %.1f seconds per problem\n\n", 178: (long)qtime, (float)qtime / nright); 179: } 180: if(!bool_sigint) { 181: (void)printf("Press RETURN to continue...\n"); 182: while(!getchar()) ; 183: } 184: (void)printf("\n"); 185: } 186: 187: /* 188: * Pick a problem and ask it. Keeps asking the same problem until supplied 189: * with the correct answer, or until EOF or interrupt is typed. Problems are 190: * selected such that the right operand and either the left operand (for +, x) 191: * or the correct result (for -, /) are in the range 0 to rangemax. Each wrong 192: * answer causes the numbers in the problem to be penalised, so that they are 193: * more likely to appear in subsequent problems. 194: */ 195: int 196: problem() 197: { 198: char *p; 199: time_t start, finish; 200: int left, op, right, result; 201: char line[80]; 202: 203: right = left = result = 0; 204: op = keys[random() % nkeys]; 205: if (op != '/') 206: right = getrandom(rangemax + 1, op, 1); 207: retry: 208: /* Get the operands. */ 209: switch (op) { 210: case '+': 211: left = getrandom(rangemax + 1, op, 0); 212: result = left + right; 213: break; 214: case '-': 215: result = getrandom(rangemax + 1, op, 0); 216: left = right + result; 217: break; 218: case 'x': 219: left = getrandom(rangemax + 1, op, 0); 220: result = left * right; 221: break; 222: case '/': 223: right = getrandom(rangemax, op, 1) + 1; 224: result = getrandom(rangemax + 1, op, 0); 225: left = right * result + random() % right; 226: break; 227: } 228: 229: /* 230: * A very big maxrange could cause negative values to pop 231: * up, owing to overflow. 232: */ 233: if (result < 0 || left < 0) 234: goto retry; 235: 236: (void)printf("%d %c %d = ", left, op, right); 237: (void)fflush(stdout); 238: (void)time(&start); 239: 240: /* 241: * Keep looping until the correct answer is given, or until EOF or 242: * interrupt is typed. 243: */ 244: for (;;) { 245: if (!fgets(line, sizeof(line), stdin)) { 246: (void)printf("\n"); 247: return(EOF); 248: } 249: for (p = line; *p && isspace((unsigned char)*p); ++p); 250: if (!isdigit((unsigned char)*p)) { 251: (void)printf("Please type a number.\n"); 252: continue; 253: } 254: if (atoi(p) == result) { 255: (void)printf("Right!\n"); 256: ++nright; 257: break; 258: } 259: /* Wrong answer; penalise and ask again. */ 260: (void)printf("What?\n"); 261: ++nwrong; 262: penalise(right, op, 1); 263: if (op == 'x' || op == '+') 264: penalise(left, op, 0); 265: else 266: penalise(result, op, 0); 267: } 268: 269: /* 270: * Accumulate the time taken. Obviously rounding errors happen here; 271: * however they should cancel out, because some of the time you are 272: * charged for a partially elapsed second at the start, and some of 273: * the time you are not charged for a partially elapsed second at the 274: * end. 275: */ 276: (void)time(&finish); 277: qtime += finish - start; 278: return(0); 279: } 280: 281: /* 282: * Here is the code for accumulating penalties against the numbers for which 283: * a wrong answer was given. The right operand and either the left operand 284: * (for +, x) or the result (for -, /) are stored in a list for the particular 285: * operation, and each becomes more likely to appear again in that operation. 286: * Initially, each number is charged a penalty of WRONGPENALTY, giving it that 287: * many extra chances of appearing. Each time it is selected because of this, 288: * its penalty is decreased by one; it is removed when it reaches 0. 289: * 290: * The penalty[] array gives the sum of all penalties in the list for 291: * each operation and each operand. The penlist[] array has the lists of 292: * penalties themselves. 293: */ 294: 295: int penalty[sizeof(keylist) - 1][2]; 296: struct penalty { 297: int value, penalty; /* Penalised value and its penalty. */ 298: struct penalty *next; 299: } *penlist[sizeof(keylist) - 1][2]; 300: 301: #define WRONGPENALTY 5 /* Perhaps this should depend on maxrange. */ 302: 303: /* 304: * Add a penalty for the number `value' to the list for operation `op', 305: * operand number `operand' (0 or 1). If we run out of memory, we just 306: * forget about the penalty (how likely is this, anyway?). 307: */ 308: void 309: penalise(value, op, operand) 310: int value, op, operand; 311: { 312: struct penalty *p; 313: 314: op = opnum(op); 315: if ((p = (struct penalty *)malloc((u_int)sizeof(*p))) == NULL) 316: return; 317: p->next = penlist[op][operand]; 318: penlist[op][operand] = p; 319: penalty[op][operand] += p->penalty = WRONGPENALTY; 320: p->value = value; 321: } 322: 323: /* 324: * Select a random value from 0 to maxval - 1 for operand `operand' (0 or 1) 325: * of operation `op'. The random number we generate is either used directly 326: * as a value, or represents a position in the penalty list. If the latter, 327: * we find the corresponding value and return that, decreasing its penalty. 328: */ 329: int 330: getrandom(maxval, op, operand) 331: int maxval, op, operand; 332: { 333: int value; 334: struct penalty **pp, *p; 335: 336: op = opnum(op); 337: value = random() % (maxval + penalty[op][operand]); 338: 339: /* 340: * 0 to maxval - 1 is a number to be used directly; bigger values 341: * are positions to be located in the penalty list. 342: */ 343: if (value < maxval) 344: return(value); 345: value -= maxval; 346: 347: /* 348: * Find the penalty at position `value'; decrement its penalty and 349: * delete it if it reaches 0; return the corresponding value. 350: */ 351: for (pp = &penlist[op][operand]; (p = *pp) != NULL; pp = &p->next) { 352: if (p->penalty > value) { 353: value = p->value; 354: penalty[op][operand]--; 355: if (--(p->penalty) <= 0) { 356: p = p->next; 357: (void)free((char *)*pp); 358: *pp = p; 359: } 360: return(value); 361: } 362: value -= p->penalty; 363: } 364: /* 365: * We can only get here if the value from the penalty[] array doesn't 366: * correspond to the actual sum of penalties in the list. Provide an 367: * obscure message. 368: */ 369: errx(1, "arithmetic: bug: inconsistent penalties."); 370: /* NOTREACHED */ 371: } 372: 373: /* Return an index for the character op, which is one of [+-x/]. */ 374: int 375: opnum(op) 376: int op; 377: { 378: char *p; 379: 380: if (op == 0 || (p = strchr(keylist, op)) == NULL) 381: errx(1, "arithmetic: bug: op %c not in keylist %s", 382: op, keylist); 383: return(p - keylist); 384: } 385: 386: /* Print usage message and quit. */ 387: void 388: usage() 389: { 390: (void)fprintf(stderr, "Usage: %s [-o +-x/] [-r range]\n", 391: getprogname()); 392: exit(1); 393: }