Law-Mud
文件大小: unknow
源码售价: 5 个金币 积分规则     积分充值
资源说明:basic exploration, classes, methods in coding a MUD
Law Mud
=======

Last November, O was gracious enough to share some of his refined wisdom with me regarding 

1. idiomatic coding syntax
2. separating a mega file into separate files
3. "meta" programming.

I have been late in posting this to github and Tugboat, per his suggestion, my apologies, but here they are, somewhat modified from gmail formatting, etc.  Thanks again, O, and this is good stuff, yo.  Check it out.

### Idiomatic Code 

So first thing I would suggest are some simple cleanup things to make the code
more Idiomatic.

First, no need to say "mlocation" or "plocation".  What you're doing there is
NameSpacing "location" with a "p" or an "m" to show it's a monster's location
or a player's location.  But the thing is is the class "Monster" or "Player" is
already doing that for you.  Just call it "location" and let the Class do it's
job of namespacing.   Otherwise you're repeating yourself when you say
"monster.mlocation" and "player.plocation".  Same with mx, px, minventory,
etc...

Second.  Organizing your files.  Normally people store their classes in one per
file.  This makes it easier to keep track of things.  Are you using textmate?
If you open the folder instead of the file "mate ." you will see a side bar
with all the files and can pop between them easily.  Anyhow doing this is a
little tricky to setup because of load paths.

The naive way to do it would be to make "item.rb" and "monster.rb" and put them
in the same folder.  Then you could 
 
```ruby
require "./monster"
require "./item"
```

at the top of the file.  The "./" means "in this folder".  The problem lies if
you run the program from a different folder, then "./" is no longer the correct
folder.  So here's the trick.  In ruby there is a special constant \_\_FILE\_\_
which is the file the current code is in.  So... using this you can then find
files relative to this file.  So lets say that I want to go from the folder
that this file is in and then into a sub-directory named "lib" and require a
file called "foobar.rb".

This is the file the code is in

```ruby
__FILE__
```

This is the directory that this ruby file is in

```ruby
File.dirname(__FILE__)
```

This is the sub-folder lib below the directory that holds the file that the ruby code is in

```ruby
File.dirname(__FILE__) + "/lib"
```

So... In this case I could

```ruby
require File.dirname(__FILE__) + "/lib/monster"
require File.dirname(__FILE__) + "/lib/item"
```

Of course another option would be to add that folder to the load path.  In
"irb" type $LOAD_PATH and you'll see its an array of directories that Ruby
looks for things to require.  An idiomatic way to do this would be.

```ruby
$LOAD_PATH.unshift File.dirname(__FILE__) + "/lib/"
require "monster"
require "item"
```

Also you will probably make a class that handles the basic game server.  You
often end up with a VERY short file which requires everything and starts things
and then all the code is organized into small single purpose files.  

```ruby
#!/usr/bin/env ruby

$LOAD_PATH.unshift File.dirname(__FILE__) + "/lib/"

require "monster"
require "item"
require "lawmud"

LawMud.start
```

And that's what the entire "law\_mud" executable would look like - assuming it
had all the other files organized in sub folders.  I forgot the mention the
"#!" or "Hash Bang" at the top is a unix trick to turn a program file into an
executable.  Once you do that and "chmod +x" the file you can execute it like
any other unix program without having explicitly evoke "ruby".

Anyhow - all that is just to help you organize in an idiomatic way and only
becomes important as the code base gets bigger and bigger and harder to manage.

> [ *Geoff* ] one thing I will mention, from a purely ignunt nub perspective,
> is that once you load the classes in their separate files, you have to make
> sure that you include the initializations and instances of those class objects
> in the master file, NOT the file that determines the classes; otherwise, the
> instances will be locked up locally in their class definition files instead of
> available for runtime

Next lets look at this

```ruby
monsterarray << growler = Monster.new("Growler", 0, 1, 3, "a booger", "a green fish")
```

First... clever use of \<\< and = ... I approve. =)

Next is when looking at that "new" I see a bunch of numbers and terms and its
not obvious what I'm looking at.  Imagine what it will look like when you add
mana and vitality, and strength, and position and age and... - its going to get
harry...  here's what I recommend.  Peel out most or all of those params and
make them an "options array".  This is another idiomatic way to handle function
calls with lots of options.  Check this out

```ruby
class Monster
  attr_accessor :name, :x, :y, :health, :inventory

  def initialize(name, options = {})
    @name  = name
    @x = options[:x] || 0
    @y = options[:y] || 0
    @health = options[:health] || 100
    @inventory = options[:inventory] || []
  end
end

monsterarray << growler = Monster.new("Growler", :x => 2, :y => 1, :inventory => [ "a booger", "a green fish"])
```

So now when you read the Monster.new you know exactly what all those numbers
are... you know that 2 is the x coord and 1 is the y coord and you don't have
to check the initialize to know what is what.  You can also not bother passing
an option and have it default.  I didn't bother with health but the monster's
health will default to 100 because that's the default.  If I add a "mana"
option later I don't have to go back and add mana to monsters who don't have
any.  Much much more flexible.  If you want to make a monster at location 0,0
with 100 health and no inventory you can just do a "Monster.new("Growler")"
with no other options...

If you want to make Big Important variables that can be "seen" from inside of
lots of functions... like monsterarray I would recommend making them constants.
monsterarray is a perfect candidate. Actually so would growler...

```ruby
Monsters = []
Monsters << Growler = Monster.new("Grower", ..)
```

Now you can "see" Monsters and Growler from inside other functions.  This can
come in handy.  You want to do as few of these as possible since they clutter
the scope but a few are ok for top-down organization of things.  I'll show you
how to cut down on them in a latter email since I don't want to smoke your
brain all at once.

Next up is you could manage that giant list of monsters automatically.
Consider this...

```ruby
Monsters = []

class Monster
    attr_accessor :name, :x, :y, :health, :inventory

    def initialize(name, options = {})
      @name      = name
      @x         = options[:x] || 0
      @y         = options[:y] || 0
      @health    = options[:health] || 100
      @inventory = options[:inventory] || []
      Monsters << self
    end
end

Growler = Monster.new("Growler", :x => 2, :y => 1, :inventory => [ "a booger", "a green fish"])
```

See what I did there at the end of initialize?  Now the monsters list is
self-managing...  "self" references the current object.

Ok so -- the attack function ... if you want to loop forever - you can do a
"while true" instead of "while 2 == 2" - same thing basically but other coders
will know what you mean with while true.  But really - you don't want to loop
forever... you want to loop until one of the two are dead.  So here's an idea.
Give monster and player a function called "alive?" and returns true if health >
0 or maybe a "dead?".

```ruby
class Monster
    def alive?
      health > 0
    end

    def dead?
      not alive?
    end
end

def attach(player, monster)
   while player.alive? and monster.alive?
      ...
   end
end
```

or

```ruby
def attach(player, monster)
   until.player.dead? or monster.dead?
     ....
   end
end
```

Some things to meditate on...

```ruby
Rooms = []

Rooms << Room.new "The Classroom", ...

class Player
  def room
    Rooms.detect { |r| room.location == location }
  end
end
```

suddenly roominventory is easy... its just player.room.inventory

A ton more cool ideas but I'll stop now b/c I'm sure your brain is quite full.
Enjoy. =)

"So what is this 'meta programming' business?
---------------------------------------------

> [ *Geoff* ] it doesn't seem to be a language, in an all-or-nothing sense ("CODE META OR GO
> HOME!"); it seems to be more a collection of techniques that help compress code
> and designs, which would make maintaining it that much easier, since there's
> less of it to review.  and might enhance speed.  slightly.

> but as far as the overall desirability of learning metaprogramming, as in the
> get-a-book, get doctrinal, get religion, etc ... ... i mean, i'm game, i'm
> down, i LOVE ME THE CODING ... (what little I know), and I'm all about trying
> to get better ... but out of curiosity ... is learning meta the necessary,
> required, doctrinal "next step," or is it more a question of
> picking-and-choosing techniques which serve you as you need them ... ?"

### O responded: 

I wouldn't stress too much about it.  Learn ruby.  Some of the code will be
meta.  Some will not. Truth be told - there's not a lot of call to
meta-programming in newbie level stuff.  I'll look for opportunities in LawMud
just so you can play with it or cut your teeth on it.

Since you asked... I think the set of tools most people call "meta programming"
are the following:

### The magical 'method_missing':

```ruby
class Echo
  def method_missing(method, *args)
    puts "You called method #{method.inspect} with arguments #{args.inspect}"
  end
end

e = Echo.new
e.foo "bar", "baz"
```

Normally calling a method that does not exist on an object will get you a nasty
and much feared "NoMethodError".  Now it will print 'You called method :foo
with arguments ["bar","baz"]'

This allows you to make an object behave as if it has methods on it you never
defined.  All kinds of fun there.

### The magical '\_\_send\_\_':

Ok - this is kind of the opposite of method\_missing.  method\_missing lets us
say "if someone calls a method give it to me as a symbol and an array of
arguments.  \_\_send\_\_ says... "if I have a symbol named matching a method name
and some arguments I want to call that method.  Or basically

```ruby
player.shout "hello", :volume => :loud
```

and

```ruby
player.__send__ :shout, "hello", :volume => :loud 
```

are exactly the same.

Ok - so big effing deal.  How do we put these together...  Ok lets say I have a
big complex program and something really strange is happening to my player
object.  I suspect someone is calling the wrong methods or maybe calling them
too many times but I'm not sure.  I really want it print out all the methods
being call and with what arguments so I can watch whats happening and see.  So
I make a BigBrother class that watches his every move.

```ruby
class BigBrother
  def initialize(victim)
    @victim = victim
  end

  def method_missing(method, *args)
    puts "#{method.inspect} called with #{args.inspect}"
    @victim.__send__ method, *args
  end
end
```

Now big brother has no methods except for initialize which gives it a victim to
watch and a method\_missing which puts out whats being called and then send's
the exact same method and args on to the victim.  So if I call "foobar" on
BigBrother it will in turn called "foobar" on @victim.  So now somewhere in my
code I have a 

```ruby
Mufferies = Player.new("Mufferies")
```

Now I just replace it with 

```ruby
Mufferies = BigBrother.new(Player.new("Mufferies"))
```

And everything will behave as before but now I will get a message whenever a
method is called of Mufferies.

### The magical 'respond\_to?'

This lets you check an object and determine if it has a method declared by some name.

```ruby
def kill(target)
  if target.respond_to? :die
    puts "#{target.name} dies a horrible death."
    target.die
  else
    puts "You cannot kill that which cannot die!"
end
```

This would make sense where if you tried to kill a coffeepot or a ghost or a
bad idea you would get a funny message - but if it was a Monster or Player it
would kill them.

### Monkey Patching:

This is the practice of opening up an existing class and adding methods to it.
This is generally not a good idea but hey - Ruby lets you do it.  The term was
borrowed from Python which also allows you to do this.  It was normally used
for fixing bugs in existing libraries without having to wait for the maintainer
to fix it for you.  Pythoners called it Guerrilla Patching since it was a
covert/irregular way to fix the bug.  Rubiests being dorks decided they would
call it Monkey Patching.  

My favorite example is what Rails (activesupport) does to the Fixnum class.
Fixnum is the class that basic numbers like 5 or 200 have.  It's something like
this.

```ruby
class Fixnum
  def seconds
    self
  end

  def minutes
    self * 60
  end

  def hours
    self * 60 * 60
  end

  def days
    self * 60 * 60 * 24
  end

  def ago
    Time.now - self
  end

  def from_now
    Time.now + self
  end
end
```

This allows you to write code that looks like this

```ruby
duration = 5.hours
start = 2.days.ago
end = 50.minutes.from_now
```

So evil.  Yet so beautiful.

There are others but I'll stop here.  instance\_eval, class\_eval.  All kinds.  Have fun =)

Note: I'm doing all of this with Ruby 1.9.2.  Some of these things are slighly different in 1.8.7.


本源码包内暂不包含可直接显示的源代码文件,请下载源码包。