I'm trying to pass a variable to numerous classes but get an 'undefined local variable or method' error.
I've created a 'Player' class which has a method to initialise an object with two parameters, and have a 'Board' class where I want to use one of these parameters (I've just included a 'puts' statement for simplicity below) but this is where the error occurs. Outside of the 'Board' class, the same statement works (currently commented out).
How can I use the player1.name value inside of the 'Board' class please? Thanks
class Player
attr_accessor :name, :symbol
def initialize(name, symbol)
#name = name
#symbol = symbol
end
end
class Board
puts player1.name
end
player1 = Player.new("Player1","X")
#puts player1.name
When you open a class with the class keyword you open a new scope and the current bindings are thrown out of the window. Furthermore your code would never work since you reference player1 before defining the variable.
To solve these two issues we have to move the player1 definition above the class. Then instead of using the class keyword to define Board class we can use the Class constructor which keeps the current bindings.
This results in:
player1 = Player.new("Player1","X")
Board = Class.new do
puts player1.name
end
Although this might solve your problem, I doubt that this is the functionality you'll actually need. The question reeks of a XY-problem. The above will bind the class as a whole to what the player1 value is at the moment of class definition.
In turn this means that every instance of Board is in some way bound to player1. I assume that every instance of Board can have its own player(s), in which case the following would be a better fit:
class Board
def initialize(player1)
#player1 = player1
end
end
It seems that you're trying to call the instance of player1 inside the Board class before it is created.
This means player1 does not exist in that context.
I guess you could either instantiate it inside the Board class or pass it as an option. Here's an example:
class Board
def initialize(options={})
# Uses the player passed to it.
#player = options[:player]
end
# Not often seen since you can get it from the player class
# but can be done here inside the Board class too if you want to.
def player_name
puts #player.name
end
end
# Instantiate your player as usual.
player1 = Player.new("Player1","X")
# Adding it to an options hash
# this step is optional, you can add it directly to board
# like you did for adding the name and symbol to the player class.
options = {player: player1}
# Instantiate a board with the player you've created.
board = Board.new(options)
# To puts the player name run the method.
board.player_name # "Player1"
See Ruby Classes and Modules if you would like more examples and learn more.
As a beginner learning to program, it is extremely helpful to have such a supportive community out there!
I am having trouble getting this 'sample' game working. I am trying to develop a battle system where the player comes across opponents as they progress through a number of rooms. For some reason, when I run it on command prompt, it simply displays "you died" then exits. I am not sure where to go from here.
Any help would be greatly appreciated.
class Player
attr_accessor :hit_points, :attack_power
def initialize(hit_points, attack_power)
#hit_points = hit_points
#attack_power = attack_power
end
def alive?
#hit_points < 1
death
end
def hurt
#hit_points -= Opponent.attack_power
end
def print_status
puts "*" * 80
puts "HP: #{hit_points}/#{MAX_HIT_POINTS}"
puts "*" * 80
end
end
class Death
puts "You died"
exit(1)
end
class Opponent
def initialize (hit_points, attack_power)
#hit_points = hit_points
#attack_power = attack_power
puts "you come across this awful opponent"
end
def alive?
#hit_points < 1
death
end
def hurt
#hit_points -= player.attack_power
end
def interact(player)
while player.alive?
hurt
break if #hit_points < 1
alive?
end
if player.alive?
print "You took #{player_damage_taken} damage and dealt #{player_damage_done} damage, killing your opponent."
room
else
death
end
end
end
class Room
puts "you are now in the scary room, and you see an opponent!"
puts "You come accross a weaker opponent. It is a fish."
puts "Do you want to (F)ight or (L)eave?"
action = $stdin.gets.chomp
if action.downcase == "f"
fish = Opponent.new(2, 1)
fish.interact
else
death
end
end
Player.new(200, 1)
Room.new
class Engine
end
This is breaking because Death is a class and all the code within it is in the body of the class. That means this code will be executed when the class is defined, not at the time that death is called.
You haven't defined a method named death.
Because the Death class is tiny, and it would be awkward to name a method within it that stops the game (Death.death, Death.die, Death.run, Death.execute... Not great), and you don't need any of the advantages of a class (such as multiple instances or attributes stored in instance variables), I suggest you make the death action a part of the Player class.
class Player
# ...
def die
puts "You died"
exit(1)
end
end
Then when you've called death (the currently undefined method) Replace it with player.die.
As noted by #Kennycoc, you'll need to define a method for the death of an enemy, too.
So, it seems like you're under the impression that the code in the top level of the class is run when the class is instantiated (Class.new). This is not the case! Everything in the top level of the class is run as it is defined.
The simplest fix to get this running would be to add all the code in your top level of each class under a method named initialize this is what's run when the class is instantiated.
Also, you're using your Death class as if it were a method. You could either change it from class Death to def death or change your calls to Death.new after moving the code to the initialize method (this is not a normal pattern, but would work).
I am creating a code for a small game of 4 horses running a set distance across my terminal. I have it to where it is outputting my horses that I have added and my users horse, but when I go to my next class to build the race it self, I keep getting method undefined errors. I searched for something similar but couldn't find anything. learningruby.com has some roundabout answers to it, but not showing me what im missing.
class Horses
##list_of_horses = []
attr_accessor :name
attr_accessor :position
def initialize
self.name = nil
self.position = 0
end
def self.add_horse(*horse_variables)
horse = Horses.new
horse.name = horse_variables[0]
##list_of_horses.push horse
end
def self.add_user(*user_variables)
add_user = Horses.new
add_user.name = user_variables[0]
##list_of_horses.push add_user
end
def self.display_data
puts "*" * 60
##list_of_horses.each do |racer|
print "-" * racer.position
puts racer.name
end
end
def move_forward
self.position += rand(1..5)
end
def self.display_horses
##list_of_horses
end
end
horse1 = Horses.add_horse ("Jackrabbit")
horse2 = Horses.add_horse ("Pokey")
horse3 = Horses.add_horse ("Snips")
user1 = Horses.add_user ("Jim")
Horses.display_data
Now when I run just this file, It will give me the printout in my terminal of
Jackrabbit
Pokey
Snips
Jim
But when I start trying to call the methods I have created in my Horses class in my next class of Race even outside of the Race class itself, Im returning method undefined.
require_relative 'Horses_class.rb'
no_winner = true
class Race
def begin_race
puts "And the Race has begun!"
end
end
while no_winner == true
puts begin_race
racing = Race.new
racing.Horses.display_data
end
So why am I not allowed to call my other methods? should I be using a splat or is there something more simplistic that im missing? Thank you in advanced.
Jim
Your begin_race method seems to be out of scope when you're calling it. You need to use either the . or the :: (scope) operator to access it.
class Race
def self.begin_race
puts "And the race has begun!"
end
end
Race::begin_race
# or
Race.begin_race
Also, when you call racing.Horses.display_data you must make sure that your Horses class is a sub-class of you racing class. You can not call a sub-class via an object, you must call it through the class constant.
class Race
class Horses
def self.display_data
puts "The Data"
end
end
end
# Access 'display_data'
Race::Horses.display_data
So in this case your require_relative should be within your Race class and your while block should look like
while no_winner == true
Race.begin_race
Race::Horses.display_data
end
I'm going through the Learn Ruby the Hard Way - ex40
Currently, the code works fine. That is not my problem. My problem is every time I add a new song. A) I need to create an instance variable inside the initialize method. B) Then, I have to give it an attr_reader.
What I know if I can A) not have to keep creating new instance variable, but simply variables inside the Song class. B) Not have to create an attr_reader for each variable.
class Song
def initialize()
#jcole_lighter = "Come here, I\'m about to take you higher"
#hold_on_drake = ["Cause you\'re a good girl and you know it",
"You act so different around me",
"Cause you\'re a good girl and you know it"]
end
def sing_me_a_song()
for line in initialize
puts line
end
end
attr_reader :jcole_lighter
attr_reader :hold_on_drake
end
thing = Song.new
puts thing.jcole_lighter()
puts "-"*10
thing= Song.new
puts thing.hold_on_drake()
Check this out for a good explanation of attr_reader, attr_writer, and attr_accessor.
And check this out for learning how to add parameters to the constructor.
You could have :attr_accessor :artists inside Song and in initialize do this:
#artists = Array.new
Then you can have a method add:
def add(artist)
#artists << artist
end
Just an idea. Always happy to help a Drake fan.
Ok, suppose I have Ruby program to read version control log files and do something with the data. (I don't, but the situation is analogous, and I have fun with these analogies). Let's suppose right now I want to support Bazaar and Git. Let's suppose the program will be executed with some kind of argument indicating which version control software is being used.
Given this, I want to make a LogFileReaderFactory which given the name of a version control program will return an appropriate log file reader (subclassed from a generic) to read the log file and spit out a canonical internal representation. So, of course, I can make BazaarLogFileReader and GitLogFileReader and hard-code them into the program, but I want it to be set up in such a way that adding support for a new version control program is as simple as plopping a new class file in the directory with the Bazaar and Git readers.
So, right now you can call "do-something-with-the-log --software git" and "do-something-with-the-log --software bazaar" because there are log readers for those. What I want is for it to be possible to simply add a SVNLogFileReader class and file to the same directory and automatically be able to call "do-something-with-the-log --software svn" without ANY changes to the rest of the program. (The files can of course be named with a specific pattern and globbed in the require call.)
I know this can be done in Ruby... I just don't how I should do it... or if I should do it at all.
You don't need a LogFileReaderFactory; just teach your LogFileReader class how to instantiate its subclasses:
class LogFileReader
def self.create type
case type
when :git
GitLogFileReader.new
when :bzr
BzrLogFileReader.new
else
raise "Bad log file type: #{type}"
end
end
end
class GitLogFileReader < LogFileReader
def display
puts "I'm a git log file reader!"
end
end
class BzrLogFileReader < LogFileReader
def display
puts "A bzr log file reader..."
end
end
As you can see, the superclass can act as its own factory. Now, how about automatic registration? Well, why don't we just keep a hash of our registered subclasses, and register each one when we define them:
class LogFileReader
##subclasses = { }
def self.create type
c = ##subclasses[type]
if c
c.new
else
raise "Bad log file type: #{type}"
end
end
def self.register_reader name
##subclasses[name] = self
end
end
class GitLogFileReader < LogFileReader
def display
puts "I'm a git log file reader!"
end
register_reader :git
end
class BzrLogFileReader < LogFileReader
def display
puts "A bzr log file reader..."
end
register_reader :bzr
end
LogFileReader.create(:git).display
LogFileReader.create(:bzr).display
class SvnLogFileReader < LogFileReader
def display
puts "Subersion reader, at your service."
end
register_reader :svn
end
LogFileReader.create(:svn).display
And there you have it. Just split that up into a few files, and require them appropriately.
You should read Peter Norvig's Design Patterns in Dynamic Languages if you're interested in this sort of thing. He demonstrates how many design patterns are actually working around restrictions or inadequacies in your programming language; and with a sufficiently powerful and flexible language, you don't really need a design pattern, you just implement what you want to do. He uses Dylan and Common Lisp for examples, but many of his points are relevant to Ruby as well.
You might also want to take a look at Why's Poignant Guide to Ruby, particularly chapters 5 and 6, though only if you can deal with surrealist technical writing.
edit: Riffing of off Jörg's answer now; I do like reducing repetition, and so not repeating the name of the version control system in both the class and the registration. Adding the following to my second example will allow you to write much simpler class definitions while still being pretty simple and easy to understand.
def log_file_reader name, superclass=LogFileReader, &block
Class.new(superclass, &block).register_reader(name)
end
log_file_reader :git do
def display
puts "I'm a git log file reader!"
end
end
log_file_reader :bzr do
def display
puts "A bzr log file reader..."
end
end
Of course, in production code, you may want to actually name those classes, by generating a constant definition based on the name passed in, for better error messages.
def log_file_reader name, superclass=LogFileReader, &block
c = Class.new(superclass, &block)
c.register_reader(name)
Object.const_set("#{name.to_s.capitalize}LogFileReader", c)
end
This is really just riffing off Brian Campbell's solution. If you like this, please upvote his answer, too: he did all the work.
#!/usr/bin/env ruby
class Object; def eigenclass; class << self; self end end end
module LogFileReader
class LogFileReaderNotFoundError < NameError; end
class << self
def create type
(self[type] ||= const_get("#{type.to_s.capitalize}LogFileReader")).new
rescue NameError => e
raise LogFileReaderNotFoundError, "Bad log file type: #{type}" if e.class == NameError && e.message =~ /[^: ]LogFileReader/
raise
end
def []=(type, klass)
#readers ||= {type => klass}
def []=(type, klass)
#readers[type] = klass
end
klass
end
def [](type)
#readers ||= {}
def [](type)
#readers[type]
end
nil
end
def included klass
self[klass.name[/[[:upper:]][[:lower:]]*/].downcase.to_sym] = klass if klass.is_a? Class
end
end
end
def LogFileReader type
Here, we create a global method (more like a procedure, actually) called LogFileReader, which is the same name as our module LogFileReader. This is legal in Ruby. The ambiguity is resolved like this: the module will always be preferred, except when it's obviously a method call, i.e. you either put parentheses at the end (Foo()) or pass an argument (Foo :bar).
This is a trick that is used in a few places in the stdlib, and also in Camping and other frameworks. Because things like include or extend aren't actually keywords, but ordinary methods that take ordinary parameters, you don't have to pass them an actual Module as an argument, you can also pass anything that evaluates to a Module. In fact, this even works for inheritance, it is perfectly legal to write class Foo < some_method_that_returns_a_class(:some, :params).
With this trick, you can make it look like you are inheriting from a generic class, even though Ruby doesn't have generics. It's used for example in the delegation library, where you do something like class MyFoo < SimpleDelegator(Foo), and what happens, is that the SimpleDelegator method dynamically creates and returns an anonymous subclass of the SimpleDelegator class, which delegates all method calls to an instance of the Foo class.
We use a similar trick here: we are going to dynamically create a Module, which, when it is mixed into a class, will automatically register that class with the LogFileReader registry.
LogFileReader.const_set type.to_s.capitalize, Module.new {
There's a lot going on in just this line. Let's start from the right: Module.new creates a new anonymous module. The block passed to it, becomes the body of the module – it's basically the same as using the module keyword.
Now, on to const_set. It's a method for setting a constant. So, it's the same as saying FOO = :bar, except that we can pass in the name of the constant as a parameter, instead of having to know it in advance. Since we are calling the method on the LogFileReader module, the constant will be defined inside that namespace, IOW it will be named LogFileReader::Something.
So, what is the name of the constant? Well, it's the type argument passed into the method, capitalized. So, when I pass in :cvs, the resulting constant will be LogFileParser::Cvs.
And what do we set the constant to? To our newly created anonymous module, which is now no longer anonymous!
All of this is really just a longwinded way of saying module LogFileReader::Cvs, except that we didn't know the "Cvs" part in advance, and thus couldn't have written it that way.
eigenclass.send :define_method, :included do |klass|
This is the body of our module. Here, we use define_method to dynamically define a method called included. And we don't actually define the method on the module itself, but on the module's eigenclass (via a small helper method that we defined above), which means that the method will not become an instance method, but rather a "static" method (in Java/.NET terms).
included is actually a special hook method, that gets called by the Ruby runtime, everytime a module gets included into a class, and the class gets passed in as an argument. So, our newly created module now has a hook method that will inform it whenever it gets included somewhere.
LogFileReader[type] = klass
And this is what our hook method does: it registers the class that gets passed into the hook method into the LogFileReader registry. And the key that it registers it under, is the type argument from the LogFileReader method way above, which, thanks to the magic of closures, is actually accessible inside the included method.
end
include LogFileReader
And last but not least, we include the LogFileReader module in the anonymous module. [Note: I forgot this line in the original example.]
}
end
class GitLogFileReader
def display
puts "I'm a git log file reader!"
end
end
class BzrFrobnicator
include LogFileReader
def display
puts "A bzr log file reader..."
end
end
LogFileReader.create(:git).display
LogFileReader.create(:bzr).display
class NameThatDoesntFitThePattern
include LogFileReader(:darcs)
def display
puts "Darcs reader, lazily evaluating your pure functions."
end
end
LogFileReader.create(:darcs).display
puts 'Here you can see, how the LogFileReader::Darcs module ended up in the inheritance chain:'
p LogFileReader.create(:darcs).class.ancestors
puts 'Here you can see, how all the lookups ended up getting cached in the registry:'
p LogFileReader.send :instance_variable_get, :#readers
puts 'And this is what happens, when you try instantiating a non-existent reader:'
LogFileReader.create(:gobbledigook)
This new expanded version allows three different ways of defining LogFileReaders:
All classes whose name matches the pattern <Name>LogFileReader will automatically be found and registered as a LogFileReader for :name (see: GitLogFileReader),
All classes that mix in the LogFileReader module and whose name matches the pattern <Name>Whatever will be registered for the :name handler (see: BzrFrobnicator) and
All classes that mix in the LogFileReader(:name) module, will be registered for the :name handler, regardless of their name (see: NameThatDoesntFitThePattern).
Please note that this is just a very contrived demonstration. It is, for example, definitely not thread-safe. It might also leak memory. Use with caution!
One more minor suggestion for Brian Cambell's answer -
In you can actually auto-register the subclasses with an inherited callback. I.e.
class LogFileReader
cattr_accessor :subclasses; self.subclasses = {}
def self.inherited(klass)
# turns SvnLogFileReader in to :svn
key = klass.to_s.gsub(Regexp.new(Regexp.new(self.to_s)),'').underscore.to_sym
# self in this context is always LogFileReader
self.subclasses[key] = klass
end
def self.create(type)
return self.subclasses[type.to_sym].new if self.subclasses[type.to_sym]
raise "No such type #{type}"
end
end
Now we have
class SvnLogFileReader < LogFileReader
def display
# do stuff here
end
end
With no need to register it
This should work too, without the need for registering class names
class LogFileReader
def self.create(name)
classified_name = name.to_s.split('_').collect!{ |w| w.capitalize }.join
Object.const_get(classified_name).new
end
end
class GitLogFileReader < LogFileReader
def display
puts "I'm a git log file reader!"
end
end
and now
LogFileReader.create(:git_log_file_reader).display
This is how I would make an extensible factory class.
module Factory
class Error < RuntimeError
end
class Base
##registry = {}
class << self
def inherited(klass)
type = klass.name.downcase.to_sym
##registry[type] = klass
end
def create(type, *args, **kwargs)
klass = ##registry[type]
return klass.new(*args, **kwargs) if klass
raise Factory::Error.new "#{type} is unknown"
end
end
end
end
class Animal < Factory::Base
attr_accessor :name
def initialize(name)
#name = name
end
def walk?
raise NotImplementedError
end
end
class Cat < Animal
def walk?; true; end
end
class Fish < Animal
def walk?; false; end
end
class Salmon < Fish
end
duck = Animal.create(:cat, "Garfield")
salmon = Animal.create(:salmon, "Alfredo")
pixou = Animal.create(:duck, "Pixou") # duck is unknown (Factory::Error)