[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Text Based RPG in Ruby

Ruairidh Mchardy

11/1/2008 6:38:00 PM

Hi all,

I'm currently coding a text-based RPG in Ruby which is great fun but I'm
having some difficulty in structuring it.

So far I have a main.rb file containing a class for the basic enemy and
then an extension for the enemy of the lowest level (Grunt). To test I
created an instance of Grunt and assigned it an hp of 100.
I then create a var for the assignment of damages (random number
between 1 and 100) and finally test it all out by hard-coding an example
battle. This also works.

My problem is that I feel my code is not clean at all and that the
structure feels inefficient. As I will be extending the game in the
future I want to make sure that every aspect of it is neat and easy to
maintain.

Attached is my script, does anyone have any suggestions?

Thanks!

Attachments:
http://www.ruby-...attachment/28...

--
Posted via http://www.ruby-....

11 Answers

Stephen Ball

11/3/2008 1:57:00 PM

0

On Sat, 01 Nov 2008 14:38:08 -0400, Ruairidh Mchardy
<ruairidh82@gmail.com> wrote:

> Hi all,
>
> I'm currently coding a text-based RPG in Ruby which is great fun but I'm
> having some difficulty in structuring it.
>
> So far I have a main.rb file containing a class for the basic enemy and
> then an extension for the enemy of the lowest level (Grunt). To test I
> created an instance of Grunt and assigned it an hp of 100.
> I then create a var for the assignment of damages (random number
> between 1 and 100) and finally test it all out by hard-coding an example
> battle. This also works.
>
> My problem is that I feel my code is not clean at all and that the
> structure feels inefficient. As I will be extending the game in the
> future I want to make sure that every aspect of it is neat and easy to
> maintain.
>
> Attached is my script, does anyone have any suggestions?
>
> Thanks!
>
> Attachments:
> http://www.ruby-forum.com/attachment/28...
>

Just some quick notes:
- you should have your enemy class track and assign damage, that way you
can have it test the attack parameters against defense and assign whatever
damage is actually dealt, rather than working out the math in the main
program
-- e.g. Player.attack(Grunto)
- the enemy class should also be responsible for reporting defeat and
damage
-- e.g. Grunto.health # => "Grunto is undamaged", Grunto.health # =>
"Grunty is wounded but can still fight!"
-- e.g. if Grunto.dead? blah blah
- right now your weapon is just a string identifier, you'll want that to
be a class that defines its damage range and other properties

You've got a fair bit of work to do yet. Locations, movement, inventory,
NPCs, conversations, etc. Good luck!

-- Stephen

Ruairidh Mchardy

11/3/2008 5:27:00 PM

0

Argh ok having problems with this. My code has just become very messy
because I can't get my head clear on this one.

I'm trying to structure the enemy classes as per your advice and so have
Grunty laid out in a manner similar to this:

class Grunty < Enemy
def initialize(hp,attack, defense, weapon)
super(hp, attack, defense)
@weapon = "AK47"
@hp =100
if Grunto.hp == 0
puts "Grunty is dead!"
else
puts "Grunty is wounded but can still fight!"
end
randy = rand(100)
health = Grunto.hp - randy
puts "Grunty's hp is #{Health}"
end

This throws up no errors and although the hit assignment is poor, it
remains functional.

I then continue to test with an example scenario battle hard coded into
the script:

Grunty = Grunt.new("hp", 300, 300, "Ak47")
puts "Grunty is battling a kitten!"
puts "The kitten attacks Grunty with an AK47 and inflicts #{randy}
points of damage!"
puts "Grunty lost #{randy} health!"
puts "Grunty's hp is #{Health} "
end
end

In theory this should work but get the following errors:

/home/roo/dev/RubyRPG/RubyRPG/lib/main.rb:19:in `initialize':
uninitialized constant Enemy::Grunt::Grunto (NameError)
from /home/roo/dev/RubyRPG/RubyRPG/lib/main.rb:31:in `new'
from /home/roo/dev/RubyRPG/RubyRPG/lib/main.rb:31
from :1
from :1

I'm not too sure why these errors are being thrown up, I assume it's not
liking the way the class has been edited?

Thanks for all the help so far Stephen, it's helped show me the
direction I should be taking!

Kind Regards,

Ruairidh McHardy

Stephen Ball wrote:
> On Sat, 01 Nov 2008 14:38:08 -0400, Ruairidh Mchardy
> <ruairidh82@gmail.com> wrote:
>
>> battle. This also works.
>> Attachments:
>> http://www.ruby-...attachment/28...
>>
>
> Just some quick notes:
> - you should have your enemy class track and assign damage, that way you
> can have it test the attack parameters against defense and assign
> whatever
> damage is actually dealt, rather than working out the math in the main
> program
> -- e.g. Player.attack(Grunto)
> - the enemy class should also be responsible for reporting defeat and
> damage
> -- e.g. Grunto.health # => "Grunto is undamaged", Grunto.health # =>
> "Grunty is wounded but can still fight!"
> -- e.g. if Grunto.dead? blah blah
> - right now your weapon is just a string identifier, you'll want that to
> be a class that defines its damage range and other properties
>
> You've got a fair bit of work to do yet. Locations, movement, inventory,
> NPCs, conversations, etc. Good luck!
>
> -- Stephen


--
Posted via http://www.ruby-....

Hugh Sasse

11/3/2008 5:48:00 PM

0

On Tue, 4 Nov 2008, Ruairidh Mchardy wrote:

> Argh ok having problems with this. My code has just become very messy
> because I can't get my head clear on this one.
>
> I'm trying to structure the enemy classes as per your advice and so have
> Grunty laid out in a manner similar to this:
>
> class Grunty < Enemy
> def initialize(hp,attack, defense, weapon)
> super(hp, attack, defense)
> @weapon = "AK47"
> @hp =100
> if Grunto.hp == 0
> puts "Grunty is dead!"
> else
> puts "Grunty is wounded but can still fight!"
> end
> randy = rand(100)
> health = Grunto.hp - randy
> puts "Grunty's hp is #{Health}"
> end
>
> This throws up no errors and although the hit assignment is poor, it
> remains functional.

Excuse me blundering into this thread, but you may find:

http://blog.jayfields.com/2008/07/ruby-dwemthys-array-using-mo...

and its associated links interesting reading.

HTH
Hugh



Ruairidh Mchardy

11/3/2008 5:58:00 PM

0

Hi there Hugh,

I'd previously taken a look at Dwemthy's array (Poignant Guide to Ruby
right) and it is undoubtedly a nice implementation of a text-based RPG
in Ruby. My problem is that I'm extremely stubborn and even lifting a
line of code makes me feel dirty.

I want this program to be 100% written by me. I know that doesn't stop
me learning from others but I often end up writing code that is far too
similar for my liking!

Also on a slightly embarassing note, my ruby skills are not to the level
where I can easily understand snippets such as:

define_method( :initialize ) do
self.class.traits.each do |k,v|
instance_variable_set("@#{k}", v)

or def self.metaclass; class << self; self; end; end

def self.traits( *arr )
return @traits if arr.empty?

attr_accessor(*arr)

arr.each do |a|
metaclass.instance_eval do
define_method( a ) do |val|
@traits ||= {}
@traits[a] = val

And so my code is far less efficient and rather ugly too :(

Thanks for the link though!

Kind Regards,

Ruairidh McHardy

Hugh Sasse wrote:
> On Tue, 4 Nov 2008, Ruairidh Mchardy wrote:
>
>> @hp =100
>> This throws up no errors and although the hit assignment is poor, it
>> remains functional.
>
> Excuse me blundering into this thread, but you may find:
>
> http://blog.jayfields.com/2008/07/ruby-dwemthys-array-using-mo...
>
> and its associated links interesting reading.
>
> HTH
> Hugh

--
Posted via http://www.ruby-....

Hugh Sasse

11/3/2008 6:11:00 PM

0



On Tue, 4 Nov 2008, Ruairidh Mchardy wrote:

> Hi there Hugh,
>
> I'd previously taken a look at Dwemthy's array (Poignant Guide to Ruby
> right) and it is undoubtedly a nice implementation of a text-based RPG
> in Ruby. My problem is that I'm extremely stubborn and even lifting a
> line of code makes me feel dirty.

I know what you mean, but it is how progress is made.
>
> I want this program to be 100% written by me. I know that doesn't stop
> me learning from others but I often end up writing code that is far too
> similar for my liking!

As long as you understand it, I'd say that's OK. But it's good to try
other approaches as well.
>
> Also on a slightly embarassing note, my ruby skills are not to the level
> where I can easily understand snippets such as:
>
> define_method( :initialize ) do
> self.class.traits.each do |k,v|
> instance_variable_set("@#{k}", v)
>
> or def self.metaclass; class << self; self; end; end
>
> def self.traits( *arr )
> return @traits if arr.empty?
>
> attr_accessor(*arr)
>
> arr.each do |a|
> metaclass.instance_eval do
> define_method( a ) do |val|
> @traits ||= {}
> @traits[a] = val
>
> And so my code is far less efficient and rather ugly too :(

Exactly why I pointed you at the article about how to achieve this with
modules, because I'm in much the same position.
>
> Thanks for the link though!
>
> Kind Regards,
>
> Ruairidh McHardy
>
Hugh

Matthew Moss

11/3/2008 6:13:00 PM

0

>
> class Grunty < Enemy
> def initialize(hp,attack, defense, weapon)
> super(hp, attack, defense)
> @weapon = "AK47"
> @hp =100
> if Grunto.hp == 0
> puts "Grunty is dead!"
> else
> puts "Grunty is wounded but can still fight!"
> end
> randy = rand(100)
> health = Grunto.hp - randy
> puts "Grunty's hp is #{Health}"
> end


First, did you mean Grunto.hp or Grunty.hp?

Second, even if you meant Grunty.hp, I do not think that means what
you think that means.

Third, if you meant @hp instead of Grunty.hp, then why even bother
checking if == 0 since you just assigned 100 to it?


Ruairidh Mchardy

11/3/2008 6:49:00 PM

0

Matthew Moss wrote:
>> end
>> randy = rand(100)
>> health = Grunto.hp - randy
>> puts "Grunty's hp is #{Health}"
>> end
>
>
> First, did you mean Grunto.hp or Grunty.hp?
>
> Second, even if you meant Grunty.hp, I do not think that means what
> you think that means.
>
> Third, if you meant @hp instead of Grunty.hp, then why even bother
> checking if == 0 since you just assigned 100 to it?

Hi,

Just modified my code a little and now have:

Grunty = Grunt.new(100, 300, 300, "Ak47")
randy = rand(100)
Health = Grunt.hp - randy
puts "Grunty is battling a kitten!"
puts "The kitten attacks Grunty with an AK47 and inflicts #{randy}
points of damage!"
puts "Grunty lost #{randy} health!"
puts "Grunty's hp is #{Health} "

So Grunty is my monster and I'm checking the starting vars from the
Grunt class, then subtracting hitpoints from a random value. So perhaps
I should do my checks of if Grunt.hp == 0 then blah in the attack class?

Attached is my (albeit messy) code so far!

Hugh,

Very true in regards to learning from others, I just feel that I
couldn't emulate that coding style as I don't understand it
sufficiently. It's very frustrating!

Thanks for the help guys!

Kind Regards,

Ruairidh McHardy
--
Posted via http://www.ruby-....

Stephen Ball

11/3/2008 6:51:00 PM

0

On Mon, 03 Nov 2008 12:27:21 -0500, Ruairidh Mchardy
<ruairidh82@gmail.com> wrote:

> Argh ok having problems with this. My code has just become very messy
> because I can't get my head clear on this one.
>
> I'm trying to structure the enemy classes as per your advice and so have
> Grunty laid out in a manner similar to this:
>
> class Grunty < Enemy
> def initialize(hp,attack, defense, weapon)
> super(hp, attack, defense)
> @weapon = "AK47"
> @hp =100
> if Grunto.hp == 0
> puts "Grunty is dead!"
> else
> puts "Grunty is wounded but can still fight!"
> end
> randy = rand(100)
> health = Grunto.hp - randy
> puts "Grunty's hp is #{Health}"
> end
>
> This throws up no errors and although the hit assignment is poor, it
> remains functional.

The check of Grunto's health shouldn't be at initialization (unless you'll
be potentially initializing enemies that are already dead). And you
certainly shouldn't be printing from the initialization method.

I rather envisioned Enemy having set methods for:
- affecting health
- returning a health string (e.g. your "Grunty is staggered but still
ready for action." kind of statements)

Something like (and this is really rough)

class Enemy
def initialize(hp)
@hp = hp
end

def hit(damage)
@hp -= damage
end

def status
if @hp < 50
return "He looks really rough."
else
return "He is in a fighting mood."
end
end
end

grunt = Enemy.new(50)
puts grunt.status
# >> He is in a fighting mood.
grunt.hit(70)
puts grunt.status
# >> He looks really rough.

You'll probably want to track max hit points and current hit points, so
you can set the health statements by percentage thresholds (e.g. current
health is less than 25% of max) in addition or instead of explicit point
limits.

Also you should probably just go ahead and create a player class and get
out of the habit of hard coding things straight into your main program
tests. Ideally you'll want to completely avoid any hardcoding (e.g. how
you hardcode "AK47" in your test attack, that should be something like
#{player.weapon}). You also have "AK47" hardcoded in your Grunt class
instead of using the weapon argument.

Here's an idea of how Player and Enemy could interact.

class Player
def attack
# this should be random, and based off of the current weapon
return 75
end
end

grunt.hit(player.attack)

>
> I then continue to test with an example scenario battle hard coded into
> the script:
>
> Grunty = Grunt.new("hp", 300, 300, "Ak47")
> puts "Grunty is battling a kitten!"
> puts "The kitten attacks Grunty with an AK47 and inflicts #{randy}
> points of damage!"
> puts "Grunty lost #{randy} health!"
> puts "Grunty's hp is #{Health} "
> end
> end
>
> In theory this should work but get the following errors:
>
> /home/roo/dev/RubyRPG/RubyRPG/lib/main.rb:19:in `initialize':
> uninitialized constant Enemy::Grunt::Grunto (NameError)
> from /home/roo/dev/RubyRPG/RubyRPG/lib/main.rb:31:in `new'
> from /home/roo/dev/RubyRPG/RubyRPG/lib/main.rb:31
> from :1
> from :1
>
> I'm not too sure why these errors are being thrown up, I assume it's not
> liking the way the class has been edited?

It looks like your syntax error results from declaring a "Grunty" class,
but then calling Grunt.new, but without seeing the entire code base I
can't be sure.

>
> Thanks for all the help so far Stephen, it's helped show me the
> direction I should be taking!

No problem, always ready to help with games. :-)

-- Stephen

Todd Benson

11/3/2008 7:03:00 PM

0

On Mon, Nov 3, 2008 at 12:51 PM, Stephen Ball <sdball@gmail.com> wrote:
> On Mon, 03 Nov 2008 12:27:21 -0500, Ruairidh Mchardy <ruairidh82@gmail.com>
> wrote:
>
>> Argh ok having problems with this. My code has just become very messy
>> because I can't get my head clear on this one.
>>
>> I'm trying to structure the enemy classes as per your advice and so have
>> Grunty laid out in a manner similar to this:
>>
>> class Grunty < Enemy
>> def initialize(hp,attack, defense, weapon)
>> super(hp, attack, defense)
>> @weapon = "AK47"
>> @hp =100
>> if Grunto.hp == 0
>> puts "Grunty is dead!"
>> else
>> puts "Grunty is wounded but can still fight!"
>> end
>> randy = rand(100)
>> health = Grunto.hp - randy
>> puts "Grunty's hp is #{Health}"
>> end
>>
>> This throws up no errors and although the hit assignment is poor, it
>> remains functional.
>
> The check of Grunto's health shouldn't be at initialization (unless you'll
> be potentially initializing enemies that are already dead). And you
> certainly shouldn't be printing from the initialization method.
>
> I rather envisioned Enemy having set methods for:
> - affecting health
> - returning a health string (e.g. your "Grunty is staggered but still ready
> for action." kind of statements)
>
> Something like (and this is really rough)
>
> class Enemy
> def initialize(hp)
> @hp = hp
> end
>
> def hit(damage)
> @hp -= damage
> end
>
> def status
> if @hp < 50
> return "He looks really rough."
> else
> return "He is in a fighting mood."
> end
> end
> end
>
> grunt = Enemy.new(50)
> puts grunt.status
> # >> He is in a fighting mood.
> grunt.hit(70)
> puts grunt.status
> # >> He looks really rough.
>
> You'll probably want to track max hit points and current hit points, so you
> can set the health statements by percentage thresholds (e.g. current health
> is less than 25% of max) in addition or instead of explicit point limits.
>
> Also you should probably just go ahead and create a player class and get out
> of the habit of hard coding things straight into your main program tests.
> Ideally you'll want to completely avoid any hardcoding (e.g. how you
> hardcode "AK47" in your test attack, that should be something like
> #{player.weapon}). You also have "AK47" hardcoded in your Grunt class
> instead of using the weapon argument.
>
> Here's an idea of how Player and Enemy could interact.
>
> class Player
> def attack
> # this should be random, and based off of the current weapon
> return 75
> end
> end
>
> grunt.hit(player.attack)
>
>>
>> I then continue to test with an example scenario battle hard coded into
>> the script:
>>
>> Grunty = Grunt.new("hp", 300, 300, "Ak47")
>> puts "Grunty is battling a kitten!"
>> puts "The kitten attacks Grunty with an AK47 and inflicts #{randy}
>> points of damage!"
>> puts "Grunty lost #{randy} health!"
>> puts "Grunty's hp is #{Health} "
>> end
>> end
>>
>> In theory this should work but get the following errors:
>>
>> /home/roo/dev/RubyRPG/RubyRPG/lib/main.rb:19:in `initialize':
>> uninitialized constant Enemy::Grunt::Grunto (NameError)
>> from /home/roo/dev/RubyRPG/RubyRPG/lib/main.rb:31:in `new'
>> from /home/roo/dev/RubyRPG/RubyRPG/lib/main.rb:31
>> from :1
>> from :1
>>
>> I'm not too sure why these errors are being thrown up, I assume it's not
>> liking the way the class has been edited?
>
> It looks like your syntax error results from declaring a "Grunty" class, but
> then calling Grunt.new, but without seeing the entire code base I can't be
> sure.
>
>>
>> Thanks for all the help so far Stephen, it's helped show me the
>> direction I should be taking!
>
> No problem, always ready to help with games. :-)

Something to think about for the future, also, is to not immediately
classify things as Enemy objects. You might want to make it more
generic like Player, or even better, Being (to keep it separate from a
Thing that doesn't have hp).

Todd

Hugh Sasse

11/3/2008 7:14:00 PM

0



On Tue, 4 Nov 2008, Ruairidh Mchardy wrote:

> Hi,
>
> Just modified my code a little and now have:
>
> Grunty = Grunt.new(100, 300, 300, "Ak47")

Grunty is a constant. (Capital letter). Probably not what you want.

> randy = rand(100)
> Health = Grunt.hp - randy

^^ also a constant.

> puts "Grunty is battling a kitten!"

puts "#{grunty.name} is battling #{article} #{enemy.name}"

> puts "The kitten attacks Grunty with an AK47 and inflicts #{randy}
> points of damage!"
> puts "Grunty lost #{randy} health!"
> puts "Grunty's hp is #{Health} "
>
> So Grunty is my monster and I'm checking the starting vars from the
> Grunt class, then subtracting hitpoints from a random value. So perhaps
> I should do my checks of if Grunt.hp == 0 then blah in the attack class?

..in the attack *method*. Yes.
>
> Attached is my (albeit messy) code so far!
>
Not sure what happened to that, didn't reach the mailing list.
Well, it didn't reach me anyway.

> Hugh,
>
> Very true in regards to learning from others, I just feel that I
> couldn't emulate that coding style as I don't understand it
> sufficiently. It's very frustrating!

Did you look at the stuff that only uses modules, and no metaclasses
at all? It takes a bit of getting used to, but I think that code is
easier. Anyway, maybe you don't need to vary your traits that much
if all characters have @hit_points, @charisma, @damage (weapon power),
@inventory = [], @description, @name.

It can get really complicated simulating all this stuff, especially
if you want to take account of where you are:
"You cannot use a two-handed sword in a narrow passage -- at least,
not without upsetting the landlord."

>
> Thanks for the help guys!
>
> Kind Regards,
>
> Ruairidh McHardy
> --
> Posted via http://www.ruby-....
>
Hugh