The 24 Game tests one's mental arithmetic.



Task
Write a program that randomly chooses and displays four digits, each from 1 ──► 9 (inclusive) with repetitions allowed.

The program should prompt for the player to enter an arithmetic expression using just those, and all of those four digits, used exactly once each. The program should check then evaluate the expression.

The goal is for the player to enter an expression that (numerically) evaluates to 24.

 Only the following operators/functions are allowed: multiplication, division, addition, subtraction
 Division should use floating point or rational arithmetic, etc, to preserve remainders.
 Brackets are allowed, if using an infix expression evaluator.
 Forming multiple digit numbers from the supplied digits is disallowed. (So an answer of 12+12 when given 1, 2, 2, and 1 is wrong).
 The order of the digits when given does not have to be preserved.


Notes
 The type of expression evaluator used is not mandated. An RPN evaluator is equally acceptable for example.
 The task is not for the program to generate the expression, or test whether an expression is even possible.


Related tasks
 24 game/Solve


Reference
 The 24 Game on h2g2.


BEGIN
 
    CLASS EXPR;
    BEGIN
 
 
        REAL PROCEDURE POP;
        BEGIN
            IF STACKPOS > 0 THEN
            BEGIN STACKPOS := STACKPOS - 1; POP := STACK(STACKPOS); END;
        END POP;
 
 
        PROCEDURE PUSH(NEWTOP); REAL NEWTOP;
        BEGIN
            STACK(STACKPOS) := NEWTOP;
            STACKPOS := STACKPOS + 1;
        END PUSH;
 
 
        REAL PROCEDURE CALC(OPERATOR, ERR); CHARACTER OPERATOR; LABEL ERR;
        BEGIN
            REAL X, Y; X := POP; Y := POP;
            IF      OPERATOR = '+' THEN PUSH(Y + X)
            ELSE IF OPERATOR = '-' THEN PUSH(Y - X)
            ELSE IF OPERATOR = '*' THEN PUSH(Y * X)
            ELSE IF OPERATOR = '/' THEN BEGIN
                                            IF X = 0 THEN
                                            BEGIN
                                                EVALUATEDERR :- "DIV BY ZERO";
                                                GOTO ERR;
                                            END;
                                            PUSH(Y / X);
                                        END
            ELSE
            BEGIN
                EVALUATEDERR :- "UNKNOWN OPERATOR";
                GOTO ERR;
            END
        END CALC;
 
 
        PROCEDURE READCHAR(CH); NAME CH; CHARACTER CH;
        BEGIN
            IF T.MORE THEN CH := T.GETCHAR ELSE CH := EOT;
        END READCHAR;
 
 
        PROCEDURE SKIPWHITESPACE(CH); NAME CH; CHARACTER CH;
        BEGIN
            WHILE (CH = SPACE) OR (CH = TAB) OR (CH = CR) OR (CH = LF) DO
                READCHAR(CH);
        END SKIPWHITESPACE;
 
 
        PROCEDURE BUSYBOX(OP, ERR); INTEGER OP; LABEL ERR;
        BEGIN
            CHARACTER OPERATOR;
            REAL NUMBR;
            BOOLEAN NEGATIVE;
 
            SKIPWHITESPACE(CH);
 
            IF OP = EXPRESSION THEN
            BEGIN
 
                NEGATIVE := FALSE;
                WHILE (CH = '+') OR (CH = '-') DO
                BEGIN
                    IF CH = '-' THEN NEGATIVE :=  NOT NEGATIVE;
                    READCHAR(CH);
                END;
 
                BUSYBOX(TERM, ERR);
 
                IF NEGATIVE THEN
                BEGIN
                    NUMBR := POP; PUSH(0 - NUMBR);
                END;
 
                WHILE (CH = '+') OR (CH = '-') DO
                BEGIN
                    OPERATOR := CH; READCHAR(CH);
                    BUSYBOX(TERM, ERR); CALC(OPERATOR, ERR);
                END;
 
            END
            ELSE IF OP = TERM THEN
            BEGIN
 
                BUSYBOX(FACTOR, ERR);
                WHILE (CH = '*') OR (CH = '/') DO
                BEGIN
                    OPERATOR := CH; READCHAR(CH);
                    BUSYBOX(FACTOR, ERR); CALC(OPERATOR, ERR)
                END
 
            END
            ELSE IF OP = FACTOR THEN
            BEGIN
 
                IF (CH = '+') OR (CH = '-') THEN
                  BUSYBOX(EXPRESSION, ERR)
                ELSE IF (CH >= '0') AND (CH <= '9') THEN
                  BUSYBOX(NUMBER, ERR)
                ELSE IF CH = '(' THEN
                BEGIN
                    READCHAR(CH);
                    BUSYBOX(EXPRESSION, ERR);
                    IF CH = ')' THEN READCHAR(CH) ELSE GOTO ERR;
                END
                ELSE GOTO ERR;
 
            END
            ELSE IF OP = NUMBER THEN
            BEGIN
 
                NUMBR := 0;
                WHILE (CH >= '0') AND (CH <= '9') DO
                BEGIN
                    NUMBR := 10 * NUMBR + RANK(CH) - RANK('0'); READCHAR(CH);
                END;
                IF CH = '.' THEN
                BEGIN
                    REAL FAKTOR;
                    READCHAR(CH);
                    FAKTOR := 10;
                    WHILE (CH >= '0') AND (CH <= '9') DO
                    BEGIN
                        NUMBR := NUMBR + (RANK(CH) - RANK('0')) / FAKTOR;
                        FAKTOR := 10 * FAKTOR;
                        READCHAR(CH);
                    END;
                END;
                PUSH(NUMBR);
 
            END;
 
            SKIPWHITESPACE(CH);
 
        END BUSYBOX;
 
 
        BOOLEAN PROCEDURE EVAL(INP); TEXT INP;
        BEGIN
            EVALUATEDERR :- NOTEXT;
            STACKPOS := 0;
            T :- COPY(INP.STRIP);
            READCHAR(CH);
            BUSYBOX(EXPRESSION, ERRORLABEL);
          ! OUTTEXT("T = '");
          ! OUTTEXT(T);
          ! OUTTEXT("'");
          ! OUTTEXT(", T.POS = ");
          ! OUTINT(T.POS, 0);
          ! OUTTEXT(", STACKPOS = ");
          ! OUTINT(STACKPOS, 0);
          ! OUTTEXT(", T.MORE = ");
          ! OUTCHAR(IF T.MORE THEN 'T' ELSE 'F');
          ! OUTTEXT(", CH = ");
          ! OUTCHAR(CH);
          ! OUTIMAGE;
            IF NOT T.MORE AND STACKPOS = 1 AND CH = EOT THEN
            BEGIN
                EVALUATED := POP;
                EVAL := TRUE;
                GOTO NOERRORLABEL;
            END;
    ERRORLABEL:
            EVAL := FALSE;
            IF EVALUATEDERR = NOTEXT THEN
                EVALUATEDERR :- "INVALID EXPRESSION: " & INP;
    NOERRORLABEL:
        END EVAL;
 
 
        REAL PROCEDURE RESULT;
            RESULT := EVALUATED;
 
        TEXT PROCEDURE ERR;
            ERR :- EVALUATEDERR;
 
        TEXT T;
 
        INTEGER EXPRESSION;
        INTEGER TERM;
        INTEGER FACTOR;
        INTEGER NUMBER;
 
        CHARACTER TAB;
        CHARACTER LF;
        CHARACTER CR;
        CHARACTER SPACE;
        CHARACTER EOT;
 
        CHARACTER CH;
        REAL ARRAY STACK(0:31);
        INTEGER STACKPOS;
 
        REAL EVALUATED;
        TEXT EVALUATEDERR;
 
        EXPRESSION := 1;
        TERM := 2;
        FACTOR := 3;
        NUMBER := 4;
 
        TAB := CHAR(9);
        LF := CHAR(10);
        CR := CHAR(13);
        SPACE := CHAR(32);
        EOT := CHAR(0);
 
    END EXPR;
 
    INTEGER ARRAY DIGITS(1:4);
    INTEGER SEED, I;
    REF(EXPR) E;
 
    E :- NEW EXPR;
    OUTTEXT("ENTER RANDOM SEED: ");
    OUTIMAGE;
    SEED := ININT;
    FOR I := 1 STEP 1 UNTIL 4 DO DIGITS(I) := RANDINT(0, 9, SEED);
 
L:  BEGIN
        INTEGER ARRAY DIGITSUSED(0:9);
        INTEGER ARRAY DIGITSTAKEN(0:9);
        CHARACTER C, LASTC;
        TEXT INP;
 
        LASTC := CHAR(0);
        OUTTEXT("MAKE 24 USING THESE DIGITS: ");
        FOR I := 1 STEP 1 UNTIL 4 DO
        BEGIN
            OUTINT(DIGITS(I), 2);
            DIGITSUSED( DIGITS(I) ) := DIGITSUSED( DIGITS(I) ) + 1;
        END;
        OUTIMAGE;
        INIMAGE;
        INP :- COPY(SYSIN.IMAGE.STRIP);
        OUTIMAGE;
        WHILE INP.MORE DO
        BEGIN
            C := INP.GETCHAR;
            IF (C >= '0') AND (C <= '9') THEN
            BEGIN
                INTEGER D;
                IF (LASTC >= '0') AND (LASTC <= '9') THEN
                BEGIN
                    OUTTEXT("NUMBER HAS TOO MANY DIGITS: ");
                    OUTCHAR(LASTC);
                    OUTCHAR(C);
                    OUTIMAGE;
                    GOTO L;
                END;
                D := RANK(C) - RANK('0');
                DIGITSTAKEN(D) := DIGITSTAKEN(D) + 1;
            END
            ELSE IF NOT ((C = '+') OR (C = '-') OR (C = '/') OR (C = '*') OR
                         (C = ' ') OR (C = '(') OR (C = ')')) THEN
            BEGIN
                OUTTEXT("ILLEGAL INPUT CHARACTER: ");
                OUTCHAR(C);
                OUTIMAGE;
                GOTO L;
            END;
            LASTC := C;
        END;
        FOR I := 0 STEP 1 UNTIL 9 DO
        BEGIN
            IF DIGITSUSED(I) <> DIGITSTAKEN(I) THEN
            BEGIN
                OUTTEXT("NOT THE SAME DIGITS.");
                OUTIMAGE;
                GOTO L;
            END;
        END;
        IF E.EVAL(INP) THEN
        BEGIN
            OUTTEXT("RESULT IS ");
            OUTFIX(E.RESULT, 4, 10);
            OUTIMAGE;
            OUTTEXT(IF ABS(E.RESULT - 24) < 0.001
                    THEN "YOU WIN"
                    ELSE "YOU LOOSE");
            OUTIMAGE;
        END
        ELSE
        BEGIN
            OUTTEXT(E.ERR);
            OUTIMAGE;
        END;
    END;
 
END.
 