21 game

You are encouraged to solve this task according to the task description, using any language you may know.
21 is a two player game, the game is played by choosing
a number (1, 2, or 3) to be added to the running total.

The game is won by the player whose chosen number causes the running total
to reach exactly 21.

The running total starts at zero.
One player will be the computer.

Players alternate supplying a number to be added to the running total.



Task

Write a computer program that will:

 do the prompting (or provide a button menu), 
 check for errors and display appropriate error messages, 
 do the additions (add a chosen number to the running total), 
 display the running total, 
 provide a mechanism for the player to quit/exit/halt/stop/close the program,
 issue a notification when there is a winner, and
 determine who goes first (maybe a random or user choice, or can be specified when the game begins). 


--
-- demo\rosetta\21_Game.exw
-- ========================
--
with javascript_semantics -- DEV NORMALIZESIZE, CANFOCUS, "You" not checked, VALUE_HANDLE.
                          -- The radio_texts simply don't do anything at all in p2js.
constant title = "21 Game",
         help_text = """
Play by choosing 1, 2, or 3 to add to the running total (initially 0).

The first player to reach 21 wins.

If the computer goes first you cannot win.

If you leave your opponent on 18, 19, or 20, they will play {3,2,1} and win.
If you leave your opponent on 17, simply match their play {1,2,3} with {3,2,1} and win.
If you leave your opponent on 14, 15, or 16, they'll leave you on 17 and win.
So the aim is 21 (doh), and before that 17, 13, 9, 5, and 1. Anything else loses.
""",
         radio_texts = {"You","Computer","Random"},
         button_text = {"one","two","three","concede","new game","quit"}
integer total = 0

include pGUI.e
Ihandle dlg, vbox, frame, radios, playstate
sequence radioset, buttons

function show_help()
    IupMessage(title,help_text)
    return IUP_IGNORE -- (don't open browser help!)
end function

function play(integer n)
    if n!=0 then
        if n=6 then return IUP_CLOSE end if
        string title
        if n>3 then
            -- concede or new_game
            total = 0
            title = iff(n=4?"(conceded) ":"")
            title &= "Total is 0"
            Ihandle r = IupGetAttributePtr(radios,"VALUE_HANDLE")
            n = find(r,radioset)
            if n=2 or (n=3 and rand(2)=1) then
                title &= "," -- trigger a computer move
            end if
            IupSetInt(buttons[1..3],"ACTIVE",true)
        else
            -- n = 1..3
            title = sprintf("Total is %d",total)
            if total=21      -- (from key_cb)
            or total+n>21 then -- (invalid)
                return IUP_IGNORE
            end if
            total += n
            title &= sprintf(", you play %d (-> %d),",{n,total})
            if total=21 then title &= " you win" end if
        end if
        if find(',',title) and total!=21 then
            -- computer move
            sequence moves = {1,rand(3),3,2}
            n = moves[mod(total,4)+1]
            total += n
            title &= sprintf(" computer plays %d (-> %d)",{n,total})
            if total=21 then
                title &= ", computer wins"
            elsif mod(total,4)=1 then
                title &= ", (you've already lost)"
            end if
        end if      
        if total=21 then
            title &= " GAME OVER"
            IupSetInt(buttons[1..4],"ACTIVE",false)
        else
            if total>18 then
                IupSetInt(buttons[22-total..3],"ACTIVE",false)
            end if
            IupSetInt(buttons[4],"ACTIVE",total!=0)
        end if      
        IupSetFocus(dlg) -- (stops inactive button beeping)
        IupSetStrAttribute(playstate,"TITLE",title)
    end if
    return IUP_IGNORE
end function

function button_cb(Ihandle ih)
    string title = IupGetAttribute(ih,"TITLE")
    return play(find(title,button_text))
end function
constant cb_button = Icallback("button_cb")

function key_cb(Ihandle /*dlg*/, atom c)
    if c=K_ESC then return IUP_CLOSE end if -- (standard practice for me)
    if c=K_F5 then return IUP_DEFAULT end if -- (let browser reload work)
    if c=K_F1 then return show_help() end if
    return play(find(upper(c),"123CNQ"))
end function

IupOpen()
playstate = IupLabel("","EXPAND=HORIZONTAL, PADDING=10x10")
radioset = apply(true,IupToggle,{radio_texts,{"RIGHTBUTTON=YES, CANFOCUS=NO"}})
buttons = apply(true,IupButton,{button_text,cb_button,{"PADDING=5x5"}})
radios = IupRadio(IupHbox(radioset,"GAP=45"))
frame = IupHbox({IupLabel(`First Player:`),radios},"NORMALIZESIZE=VERTICAL")
vbox = IupVbox({frame,playstate,IupHbox(buttons,"GAP=10")},"MARGIN=20x10")
dlg = IupDialog(vbox,`TITLE="%s", MINSIZE=540x200`,{title})
IupShow(dlg)
IupSetCallback({dlg,buttons},"KEY_CB",Icallback("key_cb"))
IupSetAttributeHandle(NULL,"PARENTDIALOG",dlg)
{} = play(find("new game",button_text))
if platform()!=JS then
    IupMainLoop()
    IupClose()
end if
