Text Based RPG in Ruby

  • Thread starter Ruairidh Mchardy
  • Start date
R

Ruairidh Mchardy

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/2882/main.rb
 
S

Stephen Ball

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/2882/main.rb

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
 
R

Ruairidh Mchardy

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
 
H

Hugh Sasse

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-modules.html

and its associated links interesting reading.

HTH
Hugh
 
R

Ruairidh Mchardy

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
 
H

Hugh Sasse

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
 
M

Matthew Moss

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?
 
R

Ruairidh Mchardy

Matthew said:
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
 
S

Stephen Ball

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
 
T

Todd Benson

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)


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.


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
 
H

Hugh Sasse

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
Hugh
 
R

Ruairidh Mchardy

Hugh said:
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
Hugh

Wow didn't expect quite so many replies! It seems I have a lot to learn
but it's highly enjoyable (after coffee). I realize that my structuring
is definitely rather odd...

I think my problem is that I haven't fully learnt the grammar of Ruby
and am probably suffering from a lack of planning in my game design. I
hadn't really taken into account all the work it requires!

I think I'll be recoding a lot of this project after seeing the replies
as my current code (now attached, forgot last time!) is far too messy.

Thanks guys!

Kind Regards,

Ruairidh Wynne-McHardy

Attachments:
http://www.ruby-forum.com/attachment/2888/main.rb
 

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

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,009
Latest member
GidgetGamb

Latest Threads

Top