* Copyright (c) 1989, 1993
* The Regents of the University of California. All rights reserved.
* This code is derived from software contributed to Berkeley by
* Eamonn McManus of Trinity College Dublin.
* %sccs.include.redist.c%
static char copyright
[] =
"@(#) Copyright (c) 1989, 1993\n\
The Regents of the University of California. All rights reserved.\n";
static char sccsid
[] = "@(#)arithmetic.c 8.1 (Berkeley) %G%";
* By Eamonn McManus, Trinity College Dublin <emcmanus@cs.tcd.ie>.
* The operation of this program mimics that of the standard Unix game
* `arithmetic'. I've made it as close as I could manage without examining
* the source code. The principal differences are:
* The method of biasing towards numbers that had wrong answers in the past
* is different; original `arithmetic' seems to retain the bias forever,
* whereas this program lets the bias gradually decay as it is used.
* Original `arithmetic' delays for some period (3 seconds?) after printing
* the score. I saw no reason for this delay, so I scrapped it.
* There is no longer a limitation on the maximum range that can be supplied
* to the program. The original program required it to be less than 100.
* Anomalous results may occur with this program if ranges big enough to
* allow overflow are given.
* I have obviously not attempted to duplicate bugs in the original. It
* would go into an infinite loop if invoked as `arithmetic / 0'. It also
* did not recognise an EOF in its input, and would continue trying to read
* after it. It did not check that the input was a valid number, treating any
* garbage as 0. Finally, it did not flush stdout after printing its prompt,
* so in the unlikely event that stdout was not a terminal, it would not work
char defaultkeys
[] = "+-";
char *keys
= defaultkeys
;
int nkeys
= sizeof(defaultkeys
) - 1;
* Select keys from +-x/ to be asked addition, subtraction, multiplication,
* and division problems. More than one key may be given. The default is
* +-. Specify a range to confine the operands to 0 - range. Default upper
* bound is 10. After every NQUESTS questions, statistics on the performance
while ((ch
= getopt(argc
, argv
, "r:o:")) != EOF
)
for (p
= keys
= optarg
; *p
; ++p
)
if (!index(keylist
, *p
)) {
"arithmetic: unknown key.\n");
if ((rangemax
= atoi(optarg
)) <= 0) {
"arithmetic: invalid range.\n");
/* Seed the random-number generator. */
srandom((int)time((time_t *)NULL
));
(void)signal(SIGINT
, intr
);
/* Now ask the questions. */
for (cnt
= NQUESTS
; cnt
--;)
/* Handle interrupt character. Print score and exit. */
/* Print score. Original `arithmetic' had a delay after printing it. */
if (nright
+ nwrong
> 0) {
(void)printf("\n\nRights %d; Wrongs %d; Score %d%%",
nright
, nwrong
, (int)(100L * nright
/ (nright
+ nwrong
)));
(void)printf("\nTotal time %ld seconds; %.1f seconds per problem\n\n",
(long)qtime
, (float)qtime
/ nright
);
* Pick a problem and ask it. Keeps asking the same problem until supplied
* with the correct answer, or until EOF or interrupt is typed. Problems are
* selected such that the right operand and either the left operand (for +, x)
* or the correct result (for -, /) are in the range 0 to rangemax. Each wrong
* answer causes the numbers in the problem to be penalised, so that they are
* more likely to appear in subsequent problems.
int left
, op
, right
, result
;
op
= keys
[random() % nkeys
];
right
= getrandom(rangemax
+ 1, op
, 1);
left
= getrandom(rangemax
+ 1, op
, 0);
result
= getrandom(rangemax
+ 1, op
, 0);
left
= getrandom(rangemax
+ 1, op
, 0);
right
= getrandom(rangemax
, op
, 1) + 1;
result
= getrandom(rangemax
+ 1, op
, 0);
left
= right
* result
+ random() % right
;
* A very big maxrange could cause negative values to pop
if (result
< 0 || left
< 0)
(void)printf("%d %c %d = ", left
, op
, right
);
* Keep looping until the correct answer is given, or until EOF or
if (!fgets(line
, sizeof(line
), stdin
)) {
for (p
= line
; *p
&& isspace(*p
); ++p
);
(void)printf("Please type a number.\n");
(void)printf("Right!\n");
/* Wrong answer; penalise and ask again. */
if (op
== 'x' || op
== '+')
* Accumulate the time taken. Obviously rounding errors happen here;
* however they should cancel out, because some of the time you are
* charged for a partially elapsed second at the start, and some of
* the time you are not charged for a partially elapsed second at the
* Here is the code for accumulating penalties against the numbers for which
* a wrong answer was given. The right operand and either the left operand
* (for +, x) or the result (for -, /) are stored in a list for the particular
* operation, and each becomes more likely to appear again in that operation.
* Initially, each number is charged a penalty of WRONGPENALTY, giving it that
* many extra chances of appearing. Each time it is selected because of this,
* its penalty is decreased by one; it is removed when it reaches 0.
* The penalty[] array gives the sum of all penalties in the list for
* each operation and each operand. The penlist[] array has the lists of
int penalty
[sizeof(keylist
) - 1][2];
int value
, penalty
; /* Penalised value and its penalty. */
} *penlist
[sizeof(keylist
) - 1][2];
#define WRONGPENALTY 5 /* Perhaps this should depend on maxrange. */
* Add a penalty for the number `value' to the list for operation `op',
* operand number `operand' (0 or 1). If we run out of memory, we just
* forget about the penalty (how likely is this, anyway?).
penalise(value
, op
, operand
)
if ((p
= (struct penalty
*)malloc((u_int
)sizeof(*p
))) == NULL
)
p
->next
= penlist
[op
][operand
];
penlist
[op
][operand
] = p
;
penalty
[op
][operand
] += p
->penalty
= WRONGPENALTY
;
* Select a random value from 0 to maxval - 1 for operand `operand' (0 or 1)
* of operation `op'. The random number we generate is either used directly
* as a value, or represents a position in the penalty list. If the latter,
* we find the corresponding value and return that, decreasing its penalty.
getrandom(maxval
, op
, operand
)
register struct penalty
**pp
, *p
;
value
= random() % (maxval
+ penalty
[op
][operand
]);
* 0 to maxval - 1 is a number to be used directly; bigger values
* are positions to be located in the penalty list.
* Find the penalty at position `value'; decrement its penalty and
* delete it if it reaches 0; return the corresponding value.
for (pp
= &penlist
[op
][operand
]; (p
= *pp
) != NULL
; pp
= &p
->next
) {
if (p
->penalty
> value
) {
if (--(p
->penalty
) <= 0) {
* We can only get here if the value from the penalty[] array doesn't
* correspond to the actual sum of penalties in the list. Provide an
(void)fprintf(stderr
, "arithmetic: bug: inconsistent penalties\n");
/* Return an index for the character op, which is one of [+-x/]. */
if (op
== 0 || (p
= index(keylist
, op
)) == NULL
) {
"arithmetic: bug: op %c not in keylist %s\n", op
, keylist
);
/* Print usage message and quit. */
(void)fprintf(stderr
, "usage: arithmetic [-o +-x/] [-r range]\n");