RosettaCodeData/Task/24-game/C/24-game.c
2014-01-17 05:34:36 +00:00

281 lines
4.8 KiB
C

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <setjmp.h>
#include <time.h>
jmp_buf ctx;
const char *msg;
enum { OP_NONE = 0, OP_NUM, OP_ADD, OP_SUB, OP_MUL, OP_DIV };
typedef struct expr_t *expr, expr_t;
struct expr_t {
int op, val, used;
expr left, right;
};
#define N_DIGITS 4
expr_t digits[N_DIGITS];
void gen_digits()
{
int i;
for (i = 0; i < N_DIGITS; i++)
digits[i].val = 1 + rand() % 9;
}
#define MAX_INPUT 64
char str[MAX_INPUT];
int pos;
#define POOL_SIZE 8
expr_t pool[POOL_SIZE];
int pool_ptr;
void reset()
{
int i;
msg = 0;
pool_ptr = pos = 0;
for (i = 0; i < POOL_SIZE; i++) {
pool[i].op = OP_NONE;
pool[i].left = pool[i].right = 0;
}
for (i = 0; i < N_DIGITS; i++)
digits[i].used = 0;
}
/* longish jumpish back to input cycle */
void bail(const char *s)
{
msg = s;
longjmp(ctx, 1);
}
expr new_expr()
{
if (pool_ptr < POOL_SIZE)
return pool + pool_ptr++;
return 0;
}
/* check next input char */
int next_tok()
{
while (isspace(str[pos])) pos++;
return str[pos];
}
/* move input pointer forward */
int take()
{
if (str[pos] != '\0') return ++pos;
return 0;
}
/* BNF(ish)
expr = term { ("+")|("-") term }
term = fact { ("*")|("/") expr }
fact = number
| '(' expr ')'
*/
expr get_fact();
expr get_term();
expr get_expr();
expr get_expr()
{
int c;
expr l, r, ret;
if (!(ret = get_term())) bail("Expected term");
while ((c = next_tok()) == '+' || c == '-') {
if (!take()) bail("Unexpected end of input");
if (!(r = get_term())) bail("Expected term");
l = ret;
ret = new_expr();
ret->op = (c == '+') ? OP_ADD : OP_SUB;
ret->left = l;
ret->right = r;
}
return ret;
}
expr get_term()
{
int c;
expr l, r, ret;
ret = get_fact();
while((c = next_tok()) == '*' || c == '/') {
if (!take()) bail("Unexpected end of input");
r = get_fact();
l = ret;
ret = new_expr();
ret->op = (c == '*') ? OP_MUL : OP_DIV;
ret->left = l;
ret->right = r;
}
return ret;
}
expr get_digit()
{
int i, c = next_tok();
expr ret;
if (c >= '0' && c <= '9') {
take();
ret = new_expr();
ret->op = OP_NUM;
ret->val = c - '0';
for (i = 0; i < N_DIGITS; i++)
if (digits[i].val == ret->val && !digits[i].used) {
digits[i].used = 1;
return ret;
}
bail("Invalid digit");
}
return 0;
}
expr get_fact()
{
int c;
expr l = get_digit();
if (l) return l;
if ((c = next_tok()) == '(') {
take();
l = get_expr();
if (next_tok() != ')') bail("Unbalanced parens");
take();
return l;
}
return 0;
}
expr parse()
{
int i;
expr ret = get_expr();
if (next_tok() != '\0')
bail("Trailing garbage");
for (i = 0; i < N_DIGITS; i++)
if (!digits[i].used)
bail("Not all digits are used");
return ret;
}
typedef struct frac_t frac_t, *frac;
struct frac_t { int denom, num; };
int gcd(int m, int n)
{
int t;
while (m) {
t = m; m = n % m; n = t;
}
return n;
}
/* evaluate expression tree. result in fraction form */
void eval_tree(expr e, frac res)
{
frac_t l, r;
int t;
if (e->op == OP_NUM) {
res->num = e->val;
res->denom = 1;
return;
}
eval_tree(e->left, &l);
eval_tree(e->right, &r);
switch(e->op) {
case OP_ADD:
res->num = l.num * r.denom + l.denom * r.num;
res->denom = l.denom * r.denom;
break;
case OP_SUB:
res->num = l.num * r.denom - l.denom * r.num;
res->denom = l.denom * r.denom;
break;
case OP_MUL:
res->num = l.num * r.num;
res->denom = l.denom * r.denom;
break;
case OP_DIV:
res->num = l.num * r.denom;
res->denom = l.denom * r.num;
break;
}
if ((t = gcd(res->denom, res->num))) {
res->denom /= t;
res->num /= t;
}
}
void get_input()
{
int i;
reinput:
reset();
printf("\nAvailable digits are:");
for (i = 0; i < N_DIGITS; i++)
printf(" %d", digits[i].val);
printf(". Type an expression and I'll check it for you, or make new numbers.\n"
"Your choice? [Expr/n/q] ");
while (1) {
for (i = 0; i < MAX_INPUT; i++) str[i] = '\n';
fgets(str, MAX_INPUT, stdin);
if (*str == '\0') goto reinput;
if (str[MAX_INPUT - 1] != '\n')
bail("string too long");
for (i = 0; i < MAX_INPUT; i++)
if (str[i] == '\n') str[i] = '\0';
if (str[0] == 'q') {
printf("Bye\n");
exit(0);
}
if (str[0] == 'n') {
gen_digits();
goto reinput;
}
return;
}
}
int main()
{
frac_t f;
srand(time(0));
gen_digits();
while(1) {
get_input();
setjmp(ctx); /* if parse error, jump back here with err msg set */
if (msg) {
/* after error jump; announce, reset, redo */
printf("%s at '%.*s'\n", msg, pos, str);
continue;
}
eval_tree(parse(), &f);
if (f.denom == 0) bail("Divide by zero");
if (f.denom == 1 && f.num == 24)
printf("You got 24. Very good.\n");
else {
if (f.denom == 1)
printf("Eval to: %d, ", f.num);
else
printf("Eval to: %d/%d, ", f.num, f.denom);
printf("no good. Try again.\n");
}
}
return 0;
}