RCRPG/Unicon

From Rosetta Code
RCRPG/Unicon is part of RCRPG. You may find other members of RCRPG at Category:RCRPG.

The following program implements the RCRPG game in Unicon.

It was fun and fairly fast to write. After some initial simple bugs, I had to deal with relatively few oversights. The most major were forgetting to back link rooms and overwriting the prize room after tunneling into it.

This version was developed from the blog description and some skimming of other solutions for messaging and consistency in handling under specified features. I believe it's a bit chattier than some of the other versions.

There are a couple embellishments.

  • Pre-loading simple aliases for ease of use.
  • A magic word for when all hope is lost. Every good adventure like game needs a magic word.
  • If you name a room but forget to give a name, a random one will be chosen.
  • Things like a low probability of breaking a sledge and taking multiple hits to break through were probably stolen from other variants.

There are quirks, probably some undiscovered ones at that. Most notably the alias command only works on the command verb. The facility could be extended but would need some safeties to prevent you from doing things like redefining north.

Programming style is slightly lazy. Locals aren't declared, most procedures fail rather than return a value even when they do the write thing. Only a few globals are used.

The implementation uses a number of minor Unicon extensions over Icon. The main extension(s) used is probably the extended delete function to remove list elements. It may reference records by subscripts but I think that is removed.

The main program follows:

record roominfo(x,y,z,name,items,n,s,e,w,u,d)    # about rooms
record urinfo(here,carrying,using)               # about your character

global Start, Finish, Directions, UR, Aliases, Command

invocable all                                    # needed for command verbs

procedure main()                                 #:  main procedure for RCRPG
   Start  := AddRoom(0,0,0,"the starting room","sledge")
   Finish := AddRoom(1,1,5,"the prize room","gold")
   Directions := table()
   every x := "north"|"east"|"south"|"west"|"up"|"down" & Directions[x|x[1]] := x
   UR := urinfo(Start,[])
   write("Welcome to RCRPG (Icon/Unicon version)!\n\n_
         Your goal is make your way to room 1,1,5.\n_
         Hint: You'll need the sledge hammer.\n")
   Help()
   (\Alias_Setup)()                                           # optional
   repeat Describe() & DoCommand()
end

The following implement the navigation procedures:

procedure AddRoom(x,y,z,name,item[])             #: Create room w/item(s)
   if *item = 0 then push(item, \?[&null,"sledge","ladder","gold"]) # randomize
   return roominfo(x,y,z,\name|"",item)   
end   

procedure Attack()                               #:  Attack
   whereto := Directions[Command[2]]
   if /NextRoom(whereto) then 
      if \UR.using == "sledge" then  {
         if ?0 > .1 then {                    # 90% chance of success
            x := UR.here.x + ((whereto=="east",1) | (whereto=="west",-1) | 0)
            y := UR.here.y + ((whereto=="north",1) | (whereto=="south",-1) | 0)
            z := UR.here.z + ((whereto=="up",1) | (whereto=="down",-1) | 0)    
            
            r := if x = Finish.x & y = Finish.y & z = Finish.z then Finish
            else AddRoom(x,y,z)
            
            NextRoom(whereto) := r
            case whereto of { "up":r.d; "down":r.u; 
               "north":r.s; "east":r.w; "south":r.n; "west":r.e } := UR.here  
            write("You created a tunnel to another room.")
            }
         else write("You're almost through, try again.")
         if ?0 < .1 then {                    # 10% chance of breakage
            UR.using := &null
            put(UR.here.items,"broken hammer")
            write("You broke your sledge hammer and left it on the ground.")
            write(if \Abracadabra then "There is a hint of magic in the air."
                  else "Hope you found another.")
            }
         }
      else write("You have no digging equipment.")
   else if not NextRoom(whereto) then write("I don't know that direction.")
   else write("There is already an opening in that direction.")
end  

procedure Go()                                   #:  Go in a direction
   if room := NextRoom(whereto := \Directions[Command[2]]) then {
      HandleLadder(whereto)    
      (UR.here := \room) | write("There is no opening going ",whereto) 
      }      
   else write("I don't know that direction.")      
end   

procedure NextRoom(whereto)                      #: return next room or fail
   return case Directions[whereto] of {
      "up"    : UR.here.u
      "down"  : UR.here.d
      "north" : UR.here.n
      "east"  : UR.here.e
      "south" : UR.here.s
      "west"  : UR.here.w    
      }  
end

The following are the command processing and non-navigational commands:

procedure Alias()                                #: make a command alias
   /Aliases := table()
   if Aliases[Command[2]] := Command[3] then 
      write(Command[2]," is now an alias for ",Command[3])
   else write("What did you want me to alias?")
end   

procedure Describe()                             #: Describe where U R
static lastroom
   if lastroom ~===:= UR.here then Look()        # don't repeat description
   if UR.here === Finish then stop("Congratulations! You won.\nCome again.")
return                                           # needed for main loop   
end  

procedure Drop()                                 #: drop item
   room := UR.here
   if item := Command[2] then 
      if item == "all" then {
         while put(room.items, get(UR.carrying))
         put(room.items,\UR.using) & UR.using := &null
         write("Everything you had is on the floor.")
         }
      else if item == \UR.using then {
         UR.using := &null   
         put(room.items,item)
         write("You dropped the ",item)
         }
      else if item == !UR.carrying then {
         delete(UR.carrying,index(UR.carrying,item))
         put(room.items,item)
         write("You dropped the ",item)
         }      
      else write("You don't have any ",item)
   else write("What do you want me to drop?")
end   

procedure DoCommand(command)                     #: Do the command
static verbs
initial {
   verbs := table()
   every p := ![Alias,Attack,Drop,Equip,Go,Help,Inventory,Look,Name,Take,Abracadabra] do
       verbs[map(image(\p))[*"procedure X":0]] := p
   }

   Command := []
   ( writes("> "), trim(pretrim(read()))) ? until pos(0) do {
      put(Command,tab(upto(' \t')|0))
      tab(many(' \t'))
      }

   repeat {
      if \Directions[\Command[1]] then push(Command,"go")
      if p := \verbs[\Command[1]] then {
         p()                                                   # call command
         return
         }
      else if Command[1] := \(\Aliases)[Command[1]] then next  # aliased, retry 
      else if Command[1] == "quit" then stop("Quitting.")
      else break write("I;m sorry I don't know that command.")
      }
end   

procedure Equip()                                #:  Equip
   if item := Command[2] then 
      if item == (\UR.using | !UR.carrying) then {
         put(UR.carrying, \UR.using)                            # save equip
         delete(UR.carrying,index(UR.carrying,UR.using:=item)) # move to using  
         write("You are now equipped with a ",UR.using)         
         }
      else write("You aren't carrying a ",item)
   else write("Equip with what?")
end   
        
procedure HandleLadder(whereto)                         #:  Handle ladder
   if whereto == "up" then {                            # only for up
      if (item := "ladder") == \UR.using then 
         put(UR.here.items,item) & UR.using := &null
      else if item == !UR.carrying then 
         put(UR.here.items,item) & delete(UR.carrying,index(UR.carrying,item))
      if item == !UR.here.items then return
      else write("You can't go up without a ladder in the room.")
      }
end   

procedure Help()                                 #: give help
write("Commands:\n_
(go) north, south, east, west, up, down - \n_
\tmoves you in the direction specified if there is an exit\n_
\ta ladder is required to go up or down\n_
look - take a look around\n_
inventory - Show everything you're carrying\n_
equip (item name) - Equip the item in question\n_
attack (direction) - attack in the direction specified\n_
drop (item name)|all - Drop the item specified or everything you'e carrying\n_
take (item name)|all - Take the item specified or everything in the room\n_
name (name) - Rename the room to whatever you want to call it\n_
alias (existing command) (new name) -\n_
\tCreate an alias for an existing command.\n_
\tYou can't redefine an existing command.\n_
quit - useful if you broke your only sledge hammer\n")
end   #

procedure Inventory()                            #: show inventory
   if /UR.using & (*UR.carrying = 0) then 
      write("You haven't got anything with you.")
   else { 
      write("You have ",ShowList((*UR.carrying>0,UR.carrying))," in your pack.")
      write("You are holding a ",\UR.using," in your hand.")
      }           
end   

procedure Look()                                 #: Look around and describe    
   r := UR.here
   write(sprintf("You are in the %s(%d,%d,%d) room.",r.name,r.x,r.y,r.z))
   if *r.items>0 then write("On the ground you can see " || ShowList(r.items))
   every insert(dl := set(), (\NextRoom(d := !Directions), d)) 
   if *dl>0 then write("There are openings in the directions ", ShowList(dl))
   else write("There are no openings.")
end   

procedure Name()                                 #:  Name the current room
   if UR.here.name := \Command[2] then
      write("The room is now called ",UR.here.name)
   else write("You didn't provide a name.  I think I shall name it ",
               UR.here.name := ?["Fred","Ringo","George","John","Paul","Ozzy",
                                 "Hamlet","Kermit","Spot","Minka", "Archie"])
end

procedure Take()                                 #: take item
   room := UR.here
   if item := Command[2] then 
      if item == "all" then {
         while put(UR.carrying,get(room.items))
         write("You have everything in the room greedy!")
         }
      else if delete(room.items,index(room.items,item)) then {
         put(UR.carrying,item)
         write("You now have the ",item)
         }
      else write("I see no ",item)
   else write("What do you want me to take?")
end

The following are miscellaneous supprt procedures:

procedure ShowList(X,c)                          #: return c separated elements
   /c := ", "
   every (s := "") ||:= !X || c
   return s[1:-*c]
end   

procedure index(L, x)		                      #: generate indices for x
   every if x === L[i := 1 to *L] then suspend i
end

The following are minor embellishments that are silently linked. If they are left out, the remaining code should be able to continue on unaffected.

procedure Abracadabra()                          #: magic word
static uses
initial uses := 3
  write("That is very old magic, I'm not sure it still works ...")
  if 0 < (uses -:= 1) then {
     write("... Poof! A sledge appears in a cloud of purple smoke")
     put(UR.here.items,"sledge")
     }
   else if uses = 0 then write("... No. Nothing happens.")
   else if uses = -1 then write("... You now have a fuchsai tail!!")
   else write("... Thankfully nothing else happens.")
end

procedure Alias_Setup()                          #: ease of use aliases
   /Aliases := table()
   every a := key(Directions) | !["inventory","attack","take","look"] do
      Aliases[a[1]] := a
   every Aliases[!["hocus","pocus","shazzam","walla","xyzzy"]] := "abracadabra"
end