// The 'Carol Vorderman' solver for Countdown. // Written is a style that should make it easy to convert into Scratch... #include <stdio.h> #include <stdlib.h> #include <ctype.h> // for 'isdigit' in argv processing. #ifndef TRUE #define TRUE (0==0) #define FALSE (0!=0) #endif #define NUM 0 #define OP 1 #define OP_ADD 1 #define OP_SUBTRACT 2 #define OP_MULTIPLY 3 #define OP_DIVIDE 4 #define OP_END 5 #define SIX 7 // :-) #define ELEVEN 12 // little joke here, it's to allow for C's 0-based arrays vs Scratch's 1-based arrays int last_rp_item = 0; int num_or_op_val[ELEVEN]; // max size is 6 nums + 5 ops - may use fewer ops if balanced tree int num_or_op_type[ELEVEN]; int selection[SIX]; int target; // remove the 0th element of initialised arrays when converting to Scratch: int order[] = { 0, 1, 2, 3, 4, 5, 6 }; // this indexes 'selection', and is shuffled by 'permute' char *opstr[] = { "", "+", "-", "*", "/" }; int nums_emitted = 0; int invalid; int seeking = TRUE; float stack[SIX]; int eval_stack_ptr; void push(float f) { stack[++eval_stack_ptr] = f; } float pop(void) { return stack[eval_stack_ptr--]; } static float a, b; void operate(int op, int io) { b = pop(); a = pop(); // order needed for - and / if (op == OP_ADD) { push(a+b); } else if (op == OP_SUBTRACT) { if (a <= b) { push(1.0); invalid = TRUE; } else push(a-b); // negative results disallowed, also subtractions that result in 0? (See 'simcarol') } else if (op == OP_MULTIPLY) { if ((a == 1.0) || (b == 1.0)) { push(1.0); invalid = TRUE; } else push(a*b); // banning * 1 or / 1 makes for more interesting solutions } else if (op == OP_DIVIDE) { if ((b == 0.0) || (b == 1.0)) { push(1.0); invalid = TRUE; } else { push(a/b); if (stack[eval_stack_ptr] - (float)((int)stack[eval_stack_ptr]) != 0.0) invalid = TRUE; } } if (io) printf("%d %s %d = %d\n", (int)a, opstr[op], (int)b, (int)stack[eval_stack_ptr]); } static float eval_result; static int eval_i; void eval(int io) { eval_stack_ptr = 0; // reset evaluation stack ptr in case a previous calculation left the stack unbalanced by setting 'invalid' and exiting for (eval_i = 1; eval_i <= last_rp_item; eval_i++) { if (num_or_op_type[eval_i] == NUM) push((float)num_or_op_val[eval_i]); else operate(num_or_op_val[eval_i], io); } eval_result = stack[eval_stack_ptr]; } static int operator; static int operator_stack[SIX]; static int operator_sp = 0; void compute(int rp_index, int nextnum) { // try all operators in all positions when we have an operator on the RP stack operator_sp += 1; operator_stack[operator_sp] = operator; if (rp_index <= last_rp_item) { if (num_or_op_type[rp_index] == NUM) { num_or_op_val[rp_index] = selection[order[nextnum]]; compute(rp_index+1, nextnum+1); } else { for (operator = OP_ADD; operator != OP_END; operator++) { num_or_op_val[rp_index] = operator; compute(rp_index+1, nextnum); } } } else { invalid = FALSE; eval(FALSE); if ((!invalid) && (eval_result == (float)target)) { eval(TRUE); seeking = FALSE; } } operator = operator_stack[operator_sp]; operator_sp -= 1; } static int permute_j; // this needs to be translated carefully into Scratch - keep a stack for the j's static int permute_j_stack[SIX]; static int permute_j_sp = 0; static int swap; void permute(int i, int n) { // calc all orderings of 6 items permute_j_sp += 1; // trick to support recursion in Scratch permute_j_stack[permute_j_sp] = permute_j; if (i == n) compute(0, 1); else { for (permute_j = i; permute_j <= n; permute_j++) { if (seeking) { swap = order[i]; order[i] = order[permute_j]; order[permute_j] = swap; permute(i+1, n); swap = order[i]; order[i] = order[permute_j]; order[permute_j] = swap; } } } permute_j = permute_j_stack[permute_j_sp]; permute_j_sp -= 1; } static int virtual_stack_depth = 0; // no of items that would be on the stack when evaluating the RP at this position void undo(int num_or_op) { if (num_or_op == NUM) { nums_emitted -= 1; virtual_stack_depth -= 1; } else { virtual_stack_depth += 1; // - 1 (rslt) + 2 (opds) } last_rp_item -= 1; } void emit(int num_or_op) { last_rp_item += 1; if ((num_or_op) == NUM) { nums_emitted += 1; virtual_stack_depth += 1; } else { virtual_stack_depth -= 1; } num_or_op_type[last_rp_item] = num_or_op; } void generate_rp_formulae(int max) { // generate all the valid RP expressions for 6 variables if (seeking) { if (nums_emitted == max) { if (virtual_stack_depth == 1) { // we've output enough ops to calculate the result permute(1, max); } else { emit(OP); generate_rp_formulae(max); undo(OP); // need more ops } } else { if (virtual_stack_depth >= 2) { emit(OP); generate_rp_formulae(max); undo(OP); } emit(NUM); generate_rp_formulae(max); undo(NUM); // haven't used all nums yet (must we?) } } } static int i; int main(int argc, char **argv) { if (argc != 8) { fprintf(stderr, "syntax: %s target n1 n2 n3 n4 n5 n6\n", argv[0]); exit(1); } if (!isdigit(*argv[1])) { fprintf(stderr, "carol: the target must be a number. (\"%s\" isn't)\n", argv[1]); exit(1); } else { target = atol(argv[1]); } // probably better to use strtol than atol, to check for characters after the integer part if (target < 100 || target >= 1000) { fprintf(stderr, "carol: the target must be a 3-digit number. (\"%s\" isn't)\n", argv[1]); exit(1); } for (i = 0; i < 6; i++) { if (!isdigit(*argv[i+2])) { fprintf(stderr, "carol: all arguments must be numbers. (\"%s\" isn't)\n", argv[i+2]); exit(1); } selection[i+1] = atol(argv[i+2]); if (selection[i+1] <= 0 || selection[i+1] >= 100) { fprintf(stderr, "carol: the numbers must be between 1 and 99. (\"%s\" isn't)\n", argv[i+2]); exit(1); } } printf("Target: %d\n", target); printf("Numbers: %d %d %d %d %d %d\n", selection[1],selection[2],selection[3],selection[4],selection[5],selection[6]); for (i = 2; i < 6; i++) { if (seeking) generate_rp_formulae(i); // we assume the target number is not trivially in the selection! } if (!seeking) exit(0); printf("No exact solution found! I haven't yet written the code to remember the next closest solution, sorry.\n"); exit(2); }