Text Adventures: Colossal Cave-Style

 if 0 {
 [A text adventure game engine]
 [Larry Smith] 5-27-2008 
 }

 proc ins { list args } {
  upvar #0 $list which

  eval lappend which $args
 }

 proc rem { list args } {
  upvar #0 $list which

  foreach item $args {
    switch -regexp -- $item {
      \[-0-9\]\[0-9\]* { set which [ lreplace $which $item $item ] }
      default           { set this [ lsearch $which $item ]
                          if { $this != -1 } {
                            set which [ lreplace $which $this $this ]
                          }
                        }
    }
  }
 }

 proc accessable { obj1 obj2 } {
  if { [ object $obj1 where ] == [ object $obj2 where ] } { return 1}
  if { [ object $obj1 hasobjs $obj2 ] } { return 1 }
  return 0
 }

 proc object { name func args } {
  global ObjDesc ObjAttrs ObjContents ObjFuncs ObjExits ObjWears ObjLoc ObjArticle ObjState

  if { "$name" == "" } { return }
  switch -exact -- $func {
     new      { set ObjDesc($name) ""
                set ObjAttrs($name) ""
                set ObjContents($name) ""
                set ObjFuncs($name) ""
                set ObjExits($name) ""
                set ObjLoc($name) ""
                set ObjState($name) "in"
                set ObjArticle($name) "a"
                foreach { i j } $args {
                  switch -exact -- $i {
                    -desc     { eval object $name setdesc $j }
                    -attrs    { eval object $name addattrs $j }
                    -contents { eval object $name insobjs $j }
                    -where    { eval object $j insobjs $name ; set ObjLoc($name) $j }
                    -exits    { eval object $name addexits $j }
                    -don      { eval set ObjWears($name) $j }
                    -define   { eval object $name define $j }
                    -article  { set ObjArticle($name) $j }
                    -state    { set ObjState($name) $j }
                  }
                }
                set body "eval object $name \$args"
                proc $name: "args" "$body"
              }
    setstate  { eval set ObjState($name) $args    }
    state     { puts -nonewline $ObjState($name)  }
    article   { if ![string equal $ObjArticle($name) "" ] {
                  puts -nonewline "$ObjArticle($name) "
                }
              }
    setdesc   { set ObjDesc($name) "$args"        }
    describe  { if ![string equal $ObjArticle($name) "" ] {
                  puts -nonewline "$ObjArticle($name) $ObjDesc($name)"
                } else {
                  puts -nonewline "$ObjDesc($name)"
                }
              }
    addexits  { eval ins ObjExits($name) $args    }
    remexits  { eval rem ObjExits($name) $args    }
    getexits  { return $ObjExits($name)           }
    hasexits  { foreach i $args { if { [ lsearch $ObjExits($name) $i ]  == -1 } { return 0 } }
                return 1
              }
    addattrs  { eval ins ObjAttrs($name) $args
                foreach i $args { if { "[ info procs $i ]" != "" } { $i $name $ObjLoc($name) } }
              }
    remattrs  { eval rem ObjAttrs($name) $args
                foreach i $args { if { "[ info procs $i ]" != "" } { $i $name $ObjLoc($name) } }
              }
    getattrs  { return ObjAttrs($name)            }
    hasattrs  { foreach i $args { if { [ lsearch $ObjAttrs($name) $i ]  == -1 } { return 0 } }
                return 1
              }
    insobjs   { eval ins ObjContents($name) $args }
    remobjs   { eval rem ObjContents($name) $args }
    getobjs   { return $ObjContents($name)        }
    hasobjs   { foreach i $args { if { [ lsearch $ObjContents($name) $i ]  == -1 } { return 0 } }
                return 1
              }
    where     { return $ObjLoc($name)             }
    take      { if { [ object $name do take $args ] } { return }
                foreach i $args {
                  if { [ object $ObjLoc($name) hasobjs $i ] } {
                    if { [ object $i hasattrs !move ] } {
                      puts "You can't take the $i."
                    } else {
                      object $ObjLoc($name) remobjs $i
                      object $name insobjs $i
                      puts "$i taken"
                    }
                  } else {
                    if { [ object $i hasattrs place ] } {
                      object $name moveto $i
                    } else {
                      puts "I see no $i here."  
                    }
                  }
                }
              }
    drop      { if { [ object $name do drop $args ] } { return }
                foreach i $args {
                  if { [ object $name hasobjs $i ] } {
                    if { [ object $i hasattrs !move ] } {
                      puts "You can't drop the $i."
                    } else {
                      object $name remobjs $i
                      object $ObjLoc($name) insobjs $i
                      puts "$i dropped."
                    }
                  } else {
                    puts "I see no $i here."  
                  }
                }
              }
    don       { if { [ object $name do don $args ] } { return }
                foreach i $args {
                 if { [ object $ObjLoc($name) hasobjs $i ] } { object $name take $i }
                 if { [ object $name hasobjs $i ] } {
                    if { [ object $i hasattrs wear ] } {
                      ins ObjWears($name) $i
                      rem ObjContents($name) $i
                      puts "You are now wearing a $i."
                    } else {
                      puts "Okay, how, exactly, do I wear a $i?"
                    }
                 } else {
                   puts "I don't see a $i here."
                 }
                }
              }
    doff      { if { [ object $name do doff $args ] } { return }
                foreach i $args {
                  if { [ object $name has_on $i ] } {
                    rem ObjWears($name) $i
                    ins ObjContents($name) $i
                    puts "$i removed."
                  } else {
                    puts "You aren't wearing a $i."
                  }
                }
              }
    wearing   { return $ObjWears($name) }
    has_on    { foreach i $args { if { [ lsearch $ObjWears($name) $i ] != -1 } { return 1 } }
                return 0
              }
    moveto    { if { [ object $name do moveto $args ] } { return }
                set exit [ lindex $args 0 ]
                if { [ object $ObjLoc($name) hasexits $exit ] } {
                  object $ObjLoc($name) remobjs $name
                  set ObjLoc($name) $exit
                  object $ObjLoc($name) insobjs $name
                } elseif { [ object $ObjLoc($name) hasobjs $exit ] } {
                  object $name enter $exit
                } else {
                  return
                }
                foreach i [ object [ object $name where ] getattrs ] {
                  if { "[ info procs $i ]" != "" } {
                    $i $name $ObjLoc($name)
                  }
                }
              }
    enter     { if { [ object $name do enter $args ] } { return }
                set what [ lindex $args 0 ]
                if { [ object $name hasobjs $what ] } {
                  puts "How can you enter that?"
                  return
                }
                if { [ object $ObjLoc($name) hasobjs $what ] } {
                  if { [ object $what hasattrs place ] } {
                    object $ObjLoc($name) remobjs $name
                    set ObjLoc($name) $what
                    object $ObjLoc($name) insobjs $name
                  } else {
                    puts -nonewline "How, exactly, do you climb into "
                    object $what describe
                    puts "?"
                  }
                } else {
                  return "I see no $what here."
                }
              }
    define    { foreach { fname body } $args {
                  set fname [ string trim $fname ]
                  eval ins ObjFuncs($name) $fname
                  uplevel #0 proc $name-$fname "args" \{$body\}
                }
              }
    do        { set fname [lindex $args 0]
                set args [ lrange $args 1 end ]
                if { "[ info procs $name-$fname ]" != "" } {
                  return [ eval uplevel #0 $name-$fname $args ]
                } else {
                  return 0
                }
              }
    default   { puts "message $func $args not understood by $name" ; exit }
  }
 }

########################################################################################
# End of Engine.  Below is demo adventure.

 proc vacuum { args } {
  if { [ me: has_on spacesuit ] } { return }
  puts "There is vacuum here - and you are not wearing a spacesuit.  You die."
  exit
 }

 object bridge new -desc {bridge of the ship} -attrs {place} -exits {passageway} -article the
 object passageway new -desc {small room aft of the bridge} -attrs {place} -exits {bridge airlock}
 object outside new -desc {outside the ship} -attrs {place} -article ""
 object airlock new -desc {airlock} -exits {passageway} -has {switch} -attrs {place} -article an
 object sphere new -desc {silvery sphere} -where {bridge} -attrs {place !move} -exits {bridge}

 object laser new -desc {laser} -where {bridge} -define {
  use {
    if { ! [ accessable me laser ] } {
      "You can't use it if you aren't holding it."
      return 0
    }
    set location [ me: where ]
    foreach item $args {
      if { "$item" == "" } { return }
      if { "$item" == "laser"} {
        puts "The laser cannot shoot itself."
        return 0
      }
      if { ("$item" != "on") && ("$item" != "at") } {
        if { [ me: hasobjs $item ] } {
          me: drop $item
        }
        if { [ [ me: where ]: hasobjs $item ] } {
          puts "$item destroyed"
          $location: remobjs $item
          return 1
        } else {
          puts "I see no $item here."
          return 0
        }
      }
    }
  }
 }

 set airlock_switch 0
 object switch new -desc {switch} -attrs {!move} -where {airlock} -define {
  use {
    global airlock_switch
    if { ! [ accessable me switch ] } {
      puts "You can't use the switch if you aren't near it."
      return 0
    }
    puts "The airlock cycles."
    if { $airlock_switch } {
      puts "The air rushes in."
      set airlock_switch 0
      passageway: addexits airlock
      outside: remexits airlock
      airlock: addexits passageway
      airlock: remexits outside
      airlock: remattrs vacuum
    } else {
      puts "The air rushes out."
      set airlock_switch 1
      passageway: remexits airlock
      outside: addexits airlock
      airlock: remexits passageway
      airlock: addexits outside
      airlock: addattrs vacuum
    }
    look
    return 1
  }
 }

 object jumper new -desc {ship's jumper} -attrs {wear}

 object spacesuit new -desc {standard-issue spacesuit} -attrs wear -where airlock

 object me new -where bridge -don jumper -define {
  moveto {
    if { "$args" == "outside" } {
      me: setstate ""
    } elseif { ("[ me: where ]" == "outside") && ("$args" == "airlock") } {
      me: setstate " in"
    }
    return 0
  }
 }

####################################################################

 proc look { } {
  puts ""
  puts -nonewline "You are "
  me: state
  [ me: where ]: describe
  puts -nonewline "."
  set inv [ [ me: where ]: getobjs ] 
  set me [ lsearch $inv me ]
  set inv [ lreplace $inv $me $me ] 
  set len [ llength $inv ]
  if { $len > 0 } {
    puts -nonewline "  There is"
    set first 1
    incr len -1
    for { set i 0 } { $i <= $len } { incr i } {
      set curitem [ lindex $inv $i ]
      if { !$first } { 
        if { "$i" == "$len" } {
          puts -nonewline " and"
        } else {
          puts -nonewline ", "
        }
      }
      set first 0
      $curitem: describe
    }
    puts -nonewline " here."
  }
  set exits [ [ me: where ]: getexits ]
  set numexits [ llength $exits ]
  puts -nonewline "  "
  if { $numexits == 0 } { puts -nonewline "There are no exits." ; return
  } elseif { $numexits == 1 } { puts -nonewline "There is an exit to the "
  } else { puts -nonewline "There are exits to the " }
  set first 1
  foreach i $exits {
    if { !$first } { puts -nonewline ", " }
    set first 0
    puts -nonewline "$i"
  }
  puts "."
 }

 proc go { where } {
  me: moveto $where
  look
 }

 proc enter { where } {
  me: enter $where
  look
 }

 proc take { args } {
  me: take $args
 }

 proc drop { args } {
  me: drop $args
 }

 proc inventory { } {
  set inv [ me: getobjs ]
  if { "$inv" != "" } {
    puts -nonewline "You are carrying: "
    set first 1
    foreach i $inv {
      if { !$first } { puts -nonewline ", " }
      set first 0
      $i: describe
    }
    puts "."
  } else {
    puts "You are carrying nothing."
  }
  set inv [ me: wearing ]
  if { "$inv" != "" } {
    puts -nonewline "You are wearing "
    set first 1
    foreach i $inv {
      if { !$first } { puts -nonewline ", " }
      set first 0
      $i: describe
    }
    puts "."
  } else {
    puts "You are wearing nothing."
  }
 }

 proc examine { what } {
  $what: describe
 }

 # The vocabulary
 proc use { what args } {
  if { ![ $what: do use $args ] } {
    puts "I see no point to that."
  }
 }

 proc don { what args } {
  me: don $what
 }

 proc doff { what args } {
  me: doff $what
 }

 proc bye { args } {
  exit
 }

 look
 while 1 {
  puts -nonewline ">>> " ; flush stdout
  if [ catch [ gets stdin ] err ] {
    puts "I didn't understand that."
  }
 }