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