RCRPG/Phix

Revision as of 18:53, 20 January 2019 by Petelomax (talk | contribs) (Phix)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Most of the complexity is contained in the get_command() routine. I went with a bespoke auto-select-by-initial-letter affair rather than forcing the full commands to be (exactly) spelt out very time. You get used to it, and can explore all available command options using the arrow keys.

RCRPG/Phix is part of RCRPG. You may find other members of RCRPG at Category:RCRPG.

I tried putting commands in a dictionary but sequences proved much easier. In contrast the rooms were always much better off in a dictionary, not least because indexing {0,0,0}/extendible in all directions could be problematic.

Notable features are the test for cyclic references in aliases (which can be individual words or any-length series of other complete commands), the ability to completely remove all the original/builtin commands/words, and showing a warning when first letters of commands/items/directions clash.

Always having a sledge in the start room was just far too dull for words, and I felt it was my sworn duty to invent some way to get yourself killed!

<lang Phix>-- demo\rosetta\RCRPG.exw constant start_text = """ Welcome to RCRPG! Press ? for help

""" -- (nb: the leading space of help_text[1] is intentional) constant help_text = """ Welcome to RCRPG!

=====

Command input is via single characters, eg n -> north, tg-> take gold. You do however need to hit return to confirm your choice. The left and right arrow keys cycle through permitted options. Use the backspace key to erase any accidentally selected commands. The up and down arror keys cycle through command history.

Available directions are north, south, east, east, west, up, and down. Take or drop the items sledge, ladder, and gold, which appear randomly. Make a tunnel by attacking a direction. You need to equip a sledge before you can attack. You need a ladder to go up, but you cannot take it between floors. The inventory command lists the items you are carrying.

You can alias a single word or an entire series of commands - type a(lias)<return> and follow the prompts (rather than all on one line). More help can be found by entering a(lias)<return> then ?<return>

Make your way to {1,1,5} for the glorious prize of ending this torture! There is also a way to kill yourself instead - can you figure out how?

"""

constant alias_help = """

       Example of using the alias command
       ==================================

Command:alias Enter alias name(? for help):shiney_shiney Alias:gold Command:inventory You are carrying one piece of gold. Command:alias Enter alias name(? for help):gold Delete existing command?(Y/N):Y Alias: Command:inventory You are carrying one piece of shiney_shiney. Command:

You can delete an alias or even an original command by setting it as an alias, confirm the delete, then hit return to abandon adding the replacement. A check is made to prevent the deletion of the last of each kind, since that would make the game completely unplayable.

As shown the item gold is no more, you now use/see shiney_shiney, that is everywhere else except for this help text. Deleting an alias or original also expunges it from the command history.

The above aliased an item; if you alias a complete command, you get reprompted with "Alias:", allowing one alias to daisy-chain several.

You can even alias the alias command itself, eg as "rename", but I have not bothered to test that very thoroughly.

While you can alias "smack bitch" to "take gold", obviously you would instead need "smack" to "take" and "bitch" to "gold" as two separate aliases to allow "smack ladder" and "drop bitch".

Note that commands and aliases are automatically selected by their first letter, and there should be no conflicts between shared first letters of any (aliased) actions and directions/items, eg "dig down" might prevent "drop" from being selected, apart from via left/right arrow keys, eg a 'd' changes "dig" -> "dig down" rather than "drop". A warning is displayed if the use of "alias" breaks this rule (and if the warning stops after a delete, you have solved the conflict).

Aliases can invoke other aliases. Circular references are detected and playback is prohibited until any such issues are resolved (by deleting or overiding the offending aliases).

The currently defined commands and aliases are: """

enum SINGLE_WORD_COMMAND, DIRECTION, NEEDS_DIRECTION, ITEM, NEEDS_ITEM, ALIAS -- (nb: NEEDS_DIRECTION==DIRECTION+1, NEEDS_ITEM==ITEM+1)

enum N,W,U,D,E,S,INVENTORY,ATTACK,TAKE,DROP,MAKEALIAS,EQUIP,SLEDGE,LADDER,GOLD,ALL,QUIT -- (nb: N/W/U/D/E/S (tee hee) must be 1..6, N<->S etc via 7-d) -- (nb: SLEDGE/LADDER/GOLD together in that order) -- (nb: This enum is fixed, whereas eg "north" can be aliased away}

-- order must match enum: constant moves = {{+1, 0, 0}, -- N

                 { 0,-1, 0},   -- W
                 { 0, 0,+1},   -- U
                 { 0, 0,-1},   -- D
                 { 0,+1, 0},   -- E
                 {-1, 0, 0}}   -- S

-- order not important: sequence cmds = {{"north",DIRECTION,N},

                {"west", DIRECTION,W},
                {"up",   DIRECTION,U},
                {"down", DIRECTION,D},
                {"east", DIRECTION,E},
                {"south",DIRECTION,S},
                {"inventory",SINGLE_WORD_COMMAND,INVENTORY},

-- {"dig",NEEDS_DIRECTION,ATTACK}, -- no (prevents drop)

                {"attack",NEEDS_DIRECTION,ATTACK},
                {"take", NEEDS_ITEM,TAKE},
                {"drop", NEEDS_ITEM,DROP},
                {"alias",SINGLE_WORD_COMMAND,MAKEALIAS},
                {"equip",NEEDS_ITEM,EQUIP},
                {"sledge",ITEM,SLEDGE},

-- {"pickaxe",ITEM,SLEDGE},

                {"ladder",ITEM,LADDER},
                {"gold",ITEM,GOLD},
                {"all",ITEM,ALL},
                {"quit",SINGLE_WORD_COMMAND,QUIT},
                {"n",ALIAS,{{"attack","north"},{"north"}}}}, -- (eg)
        {commands,kinds,data} = columnize(cmds)

procedure clear_prompt(string prompt, sequence words)

   string s = prompt&join(words)
   puts(1,"\r"&repeat(' ',length(s))&"\r")

end procedure

function next_cmd(integer k, shift)

   k += shift
   if k=0 then
       k = length(cmds)
   elsif k>length(cmds) then
       k = 1
   end if
   return k

end function

function begins_with(integer ch, kind, bool isalias)

   if kind=ALIAS then ?9/0 end if -- sanity check
   sequence items = {}
   for i=1 to length(commands) do
       if ((kind=0 and (kinds[i]!=ITEM or isalias)) or
            kinds[i]=kind)
       and commands[i][1]=ch then
           items = append(items,commands[i])
       end if
   end for
   return items

end function

sequence history = {}

function get_command(string prompt="") -- -- Selects by ititial letter, eg tg -> "take gold" (<return> confirms) -- -- Returns eg {"take","gold"}, or {} to quit (already confirmed) -- -- Could probably be improved by allowing full entry of eg "take gold" -- to ignore entered characters that match auto-fills from the t and g. --

   string s = ""
   sequence words = {}, items
   integer last, this
   bool show = false, bExtend, isalias = (prompt="Alias:")
   integer k, shift, hdx = 0
   puts(1,prompt)
   while 1 do
       integer ch = lower(wait_key())
       if ch='\r' then
           if isalias then exit end if
           if length(words) then
               if find(last,{NEEDS_DIRECTION,NEEDS_ITEM}) then
                   clear_prompt(prompt,words)
                   string what = iff(last=NEEDS_ITEM?"item":"direction")
                   printf(1,"missing %s\n",{what})
                   show = true
               else
                   exit
               end if
           else
               clear_prompt(prompt,words)
               puts(1,"Quit?(Y/N):")
               ch = upper(wait_key())
               puts(1,ch)
               if ch='Y' then exit end if
               puts(1,"\r            \r")
               show = true
           end if
       elsif ch=#1B
          or ch='\b' then
           if ch=#1B then -- escape
               if length(words)=0 then exit end if
               -- (else treat as \b)
           end if
           if length(words)>0 then
               clear_prompt(prompt,words)
               words = words[1..$-1]
               show = true
           end if
       elsif ch='?' then
           clear_prompt(prompt,words)
           puts(1,help_text)
           show = true
       elsif ch='!' then
           ?9/0
       elsif ch>=' ' and ch<='~' then
           items = {}
           if length(words)
           and find(last,{NEEDS_DIRECTION,NEEDS_ITEM}) then
               items = begins_with(ch,last-1,false)
               bExtend = true
           end if
           if length(items)=0 then
               items = begins_with(ch,iff(length(words)?last:0),isalias)
               if (length(items)=0 and length(words)=1)
               or items=words then
                   items = begins_with(ch,0,isalias)
                   bExtend = false
               else
                   bExtend = length(words)=0
               end if
           end if
           if length(items) then
               k = iff(length(words)=0?0:find(words[$],items))
               if k=0 or k=length(items) then
                   k = 1
               else
                   k += 1
               end if
               string key = items[k]
               if bExtend then
                   if length(words) then puts(1," ") end if
                   words = append(words,key)
                   puts(1,key)
               else
                   clear_prompt(prompt,words)
                   words[$] = key
                   show = true
               end if
               last = kinds[find(key,commands)]
               if last=ALIAS then last = 0 end if
           end if
       elsif ch=331        -- leftarrow
          or ch=333 then   -- rightarrow
           shift = iff(ch=331?-1:+1)
           clear_prompt(prompt,words)
           if length(words) then
               k = find(words[$],commands)
               if k=0 then ?9/0 end if
           else
               k = iff(ch=331?length(cmds)+1:0)
           end if
           if length(words)=2 then
               while true do
                   k = next_cmd(k,shift)
                   if kinds[k]=last then
                       words[$] = commands[k]
                       exit
                   end if
               end while
           else
               while true do
                   k = next_cmd(k,shift)
                   last = kinds[k]
                   if last=ALIAS then last = 0 end if
                   if last!=ITEM then
                       words = {commands[k]}
                       exit
                   end if
               end while
           end if
           show = true
       elsif ch=328        -- uparrow
          or ch=336 then   -- downarrow
           clear_prompt(prompt,words)
           if length(history)=0 then
               puts(1,"no history\n")
           else
               shift = iff(ch=328?-1:+1)
               hdx += shift
               if hdx<=0 then hdx = length(history)
               elsif hdx>length(history) then hdx = 1 end if
               words = history[hdx]
           end if
           show = true
       end if
       if show then
           show = false
           s = prompt&join(words)
           puts(1,s)
           if length(words) then
               last = kinds[find(words[$],commands)]
               if last=ALIAS then last = 0 end if
           end if
       end if
   end while
   printf(1,"\n")
   if length(words) then
       k = find(words,history)
       if k then
           history[k..k] = {}
       end if
       history = append(history,words)
   end if
   return words

end function

bool aliases_banned = false

function check_circular(string name, integer k, sequence seen={}) -- -- eg 1) alias "yy" to something benign, such as inventory. -- 2) alias "zz" to "yy" (quite possibly indirectly) -- 3) re-alias "yy" to "zz", oh dear, infinite loop... -- -- solution: ban the running of aliases until such resolved. --

   if kinds[k]!=ALIAS then return false end if
   sequence kk = data[k]
   for i=1 to length(kk) do
       string cmd = kk[i][1]
       k = find(cmd,commands)
       if not find(k,seen) then
           seen &= k
           if cmd=name or k=0 or check_circular(name,k,seen) then
               return true
           end if
       end if
   end for
   return false

end function

procedure check_conflicts() -- -- warn if eg "dig down" is going to conflict with "drop" -- (potentially preventing selection of "drop" via 'd') -- unlike check_circular, this does not ban anything. --

   string needs = "", ids = ""
   sequence needn = {}
   integer ch, k, ki
   for i=1 to length(kinds) do
       ch = commands[i][1]
       ki = kinds[i]
       if find(ki,{NEEDS_DIRECTION,NEEDS_ITEM}) then
           k = find(ch,needs)
           if k=0 then
               needs &= ch
               needn &= 1
           else
               needn[k] += 1
           end if
       elsif find(ki,{DIRECTION,ITEM}) then
           k = find(ch,ids)
           if k=0 then
               ids &= ch
           end if
       end if
   end for
   for i=1 to length(ids) do
       ch = ids[i]
       k = find(ch,needs)
       if k and needn[k]>1 then
           printf(1,"warning: command/direction/item conflict over '%c'\n",ch)
       end if
   end for

end procedure check_conflicts()

procedure alias() -- -- single word aliases are added to "cmds" as per the originals. -- whole command [set]s are added to "cmds" as type ALIAS. -- -- note this is open to all kinds of abuse I had no time to test for, -- eg you can alias the alias command itself as rename, which is fine, -- but you could also specify rename as attack north/alias/take gold, -- which makes no proper sense, and would re-prompt at every mid-run. --

   string name = lower(prompt_string("Enter alias name(? for help):"))
   if name="?" then
       puts(1,alias_help)
       sequence sets = {{"DIRECTION",DIRECTION},
                        {"INVENTORY",INVENTORY},
                        {"MAKEALIAS",MAKEALIAS},
                        {"ATTACK",ATTACK},
                        {"TAKE",TAKE},
                        {"DROP",DROP},
                        {"EQUIP",EQUIP},
                        {"SLEDGE",SLEDGE},
                        {"LADDER",LADDER},
                        {"GOLD",GOLD},
                        {"ALL",ALL},
                        {"QUIT",QUIT}}
       for i=1 to length(sets) do
           {string kindstr, integer kind} = sets[i]
           sequence set = {}
           for k=1 to length(cmds) do
               if iff(kind=DIRECTION?kinds[k]:data[k])=kind then
                   set = append(set,commands[k])
               end if
           end for
           printf(1,"%s: %s\n",{kindstr,join(set,",")})
       end for
       for i=1 to length(kinds) do
           if kinds[i]=ALIAS then
               sequence di = data[i]
               for j=1 to length(di) do di[j] = join(di[j]," ") end for
               printf(1,"%s : %s\n",{commands[i],join(di,", ")})
           end if
       end for
       printf(1,"\n")
   elsif name!="" then
       integer k = find(name,commands)
       if k then
           -- note: no check is made as to whether a command is still 
           --       being referenced, just "not the last of its kind".
           if kinds[k]!=ALIAS then
               integer k2 = 0
               for i=1 to length(kinds) do
                   if i!=k 
                   and kinds[i]=kinds[k]
                   and data[i]=data[k] then
                       k2 = i
                       exit
                   end if
               end for
               if k2=0 then
                   -- user needs to copy(/alias), then delete...
                   printf(1,"last of kind, cannot delete\n")
                   return
               end if
           end if
           printf(1,"Delete existing command?(Y/N):")
           integer ch = upper(wait_key())
           printf(1,"%c\n",ch)
           if ch!='Y' then return end if
           cmds[k..k] = {}
           commands[k..k] = {}
           kinds[k..k] = {}
           data[k..k] = {}
           for i=length(history) to 1 by -1 do
               if find(name,history[i]) then
                   history[i..i] = {}
               end if
           end for
       end if
       sequence set = {}
       while true do
           object one = get_command("Alias:")
           if one={} then exit end if
           k = find(one[1],commands)
           integer kk = kinds[k]
           if length(one)=1
           and kk!=ALIAS then
               -- single word
               if find(kk,{NEEDS_DIRECTION,NEEDS_ITEM,ITEM}) then
                   if length(set) then
                       puts(1,"invalid - whole commands only, in a set\n")
                   else
                       cmds = append(cmds,{name,kk,data[k]})
                       commands = append(commands,name)
                       kinds = append(kinds,kk)
                       data = append(data,data[k])
                       check_conflicts()
                       exit
                   end if
               else
                   set = append(set,one)
               end if
           else
               -- whole command(s)
               set = append(set,one)
           end if
       end while
       if length(set) then
           sequence kind = {name,ALIAS,set}
           cmds = append(cmds,kind)
           commands = append(commands,name)
           kinds = append(kinds,ALIAS)
           data = append(data,set)
       end if
       aliases_banned = false
       for k=1 to length(cmds) do
           if check_circular(commands[k],k) then
               puts(1,"invalid - circular or undefined reference\n")
               aliases_banned = true
               exit
           end if
       end for
   end if

end procedure

constant rooms = new_dict() sequence location = {0,0,0}

constant room_descriptions = {"cold dark room",

                             "very stinky room",
                             "small but comfortable room",
                             "room with an incredible echo! Yahollaydoo!",
                             "grim room resembling a window-less jail",
                             "green room",
                             "black room",
                             "blue room",
                             "brown sad room",
                             "room which breathes pain"}

bool game_over = false,

    equipped = false   -- (possibly a bit too simple)

integer others = 1 -- ensure a SLEDGE is available somewhere,

                   -- set to -1 when that's all done & dusted

procedure make_room(sequence location, integer hole=0)

   integer desc = rand(length(room_descriptions))
   sequence items = {},    -- (nb no quantities, as yet...)
            exits = repeat(false,S)
   if hole then exits[hole] = true end if
   for d=N to S do
       -- No up until we've placed a sledge, otherwise would
       --    need to ensure ladder was present on each floor.
       if others=-1 or d!=U then
           sequence next_door = sq_add(location,moves[d])
           integer node = getd_index(next_door,rooms)
           if node=NULL then
               if rand(5)=1 then
                   exits[d] = true
                   if others!=-1 then others += 1 end if
               end if
           else
               sequence {?,?,nexits} = getd_by_index(node,rooms)
               exits[d] = nexits[7-d]
           end if
       end if
   end for
   others -= 1
   for item=SLEDGE to GOLD do -- (LADDER in the middle)
       if rand(3)=1 
       or others=0             -- ensure sledge if last room
       or (exits[U] and item=LADDER) then -- no death traps
           items &= item
           if others=0 then others = -1 end if
       end if
   end for
   sequence room = {desc,items,exits}
   setd(location,room,rooms)

end procedure if getd_index(location,rooms)=NULL then make_room(location) end if

sequence carryable = {SLEDGE,LADDER,GOLD},

        carrying = repeat(0,length(carryable))

procedure inventory()

   string totes = ""
   if sum(carrying)=0 and not game_over then
       totes = "nothing"
   else
       for i=1 to length(carrying) do
           integer ci = carryable[i], cq = carrying[i]
           if cq!=0 then
               if length(totes) then totes &= ", " end if
               string name = commands[find(ci,data)]
               if cq=1 then
                   totes &= iff(ci=GOLD?"one piece of ":"a ")&name
               else
                   totes &= sprintf("%d ",cq)&iff(ci=GOLD?"pieces of "&name:name&"s")
               end if
           end if
       end for
       if game_over then
           if length(totes) then totes &= ", and " end if
           totes &= "a printed A4 certificate of achievement as given to all winners"
       end if
   end if
   printf(1,"You are carrying %s.\n",{totes})

end procedure

procedure show_room()

   {integer desc, sequence items, sequence exits} = getd(location,rooms)
   printf(1, "You are in a %s %v\n",{room_descriptions[desc],location})
   if length(items) then
       for i=1 to length(items) do
           items[i] = commands[find(items[i],data)]
       end for
       if length(items)=1 and items!={"gold"} then
           items = "a "&items[1]
       else
           items = join(items,", ")
       end if
       printf(1,"You see %s.\n",{items})
   end if
   if find(true,exits)!=0 then
       for i=length(exits) to 1 by -1 do
           if exits[i] then
               exits[i] = commands[find(i,data)]
           else
               exits[i..i] = {}
           end if
       end for
       string isare = iff(length(exits)=1?"is an exit":"are exits")
       printf(1,"There %s %s.\n",{isare,join(exits,", ")})
   end if
   printf(1,"\n")
   if location={1,1,5} then
       game_over = true
       printf(1,"You win! Game over.\n\n")
       inventory()
   end if

end procedure

procedure move(integer direction)

   {integer desc, sequence items, sequence exits} = getd(location,rooms)
   if not exits[direction] then
       printf(1,"There is no passage that way.\n")
   elsif direction=U
     and not find(LADDER,items) then
       printf(1,"There is no ladder in the room.\n")
   else
       location = sq_add(location,moves[direction])
       if getd_index(location,rooms)=NULL then
           make_room(location,7-direction)
       end if
       {desc, items, exits} = getd(location,rooms)
       if exits[D] then
           sequence below = sq_add(location,moves[D])
           integer node = getd_index(below,rooms)
           if node!=NULL then
               {desc, items, exits} = getd_by_index(node,rooms)
               if find(LADDER,items)=0 then
                   printf(1,"You fall down a hole in the floor and die.\n")
                   game_over = true
                   return
               end if
           end if
       end if
       show_room()
   end if

end procedure

procedure dig(integer direction)

   {integer desc, sequence items, sequence exits} = getd(location,rooms)
   if not equipped then
       printf(1,"You claw at the rock with your bare hands until your fingers bleed.\n")
   elsif rnd()<0.1 then
       -- (if it is the last one then game over...)
       printf(1,"Your sledge has broken.\n")
       equipped = false
       carrying[find(SLEDGE,carryable)] -= 1
   elsif exits[direction] then
       printf(1,"There is already a passage that way, you make it a little bigger.\n")
   else
       sequence new_location = sq_add(location,moves[direction])
       if getd_index(new_location,rooms)=NULL then
           make_room(new_location)
       end if
       exits[direction] = true
       sequence die = {desc,items,exits} -- (save before overwriting)
       {desc,items,exits} = getd(new_location,rooms)
       integer hole = 7-direction
       if exits[hole] then ?9/0 end if
       if direction=D
       and not find(LADDER,items) then
           printf(1,"You dig a tiny hole no bigger than your fist.\n")
           printf(1,"Seeing no ladder, you decide it is too risky to continue.\n")
       else
           exits[hole] = true
           setd(location,die,rooms)
           setd(new_location,{desc,items,exits},rooms)
           string way = commands[find(direction,data)]
           printf(1,"You dig a passage %s.\n",{way})
       end if
   end if

end procedure

procedure take(integer item)

   {integer desc, sequence items, sequence exits} = getd(location,rooms)
   if item=ALL then
       for i=1 to length(items) do take(items[i]) end for
   else
       string name = commands[find(item,data)]
       integer k = find(item,items)
       if k=0 then
           printf(1,"There isn't any %s here.\n",{name})
       else
           items[k..k] = {}
           setd(location,{desc,items,exits},rooms)
           k = find(item,carryable)
           carrying[k] += 1
           printf(1,"You pick up the %s.\n",{name})
       end if
   end if

end procedure

procedure drop(integer item, integer qty=1) -- note: dropping (all) 8 pieces of gold results in one on the floor...

   if item=ALL then
       if sum(carrying)=0 then
           printf(1,"You aren't carrying anything.\n")
       else
           for i=1 to length(carrying) do
               qty = carrying[i]
               drop(carryable[i],qty)
           end for
       end if
   elsif qty then
       string name = commands[find(item,data)]
       integer k = find(item,carryable)
       if carrying[k]=0 then
           printf(1,"You aren't carrying any %s.\n",{name})
       else
           carrying[k] -= qty
           {integer desc, sequence items, sequence exits} = getd(location,rooms)
           if not find(item,items) then
               items &= item
               setd(location,{desc,items,exits},rooms)
           end if
           printf(1,"You drop the %s.\n",{name})
           if item=SLEDGE then equipped = false end if
       end if
   end if

end procedure

procedure equip(integer item)

   string name = commands[find(item,data)]
   integer k = find(item,carryable)
   if carrying[k]=0 then
       printf(1,"You aren't carrying %s %s.\n",{iff(item=GOLD?"any":"a"),name})
   elsif item!=SLEDGE then
       printf(1,"You can't equip %s%s.\n",{iff(item=GOLD?"":"a "),name})
   elsif equipped then
       printf(1,"already equipped.\n")
   else
       equipped = true
       printf(1,"You equip the %s.\n",{name})
   end if

end procedure

procedure process_command(sequence c) -- -- tip: replace the inner switches with tables of routine_ids, -- that is should they start to get a little too unwieldy. --

   integer k = find(c[1],commands)
   switch kinds[k] do
       case ALIAS: -- (the nested variety)
           if aliases_banned then
               printf(1,"Sorry, aliases banned until circular references resolved.\n")
           else
               for i=1 to length(data[k]) do
                   printf(1,"Running:%s\n",{join(data[k][i])})
                   process_command(data[k][i])
                   if game_over then exit end if
               end for
           end if
       case DIRECTION:
           move(data[k])
       case NEEDS_DIRECTION, NEEDS_ITEM:
           integer dk = data[find(c[2],commands)]
           switch data[k] do
               case ATTACK: dig(dk)
               case TAKE:   take(dk)
               case DROP:   drop(dk)
               case EQUIP:  equip(dk)
               default: ?9/0
           end switch
       case SINGLE_WORD_COMMAND:
           switch data[k] do
               case INVENTORY: inventory()
               case MAKEALIAS: alias()
               case QUIT: game_over = true
               default: ?9/0
           end switch
       default: ?9/0
   end switch

end procedure

puts(1,start_text) show_room() while not game_over do

   sequence c = get_command("Command:")
   if c={} then exit end if
   process_command(c)

end while</lang>