[QUIZ] MUD Client (#45)

R

Ruby Quiz

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:
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!
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
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.
 
Z

Zed A. Shaw

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. :)


On Fri, 2 Sep 2005 21:27:45 +0900

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

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>
 
J

James Edward Gray II

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. :)

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
 
J

James Edward Gray II

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
 
M

Morgan

James said:
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
 
J

James Edward Gray II

James Edward Gray II wrote:



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.
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
 
M

Morgan

James said:
A very basic quick solution I thew together.

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

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. 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
 
J

James Edward Gray II

James Edward Gray II wrote:



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
 
B

Bill Kelly

From: "James Edward Gray II said:
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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top