RCRPG/uBasic-4tH

From Rosetta Code

The game

The user starts on the bottom floor in a room with a sledge. That seems like a reasonable precaution, because all rooms are completely sealed. You have to equip a sledge every time you want to knock down a wall. If you want to make a hole in the ceiling, you not only got to have a ladder - you have to equip it too. As well as the sledge.

There is one ladder per floor. If you want to go up, there has to be a ladder in the room. If there is no ladder in the room you can't go up. If you happen to be carrying one, you have to drop it first. And you can't take it with you when you go up, because you're standing on it.

Vice versa - if you want to go down, there'd better be a ladder. For the same reason you can't make holes in the floor. It's far too dangerous to jump. The outer walls are impenetrable as well.

All commands are supported - except for the ones used to name the rooms. There is even a neat "help" command and lots of short aliases. Commands can be concatenated, so a sequence like:

take ladder equip ladder equip sledge attack up up

Will make you take the ladder, equip it, equip the sledge as well, tear down the ceiling and get you up to the next floor. In theory, you could solve the entire game with one (very) long command.

There are pieces of gold and sledges dispersed in the building, but there is only one Prize Room. It contains gold. Lots of it. And every game it is a different room.

Implementation

uBasic/4tH has a single array of 256 elements. It can barely accommodate a building of six floors of 36 rooms. For that reason you can't "name" any rooms. There are just not enough variables to store them.

The entire room record is bitmapped and designed to fit a signed 32 bit integer:

8 bits = #gold
8 bits = #sledge
1 bit  = ladder
1 bit  = target
6 bits = directions (udsnew)

The "player" structure was crammed in an integer as well - but that wasn't really required. It's just neat since it makes the code more interesting.

This implementation really uses uBasic/4tH strengths. The NAME() function converts an ordinary string to a label, while LINE() returns true when the label has been defined. The PROC keyword can call a procedure directly by using an expression.

So - in essence the user directly enters procedure names. uBasic/4tH has an intimate relation with Forth, so it's no surprise that the parser closely resembles the classic Forth parser.

The entire program is about 450 lines. It could have been factored a bit better, agreed, but it feels more readable now.

The code

Dim @r(216)                            ' these are our rooms

i = Rnd(36)                            ' now place the person in a room, floor 0

For x = 0 To 215 : @r(x) = 63 : Next   ' all directions are closed
For x = 0 To 215 : @r(x) = @r(x) + (65536 * (Rnd(10) = 1)) : Next
For x = 0 To 215 : @r(x) = @r(x) + (256 * (Rnd(25) = 1)) : Next
                                       ' distribute gold and sledges
For x = 0 To 4 : r = Rnd(36) : @r((x*36) + r) = @r((x*36) + r) + 128 : Next
                                       ' place a single ladder on every floor,
@r(i) = Or(@r(i), 256)                 ' except the top floor
                                       ' give the guy a sledge
x = Rnd(216) : @r(x) = @r(x) + 64 + (65536 * (Rnd(25) + 15))
x = FUNC(_Describe (And(i, 255)))      ' set prize room and describe position

Do                                     ' go into the main command loop
  While FUNC(_Interpret(Ask("<Command> ")))
Loop

End

_Interpret                             ' interpret a command
  Param (1)
  Local (1)

  c = a@ : Print

  Do While Len(c)                      ' if it is a valid label
    If Line(Name(Set (b@, FUNC(_Token)))) Then
      Proc Name(b@)                    ' then call it
    Else                               ' otherwise not a valid command
      Print "I don't understand this \q"; Show(b@);"\q.\n"
    EndIf
  Loop
                                       ' describe the room
  If FUNC(_Describe (And(i, 255))) Then Return (1)
Return (0)

_Describe                              ' describe the room
  Param (1)                            ' clean location
  Local (3)                            ' amount of gold and sledges
                                       ' list the location
  Print "***************************"
  Print "You're on floor ";a@/36;", room ";(a@%36)/6;".";(a@%36)%6
  Print "***************************"
  Print
  Print "Directions you may proceed in:"
  Print "\t";                          ' list all valid directions

  If And(@r(a@), 63) = 63 Then Print "None";
  If And(@r(a@), 1) = 0   Then Print "West",
  If And(@r(a@), 2) = 0   Then Print "East",
  If And(@r(a@), 4) = 0   Then Print "North",
  If And(@r(a@), 8) = 0   Then Print "South",
  If And(@r(a@), 16) = 0  Then Print "Down",
  If And(@r(a@), 32) = 0  Then Print "Up",
  Print : Print

  Print "Things of interest here:"     ' list anything of interest

  b@ = And(@r(a@)/65536, 255)          ' calculate pieces of gold
  c@ = And(@r(a@)/256, 255)            ' calculate sledges

  If b@ Then Print "\t";b@;" piece(s) of gold"
  If c@ Then Print "\t";c@;" sledge(s)"
  If And(@r(a@), 128) Then Print "\tA ladder"
  If @r(a@)/128 = 0 Then Print "\tNothing"

  If And(@r(a@), 64) Then              ' see if we are in the prize room
    Print
    Print "You found the Prize Room!"
    Print "Congratulations! You have won!"
    Return (0)                         ' we can safely quit now
  EndIf
  Print
Return (1)                             ' no, this isn't the prize room

_Token                                 ' split token
  Local (1)                            ' token string

  Do
    While Len(c)                       ' any string left?
    While Peek(c, 0) = Ord(" ")        ' is it a space?
    c = Chop(c, 1)                     ' split off space
  Next

  a@ := ""                             ' start with an empty token

  Do                                   ' start after leading spaces
    While Len(c)
    While Peek(c, 0) # Ord(" ")        ' until we find another space
    a@ = Join(a@, Char(Peek (c, 0)))   ' add character to token
    c = Chop(c, 1)                     ' split off character
  Loop

Return (a@)                            ' return remainder and token

_exit                                  ' this is the exit command
  Print "You realize there's no way out of here."
  Print "You decide to commit suicide."
End

_list Goto _inventory                  ' alias for "inventory"
_inv Goto _inventory                   ' alias for "inventory"

_inventory
  Print "You're carrying:"             ' list all items
  If And(i/256, 131071) = 0 Then Print "\tNothing"
  If And(i, 16777216)   Then Print "\tA ladder"
  If And(i/256, 255)    Then Print "\t";And(i/256, 255);" sledge(s)"
  If And(i/65536, 255)  Then Print "\t";And(i/65536, 255);" piece(s) of gold"
  Print
Return

_take                                  ' take any item
  Local (1)

  a@ = FUNC(_Token)

  If Comp(a@, "sledge") = 0 Then
    Proc _TakeSledge                   ' get the sledge
  Else If Comp(a@, "gold") = 0 Then
    Proc _TakeGold                     ' take the gold
  Else If Comp(a@, "ladder") = 0 Then
    Proc _TakeLadder                   ' get the ladder
  Else If Comp(a@, "all") = 0 Then
    Proc _TakeAll                      ' be greedy
  Else Print "Hey, just what you see, pal!\n"
  EndIf EndIf EndIf EndIf              ' no such thing
Return

_TakeSledge
  Local (2)

  If Set(a@, And(@r(Set(b@, And(i, 255)))/256, 255)) Then
    @r(b@) = ((@r(b@)/65536) * 65536) + ((a@ - 1) * 256) + And(@r(b@), 255)
    i = ((i/65536) * 65536) + ((And(i/256, 255) + 1) * 256) + b@
  Else
    Print "There is no sledge here.\n"
  EndIf
Return

_TakeGold
  Local (2)

  If Set(a@, And(@r(Set(b@, And(i, 255)))/65536, 255))  Then
    @r(b@) = ((a@-1) * 65536) + And(@r(b@), 65535)
    i = ((i/16777216) * 16777216) + ((And(i/65536, 255) + 1) * 65536) + And(i, 65535)
  Else
    Print "There is no gold here.\n"
  EndIf
Return

_TakeLadder
  Local (1)

  If And(@r(Set (a@, And(i, 255))), 128) Then
    @r(a@) = And(@r(a@), Not(128))
    i = Or(i, 16777216)
  Else
    Print "There is no ladder here.\n"
  EndIf
Return

_TakeAll
  Local (1)

  If @r(Set (a@, And(i, 255)))/128 > 0 Then
    If And(@r(a@), 128) Then
      Proc _TakeLadder
    EndIf

    Push ((And(i/65536, 255) + And(@r(a@)/65526, 255)) * 65536)
    Push ((And(i/256, 255) + And(@r(a@)/256, 255)) * 256)
    i = ((i/16777216) * 16777216) + Pop() + Pop() + a@
    @r(a@) = And(@r(a@), 63)
  Else
    Print "Nothing of interest here.\n"
  EndIf
Return

_arm Goto _equip                       ' alias for "equip"

_equip                                 ' equip any item
  Local (1)

  a@ = FUNC(_Token)

  If Comp(a@, "sledge") = 0 Then
    Proc _EquipSledge                  ' equip the sledge
  Else If Comp(a@, "ladder") = 0 Then
    Proc _EquipLadder                  ' equip the ladder
  Else Print "You can't equip that.\n"
  EndIf EndIf                          ' no such thing
Return

_EquipSledge
  If And(i/256, 255) Then
    i = Or(i, 33554432)
    Print "The sledge is equipped."
  Else
    Print "You're not carrying a sledge."
  EndIf

  Print
Return

_EquipLadder
  If And(i, 16777216) Then
    i = Or(i, 67108864)
    Print "The ladder is equipped."
  Else
    Print "You're not carrying a ladder."
  EndIf

  Print
Return

_drop                                  ' drop any item
  Local (1)

  a@ = FUNC(_Token)

  If Comp(a@, "sledge") = 0 Then
    Proc _DropSledge                   ' get the sledge
  Else If Comp(a@, "gold") = 0 Then
    Proc _DropGold                     ' take the gold
  Else If Comp(a@, "ladder") = 0 Then
    Proc _DropLadder                   ' get the ladder
  Else If Comp(a@, "all") = 0 Then
    Proc _DropAll                      ' be greedy
  Else Print "You can't drop what you don't have.\n"
  EndIf EndIf EndIf EndIf              ' no such thing
Return

_DropSledge
  Local (2)

  If Set(a@, And(i/256, 255)) Then
    b@ = And(i, 255)
    @r(b@) = ((@r(b@)/65536) * 65536) + ((And(@r(b@)/256, 255) + 1) * 256) + And(@r(b@), 255)
    i = And(((i/65536) * 65536) + ((a@ - 1) * 256) + b@, Not(33554432))
  Else
    Print "You're not carrying a sledge.\n"
  EndIf
Return

_DropGold
  Local (2)

  If Set(a@, And(i/65536, 255)) Then Then
    b@ = And(i, 255)
    @r(b@) = (((@r(b@)/65536) + 1) * 65536) + And(@r(b@), 65535)
    i = ((i/16777216) * 16777216) + ((a@ - 1) * 65536) + And(i, 65535)
  Else
    Print "There is no gold here.\n"
  EndIf
Return

_DropLadder
  Local (1)

  If And(i, 16777216) Then
    a@ = And(i, 255)
    @r(a@) = Or(@r(a@), 128)
    i = And(i, Not(16777216 + 67108864))
  Else                                 ' disable ladder
    Print "You're not carrying a ladder.\n"
  EndIf
Return

_DropAll
  Local (1)

  If And(i/256, 131071) Then
    a@ = And(i , 255)

    If And(i, 16777216) Then
      Proc _DropLadder
    EndIf

    Push ((@r(a@)/65536 + And(i/65536, 255)) * 65536)
    Push ((And(@r(a@)/256, 255) + And(i/256, 255)) * 256)
    @r(a@) = Pop() + Pop() + And(@r(a@), 255)
    i = And(i, 255)
  Else
    Print "Nothing of interest here.\n"
  EndIf
Return

' west  = -1
' east  = +1
' north = -6
' south = +6
' down  = -36
' up    = +36

_s Goto _south                         ' aliases for directions
_n Goto _north
_w Goto _west
_e Goto _east
_u Goto _up
_d Goto _down

_west
  If And(@r(And(i, 255)), 1) = 0 Then i = i - 1 : Return
  Print "You can't go west." : Print
Return

_east
  If And(@r(And(i, 255)), 2) = 0 Then i = i + 1 : Return
  Print "You can't go east." : Print
Return

_north
  If And(@r(And(i, 255)), 4) = 0 Then i = i - 6 : Return
  Print "You can't go north." : Print
Return

_south
  If And(@r(And(i, 255)), 8) = 0 Then i = i + 6 : Return
  Print "You can't go south." : Print
Return

_down
  If And(i, 255) < 36 Then Print "You can't go down.\n" : Return
  If And(@r(And(i, 255) - 36), 128) = 0 Then Print "You'll need a ladder.\n" : Return
  If And(@r(And(i, 255)), 16) = 0 Then i = i - 36 : Return
  Print "You can't go down." : Print
Return

_up
  If And(@r(And(i, 255)), 128) = 0 Then Print "You'll need a ladder.\n" : Return
  If And(@r(And(i, 255)), 32) = 0 Then i = i + 36 : Return
  Print "You can't go up." : Print
Return

_help                                  ' a quick help
  Print "north, south, east, west, up, down"
  Print "\tMove in the direction specified. You won’t be able"
  Print "\tto move if there isn’t an exit in that direction."
  Print
  Print "attack (direction)"
  Print "\tAttack in the direction specified. (Hint: equip stuff first.)"
  Print
  Print "drop (all|item name)"
  Print "\tDrop the item specified. Or drop everything you’re carrying."
  Print
  Print "take (all|item name)"
  Print "\tTake the item specified. Or take everything in the room."
  Print
  Print "inventory"
  Print "\tDisplay everything you’re carrying."
  Print
  Print "equip (item name)"
  Print "\tEquip the item in question."
  Print
Return

' if (room) / 36 = 0       then no down
' 1f (room) / 36 = 5       then no up
' if ((room) % 36) / 6 = 5 then no south
' if ((room) % 36) / 6 = 0 then no north
' if ((room) % 36) % 6 = 5 then no east
' if ((room) % 36) % 6 = 0 then no west

_smash Goto _attack                    ' aliases for "attack"
_go Goto _attack

_attack                                ' attack a direction
  Local (1)                            ' just make a hole in the wall

  a@ = FUNC(_Token)

  If Comp(a@, "north") = 0 Then
    Proc _SmashNorth (And (i, 255))
  Else If Comp(a@, "south") = 0 Then
    Proc _SmashSouth (And (i, 255))
  Else If Comp(a@, "west") = 0 Then
    Proc _SmashWest (And (i, 255))
  Else If Comp(a@, "east") = 0 Then
    Proc _SmashEast (And (i, 255))
  Else If Comp(a@, "up") = 0 Then
    Proc _SmashUp (And (i, 255))
  Else If Comp(a@, "down") = 0 Then
    Print "That's quite a drop, dude..\n"
  Else Print "You don't wanna go there..\n"
  EndIf EndIf EndIf EndIf EndIf EndIf
Return
                                       ' do we have a sledge - or a ladder?
_Sledge? Return(And(i/256, 255)  * And(i, 33554432))
_Ladder? Return(And(i, 16777216) * And(i, 67108864))

_SmashWest
  Param (1)

  If FUNC(_Sledge?) Then
    If (a@ % 36) % 6 = 0 Then
      Print "That looks like a tough west wall..\n"
    Else
      @r(a@) = And(@r(a@), Not(1))
      @r(a@-1) = And(@r(a@-1), Not(2))
      i = And(i, Not(33554432))
    EndIf
  Else
    Print "Equip the sledge first. If you got one..\n"
  EndIf
Return

_SmashEast
  Param (1)

  If FUNC(_Sledge?) Then
    If (a@ % 36) % 6 = 5 Then
      Print "That looks like one tough east wall..\n"
    Else
      @r(a@) = And(@r(a@), Not(2))
      @r(a@+1) = And(@r(a@+1), Not(1))
      i = And(i, Not(33554432))
    EndIf
  Else
    Print "Activate the sledge first. If you got one..\n"
  EndIf
Return

_SmashNorth
  Param (1)

  If FUNC(_Sledge?) Then
    If (a@ % 36) / 6 = 0 Then
      Print "That looks like one tough north wall..\n"
    Else
      @r(a@) = And(@r(a@), Not(4))
      @r(a@-6) = And(@r(a@-6), Not(8))
      i = And(i, Not(33554432))
    EndIf
  Else
    Print "Equip the sledge first. If you got one..\n"
  EndIf
Return

_SmashSouth
  Param (1)

  If FUNC(_Sledge?) Then
    If (a@ % 36) / 6 = 5 Then
      Print "That looks like one tough south wall..\n"
    Else
      @r(a@) = And(@r(a@), Not(8))
      @r(a@+6) = And(@r(a@+6), Not(4))
      i = And(i, Not(33554432))
    EndIf
  Else
    Print "Equip the sledge first. If you got one..\n"
  EndIf
Return

_SmashUp
  Param (1)

  If FUNC(_Sledge?) Then
    If a@ / 36 = 5 Then
      Print "That looks like one tough ceiling..\n"
    Else
      If FUNC(_Ladder?) Then           ' note we drop the ladder
        @r(a@) = And(@r(a@), Not(32)) + 128
        @r(a@+36) = And(@r(a@+36), Not(16))
        i = And(i, Not(16777216 + 33554432 + 67108864))
      Else                             ' nothing equipped and NO ladder
        Print "Equip the ladder first. If you got one..\n"
      EndIf
    EndIf
  Else
    Print "Equip the sledge first. If you got one..\n"
  EndIf
Return