24 game
From Rosetta Code
The 24 Game tests ones mental arithmetic.
Write a program that randomly chooses and displays four digits, each from one to nine, with repetitions allowed. The program should prompt for the player to enter an equation using just those, and all of those four digits. The program should check then evaluate the expression. The goal is for the player to enter an expression that evaluates to 24.
- Only multiplication, division, addition, and subtraction operators/functions are allowed.
- Division should use floating point or rational arithmetic, etc, to preserve remainders.
- If using an infix expression evaluator then brackets are allowed.
- 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.
Note:
- 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.
C.f: 24 game Player
Reference
- The 24 Game on h2g2.
Contents |
[edit] AutoHotkey
All supported operators allowed.
#SingleInstance OFF
#Persistent
SetBatchLines, -1
Loop, 4
Random, r%A_Index%, 1, 9
Gui, +OwnDialogs
Gui, Add, Text,, The numbers: %r1%, %r2%, %r3%, and %r4% - Good luck!
Gui, Add, Edit, vAns w500
Gui, Add, Button, w500, Submit
Gui, Show,, 24 Game
Return
ButtonGo:
Gui, Submit, NoHide
Wrong :=
Loop, 9
{
If A_Index in %r1%,%r2%,%r3%,%r4%
Continue
Else If InStr(Ans, A_Index)
{
wrong := 1
Break
}
}
If (Eval(Ans) = 24 && !wrong)
Goto, WIN
MsgBox,, 24 Game, Incorrect solution!`n%Ans%
Return
WIN:
MsgBox, 4, 24 Game, Correct solution!`n%Ans%`nPlay again?
IfMsgBox, Yes
Reload
ExitApp
Eval(x=0)
{
Return % v, VarSetCapacity(b,44,0), NumPut(102|1<<8,b,0,"s"), NumPut(&b+32,b,4), NumPut(1,b,33,"c"), cb:=RegisterCallback("stub"), NumPut(&b,NumGet(cb+28),4), DllCall("GlobalFree","uint",cb), NumPut(StrLen(x)+1,b,34,"s"), NumPut(&x,b,36), v:=stub() ; Taken almost directly (deleted x:="expression", as x is the functions parameter) from Lexikos at http://www.autohotkey.com/forum/topic32071.html (one-liner contest), may or may not be functional. If no, I'd appreciate someone correcting it.
}
stub()
{
Return
}
[edit] C
See 24 game/C
[edit] Clojure
(ns game
(:use clojure.contrib.fcase))
(def op-map {\+ + \- - \* * \/ /})
; Returns nil iff x is not an operator char
(defn- op? [x] (get op-map x))
; Sneaky... but works because of op?'s implementation
(def get-op op?)
(def ws-set #{\space \tab \newline})
(defn- ws? [x] (contains? ws-set x))
; Returns the numeric value of an ASCII digit character.
; Returns -1 if the character is not a digit.
(defn- atoi [ch] (Character/getNumericValue ch))
(defn- digit? [x] (Character/isDigit x))
(defn- fail [& msg-parts]
(throw (RuntimeException. (apply str msg-parts))))
; Returns a seq of n random ints from 1 to 9 inclusive
(defn- pick-nums [n]
(for [i (range n)] (inc (rand-int 9))))
; Returns a (lazy) seq of numbers and operators
; as seen in s.
(defn- lex [s]
(filter #(not (nil? %)) ; Skip whitespace
(for [ch s]
(cond
(ws? ch) nil
(pos? (atoi ch)) (atoi ch)
(op? ch) (get-op ch)
true (fail "Invalid input character: " ch)))))
; Returns the value for 'a0 op a1'
(defn- dispatch [op a0 a1]
(if-not (and (op? op) (number? a0) (number? a1))
(fail "Doesn't parse!")
(op a0 a1)))
; Evaluates an rpn expression prefix
; Returns [the-rest-of-the-input, the-value-computed]
(defn- do-eval [in]
(let [hd (first in) tl (rest in)]
(cond
(number? hd) [tl hd]
(op? hd)
(let [[rst a0] (do-eval tl) [rst a1] (do-eval rst)]
[rst (dispatch hd a0 a1)])
true (fail "Tried to eval '" hd "' but needed op or num."))))
; Returns coll without the first ocurrence of x.
; If coll didn't contain x, returns nil.
(defn- without [coll x]
(let [[a b] (split-with #(not= x %) coll)]
(if (empty? b)
nil
(concat a (rest b)))))
; Returns true iff 'in' contains exactly one
; ocurrence of each number in 'expect'
(defn- check [expect in]
(if (empty? expect)
(or (empty? in)
(and (not (number? (first in)))
(recur expect (rest in))))
(if (empty? in)
nil ; Didn't use all numbers
(let [hd (first in) tl (rest in)]
(if-not (number? hd)
(recur expect tl)
(when-let [ok (without expect hd)]
(recur ok tl)))))))
; Returns true iff input evaluates to 'result' and
; consists of each number from nums combined
; via +, -, * and /
(defn- decide
([nums input] (decide nums input 24))
([nums input result]
(let [tokens (lex input)] ; Scan the input for meaningful tokens
(when-not (check nums tokens) (fail "Didn't use the right numbers."))
(let [[leftovers answer] (do-eval tokens)]
(when-not (empty? leftovers) (fail "Doesn't parse!"))
(when-not (= result answer) (fail "That equals " answer))
nil)))) ; WIN!
; Asks the user if they would like to play again.
(defn- new-game? []
(println "Would you like to play again? (y/n)")
(let [input (read-line)]
(case input
"y" true
"n" nil
(recur))))
; Plays a game of 24
(defn play-game []
(let [nums (pick-nums 4)]
; Prompt the user
(print "Use these numbers to make 24:")
(doseq [n nums] (print (str " " n)))
(newline)
(flush)
; Get some input
(let [input (read-line)]
(try
(decide nums input)
(println "You win!")
(catch RuntimeException e
(println (. e getMessage) "You lose.")))
(if (new-game?)
(recur)
(println "Goodbye!")))))
[edit] Common Lisp
(define-condition choose-digits () ())
(define-condition bad-equation (error) ())
(defun 24-game ()
(let (chosen-digits)
(labels ((prompt ()
(format t "Chosen digits: ~{~D~^, ~}~%~
Enter expression (or `bye' to quit, `!' to choose new digits): "
chosen-digits)
(read))
(lose () (error 'bad-equation))
(choose () (setf chosen-digits (loop repeat 4 collecting (random 10))))
(check (e)
(typecase e
((eql bye) (return-from 24-game))
((eql !) (signal 'choose-digits))
(atom (lose))
(cons (check-sub (car e) (check-sub (cdr e) chosen-digits)) e)))
(check-sub (sub allowed-digits)
(typecase sub
((member nil + - * /) allowed-digits)
(integer
(if (member sub allowed-digits)
(remove sub allowed-digits :count 1)
(lose)))
(cons (check-sub (car sub) (check-sub (cdr sub) allowed-digits)))
(t (lose))))
(win ()
(format t "You win.~%")
(return-from 24-game)))
(choose)
(loop
(handler-case
(if (= 24 (eval (check (prompt)))) (win) (lose))
(error () (format t "Bad equation, try again.~%"))
(choose-digits () (choose)))))))
Verbose Implementation
Works with: clisp version 2.47
(defconstant +ops+ '(* / + -))
(defun expr-numbers (e &optional acc)
"Return all the numbers in argument positions in the expression."
(cond
((numberp e) (cons e acc))
((consp e)
(append (apply #'append
(mapcar #'expr-numbers (cdr e)))
acc))))
(defun expr-well-formed-p (e)
"Return non-nil if the given expression is well-formed."
(cond
((numberp e) t)
((consp e)
(and (member (car e) +ops+)
(every #'expr-well-formed-p (cdr e))))
(t nil)))
(defun expr-valid-p (e available-digits)
"Return non-nil if the expression is well-formed and uses exactly
the digits specified."
(and (expr-well-formed-p e)
(equalp (sort (copy-seq available-digits) #'<)
(sort (expr-numbers e) #'<))))
(defun expr-get (&optional using)
(emit "Enter lisp form~@[ using the digit~P ~{~D~^ ~}~]: "
(when using
(length using)) using)
(let (*read-eval*)
(read)))
(defun digits ()
(sort (loop repeat 4 collect (1+ (random 9))) #'<))
(defun emit (fmt &rest args)
(format t "~&~?" fmt args))
(defun prompt (digits)
(emit "Using only these operators:~%~%~
~2T~{~A~^ ~}~%~%~
And exactly these numbers \(no repetition\):~%~%~
~2T~{~D~^ ~}~%~%~
~A"
+ops+ digits (secondary-prompt)))
(defun secondary-prompt ()
(fill-to 50 "Enter a lisp form which evaluates to ~
the integer 24, or \"!\" to get fresh ~
digits, or \"q\" to abort."))
(defun fill-to (n fmt &rest args)
"Poor-man's text filling mechanism."
(loop with s = (format nil "~?" fmt args)
for c across s
and i from 0
and j = 0 then (1+ j) ; since-last-newline ctr
when (char= c #\Newline)
do (setq j 0)
else when (and (not (zerop j))
(zerop (mod j n)))
do (loop for k from i below (length s)
when (char= #\Space (schar s k))
do (progn
(setf (schar s k) #\Newline
j 0)
(loop-finish)))
finally (return s)))
(defun 24-game ()
(loop with playing-p = t
and initial-digits = (digits)
for attempts from 0
and digits = initial-digits then (digits)
while playing-p
do (loop for e = (expr-get (unless (zerop attempts)
digits))
do
(case e
(! (loop-finish))
(Q (setq playing-p nil)
(loop-finish))
(R (emit "Current digits: ~S" digits))
(t
(if (expr-valid-p e digits)
(let ((v (eval e)))
(if (eql v 24)
(progn
(emit "~%~%---> A winner is you! <---~%~%")
(setq playing-p nil)
(loop-finish))
(emit "Sorry, the form you entered ~
computes to ~S, not 24.~%~%"
v)))
(emit "Sorry, the form you entered did not ~
compute.~%~%")))))
initially (prompt initial-digits)))
Example Usage:
CL-USER 97 > (24-game) Using only these operators: * / + - And exactly these numbers (no repetition): 3 7 7 9 Enter a lisp form which evaluates to the integer 24, or "!" to get fresh digits, or "q" to abort. Enter lisp form: (eval (read-from-string "(/ 1 0)")) Sorry, the form you entered did not compute. Enter lisp form: ! Enter lisp form using the digits 4 5 7 8: ! Enter lisp form using the digits 1 2 4 5: (* 4 (* 5 (- 2 1))) Sorry, the form you entered computes to 20, not 24. Enter lisp form using the digits 1 2 4 5: (* 4 (+ 5 (- 2 1))) ---> A winner is you! <--- NIL
[edit] Falcon
load compiler
function genRandomNumbers( amount )
rtn = []
for i in [ 0 : amount ]: rtn += random( 1, 9 )
return( rtn )
end
function getAnswer( exp )
ic = ICompiler()
ic.compileAll(exp)
return( ic.result )
end
function validInput( str )
for i in [ 0 : str.len() ]
if str[i] notin ' ()[]0123456789-+/*'
> 'INVALID Character = ', str[i]
return( false )
end
end
return( true )
end
printl('
The 24 Game
Given any four digits in the range 1 to 9, which may have repetitions,
Using just the +, -, *, and / operators; and the possible use of
brackets, (), show how to make an answer of 24.
An answer of "q" will quit the game.
An answer of "!" will generate a new set of four digits.
Otherwise you are repeatedly asked for an expression until it evaluates to 24
Note: you cannot form multiple digit numbers from the supplied digits,
so an answer of 12+12 when given 1, 2, 2, and 1 would not be allowed.
')
num = genRandomNumbers( 4 )
while( true )
>> "Here are the numbers to choose from: "
map({ a => print(a, " ") }, num)
>
exp = input()
switch exp
case "q", "Q"
exit()
case "!"
> 'Generating new numbers list'
num = genRandomNumbers( 4 )
default
if not validInput( exp ): continue
answer = getAnswer( exp )
if answer == 24
> "By George you GOT IT! Your expression equals 24"
else
> "Ahh Sorry, So Sorry your answer of ", answer, " does not equal 24."
end
end
end
[edit] Haskell
import Char
import Control.Monad.Error
import Data.List
import IO
import Maybe
import Random
main = do
hSetBuffering stdout NoBuffering
mapM_ putStrLn
[ "THE 24 GAME\n"
, "Given four digits in the range 1 to 9"
, "Use the +, -, *, and / operators in reverse polish notation"
, "To show how to make an answer of 24.\n"
]
digits <- liftM (sort . take 4 . randomRs (1,9)) getStdGen :: IO [Int]
putStrLn ("Your digits: " ++ intercalate " " (map show digits))
guessLoop digits
where guessLoop digits =
putStr "Your expression: " >>
liftM (processGuess digits . words) getLine >>=
either (\m -> putStrLn m >> guessLoop digits) putStrLn
processGuess _ [] = Right ""
processGuess digits xs | not $ matches = Left "Wrong digits used"
where matches = digits == (sort . map read $ filter (all isDigit) xs)
processGuess digits xs = calc xs >>= check
where check 24 = Right "Correct"
check x = Left (show (fromRational (x :: Rational)) ++ " is wrong")
-- A Reverse Polish Notation calculator with full error handling
calc = result []
where result [n] [] = Right n
result _ [] = Left "Too few operators"
result ns (x:xs) = simplify ns x >>= flip result xs
simplify (a:b:ns) s | isOp s = Right ((fromJust $ lookup s ops) b a : ns)
simplify _ s | isOp s = Left ("Too few values before " ++ s)
simplify ns s | all isDigit s = Right (fromIntegral (read s) : ns)
simplify _ s = Left ("Unrecognized symbol: " ++ s)
isOp v = elem v $ map fst ops
ops = [("+",(+)), ("-",(-)), ("*",(*)), ("/",(/))]
[edit] J
require'misc'
deal=: 1 + ? bind 9 9 9 9
rules=: smoutput bind 'see http://en.wikipedia.org/wiki/24_Game'
input=: prompt @ ('enter 24 expression using ', ":, ': '"_)
wellformed=: (' '<;._1@, ":@[) -:&(/:~) '(+-*%)' -.&;:~ ]
is24=: 24 -: ". ::0:@]
respond=: (;:'no yes') {::~ wellformed * is24
game24=: (respond input)@deal@rules
Example use:
game24 '' see http://en.wikipedia.org/wiki/24_Game enter 24 expression using 6 5 9 4: 6+5+9+4 yes game24 '' see http://en.wikipedia.org/wiki/24_Game enter 24 expression using 3 3 3 3: 3+3+3+3+3+3+3+3 no
[edit] Lua
local function help()
print [[
The 24 Game
Given any four digits in the range 1 to 9, which may have repetitions,
Using just the +, -, *, and / operators; and the possible use of
brackets, (), show how to make an answer of 24.
An answer of "q" will quit the game.
An answer of "!" will generate a new set of four digits.
Note: you cannot form multiple digit numbers from the supplied digits,
so an answer of 12+12 when given 1, 2, 2, and 1 would not be allowed.
]]
end
local function generate(n)
result = {}
for i=1,n do
result[i] = math.random(1,9)
end
return result
end
local function check(answer, digits)
local adig = {}
local ddig = {}
local index
local lastWasDigit = false
for i=1,9 do adig[i] = 0 ddig[i] = 0 end
allowed = {['(']=true,[')']=true,[' ']=true,['+']=true,['-']=true,['*']=true,['/']=true,['\t']=true,['1']=true,['2']=true,['3']=true,['4']=true,['5']=true,['6']=true,['7']=true,['8']=true,['9']=true}
for i=1,string.len(answer) do
if not allowed[string.sub(answer,i,i)] then
return false
end
index = string.byte(answer,i)-48
if index > 0 and index < 10 then
if lastWasDigit then
return false
end
lastWasDigit = true
adig[index] = adig[index] + 1
else
lastWasDigit = false
end
end
for i,digit in next,digits do
ddig[digit] = ddig[digit]+1
end
for i=1,9 do
if adig[i] ~= ddig[i] then
return false
end
end
return loadstring('return '..answer)()
end
local function game24()
help()
math.randomseed(os.time())
math.random()
local digits = generate(4)
local trial = 0
local answer = 0
local ans = false
io.write 'Your four digits:'
for i,digit in next,digits do
io.write (' ' .. digit)
end
print()
while ans ~= 24 do
trial = trial + 1
io.write("Expression "..trial..": ")
answer = io.read()
if string.lower(answer) == 'q' then
break
end
if answer == '!' then
digits = generate(4)
io.write ("New digits:")
for i,digit in next,digits do
io.write (' ' .. digit)
end
print()
else
ans = check(answer,digits)
if ans == false then
print ('The input '.. answer ..' was wonky!')
else
print (' = '.. ans)
if ans == 24 then
print ("Thats right!")
end
end
end
end
end
game24()
Alternately, using the lpeg.re module:
function twentyfour()
print [[
The 24 Game
Given any four digits in the range 1 to 9, which may have repetitions,
Using just the +, -, *, and / operators; and the possible use of
brackets, (), show how to make an answer of 24.
An answer of "q" will quit the game.
An answer of "!" will generate a new set of four digits.
Note: you cannot form multiple digit numbers from the supplied digits,
so an answer of 12+12 when given 1, 2, 2, and 1 would not be allowed.
]]
expr = re.compile[[ --matches properly formatted infix expressions and returns all numerals as captures
expr <- (!.) / (<paren> / <number>) (<ws> <oper> <ws> <expr>)?
number <- {[0-9]}
ws <- " "*
oper <- [-+/*]
paren <- "(" <ws> <expr> <ws> ")" ]]
local val_t = {math.random(9), math.random(9), math.random(9), math.random(9)}
table.sort(val_t)
print("the digits are " .. table.concat(val_t, ", "))
local ex = io.read()
a, b, c, d, e = expr:match(ex)
if a and b and c and d and not e then --if there is a fifth numeral the player is cheating
local digs = {a + 0, b + 0, c + 0, d + 0}
local flag = false -- (terrorism!)
table.sort(digs)
for i = 1, 4 do
flag = digs[i] ~= val_t[i] and not print"Wrong digits!" or flag
end
if not flag and loadstring("return " .. ex)() == 24 then
print"You win!"
else
print"You lose."
end
else print"wat" --expression could not be interpreted as arithmetic
end
end
twentyfour()
[edit] Mathematica
Works with: Mathematica version 6
Since Mathematica hasn't historically had good custom I/O support (the command-line allowed all operations, not very good for UI-generation), I had to roll some custom GUI (with a text box), which requires Mathematica 6.
Most of the job is already done by Mathematica (the expression conversion); in fact, it is too good—it automatically converts ex. 3/4 to Times[3, Power[4, -1]], which we have to specifically test for so that real powers don't get through.
isLegal[n_List, x_String] :=
Quiet[Check[
With[{h = ToExpression[x, StandardForm, HoldForm]},
If[Cases[Level[h, {2, \[Infinity]}, Hold, Heads -> True],
Except[_Integer | Plus | _Plus | Times | _Times | Power |
Power[_, -1]]] === {} &&
Sort[Level[h /. Power[q_, -1] -> q, {-1}] /.
q_Integer -> Abs[q]] === Sort[n], ReleaseHold[h]]], Null]]
Grid[{{Button[
"new numbers", {a, b, c, d} = Table[RandomInteger[{1, 9}], {4}]],
InputField[Dynamic[x], String]}, {Dynamic[{a, b, c, d}],
Dynamic[Switch[isLegal[{a, b, c, d}, x], Null,
"Sorry, that is invalid.", 24, "Congrats! That's 24!", _,
"Sorry, that makes " <> ToString[ToExpression@x, InputForm] <>
", not 24."]]}}]
[edit] OCaml
Compile with:
ocamlopt -pp camlp4o g24.ml -o g24.opt
type expression =
| Const of float
| Sum of expression * expression (* e1 + e2 *)
| Diff of expression * expression (* e1 - e2 *)
| Prod of expression * expression (* e1 * e2 *)
| Quot of expression * expression (* e1 / e2 *)
let rec eval expr =
match expr with
| Const c -> c
| Sum (f, g) -> eval f +. eval g
| Diff(f, g) -> eval f -. eval g
| Prod(f, g) -> eval f *. eval g
| Quot(f, g) -> eval f /. eval g
let rec extract acc expr =
match expr with
| Const c -> (c::acc)
| Sum (f, g) -> (extract acc f) @ (extract [] g)
| Diff(f, g) -> (extract acc f) @ (extract [] g)
| Prod(f, g) -> (extract acc f) @ (extract [] g)
| Quot(f, g) -> (extract acc f) @ (extract [] g)
open Genlex
let lexer = make_lexer ["("; ")"; "+"; "-"; "*"; "/"]
let rec parse_expr = parser
[< e1 = parse_mult; e = parse_more_adds e1 >] -> e
and parse_more_adds e1 = parser
[< 'Kwd "+"; e2 = parse_mult; e = parse_more_adds (Sum(e1, e2)) >] -> e
| [< 'Kwd "-"; e2 = parse_mult; e = parse_more_adds (Diff(e1, e2)) >] -> e
| [< >] -> e1
and parse_mult = parser
[< e1 = parse_simple; e = parse_more_mults e1 >] -> e
and parse_more_mults e1 = parser
[< 'Kwd "*"; e2 = parse_simple; e = parse_more_mults (Prod(e1, e2)) >] -> e
| [< 'Kwd "/"; e2 = parse_simple; e = parse_more_mults (Quot(e1, e2)) >] -> e
| [< >] -> e1
and parse_simple = parser
| [< 'Int i >] -> Const(float i)
| [< 'Float f >] -> Const f
| [< 'Kwd "("; e = parse_expr; 'Kwd ")" >] -> e
let parse_expression = parser [< e = parse_expr; _ = Stream.empty >] -> e
let read_expression s = parse_expression(lexer(Stream.of_string s))
let () =
Random.self_init();
print_endline "
The 24 Game
Given any four digits in the range 1 to 9, which may have repetitions,
Using just the +, -, *, and / operators; and the possible use of
brackets, (), show how to make an answer of 24.
An answer of 'q' will quit the game.
An answer of '!' will generate a new set of four digits.
Otherwise you are repeatedly asked for an expression until it evaluates to 24
Note: you cannot form multiple digit numbers from the supplied digits,
so an answer of 12+12 when given 1, 2, 2, and 1 would not be allowed.\n";
let sort = List.sort compare in
let digits = ref [] in
let digit_set () =
let ar = Array.init 4 (fun _ -> 1 + Random.int 9) in
digits := Array.to_list(Array.map float_of_int ar);
print_string "The four digits: ";
List.iter (Printf.printf " %g") !digits;
print_newline();
in
digit_set();
while true do
print_string "Expression: ";
let str = read_line() in
if str = "q" then exit 0;
if str = "!" then digit_set()
else begin
let expr = read_expression str in
let res = eval expr in
Printf.printf " = %g\n%!" res;
if res = 24.
&& (sort !digits) = (sort (extract [] expr))
then (print_endline "Congratulations!"; digit_set())
else print_endline "Try again"
end
done
[edit] Perl
qq(
The 24 Game
Given any four digits in the range 1 to 9, which may have repetitions,
Using just the +, -, *, and / operators; and the possible use of
brackets, (), show how to make an answer of 24.
An answer of "q" will quit the game.
An answer of "!" will generate a new set of four digits.
Otherwise you are repeatedly asked for an expression until it evaluates to 24
Note: you cannot form multiple digit numbers from the supplied digits,
so an answer of 12+12 when given 1, 2, 2, and 1 would not be allowed.
);
while(1)
{
$it = 0;
&mkNumbers;
while(1)
{
$it++;
print "Expression $it: ";
chomp($ent = <>);
last if $ent eq '!';
exit if $ent eq 'q';
my $r = &play;
if(!defined $r)
{
print "That's not valid\n";
next;
}elsif($r != 24)
{
print "Sorry, that's $r\n";
next;
}else{ print "That's right! 24!!\n";exit; }
}
}
sub mkNumbers
{
%numbers = ();
print "Your four digits:";
for(1..4)
{
$i = 1 + int(rand(9));
$numbers{$i}++;
print "$i ";
}
print "\n";
}
sub play
{
%bak_numbers = %numbers;
@itens = split //,$ent;
$operator = 1;
for(@itens)
{
next if $_ =~ /[()]/;
$operator = !$operator;
if(!$operator)
{
if( defined $numbers{$_} && $numbers{$_} > 0)
{
$numbers{$_}--;
next;
}else{ return undef; }
}else{
return undef if $_ !~ m{[-+*/]};
}
}
%numbers = %bak_numbers;
return eval($ent);
}
[edit] PicoLisp
(de checkExpression (Lst Exe)
(make
(when (diff Lst (fish num? Exe))
(link "Not all numbers used" ) )
(when (diff (fish num? Exe) Lst)
(link "Using wrong number(s)") )
(when (diff (fish sym? Exe) '(+ - * /))
(link "Using illegal operator(s)") ) ) )
(loop
(setq Numbers (make (do 4 (link (rand 1 9)))))
(prinl
"Please enter a Lisp expression using (, ), +, -, *, / and "
(glue ", " Numbers) )
(prin "Or a single dot '.' to stop: ")
(T (= "." (setq Reply (catch '(NIL) (in NIL (read)))))
(bye) )
(cond
((str? Reply)
(prinl "-- Input error: " Reply) )
((checkExpression Numbers Reply)
(prinl "-- Illegal Expression")
(for S @
(space 3)
(prinl S) ) )
((str? (setq Result (catch '(NIL) (eval Reply))))
(prinl "-- Evaluation error: " @) )
((= 24 Result)
(prinl "++ Congratulations! Correct result :-)") )
(T (prinl "Sorry, this gives " Result)) )
(prinl) )
Output:
Please enter a Lisp expression using (, ), +, -, *, / and 1, 3, 3, 5 Or a single dot '.' to stop: (* (+ 3 1) (+ 5 1)) ++ Congratulations! Correct result :-) Please enter a Lisp expression using (, ), +, -, *, / and 8, 4, 7, 1 Or a single dot '.' to stop: (* 8 (% 7 3) 9) -- Illegal Expression Not all numbers used Using wrong number(s) Using illegal operator(s) Please enter a Lisp expression using (, ), +, -, *, / and 4, 2, 2, 3 Or a single dot '.' to stop: (/ (+ 4 3) (- 2 2)) -- Evaluation error: Div/0 Please enter a Lisp expression using (, ), +, -, *, / and 8, 4, 5, 9 Or a single dot '.' to stop: .
[edit] Python
Uses eval, the in-built expression evaluator of infix expressions.
'''
The 24 Game
Given any four digits in the range 1 to 9, which may have repetitions,
Using just the +, -, *, and / operators; and the possible use of
brackets, (), show how to make an answer of 24.
An answer of "q" will quit the game.
An answer of "!" will generate a new set of four digits.
Otherwise you are repeatedly asked for an expression until it evaluates to 24
Note: you cannot form multiple digit numbers from the supplied digits,
so an answer of 12+12 when given 1, 2, 2, and 1 would not be allowed.
'''
from __future__ import division, print_function
import random, ast, re
import sys
if sys.version_info[0] < 3: input = raw_input
def choose4():
'four random digits >0 as characters'
return [str(random.randint(1,9)) for i in range(4)]
def welcome(digits):
print (__doc__)
print ("Your four digits: " + ' '.join(digits))
def check(answer, digits):
allowed = set('() +-*/\t'+''.join(digits))
ok = all(ch in allowed for ch in answer) and \
all(digits.count(dig) == answer.count(dig) for dig in set(digits)) \
and not re.search('\d\d', answer)
if ok:
try:
ast.parse(answer)
except:
ok = False
return ok
def main():
digits = choose4()
welcome(digits)
trial = 0
answer = ''
chk = ans = False
while not (chk and ans == 24):
trial +=1
answer = input("Expression %i: " % trial)
chk = check(answer, digits)
if answer.lower() == 'q':
break
if answer == '!':
digits = choose4()
print ("New digits:", ' '.join(digits))
continue
if not chk:
print ("The input '%s' was wonky!" % answer)
else:
ans = eval(answer)
print (" = ", ans)
if ans == 24:
print ("Thats right!")
print ("Thank you and goodbye")
main()
Sample Output
The 24 Game Given any four digits in the range 1 to 9, which may have repetitions, Using just the +, -, *, and / operators; and the possible use of brackets, (), show how to make an answer of 24. An answer of "q" will quit the game. An answer of "!" will generate a new set of four digits. Note: you cannot form multiple digit numbers from the supplied digits, so an answer of 12+12 when given 1, 2, 2, and 1 would not be allowed. Your four digits: 3 2 4 6 Expression 1: (3 - 1)*(6*4) The input '(3 - 1)*(6*4)' was wonky! Expression 2: (3 - 2) * 6 * 4 = 24 Thats right! Thank you and goodbye
[edit] R
A loose Translation of: Python
Helper functions:
welcomemessage <- function()
{
cat(
'The 24 Game\n
Given any four digits in the range 1 to 9, which may have repetitions,
Using just the +, -, *, and / operators; and the possible use of\nbrackets, (), show how to make an answer of 24.\n
An answer of "q" will quit the game.
An answer of "!" will generate a new set of four digits.
Otherwise you are repeatedly asked for an expression until it evaluates to 24.\n
Note: you cannot form multiple digit numbers from the supplied digits,
so an answer of 12+12 when given 1, 2, 2, and 1 would not be allowed.
')
}
generatenumbers <- function()
{
sample(1:9, 4, replace=TRUE)
}
printnumbers <- function(num)
{
cat("Your four digits: ", paste(num, collapse=", "), ".\n", sep="")
}
checkinput <- function(input, num)
{
# First check: are all the characters arithmetic operators or brackets or spaces or the input numbers?
allowedchars <- paste("^[-+*/()[:space:]", paste(unique(num), collapse=""), "]*$", sep="")
if(regexpr(allowedchars, input) < 0) return(FALSE)
# Second check: are all the numbers single digits?
if(regexpr("[1-9]{2,}", input) > 0) return(FALSE)
# Third check: do the digits in the input exactly match the generated set?
digits <- as.numeric(strsplit(input, "")[[1]])
digits <- digits[!is.na(digits)]
if(!isTRUE(all.equal(sort(digits), sort(num)))) return(FALSE)
# Fourth check: is the input valid R code?
parsed <- try(parse(text=input))
if(class(parsed) == "try-error") return(FALSE)
# Fifth check: is the answer 24?
ans <- suppressWarnings(eval(parsed))
ans == 24
}
wonkymessage <- function(input)
{
cat("The input", input, "was wonky!\n")
}
Main game function
twentyfour <- function()
{
welcomemessage()
gennew <- TRUE
repeat
{
if(gennew)
{
num <- generatenumbers()
printnumbers(num)
}
input <- readline(prompt="Enter your solution >>")
if(tolower(input) =="q") # Quit
{
break
}
if(input == "!") # New game
{
gennew <- TRUE
next
}
if(!checkinput(input, num))
{
wonkymessage(input)
gennew <- FALSE
next
} else
{
cat ("Thats right!\n")
break
}
}
cat("Thank you and goodbye\n")
}
Example Usage
twentyfour()
[edit] Ruby
require "rational"
def play
digits = Array.new(4) {1+rand(9)}
loop do
guess = get_guess(digits)
result = evaluate(guess)
if result == 24.0
puts "yes!"
break
else
puts "nope: #{guess} = #{result}"
puts "try again"
end
end
end
def get_guess(digits)
loop do
print "\nEnter your guess using #{digits.inspect}: "
guess = $stdin.gets.chomp
# ensure input is safe to eval
invalid_chars = guess.scan(%r{[^\d\s()+*/-]})
unless invalid_chars.empty?
puts "invalid characters in input: #{invalid_chars.inspect}"
next
end
guess_digits = guess.scan(/\d/).map {|ch| ch.to_i}
if guess_digits.sort != digits.sort
puts "you didn't use the right digits"
next
end
if guess.match(/\d\d/)
puts "no multi-digit numbers allowed"
next
end
return guess
end
end
# convert expression to use rational numbers, evaluate, then return as float
def evaluate(guess)
as_rat = guess.gsub(/(\d)/, 'Rational(\1,1)')
begin
eval "(#{as_rat}).to_f"
rescue SyntaxError
"[syntax error]"
end
end
play
[edit] Scala
The solution below is much more complex than strictly needed, because it shows off Scala's Parser library, which enables easy construction of parsers from EBNF grammars.
Only problems with solution are shown to the user.
object TwentyFourGame {
def main(args: Array[String]) {
import Parser.TwentyFourParser
println(welcome)
var parser = new TwentyFourParser(problemsIterator.next)
println("Your four digits: "+parser+".")
var finished = false
var expressionCount = 1
do {
val line = Console.readLine("Expression "+expressionCount+": ")
line match {
case "!" =>
parser = new TwentyFourParser(problemsIterator.next)
println("New digits: "+parser+".")
case "q" =>
finished = true
case _ =>
parser readExpression line match {
case Some(24) => println("That's right!"); finished = true
case Some(n) => println("Sorry, that's "+n+".")
case None =>
}
}
expressionCount += 1
} while (!finished)
println("Thank you and goodbye!")
}
val welcome = """|The 24 Game
|
|Given any four digits in the range 1 to 9, which may have repetitions,
|Using just the +, -, *, and / operators; and the possible use of
|brackets, (), show how to make an answer of 24.
|
|An answer of "q" will quit the game.
|An answer of "!" will generate a new set of four digits.
|Otherwise you are repeatedly asked for an expression until it evaluates to 24
|
|Note: you cannot form multiple digit numbers from the supplied digits,
|so an answer of 12+12 when given 1, 2, 2, and 1 would not be allowed.
|""".stripMargin
val problemsIterator = (
Iterator
continually List.fill(4)(scala.util.Random.nextInt(9) + 1 toDouble)
filter hasSolution
)
def hasSolution(l: List[Double]) = permute(l) flatMap computeAllOperations exists (_ == 24)
def computeAllOperations(l: List[Double]): List[Double] = l match {
case Nil => Nil
case x :: Nil => l
case x :: xs =>
for {
y <- computeAllOperations(xs)
z <- if (y == 0) List(x*y, x+y, x-y) else List(x*y, x/y, x+y, x-y)
} yield z
}
def permute(l: List[Double]): List[List[Double]] = l match {
case Nil => List(Nil)
case x :: xs =>
for {
ys <- permute(xs)
position <- 0 to ys.length
(left, right) = ys splitAt position
} yield left ::: (x :: right)
}
object Parser {
/* Arithmetic expression grammar production rules in EBNF form:
*
* <expr> --> <term> ( '+' <term> | '-' <term> )*
* <term> --> <factor> ( '*' <factor> | '/' <factor> )*
* <factor> --> '(' <expr> ')' | <digit>
* <digit> --> 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
*
* Semantically, <digit> can only be a digit from the list of remaining digits.
*/
class TwentyFourParser(digits: List[Double]) extends scala.util.parsing.combinator.RegexParsers {
require(digits.length == 4 && digits.forall(d => 0 <= d && d <= 9))
override val toString = digits.map(_.toInt).mkString(", ")
// Grammar
def exprConsumingAllDigits = expr ^? (remainingDigits.allDigitsConsumed, digitsRemainingError) // Guarantees all digits consumed
def expr : Parser[Double] = term ~ rep( "+" ~ term | "-" ~ term) ^^ solveOperationChain
def term = factor ~ rep( "*" ~ factor | "/" ~ factor) ^^ solveOperationChain
def factor = "(" ~> expr <~ ")" | digit
def digit = digitRegex ^? (remainingDigits.consumeDigit, digitNotAllowedError)
def digitRegex = "\\d".r | digitExpected
def digitExpected: Parser[String] = ".".r <~ failure(expectedDigitError) // Produces clear error messages
// Evaluate expressions
def readExpression(input: String): Option[Double] = {
remainingDigits = new DigitList(digits) // Initialize list of digits to be consumed
parseAll(exprConsumingAllDigits, input) match {
case Success(result, _) => Some(result)
case NoSuccess(msg, next) =>
println(ParsingErrorFormatter(msg, next))
None
}
}
// List of digits to be consumed
private var remainingDigits: DigitList = _
// Solve partial results from parsing
private def solveOperationChain(partialResult: ~[Double,List[~[String,Double]]]): Double = partialResult match {
case first ~ chain => chain.foldLeft(first)(doOperation)
}
private def doOperation(acc: Double, op: ~[String, Double]): Double = op match {
case "+" ~ operand => acc + operand
case "-" ~ operand => acc - operand
case "*" ~ operand => acc * operand
case "/" ~ operand => acc / operand
case x => error("Unknown operation "+x+".")
}
// Error messages
private def digitNotAllowedError(d: String) = "Digit "+d+" is not allowed here. Available digits: "+remainingDigits+"."
private def digitsRemainingError(x: Any) = "Not all digits were consumed. Digits remaining: "+remainingDigits+"."
private def expectedDigitError = "Unexpected input. Expected a digit from the list: "+remainingDigits+"."
}
private object ParsingErrorFormatter {
def apply[T](msg: String, next: scala.util.parsing.input.Reader[T]) =
"%s\n%s\n%s\n" format (msg, next.source.toString.trim, " "*(next.offset - 1)+"^")
}
private class DigitList(digits: List[Double]) {
private var remainingDigits = digits
override def toString = remainingDigits.map(_.toInt).mkString(", ")
def consumeDigit: PartialFunction[String, Double] = {
case d if remainingDigits contains d.toDouble =>
val n = d.toDouble
remainingDigits = remainingDigits diff List(n)
n
}
def allDigitsConsumed: PartialFunction[Double, Double] = {
case n if remainingDigits.isEmpty => n
}
}
}
}
Sample Output
C:\Workset>scala TwentyFourGame The 24 Game Given any four digits in the range 1 to 9, which may have repetitions, Using just the +, -, *, and / operators; and the possible use of brackets, (), show how to make an answer of 24. An answer of "q" will quit the game. An answer of "!" will generate a new set of four digits. Otherwise you are repeatedly asked for an expression until it evaluates to 24 Note: you cannot form multiple digit numbers from the supplied digits, so an answer of 12+12 when given 1, 2, 2, and 1 would not be allowed. Your four digits: 2, 7, 7, 2. Expression 1: 2*7+2+7 Sorry, that's 23.0. Expression 2: 7*7/2-2 Sorry, that's 22.5. Expression 3: 2*7+(7-2) Sorry, that's 19.0. Expression 4: 2*(7+7-2) That's right! Thank you and goodbye!
[edit] Scheme
Works with: PLT Scheme version 4
This uses read to read in a scheme expression, and eval to evaluate it, so in that sense it's not ideal (eval is evil etc.) but any expression that is valid should be safe and terminate in a timely manner.
#lang scheme
(require srfi/27 srfi/1) ;; random-integer, every
(define (play)
(let* ([numbers (build-list 4 (lambda (n)
(add1 (random-integer 9))))]
[valid? (curryr valid? numbers)])
(printf startup-message numbers)
(let loop ([exp (read)])
(with-handlers ([exn:fail? (lambda (err)
(printf error-message exp (exn-message err))
(loop (read)))])
(cond [(eq? exp '!) (play)]
[(or (eq? exp 'q)
(eof-object? exp)) (printf quit-message)]
[(not (valid? exp))
(printf bad-exp-message exp)
(loop (read))]
[(not (= (eval exp) 24))
(printf bad-result-message exp (eval exp))
(loop (read))]
[else (printf winning-message)])))))
(define (valid? exp numbers)
;; must contain each number exactly once and only valid symbols
(define (valid-symbol? sym)
;; only +, -, *, and / are valid
(case sym
[(+ - * /) #t]
[else #f]))
(let* ([ls (flatten exp)]
[numbers* (filter number? ls)]
[symbols (remove number? ls)])
(and (equal? (sort numbers <)
(sort numbers* <))
(every valid-symbol? symbols))))
(define startup-message "
Write a lisp expression that evaluates to 24
using only (, ), +, -, *, /
and these four numbers: ~a
or '!' to get a new set of numbers
or 'q' to quit")
(define error-message "
Your expression ~a raised an exception:
\"~a\"
Please try again")
(define bad-exp-message "Sorry, ~a is a bad expression.")
(define bad-result-message "Sorry, ~a evaluates to ~a, not 24.")
(define quit-message "Thanks for playing...")
(define winning-message "You win!")
(provide play)
Sample Output
> (require "24game.ss") > (play) Write a lisp expression that evaluates to 24 using only (, ), +, -, *, / and these four numbers: (2 7 2 5) or '!' to get a new set of numbers or 'q' to quit ! Write a lisp expression that evaluates to 24 using only (, ), +, -, *, / and these four numbers: (9 2 7 6) or '!' to get a new set of numbers or 'q' to quit (9 7 6 2) Your expression (9 7 6 2) raised an exception: "procedure application: expected procedure, given: 9; arguments were: 7 6 2" Please try again (+ 9 7 6 2) You win!
[edit] Tcl
Translation of: Python
This version also terminates cleanly on end-of-file.
# Four random non-zero digits
proc choose4 {} {
set digits {}
foreach x {1 2 3 4} {lappend digits [expr {int(1+rand()*9)}]}
return [lsort $digits]
}
# Print out a welcome message
proc welcome digits {
puts [string trim "
The 24 Game
Given any four digits in the range 1 to 9, which may have repetitions,
Using just the +, -, *, and / operators; and the possible use of
brackets, (), show how to make an answer of 24.
An answer of \"q\" will quit the game.
An answer of \"!\" will generate a new set of four digits.
Otherwise you are repeatedly asked for an expression until it evaluates to 24
Note: you cannot form multiple digit numbers from the supplied digits,
so an answer of 12+12 when given 1, 2, 2, and 1 would not be allowed.
"]
puts "\nYour four digits: $digits"
}
# Check whether we've got a legal answer
proc check {answer digits} {
if {
[regexp "\[^-+*/() \t[join $digits {}]\]" $answer]
|| [regexp {\d\d} $answer]
} then {
return false
}
set digs [lsort [regexp -inline -all {\d} $answer]]
if {$digs ne $digits} {
return false
}
expr {![catch {expr $answer}]}
}
# The main game loop
proc main {} {
fconfigure stdout -buffering none
set digits [choose4]
welcome $digits
set trial 0
while true {
puts -nonewline "Expression [incr trial]: "
gets stdin answer
# Check for various types of non-answer
if {[eof stdin] || $answer eq "q" || $answer eq "Q"} {
break
} elseif {$answer eq "!"} {
set digits [choose4]
puts "New digits: $digits"
continue
} elseif {![check $answer $digits]} {
puts "The input '$answer' was wonky!"
continue
}
# Check to see if it is the right answer
set ans [expr [regsub {\d} $answer {&.0}]]
puts " = [string trimright $ans .0]"
if {$ans == 24.0} {
puts "That's right!"
break
}
}
puts "Thank you and goodbye"
}
main







