⚠️ Warning: This is a draft ⚠️

This means it might contain formatting issues, incorrect code, conceptual problems, or other severe issues.

If you want to help to improve and eventually enable this page, please fork RosettaGit's repository and open a merge request on GitHub.

{{collection|RCRPG}}[[implementation of task::RCRPG| ]] This [[Tcl]] version of [[RCRPG]] was typed and tested on a cellphone, so pardon my brevity.

#!/usr/bin/env tclsh
proc help args {
   return "RosettaCode 3D single user dungeon in Tcl. Type a command:
    e(ast), w(est), n(orth), s(outh), u(p), d(own)
   t(ake) sth|all, drop sth|all
   a(ttack) direction: to break a hole in the wall (needs sledge)
   describe   : get told where you are
   i(nventory) : get told what you have
   For going up, you also need a ladder."}

proc main argv {
   Room 0,0,0 StartRoom sledge
   Room 1,1,5 PrizeRoom {gold gold gold}
   array set ::Self {coords 0,0,0 items {}}
   foreach i {east west north south up down} {
       alias $i = go $i
       alias [string index $i 0] = go $i
   }
   foreach {new old} {a attack i inventory t take} {
       alias $new = $old
   }
   puts [help]
   describe
   while 1 { #-- Read-Eval-Print loop
       puts -nonewline "> "; flush stdout
       catch [gets stdin] res
       if {$res ne ""} {puts $res}
   }
}

proc Room {xyz {name {}} {items {}}} { #-- "constructor"
   if {$name eq ""} {set name R.[incr ::R()]}
   if {![llength $items]} {set items [lpick {sledge {} ladder gold}]}
   array set ::R [list $xyz.name $name $xyz.items $items $xyz.exits {}]
}

proc Inverse where {
   switch -- $where {
      east  {I west}  west  {I east}
      north {I south} south {I north}
      up    {I down}  down  {I up}
      default {error "bad direction $where"}
   }
}

proc Normalize where {
   switch -- $where {
      e {I east} w {I west} n {I north} s {I south}
      u {I up}   d {I down}
      default {I $where}
   }
}

proc attack where {
   if {"sledge" ni $::Self(items)} {return "need sledge to attack!"}
   set where [Normalize $where]
   set xyz $::Self(coords)
   if {$where in $::R($xyz.exits)} {
       puts "No need to attack.."
       return [go $where]
   }
   if {$where eq "up" && "ladder" ni $::R($xyz.items)} {
       return "You can't go up without a ladder."
   }
   lappend ::R($xyz.exits) $where
   go $where 0
   lappend ::R($::Self(coords).exits) [Inverse $where]
   describe
}

proc describe {} {
   set xyz  $::Self(coords)
   set name $::R($xyz.name)
   set items [pretty $::R($xyz.items)]
   puts "You are in $name ($xyz) and see $items."
   if {$name eq "PrizeRoom"} {
       puts "Congratulations - you won!"
       exit
   }
   set exits $::R($xyz.exits)
   if {![llength $exits]} {set exits nowhere}
   puts "There are exits towards: [join $exits {, }]"
   inventory
}

proc drop what {
   set xyz $::Self(coords)
   if {$what eq "all"} {set what $::Self(items)}
   foreach i $what {
       if {$i ni $::Self(items)} {return "You don't carry a $i."}
       lremove ::Self(items)   $i
       lappend ::R($xyz.items) $i
   }
   inventory
}

proc go {where {describe 1}} {
   set where [Normalize $where]
   if {$where ni $::R($::Self(coords).exits)} {
      return "No exit $where, consider an attack."
   }
   if {$where eq "up" && "ladder" ni $::R($::Self(coords).items)} {
       return "You can't go up without a ladder."
   }
   foreach {x y z} [split $::Self(coords) ,] break
   switch -- $where {
       east  {incr x} west  {incr x -1}
       north {incr y} south {incr y -1}
       up    {incr z} down  {incr z -1}
   }
   set xyz $x,$y,$z
   if {![info exists ::R($xyz.name)]} {Room $xyz}
   set ::Self(coords) $xyz
   if {$describe} describe
}

proc inventory {} {
   return "You have [pretty $::Self(items)]."
}

proc name what {
   set ::R($::Self(coords).name) $what
   return "This room is now named $what."
}

proc take what {
   set xyz $::Self(coords)
   if {$what eq "all"} {set what $::R($xyz.items)}
   foreach i $what {
       if {$i ni $::R($xyz.items)} {return "There is no $i here."}
       lremove ::R($xyz.items) $i
       lappend ::Self(items)   $i
   }
   inventory
}

#--- general utilities
proc alias {new = args} {interp alias {} $new {} {*}$args}
proc I     x   {return $x} ;# identity
proc lpick lst {lindex $lst [expr {int(rand()*[llength $lst])}]}
proc lremove {_lst what} {
   upvar 1 $_lst lst
   set pos [lsearch -exact $lst $what]
   set lst [lreplace $lst $pos $pos]
}
proc pretty lst {
   if {![llength $lst]} {return nothing}
   foreach i $lst {lappend tmp "a $i"}
   regsub {(.+),} [join $tmp ", "] {\1, and}
}

main $argv

==Alternative Version== The following version is functionally identical, but uses a setter/getter function "@" to hide away the data representation from most of the code (except in the definition of "@" itself). Examples: @ my coords $x,$y,$z ;#-- modify an "instance variable" set items [@ my items] ;#-- items I carry set items [@ here items] ;#-- items in the current room set items [@ $x,$y,$z items] ;#-- items in the given room lappend [@ my items &] teacup ;#-- returns a reference

proc help args {
    return "RosettaCode 3D single-user dungeon in Tcl. Type a command:
     e(ast), s(outh), n(orth), w(est), u(p), d(own)
     t(ake) something|all, drop something|all
     a(ttack) direction: to break a wall (needs a sledge)
     d(escribe):         get told where you are
     help:               get this message
     i(nventory):        get told what you have
     name something:     give the current room another name
     For going up, you also need a ladder."}

proc main argv {
    Room  0,0,0  StartRoom  sledge
    Room  1,1,5  PrizeRoom  {gold gold gold}
    @ my coords 0,0,0
    @ my items  {}
    foreach i {east west north south up down} {
	alias $i                  = go $i
	alias [string index $i 0] = $i
    }
    foreach {new old} {a attack d describe i inventory t take} {
	alias $new = $old
    }
    puts [help]
    describe
    while 1 {          #-- REPL: Read-Eval-Print Loop
	puts -nonewline "> "; flush stdout
	catch [gets stdin] res
	if {$res ne ""} {puts $res}
    }
}

proc Room {xyz {name {}} {items {}}} { #-- "constructor"
    if {$name eq ""} {set name R.[incr ::ID]}
    if {$items eq {}} {set items [lpick {sledge {} ladder gold}]}
    @ $xyz name  $name
    @ $xyz items $items
    @ $xyz exits {}
}

proc Inverse where {
    switch -- $where {
	east  {I west}  west  {I east}
	north {I south} south {I north}
	up    {I down}  down  {I up}
	default {error "No inverse defined for $where"}
    }
}

proc Normalize where {
    switch -- $where {
	e {I east} w {I west} n {I north} s {I south} u {I up} d {I down}
	default {I $where}
    }
}

proc @ {coords what {value --}} { #-- universal setter/getter
    if {$coords eq "my"} {
	if {$value eq "--"} {return $::Self($what)}
	return [expr {$value eq "&"? "::Self($what)" : [set ::Self($what) $value]}]
    }
    if {$coords eq "here"} {set coords $::Self(coords)}
    if {$value eq "&"}     {return ::R($coords.$what)} ;# reference
    if {$value eq "--"} {
	    set ::R($coords.$what)
    } else {set ::R($coords.$what) $value}
}

#------------------- commands in Afferbeck Lauder
proc attack where {
    set where  [Normalize $where]
    set coords [@ my coords]
    if {$where in [@ $coords exits]} {
	puts   "No need to attack $where, the road is open."
	return [go $where]
    } elseif {"sledge" ni [@ my items]} {
	return "You can't attack without a sledge."
    }
    if {$where eq "up"} {
	if {"ladder" ni [@ $coords items]} {
	    return "You can't go up without a ladder."
	}
    }
    lappend [@ here exits &] $where
    go $where 0 ;#-- describe later
    lappend [@ here exits &] [Inverse $where]
    describe
}

proc describe {} {
    set coords [@ my coords]
    set name   [@ here name]
    puts "You are in $name ($coords). You see [pretty [@ here items]]."
    if {$name eq "PrizeRoom"} {
	puts "Congratulations -- You Won!!!"; exit
    }
    set exits [@ here exits]
    if {![llength $exits]} {set exits nowhere}
    puts "There are exits towards: [join $exits {, }]."
    inventory
}

proc drop what {
    if {$what eq "all"} {set what [@ my items]}
    foreach i $what {
	if {$i ni [@ my items]} {return "You don't have a $i."}
	lremove [@ my items &]   $i
	lappend [@ here items &] $i
    }
    inventory
}

proc go {where {describe 1}} {
    set where  [Normalize $where]
    foreach {x y z} [split [@ my coords] ,] break
    switch -- $where {
	east  {incr x} west  {incr x -1}
	north {incr y} south {incr y -1}
	up    {incr z} down  {incr z -1}
	default {return "usage: go (east|west|north|south|up|down)"}
    }
    set coords $x,$y,$z
    if {$where eq "up" && "ladder" ni [@ here items]} {
	return "You can't go up without a ladder."
    }
    if {$where ni [@ here exits]} {
	return "No exit towards $where, consider an attack..."
    }
    if {[catch {@ $coords name}]} {Room $coords}
    @ my coords $coords
    if {$describe} describe
}

proc inventory {} {return "You have [pretty [@ my items]]."}
proc name what {
    return "This room is now named [@ here name $what]."
}

proc take what {
    if {$what eq "all"} {set what [@ here items]}
    foreach i $what {
	if {$i ni [@ here items]} {return "There is no $i here."}
	lremove [@ here items &] $i
	lappend [@ my items &]   $i
    }
    inventory
}

#----------------------- general utilities
proc alias {new = args} {interp alias {} $new {} {*}$args}
proc I     x            {return $x} ;#-- Identity: simple but useful
proc lpick lst        {lindex $lst [expr {int(rand()*[llength $lst])}]}
proc lremove {_lst what} {
    upvar 1 $_lst lst
    set pos [lsearch -exact $lst $what]
    set lst [lreplace $lst $pos $pos]
}
proc pretty lst {
    if {![llength $lst]} {return nothing}
    foreach i $lst {lappend tmp [expr {$i eq "gold"? $i : "a $i"}]}
    regsub {(.+),} [join $tmp ", "] {\1, and}
}
main $argv