[QUIZ] MUD Client (#45)

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

···

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Sy has been searching for a Ruby scriptable MUD client via Ruby Talk and so far,
there hasn't been many helpful answers posted. Let's generate some.

This week's Ruby Quiz is to create a basic MUD client that must be scriptable in
the Ruby programming language. That's pretty vague, so let me see if I can
answer the questions I'm sure at least some of you still have.

What is a MUD?

MUD stands for Multi-User Dungeon/Dimension, depending on who you ask. They are
old text-based game servers with many role playing game elements to them.
Here's a fictional example of MUD playing:

  > look
  Sacred Grove
  
  You are standing in the legendary resting place of The Dagger in The Stone.
  Many have tried to free the mystical blade before, but none were worthy.
  
  You can see the Castle of Evil to the west.
  
  What's here:
    The Dagger in The Stone
  
  >look dagger
  The all-powerful blade begs to be stolen!
  
  >get dagger
  You take the dagger. (Well, that was easy, wasn't it?)
  
  >equip dagger
  You are now the most dangerous warrior in the kingdom!
  
  >west
  The Gates of Castle of Evil
  
  A very big, very black, very evil castle.
  
  You can enter the castle to the north, or return to the Sacred Grove in
  the east.
  
  What's here:
    Grog, Castle Guardian
  
  >north
  Grog move's in front of the gate and laughs mercilessly at you.
  
  >kill grog
  You slice Grog with the mighty dagger for 5 points of damage.
  Grog chews off your left ear for 15 points of damage.
  
  You swing at Grog and miss.
  Grog breaks the little finger on your right hand for 10 points of damage.
  
  ...

If you would like to find some MUDs to play on, try a listing service like:

  http://www.mudconnect.com/

That siteh also has a MUD FAQ that probably answers a lot more questions than
this short introduction:

  http://www.mudconnect.com/mudfaq/index.html

What is a MUD client?

While there are some advanced MUD protocols, the truth is that most of them talk
to any Telnet client just fine. We will focus on that for this quiz, to keep
things simple. Our goal is to create a Ruby scriptable Telnet client, more or
less.

What would we want to script?

Different people would have different requests I'm sure, but I'll give a few
examples. One idea is that you may want your client to recognize certain
commands commands and expand them into many MUD actions:

  > prep for battle
  
  > equip Vorpal Sword
  You ready your weapon of choice.
  
  > equip Diamond Armor
  You protect yourself and still manage to look good.
  
  > wear Ring of Invisibility
  Where did you go?
  
  ...

Another interesting possibility is to have functionality where you can execute
code when certain output is seen from the server. Here's an example:

  > kill grog
  You slash Grog with the dagger for 2 points of damage.
  Grog disarms you!
  
  >get dagger
  You take up the dagger.
  
  You punch Grog in the mouth for 2 points of damage.
  Grog sings. You take 25 points of damage to the ear drums.
  
  >equip dagger
  You're now armed and dangerous.
  
  You slash Grog with the dagger for 5 points of damage.
  Grog slugs you for 12 points of damage.
  
  ...

Here the idea is that the client noticed you were disarmed and automatically
retrieved and equipped your weapon. This saved you from having to quickly type
these commands in the middle of combat.

There are many other possibilities for scripting, but that gives us a starting
point.

This is so bizarre, I've been working on a telnet emulator using Myriad for the last week so I can script some green screen applications at work. Who'd have thought I could post a ruby quiz solution too. :slight_smile:

<snip>

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Sy has been searching for a Ruby scriptable MUD client via Ruby Talk and so far,
there hasn't been many helpful answers posted. Let's generate some.

This week's Ruby Quiz is to create a basic MUD client that must be scriptable in
the Ruby programming language. That's pretty vague, so let me see if I can
answer the questions I'm sure at least some of you still have.

<snip>

···

On Fri, 2 Sep 2005 21:27:45 +0900 Ruby Quiz <james@grayproductions.net> wrote:

A very basic quick solution I thew together. This only works on Unix (requires stty). If you want to play with scripting it, run once, then edit ~/.mud_client_rc:

#!/usr/local/bin/ruby -w

require "thread"
require "socket"
require "io/wait"

def show_prompt
     puts "\r\n"
     print "#{$prompt} #{$output_buffer}"
     $stdout.flush
end

$input_buffer = Queue.new
$output_buffer = String.new

$end_session = false
$prompt = ">"
$reader = lambda { |line| $input_buffer << line.strip }
$writer = lambda do |buffer|
     $server.puts "#{buffer}\r\n"
     buffer.replace("")
end

$server = TCPSocket.new(ARGV.shift || "localhost", ARGV.shift || 61676)

config = File.join(ENV["HOME"], ".mud_client_rc")
if File.exists? config
     eval(File.read(config))
else
     File.open(config, "w") { |file| file.puts(<<'END_CONFIG') }
# Place any code you would would like to execute inside the Ruby MUD client at
# start-up, in this file. This file is expected to be valid Ruby syntax.

# Set $prompt to whatever you like as long as it supports to_s().

# You can set $end_session = true to exit the program at any time.

# $reader and $writer hold lambdas that are passes the line read from the
# server and the line read from the user, respectively.

···

#
# The default $reader is:
# lambda { |line| $input_buffer << line.strip }
#
# The default $writer is:
# lambda do |buffer|
# $server.puts "#{buffer}\r\n"
# buffer.replace("")
# end

END_CONFIG
end

Thread.new($server) do |socket|
     while line = socket.gets
         $reader[line]
     end

     puts "Connection closed."
     exit
end

$terminal_state = `stty -g`
system "stty raw -echo"

show_prompt

until $end_session
     if $stdin.ready?
         character = $stdin.getc
         case character
         when ?\C-c
             break
         when ?\r, ?\n
             $writer[$output_buffer]

             show_prompt
         else
             $output_buffer << character

             print character.chr
             $stdout.flush
         end
     end

     break if $end_session

     unless $input_buffer.empty?
         puts "\r\n"
         puts "#{$input_buffer.shift}\r\n" until $input_buffer.empty?

         show_prompt
     end
end

puts "\r\n"
$server.close
END { system "stty #{$terminal_state}" }

__EMD__

While I was making this, I wanted a stupid simple server to play with, so I rolled one up:

#!/usr/local/bin/ruby -w

require "gserver"

class ChattyServer < GServer
     def initialize( port = 61676, *args )
         super(port, *args)
     end

     def serve( io )
         messages = Array[ "Hello there.",
                           "Welcome to ChattyServer.",
                           "Isn't this a lovely conversation we're having?",
                           "Is this \e[31mred\e[0m?" ]

         loop do
             io.puts messages[rand(messages.size)]
             sleep 5
         end
     end
end

server = ChattyServer.new
server.start
server.join

__EMD__

James Edward Gray II

Great. We need some clients to go with all the cool MUDs posted to Ruby Talk last week.

I coded up a solution myself today and was glad to see that it wasn't too tricky. I hope lot's of people can join in on the fun.

James Edward Gray II

···

On Sep 2, 2005, at 6:44 PM, Zed A. Shaw wrote:

This is so bizarre, I've been working on a telnet emulator using Myriad for the last week so I can script some green screen applications at work. Who'd have thought I could post a ruby quiz solution too. :slight_smile:

James Edward Gray II wrote:

A very basic quick solution I thew together. This only works on Unix
(requires stty). If you want to play with scripting it, run once,
then edit ~/.mud_client_rc:

Well, since I have no unix available, I can't try this myself. But I have
a couple questions.

require "io/wait"

What's this do for you? (My ruby install doesn't seem to have it...)

Also, does this client handle lines coming from the mud that don't
end with "\n"? (AKA, can you log into rom.org:9000 and see the login
prompt?)

(Ironically enough, though I started working on a client before this quiz
was mentioned, I won't have time to get it to releasable levels until next
week...)

-Morgan

···

--
No virus found in this outgoing message.
Checked by AVG Anti-Virus.
Version: 7.0.344 / Virus Database: 267.10.18/89 - Release Date: 09/02/2005

James Edward Gray II wrote:

A very basic quick solution I thew together.

And another question. You use a Queue in this.
Do you actually need to do that?

have one thread pushing items onto an array, and
another thread using shift to pull them off (and ONLY
one thread doing each), you'll still get all your data in
proper order without having Thread.critical calls slowing
down your application. Since I'm not sure how expensive
all the *other* stuff my client will be doing will be, this
is important to me. (That, and it's sort of an esthetic issue
with me - if I can get away with not using Thread.critical,
that's what I want to do.)

-Morgan

···

From my understanding (and a little testing), if you

--
No virus found in this outgoing message.
Checked by AVG Anti-Virus.
Version: 7.0.344 / Virus Database: 267.10.18/91 - Release Date: 09/06/2005

James Edward Gray II wrote:

A very basic quick solution I thew together. This only works on Unix
(requires stty). If you want to play with scripting it, run once,
then edit ~/.mud_client_rc:

Well, since I have no unix available, I can't try this myself. But I have
a couple questions.

I'll be happy to answer them.

require "io/wait"

What's this do for you? (My ruby install doesn't seem to have it...)

I was under the impression that this is a standard library, but it may just be a Unix tool.

It adds the method ready?() to IO objects, among other things. I use this to see if STDIN has input waiting from the user. I only read STDIN character by character with a ready?() check each time before I do, so I can keep watching the MUD for output.

Also, does this client handle lines coming from the mud that don't
end with "\n"? (AKA, can you log into rom.org:9000 and see the login
prompt?)

No, it doesn't and that's quite a weakness. Good point. That's something that would need to be fixed to make this a serious option.

(Ironically enough, though I started working on a client before this quiz
was mentioned, I won't have time to get it to releasable levels until next
week...)

Post it when you're done, if you would like. I'm sure we would all love to see it.

James Edward Gray II

···

On Sep 5, 2005, at 11:07 PM, Morgan wrote:

James Edward Gray II wrote:

A very basic quick solution I thew together.

And another question. You use a Queue in this.
Do you actually need to do that?

When I start threading, I instantly activate my thread-safe paranoia. I think that's a good habit to build really. Threads are tricky, so I believe it's important to play it safe, whenever possible. So to answer your question, yes, I need the Queue to feel comfortable. Does Ruby need it? I'm not sure.

From my understanding (and a little testing), if you
have one thread pushing items onto an array, and
another thread using shift to pull them off (and ONLY
one thread doing each), you'll still get all your data in
proper order without having Thread.critical calls slowing
down your application.

It wouldn't surprise me at all if you're right, but you're basically counting on Ruby internals here and I have a hard time seeing that as a good idea. What if the scripting interface launches a Thread to scan the Queue for whatever reason? Your assumptions have now been violated and who knows what's going to happen. The user made that choice, so you were never consulted.

Since I'm not sure how expensive
all the *other* stuff my client will be doing will be, this
is important to me. (That, and it's sort of an esthetic issue
with me - if I can get away with not using Thread.critical,
that's what I want to do.)

A client should have little trouble keeping up with a user and the average MUD on modern hardware, even when taking actions based on what it reads. I expect it to be doing nothing a lot more often than not. When I was in high school I played on a BBS (similar to MUDs) on a 66 Mhz machine and it kept up with me okay. Network IO was the slow part then and still is now, so we should have plenty of processing time.

More importantly, when it starts getting slow, look for a way to speed it up. Until then, why remove safety features for a problem that doesn't exist? Remember, premature optimization is the root of all evil.

James Edward Gray II

···

On Sep 7, 2005, at 10:33 PM, Morgan wrote:

> From my understanding (and a little testing), if you
> have one thread pushing items onto an array, and
> another thread using shift to pull them off (and ONLY
> one thread doing each), you'll still get all your data in
> proper order without having Thread.critical calls slowing
> down your application.

It wouldn't surprise me at all if you're right, but you're basically
counting on Ruby internals here and I have a hard time seeing that as
a good idea. What if the scripting interface launches a Thread to
scan the Queue for whatever reason? Your assumptions have now been
violated and who knows what's going to happen. The user made that
choice, so you were never consulted.

Also, what about when Ruby switches to a bytecode-based VM?
The granularity between context switches may change a lot.

Additionally, in the "Premature optimization is the root of
all evil" vein...

$ time ruby -e '1_000_000.times { Thread.critical = true; Thread.critical = false }'

real 0m1.791s
user 0m0.030s
sys 0m0.000s

Less than two seconds on an old 1.3GHz Athlon system. So it
seems fair to question the perception that Thread.critical
results in application slow-down.

Especially given:

$ time ruby -e 'class Foo; class << self; attr_accessor :spleen; end; end; \
1_000_000.times { Foo.spleen = true; Foo.spleen = false }'

real 0m2.143s
user 0m0.010s
sys 0m0.010s

It appears Thread.critical is even faster than calling a
"normal" ruby accessor method.

Regards,

Bill

···

From: "James Edward Gray II" <james@grayproductions.net>

On Sep 7, 2005, at 10:33 PM, Morgan wrote: