Ruby revisted

Posted by Ariesroyaal on Sat 05 May 2007 12:42 PM — 34 posts, 129,847 views.

#0
What would it take to get you to revisit Ruby? I am a long time Ruby developer and would love to see this in MUSHClient.

In the last post I saw about this you reported this error:

# --> undefined method `nickgammon' for #<Module:0x1b65038>

You could try defining a the script within a module

module NickTest
def nickgammon
@world.Note("Hello World!")
end
end

If you are willing to check it out again, I'd be willing to hash out any errors you run into.
Australia Forum Administrator #1
I am not sure which version of Ruby I had installed - I got the latest one (1.8.6.2) from this page:

http://arton.hp.infoseek.co.jp/index.html

Namely this file:

http://arton.hp.infoseek.co.jp/ActiveRuby18.msi

However the file ActiveRuby18.msi was what I had last time. The old one was 4.50 Mb, the new one is 14.3 Mb, so obviously there are changes.

Before installing the new version I re-enabled it in MUSHclient and tried a test. Typing this into the command window worked:


/ @world.Note("Hello World!")


(Slash is the scripting prefix).

My earlier test file had this as a test trigger:


def mytrigger (name, line, wildcards)
  @world.Note (name)
  @world.Note (line)
end 


It compiled OK, but reported that there was no script function mytrigger.

I then did what you suggested and added a module to it:


module NickTest
def mytrigger (name, line, wildcards)
  @world.Note (name)
  @world.Note (line)
end 
end


That compiled OK too, but still couldn't find "mytrigger".

I then installed the latest Ruby (1.8.6.2), and then attempting to start MUSHclient resulted in an immediate crash with an access violation.

Amended on Sun 06 May 2007 06:07 AM by Nick Gammon
Australia Forum Administrator #2
I then uninstalled the latest version which crashes, and tried an earlier one (1.6.8.3):

http://arton.hp.infoseek.co.jp/ActiveRuby.msi


That seems to work (in the sense that it doesn't crash the client), and will display the "hello world" in the output window, but still doesn't recognise the "mytrigger" function.

My guess is that it isn't exporting things defined in Ruby to be available to the "outside world" - which is required if MUSHclient is going to call scripts by name.

I don't have a big problem with leaving it enabled, so you can try to experiment with it and get it to work.

Australia Forum Administrator #3
This new version is now available, if you want to play with it:

http://www.gammon.com.au/forum/?id=7863
#4
Nick,

Which ActiveScript DLL are you using?

RScript.dll or GRScript.dll?
#5
after reading up on it, you are probably using the regular RScript.dll. GRScript registers everything on the global level....

-waits impatiently for an update-
#6
sorry.. excited to see it work even on in a small way ;)
Australia Forum Administrator #7
My quick answer is that I don't know. When you install Ruby, it links a DLL with "rubyscript" as the script engine. Thus the only word I needed to add in MUSHclient was "rubyscript" (plus various places where you get a choice of script engines).

I would have to check with RegEdit to see which DLL was actually registered by the Ruby installer.
Australia Forum Administrator #8
OK, I am getting a bit closer. It wasn't particularly obvious that I should have used "GlobalRubyScript" rather than "RubyScript".

Doing that seems to partly fix the problem. Now if I make a trigger it recognises that "mytrigger" exists in the script file, and thus the trigger can be added OK. However now it doesn't recognise the world functions:


Script error
World: SMAUG
Execution of line 3 column 1
Function/Sub: mytrigger called by trigger
Reason: processing trigger ""
undefined method `Note' for nil


This is my script:


def mytrigger (name, line, wildcards)
  @world.Note (name)
  @world.Note (line)
end 


I am presuming now that it needs to be told about the "world." functions somehow.

To recap, with "RubyScript", I could do this:


/ @world.Note ("hi there")


However, function names were not exported. With GlobalRubyScript function names are exported but "world" names are not imported.
#9
world will probably be a global now, not an instance.

Try $world.Note instead of @world.Note.
Australia Forum Administrator #10
If I do this:


/$world.Note ("hi there")


I get this:


Script error
World: SMAUG
Execution of line 2 column 1
Immediate execution
undefined method `Note'
Line in error: 
$world.Note ("hi there")
#11
maybe just use


world.Note might work as it is a local variable, as everything is global it would make sense that it could be just local now.

However it may be $World and not $world?

Lastly there are class variables...

@@world

----
GRScript.dll (progid="GlobalRubyScript") is an engine just fit for Windows Script Host Scheme.
It has no capability to handle multiple IActiveScript interface, but very similar environment to original Ruby.exe. Because the script evaluate always in the top level context, with global name space.
In addition with CScript (not WScript), you can use STDIN/STDOUT.

* Imported Objects are referenced as Global Variable. In Ruby, the global variable starts doller sign, so

sample script:

$WScript.Echo "Hello World !"
----

Australia Forum Administrator #12
None of those variations work, plus when I use GlobalRubyScript, for some strange reason, I can't connect to the world any more.

I get an error "Unable to create TCP/IP socket for "SMAUG", code 10022 (Invalid argument)".

Somehow Ruby is making TCP connections fail. I have to close and re-open MUSHclient before I can connect again.
#13
That is odd. Also the new version of activescript is not crashing for me, maybe a conflict of some sort?
#14
Im getting

Error number: -2147221231
Event: loading scripting engine
Description: Error -2147221231 occurred when loading scripting engine:

ClassFactory cannot supply requested class

Called by:


with the RScript version...
#15
Nick, forget the GRuby implementation. I figured it out. Should have thought of it before really. try

def self.sayhello
@world.note("hello world!")
end
Australia Forum Administrator #16
OK, so that means that the version you have works then? Since it uses "RubyScript" already?

Maybe if you did a few posts showing how to do some general things in Ruby (like some of the posts about Lua for example), that might help others get started. :)
USA #17
I'd love to see if I can get ruby working on MUSHclient through Wine. Granted, this means I'll have to start working on learning ruby again, but it was a fun language while I was using it. Does this mean we will be getting a ruby board here once enough people have posted questions and feedback notes?

Ariesroyaal: Please let us know how you got ruby working. I'm going to have to jump through a few more hoops than you to set it up most likely, but I think it might be worth the extra effort.
#18
I -have- run into a few hangups. Particularly you cannot call methods from within the script. Still trying to figure that one out. Nick, do you have a client with the GRScript on it I can mess with as well? maybe I can get that one to work more predictably.
#19
nevermind, I was probably smoking something. It works. Heres a simple example.

def self.crunchy_bacon
string = "CrUnChy BaCOn"
@world.note(string)
@world.note(string.reverse)
@world.note(string.swapcase)
end

self.crunchy_bacon
#20
a note:

ActiveScript Ruby doesnt seem to run scripts in the same way ruby does. In a normal Ruby environment you can call a method anywhere in a script as long as it is defined someplace in the script. In MUSHClient you will need to define any methods you are going to call from within the script before you call them.
USA #21
That's kind of odd. Is this similar to those old procedural languages with having to define everything top down? It's probably just how ActiveRuby loads the script files. Might want to check around to see if it's just a MUSHclient thing, or if it's a common occurrence.

Also, I notice that you keep using "def self.foo" for functions. Is this necessary, or can you just do "def foo" for local functions?
#22
Because of the ActiveRuby engine you have to define self on all functions. This assigns the method as an instance method to the parent class. Its a slight pain, but worth it to me to be able use Ruby as a scripting language.
#23
Upon further investigation the reason self needs to be called is primarily because

def crunchy_bacon
end

this is an instance method, and is only callable in an actual instance of a parent module (Module). The script is more or less an uninstantiated class so everything needs to be defined as a class method.


There are still hangups... any chance of getting the GRScript version still?
Australia Forum Administrator #24
As I said before, if I use the GlobalRubyScript version, I find I can't connect to the MUD.

I would have thought that the script engine implementation should have worked more smoothly than that.
#25
what does it throw when you try and connect?
#26
also, what how are you defining world when instantiating ruby?
#27
Ruby Finale

Alright so heres what I came up with for ruby scripting. There are various little humps to get over to get a ruby script to behave with MUSHClient, and it is not for the faint of heart. While for my particular scripts I find useful to use Ruby despite the difficulties, I don't suggest using it unless you are wanting to work at it.

heres how it works out. first, MUSHClient evaluates a Ruby script in an uninstantiated class. This means that it does not see instance methods such as:


def hello_world
   @world.note("hello world")
end


for this reason methods that you want your triggers, aliases and whatever else to call from MUSHClient require you to define them like this:


def self.hello_world
   @world.note("hello world")
end


not so hard yet right? heres the tricky part. because it is an uninstantiated class it is executed in a strictly linear fashion. so this wouldn't work.


self.hello_world

def self.hello_world
   @world.note("hello world")
end


for this reason, you need a second subclass for special 'global' methods. Lets call this class Global:


class Global
   def hello_world
      return "hello world"
   end
end


Now make a new instance of the global class:


@global = Global.new 
#make sure you use the @, you want it to be accessible to everything from here on out. you can use $ as well.


now lets make some calls


def self.hello_world
   @world.note(@global.hello_world)
end



so lets see a real world example:


#create  a global version of @world so that my
#core class can use it.

$world = @world

#heres our core class, this is where the real work is done

class Core

   #we need to track balance, so lets set up some balance
   #attributes complete with callbacks when they are 
   #changed! (this is why I LOVE Ruby)

   attr_accessor :balance, :potion_balance, 
      :herb_balance, :all _balance
   #each of the following are called when the particular
   #attribute is assigned
   def balance=(state)
      @balance = state
      evaluate_balance
   end

   def herb_balance=(state)
      @balance = state
      evaluate_balance
   end

   def potion_balance=(state)
      @balance = state
      evaluate_balance
   end

   #dynamically decide the value of full_balance
   def full_balance
       if (potion_balance and balance and 
           herb_balance)
           @full_balance = true
       else
           @full_balance = false
       end
   end

   #this is called everytime a balance is set
   def evaluate_balance
       #do cool balancing stuff here
   end
end

#now we're back in the main class where MUSHClient calls are
#defined
@core = Core.new

def self.set_balance(name, line, wilds)
    #for simplicities sake lets say that the wilds
    #are the actual states the balance is at.
    @core.balance = wilds[0]
    @core.herb_balance = wilds[1]
    @core.potion_balance = wilds[3]
    $world.note(full_balance.to_s) #==> true
end
Amended on Fri 11 May 2007 10:06 PM by Nick Gammon
Australia Forum Administrator #28
Thanks for that - I tidied up the forum tags a bit.

Can you give the recommended format for a list of constants? See this page:

http://www.gammon.com.au/scripts/function.php?action=errors

If you can give an example of how that would look in Ruby (a couple of lines will do), I can make a Ruby version of the constants list.
#29
globals start with $
Australia Forum Administrator #30
So you would write:


$eOK = 0 # No error
$eWorldOpen = 30001 # The world is already open
$eWorldClosed = 30002 # The world is closed, this action cannot be performed 


... and so on?
#31
yep
#32
No globals joy!
#33
As a general rule with ruby, there is always what is called the Default Receiver, everything is an object, or maybe an object context. ActiveRuby doesn't seem to obey this in the strictest sense.

Normally, if you just create a ruby file and type def some_method, those methods are auto added to this kind of global class object. Obviously, through some magic, it isn't happening when you are using ActiveRuby so a better shorcut is to just use:

class << self
  def hello_world
    @world.note('Hello World!')
  end
end

...

hello


This worked for me fine in the latest version.

If I were to take a stab, I would say that some disconnect has happened in the way ruby falls back to the default receiver.

for instance, this works:

class << self
  def hello(friend)
   @world.note(friend)
  end
end

hello @world.inspect

#=> #<WIN32OLEEX:0x1f0ce40>

Meaning world is added to the global class' instance variables, we can access all over the place, so there's no real need for changing it to a global variable.

We cant inspect the global class a bit and see what it has, like:

self.methods.each do |m|
  @world.note(m)
end

self.instance_variables.each do |iv|
  @world.note(iv)
end

@world.note(self.class.to_s) #=> Module aha! Normally this would be: #=> Object

At this point scripting completely stopped working,when I tried to list out the ole_methods etc.

You can list out all global variables like so, this took a lot of time to track down:

$testy = 'x'
Kernel.global_variables.each do |m|
	@world.note(m)
end


You'll notice that $testy will appear in there.

Now, I tried a random function call, WindowCreate, doesn't work, neither windowCreate, windowcreate, window_create etc.

I also tried @world.GetInfo and various cases, it also returns method_missing...

If I try to get the ole_methods from the object it returns:

Immediate execution
fail to GetTypeInfo
    HRESULT error code:0x80004001
      Not implemented


This also happens with ole_method_help etc.

I can't seem to call anything, except note, I am probably just missing some obvious piece of information, I am a little dense sometimes. Can anyone point me in the right direction?

/Jason