Thursday, November 21, 2013

How Does Twentyfour Challenge Work?

Download Twentyfour Challenge (it's free) for Android and iOS from these locations:
Google Play
iTunes Store






The Phonegap Diaries describe how the Twentyfour Challenge app was built, but they don't really describe how it works. That's what I'm going to do here.

You probably guessed that the app has a built-in (recursive descent) parser that processes what is in the expression text box on the solver screen.
Within the app the text box does not permit data entry. If it did the user could just type in the goal number (say 24) and would immediately be rewarded with solving the problem. While parsing the code, it also builds an expression tree reflecting operator precedence for this ANTLR grammar (ANTLR is not used within the app):

grammar twentyfour;
solution:	expr ;
expr	:	multExpr ('+' multExpr | '-' multExpr)*
	;
multExpr:	atom ('*' atom | '/' atom)*
	;
atom	:	RATIONAL
	|	'(' expr ')'
	;
RATIONAL:	'0'..'9'+ ('/' '0'..'9')+;

The parser and rational expression evaluator are at the heart of the application, but where do the problems come from? The problems (and there are hundreds of them) are pre-programmed within the application. Each has this form:

// lvl :goal:   tuple1   :   tuple2   :     solution
    "4 : 24 : 6,10,15,6  : 2,10,15,10 : ((15-10)*6)-6"

A valid problem must have these properties:
  1. One of the tuples must yield a solution whereas the other must not
  2. The correct tuple values must appear in the solution expression.
  3. The solution expression must evaluate to the goal
Note that there may be more than one valid solution expression using the correct tuple. This just reflects the fact that 4 = 2+2 and 4 = 2*2. Only one of these valid solutions is pre-programmed, but the user can enter either solution to get the correct answer.

This brings up the subject of hints. I tried lots of alternative versions of the 24 game from the iTunes store. Most use four operands and some of the problems would stump me for minutes on end. All of the programs have a timer which makes you nervous as the seconds keep ticking away. If only the app would give me a hint!

That's it! in the next version of Twentyfour Challenge I will make the app both harder and easier. I will implement a level 4 of difficulty (that is, four operands) and I will implement a hint function at all levels of difficulty.

So how do hints work? There are two contexts -- the problem screen (below) and the solver screen.


Here the hint has been given as the incorrect tuple is disabled. The user should click the active tuple and move to the solver screen.

On the solver screen the hint button progressively shows the solution to the problem in the hint area (below).

Is this the best design for hints? Maybe not, but it certainly was easy to implement using regular expressions. The program examines the canned solution and extracts the operands from it. Here's the regular expression used to extract integer operands:

game.hintNumbers = game.problemSolution.match(/(\d+)/g);
game.hintSolution = game.problemSolution.replace(operand, "?");

When hints are given it just substitutes these numbers back one by one.

At first I though the hints should build on the progress the user has made on her own. It would suggest the next operator or operand (or clear a bad operator/operand). This was very hard to implement. Keep it simple, Sam!

Finally I should mention a back office application I wrote (thankfully) in Java that generated level 3 (three operands) and level 4 (four operands) problems. I had to write this program to guarantee that the pair of tuples for level 4 problems had the desired property of one correct and one incorrect tuple (the incorrectness had to be guaranteed).

This was done by brute force computation. The program generates four random integers and then all possible expressions using them as operands. It then evaluates each expression tree and records the results. Here's some output from the program:

	"4 : 24 : 8,11,15,9 : 6,9,18,8 : ((9-8)*18)+6",
//                                6-((8-9)*18)
//                                ((9/18)*8)*6
//                                6/((18/9)/8)
//                                ((6/18)*8)*9
//                                9/((18/6)/8)
//                                ((9-8)*6)+18
//                                18-((8-9)*6)
//                                ((6*9)*8)/18
//                                ((6*9)/18)*8
//                                8/((18/6)/9)
//                                (6*9)*(8/18)
//                                (6*9)/(18/8)
//                                (6/18)*(9*8)
//                                (9*8)/(18/6)
//                                (6+18)*(9-8)
//                                (6+18)/(9-8)
//                                (6*8)*(9/18)
//                                (6*8)/(18/9)
//                                (9/18)*(6*8)
//                                (6*8)/(18/9)
//                                (9-8)*(6+18)
//                                (6+18)/(9-8)
//                                (9*8)*(6/18)
//                                (9*8)/(18/6)
//                                (8/18)*(6*9)
//                                (6*9)/(18/8)

Obviously this problem has tons of solutions, but there is no solution involving [8,11,15,9].