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