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.


MODULE TwentyFour;
 
FROM	InOut			IMPORT WriteString, WriteLn, Write, ReadString, WriteInt;
FROM	RandomGenerator IMPORT Random;
 
TYPE	operator_t 		= (add, sub, mul, div);
		expr_t			= RECORD
							operand		: ARRAY[0..3] OF CARDINAL;
					  		operator 	: ARRAY[1..3] OF operator_t;
						  END;(*of RECORD*)
		numbers_t		= SET OF CHAR;
 
VAR		expr 	: expr_t;
		numbers	: numbers_t;
(*******************************************************************createExpr*)
(*analyse the input string                                                    *)
PROCEDURE createExpr(s: ARRAY OF CHAR);
 
VAR index, counter	: INTEGER;
	token 			: CHAR;
	temp_expr 		: expr_t;
	operand   		: CARDINAL;
	operator		: operator_t;
 
	(************************************nextToken*)
	(* returns the next CHAR that isn`t a space    *)
	PROCEDURE nextToken(): CHAR;
	BEGIN
		INC(index);
		WHILE (s[index] = ' ') DO
			INC(index);
		END;(*of WHILE*)
		RETURN(s[index]);
	END nextToken;
	(***********************************set_operand*)
	(* checks if the CHAR o inerhits a valid number*)
	(* and sets 'operand' to its value             *)	
	PROCEDURE set_operand(o: CHAR);
	BEGIN
		CASE o OF
			'0'..'9':	IF o IN numbers THEN
							operand := ORD(o)-48;
							numbers := numbers - numbers_t{o};
						ELSE
							WriteString("ERROR: '");
							Write(					o);
							WriteString(				"' isn`t a available number ");
							WriteLn;
							HALT;
						END;(*of IF*)|
			0  : WriteString("ERROR: error in input ");
					WriteLn;
					HALT;
			ELSE
				WriteString("ERROR: '");
				Write(					o);
				WriteString(				"' isn`t a number ");
				WriteLn;
				HALT;
		END;(*of CASE*)
	END set_operand;
	(**********************************set_operator*)
	(* checks if the CHAR o inerhits a valid       *)
	(* operator and sets 'operator' to its value   *)
	PROCEDURE set_operator(o: CHAR);
	BEGIN
		CASE o OF
			'+' : operator := add;|
			'-' : operator := sub;|
			'*' : operator := mul;|
			'/' : operator := div;|
			0  : WriteString("ERROR: error in input ");
					WriteLn;
					HALT;
		ELSE
			WriteString("ERROR: '");
				Write(					o);
				WriteString(				"' isn`t a operator ");
				WriteLn;
				HALT;
		END;(*of CASE*)
	END set_operator;
	(************************************************)
BEGIN
	index := -1;
 
	token := nextToken();
	set_operand(token);
	expr.operand[0] := operand;
 
	token := nextToken();
	set_operator(token);
	expr.operator[1] := operator;
 
 
	token := nextToken();
	set_operand(token);
	expr.operand[1] := operand;
 
	token := nextToken();
	set_operator(token);
	expr.operator[2] := operator;
 
	token := nextToken();
	set_operand(token);
	expr.operand[2] := operand;
 
	token := nextToken();
	set_operator(token);
	expr.operator[3] := operator;
 
	token := nextToken();
	set_operand(token);
	expr.operand[3] := operand;
END createExpr;
 
(*****************************************************************evaluateExpr*)
(* evaluate the expresion that was createt by 'createExpr'                    *)
 
PROCEDURE evaluateExpr(VAR num: REAL);
 
VAR	index	: INTEGER;
BEGIN
	WITH expr DO
		num := VAL(REAL,operand[0]);
		FOR index := 1 TO 3 DO
			CASE operator[index] OF
				add : num := num + VAL(REAL,operand[index]);|
				sub : num := num - VAL(REAL,operand[index]);|
				mul : num := num * VAL(REAL,operand[index]);|
				div : num := num / VAL(REAL,operand[index]);
			END;(*of CASE*)
		END;(*of FOR*)
	END;(*of WITH*)
END evaluateExpr;
 
(**************************************************************generateNumbers*)
(* generates the 4 random numbers ond write them                              *)
PROCEDURE generateNumbers;
VAR	index,ran : INTEGER;
BEGIN
	numbers := numbers_t{};
	ran := Random(0,9);
	FOR index := 1 TO 4 DO
		WHILE (CHR(ran+48) IN numbers )DO
			ran := Random(0,9);
		END;(*of While*)
		Write(CHR(ran+48));
		WriteLn;
		numbers := numbers + numbers_t{CHR(ran+48)}
	END;(*of FOR*)
END generateNumbers;
(****************************************************************Main Programm*)
VAR	str : ARRAY[0..255] OF CHAR;
	sum : REAL;
BEGIN
	WriteString("Welcome to the 24 game in MODULA-2");
	WriteLn;
	WriteString("Here are your numbers:");
	WriteLn;
	generateNumbers;
	WriteString("Enter your equation(This implementation dosn`t support brackets yet): ");
	WriteLn;
	ReadString(str);
	createExpr(str);
	evaluateExpr(sum);
	WriteLn;
	WriteString("Result:");
	WriteLn;
	WriteInt(TRUNC(sum),0);
	WriteLn;
	CASE (TRUNC(sum) - 24) OF
		0 : WriteString("Perfect!");|
		1 : WriteString("Almost perfect.");
		ELSE 
			WriteString("You loose!");
	END;(*of CASE*)
	WriteLn;
END TwentyFour.