Commit | Line | Data |
---|---|---|

984263bc MD |
1 | /* |

2 | * Copyright (c) 1989, 1993 | |

3 | * The Regents of the University of California. 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, with or without | |

9 | * modification, are permitted provided that the following conditions | |

10 | * are met: | |

11 | * 1. Redistributions of source code must retain the above copyright | |

12 | * notice, this list of conditions and the following disclaimer. | |

13 | * 2. Redistributions in binary form must reproduce the above copyright | |

14 | * notice, this list of conditions and the following disclaimer in the | |

15 | * documentation and/or other materials provided with the distribution. | |

16 | * 3. All advertising materials mentioning features or use of this software | |

17 | * must display the following acknowledgement: | |

18 | * This product includes software developed by the University of | |

19 | * California, Berkeley and its contributors. | |

20 | * 4. Neither the name of the University nor the names of its contributors | |

21 | * may be used to endorse or promote products derived from this software | |

22 | * without specific prior written permission. | |

23 | * | |

24 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | |

25 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |

26 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |

27 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | |

28 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |

29 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |

30 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |

31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |

32 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |

33 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |

34 | * SUCH DAMAGE. | |

1de703da MD |
35 | * |

36 | * @(#) Copyright (c) 1989, 1993 The Regents of the University of California. All rights reserved. | |

37 | * @(#)arithmetic.c 8.1 (Berkeley) 5/31/93 | |

38 | * $FreeBSD: src/games/arithmetic/arithmetic.c,v 1.10 1999/12/12 06:40:28 billf Exp $ | |

851dc90d | 39 | * $DragonFly: src/games/arithmetic/arithmetic.c,v 1.3 2003/11/12 14:53:52 eirikn Exp $ |

984263bc MD |
40 | */ |

41 | ||

984263bc MD |
42 | /* |

43 | * By Eamonn McManus, Trinity College Dublin <emcmanus@cs.tcd.ie>. | |

44 | * | |

45 | * The operation of this program mimics that of the standard Unix game | |

46 | * `arithmetic'. I've made it as close as I could manage without examining | |

47 | * the source code. The principal differences are: | |

48 | * | |

49 | * The method of biasing towards numbers that had wrong answers in the past | |

50 | * is different; original `arithmetic' seems to retain the bias forever, | |

51 | * whereas this program lets the bias gradually decay as it is used. | |

52 | * | |

53 | * Original `arithmetic' delays for some period (3 seconds?) after printing | |

54 | * the score. I saw no reason for this delay, so I scrapped it. | |

55 | * | |

56 | * There is no longer a limitation on the maximum range that can be supplied | |

57 | * to the program. The original program required it to be less than 100. | |

58 | * Anomalous results may occur with this program if ranges big enough to | |

59 | * allow overflow are given. | |

60 | * | |

61 | * I have obviously not attempted to duplicate bugs in the original. It | |

62 | * would go into an infinite loop if invoked as `arithmetic / 0'. It also | |

63 | * did not recognise an EOF in its input, and would continue trying to read | |

64 | * after it. It did not check that the input was a valid number, treating any | |

65 | * garbage as 0. Finally, it did not flush stdout after printing its prompt, | |

66 | * so in the unlikely event that stdout was not a terminal, it would not work | |

67 | * properly. | |

68 | */ | |

69 | ||

70 | #include <sys/types.h> | |

71 | #include <sys/signal.h> | |

72 | #include <ctype.h> | |

73 | #include <stdio.h> | |

74 | #include <string.h> | |

75 | #include <stdlib.h> | |

76 | #include <time.h> | |

77 | #include <unistd.h> | |

78 | ||

79 | const char keylist[] = "+-x/"; | |

80 | const char defaultkeys[] = "+-"; | |

81 | const char *keys = defaultkeys; | |

82 | int nkeys = sizeof(defaultkeys) - 1; | |

83 | int rangemax = 10; | |

84 | int nright, nwrong; | |

85 | time_t qtime; | |

86 | #define NQUESTS 20 | |

87 | ||

851dc90d EN |
88 | static void usage (void); |

89 | int getrandom (int, int, int); | |

90 | void intr (int); | |

91 | int opnum (int); | |

92 | void penalise (int, int, int); | |

93 | int problem (void); | |

94 | void showstats (void); | |

984263bc MD |
95 | |

96 | /* | |

97 | * Select keys from +-x/ to be asked addition, subtraction, multiplication, | |

98 | * and division problems. More than one key may be given. The default is | |

99 | * +-. Specify a range to confine the operands to 0 - range. Default upper | |

100 | * bound is 10. After every NQUESTS questions, statistics on the performance | |

101 | * so far are printed. | |

102 | */ | |

103 | int | |

104 | main(argc, argv) | |

105 | int argc; | |

106 | char **argv; | |

107 | { | |

108 | int ch, cnt; | |

109 | ||

110 | /* Revoke setgid privileges */ | |

111 | setgid(getgid()); | |

112 | ||

113 | while ((ch = getopt(argc, argv, "r:o:")) != -1) | |

114 | switch(ch) { | |

115 | case 'o': { | |

116 | const char *p; | |

117 | ||

118 | for (p = keys = optarg; *p; ++p) | |

119 | if (!index(keylist, *p)) { | |

120 | (void)fprintf(stderr, | |

121 | "arithmetic: unknown key.\n"); | |

122 | exit(1); | |

123 | } | |

124 | nkeys = p - optarg; | |

125 | break; | |

126 | } | |

127 | case 'r': | |

128 | if ((rangemax = atoi(optarg)) <= 0) { | |

129 | (void)fprintf(stderr, | |

130 | "arithmetic: invalid range.\n"); | |

131 | exit(1); | |

132 | } | |

133 | break; | |

134 | case '?': | |

135 | default: | |

136 | usage(); | |

137 | } | |

138 | if (argc -= optind) | |

139 | usage(); | |

140 | ||

141 | /* Seed the random-number generator. */ | |

142 | srandomdev(); | |

143 | ||

144 | (void)signal(SIGINT, intr); | |

145 | ||

146 | /* Now ask the questions. */ | |

147 | for (;;) { | |

148 | for (cnt = NQUESTS; cnt--;) | |

149 | if (problem() == EOF) | |

150 | exit(0); | |

151 | showstats(); | |

152 | } | |

153 | /* NOTREACHED */ | |

154 | } | |

155 | ||

156 | /* Handle interrupt character. Print score and exit. */ | |

157 | void | |

158 | intr(sig) | |

159 | int sig; | |

160 | { | |

161 | showstats(); | |

162 | exit(0); | |

163 | } | |

164 | ||

165 | /* Print score. Original `arithmetic' had a delay after printing it. */ | |

166 | void | |

167 | showstats() | |

168 | { | |

169 | if (nright + nwrong > 0) { | |

170 | (void)printf("\n\nRights %d; Wrongs %d; Score %d%%", | |

171 | nright, nwrong, (int)(100L * nright / (nright + nwrong))); | |

172 | if (nright > 0) | |

173 | (void)printf("\nTotal time %ld seconds; %.1f seconds per problem\n\n", | |

174 | (long)qtime, (float)qtime / nright); | |

175 | } | |

176 | (void)printf("\n"); | |

177 | } | |

178 | ||

179 | /* | |

180 | * Pick a problem and ask it. Keeps asking the same problem until supplied | |

181 | * with the correct answer, or until EOF or interrupt is typed. Problems are | |

182 | * selected such that the right operand and either the left operand (for +, x) | |

183 | * or the correct result (for -, /) are in the range 0 to rangemax. Each wrong | |

184 | * answer causes the numbers in the problem to be penalised, so that they are | |

185 | * more likely to appear in subsequent problems. | |

186 | */ | |

187 | int | |

188 | problem() | |

189 | { | |

190 | char *p; | |

191 | time_t start, finish; | |

192 | int left, op, right, result; | |

193 | char line[80]; | |

194 | ||

195 | left = 0; | |

196 | right = 0; | |

197 | result = 0; | |

198 | op = keys[random() % nkeys]; | |

199 | if (op != '/') | |

200 | right = getrandom(rangemax + 1, op, 1); | |

201 | retry: | |

202 | /* Get the operands. */ | |

203 | switch (op) { | |

204 | case '+': | |

205 | left = getrandom(rangemax + 1, op, 0); | |

206 | result = left + right; | |

207 | break; | |

208 | case '-': | |

209 | result = getrandom(rangemax + 1, op, 0); | |

210 | left = right + result; | |

211 | break; | |

212 | case 'x': | |

213 | left = getrandom(rangemax + 1, op, 0); | |

214 | result = left * right; | |

215 | break; | |

216 | case '/': | |

217 | right = getrandom(rangemax, op, 1) + 1; | |

218 | result = getrandom(rangemax + 1, op, 0); | |

219 | left = right * result + random() % right; | |

220 | break; | |

221 | } | |

222 | ||

223 | /* | |

224 | * A very big maxrange could cause negative values to pop | |

225 | * up, owing to overflow. | |

226 | */ | |

227 | if (result < 0 || left < 0) | |

228 | goto retry; | |

229 | ||

230 | (void)printf("%d %c %d = ", left, op, right); | |

231 | (void)fflush(stdout); | |

232 | (void)time(&start); | |

233 | ||

234 | /* | |

235 | * Keep looping until the correct answer is given, or until EOF or | |

236 | * interrupt is typed. | |

237 | */ | |

238 | for (;;) { | |

239 | if (!fgets(line, sizeof(line), stdin)) { | |

240 | (void)printf("\n"); | |

241 | return(EOF); | |

242 | } | |

243 | for (p = line; *p && isspace(*p); ++p); | |

244 | if (!isdigit(*p)) { | |

245 | (void)printf("Please type a number.\n"); | |

246 | continue; | |

247 | } | |

248 | if (atoi(p) == result) { | |

249 | (void)printf("Right!\n"); | |

250 | ++nright; | |

251 | break; | |

252 | } | |

253 | /* Wrong answer; penalise and ask again. */ | |

254 | (void)printf("What?\n"); | |

255 | ++nwrong; | |

256 | penalise(right, op, 1); | |

257 | if (op == 'x' || op == '+') | |

258 | penalise(left, op, 0); | |

259 | else | |

260 | penalise(result, op, 0); | |

261 | } | |

262 | ||

263 | /* | |

264 | * Accumulate the time taken. Obviously rounding errors happen here; | |

265 | * however they should cancel out, because some of the time you are | |

266 | * charged for a partially elapsed second at the start, and some of | |

267 | * the time you are not charged for a partially elapsed second at the | |

268 | * end. | |

269 | */ | |

270 | (void)time(&finish); | |

271 | qtime += finish - start; | |

272 | return(0); | |

273 | } | |

274 | ||

275 | /* | |

276 | * Here is the code for accumulating penalties against the numbers for which | |

277 | * a wrong answer was given. The right operand and either the left operand | |

278 | * (for +, x) or the result (for -, /) are stored in a list for the particular | |

279 | * operation, and each becomes more likely to appear again in that operation. | |

280 | * Initially, each number is charged a penalty of WRONGPENALTY, giving it that | |

281 | * many extra chances of appearing. Each time it is selected because of this, | |

282 | * its penalty is decreased by one; it is removed when it reaches 0. | |

283 | * | |

284 | * The penalty[] array gives the sum of all penalties in the list for | |

285 | * each operation and each operand. The penlist[] array has the lists of | |

286 | * penalties themselves. | |

287 | */ | |

288 | ||

289 | int penalty[sizeof(keylist) - 1][2]; | |

290 | struct penalty { | |

291 | int value, penalty; /* Penalised value and its penalty. */ | |

292 | struct penalty *next; | |

293 | } *penlist[sizeof(keylist) - 1][2]; | |

294 | ||

295 | #define WRONGPENALTY 5 /* Perhaps this should depend on maxrange. */ | |

296 | ||

297 | /* | |

298 | * Add a penalty for the number `value' to the list for operation `op', | |

299 | * operand number `operand' (0 or 1). If we run out of memory, we just | |

300 | * forget about the penalty (how likely is this, anyway?). | |

301 | */ | |

302 | void | |

303 | penalise(value, op, operand) | |

304 | int value, op, operand; | |

305 | { | |

306 | struct penalty *p; | |

307 | ||

308 | op = opnum(op); | |

309 | if ((p = (struct penalty *)malloc((u_int)sizeof(*p))) == NULL) | |

310 | return; | |

311 | p->next = penlist[op][operand]; | |

312 | penlist[op][operand] = p; | |

313 | penalty[op][operand] += p->penalty = WRONGPENALTY; | |

314 | p->value = value; | |

315 | } | |

316 | ||

317 | /* | |

318 | * Select a random value from 0 to maxval - 1 for operand `operand' (0 or 1) | |

319 | * of operation `op'. The random number we generate is either used directly | |

320 | * as a value, or represents a position in the penalty list. If the latter, | |

321 | * we find the corresponding value and return that, decreasing its penalty. | |

322 | */ | |

323 | int | |

324 | getrandom(maxval, op, operand) | |

325 | int maxval, op, operand; | |

326 | { | |

327 | int value; | |

328 | struct penalty **pp, *p; | |

329 | ||

330 | op = opnum(op); | |

331 | value = random() % (maxval + penalty[op][operand]); | |

332 | ||

333 | /* | |

334 | * 0 to maxval - 1 is a number to be used directly; bigger values | |

335 | * are positions to be located in the penalty list. | |

336 | */ | |

337 | if (value < maxval) | |

338 | return(value); | |

339 | value -= maxval; | |

340 | ||

341 | /* | |

342 | * Find the penalty at position `value'; decrement its penalty and | |

343 | * delete it if it reaches 0; return the corresponding value. | |

344 | */ | |

345 | for (pp = &penlist[op][operand]; (p = *pp) != NULL; pp = &p->next) { | |

346 | if (p->penalty > value) { | |

347 | value = p->value; | |

348 | penalty[op][operand]--; | |

349 | if (--(p->penalty) <= 0) { | |

350 | p = p->next; | |

351 | (void)free((char *)*pp); | |

352 | *pp = p; | |

353 | } | |

354 | return(value); | |

355 | } | |

356 | value -= p->penalty; | |

357 | } | |

358 | /* | |

359 | * We can only get here if the value from the penalty[] array doesn't | |

360 | * correspond to the actual sum of penalties in the list. Provide an | |

361 | * obscure message. | |

362 | */ | |

363 | (void)fprintf(stderr, "arithmetic: bug: inconsistent penalties\n"); | |

364 | exit(1); | |

365 | /* NOTREACHED */ | |

366 | } | |

367 | ||

368 | /* Return an index for the character op, which is one of [+-x/]. */ | |

369 | int | |

370 | opnum(op) | |

371 | int op; | |

372 | { | |

373 | char *p; | |

374 | ||

375 | if (op == 0 || (p = index(keylist, op)) == NULL) { | |

376 | (void)fprintf(stderr, | |

377 | "arithmetic: bug: op %c not in keylist %s\n", op, keylist); | |

378 | exit(1); | |

379 | } | |

380 | return(p - keylist); | |

381 | } | |

382 | ||

383 | /* Print usage message and quit. */ | |

384 | static void | |

385 | usage() | |

386 | { | |

387 | (void)fprintf(stderr, "usage: arithmetic [-o +-x/] [-r range]\n"); | |

388 | exit(1); | |

389 | } |