Reputation: 123
I am a complete beginner to Ruby. I am working on Lesson 45 of Learn Ruby the Hard Way currently and am creating a game similar to Zork and Adventure.
I have created a structure where I am creating 'scenes' in different files and requiring all the scenes in one file where I have an engine/map that ensures if the current scene does not equal 'finished' that it runs 'X' scene's 'enter' method.
However I have two issues: 1) I keep getting a error saying 'Warning class variable access from top level' 2) Even though the script is running I get
ex45.rb:30:in `play': undefined method `enter' for nil:NilClass (NoMethodError) from ex45.rb:59:in
The following is all of my code from each file. My apologies if it's a long read, but I would love to know why I am getting these two errors and what I can do to fix them.
Ex45.rb:
require "./scene_one.rb"
require "./scene_two.rb"
require "./scene_three.rb"
@@action = SceneOne.new
@@action_two = SceneTwo.new
@@action_three = SceneThree.new
class Engine
def initialize(scene_map)
@scene_map = scene_map
end
def play()
current_scene = @scene_map.opening_scene()
last_scene = @scene_map.next_scene('finished')
while current_scene != last_scene
next_scene_name = current_scene.enter()
current_scene = @scene_map.next_scene(next_scene_name)
end
current_scene.enter()
end
end
class Map
@@scenes = {
'scene_one' => @@action,
'scene_two' => @@action_two,
'scene_three' => @@action_three
}
def initialize(start_scene)
@start_scene = start_scene
end
def next_scene(scene_name)
val = @@scenes[scene_name]
return val
end
def opening_scene()
return next_scene(@start_scene)
end
end
a_map = Map.new('scene_one')
a_game = Engine.new(a_map)
a_game.play()
scene_one.rb:
class SceneOne
def enter
puts "What is 1 + 2?"
print "> "
answer = $stdin.gets.chomp
if answer == "3"
puts "Good job"
return 'scene_two'
else
puts "try again"
test
end
end
end
scene_two.rb
class SceneTwo
def enter
puts "1 + 3?"
print "> "
action = $stdin.gets.chomp
if action == "4"
return 'scene_three'
else
puts "CANNOT COMPUTE"
end
end
end
scene_three.rb
class SceneThree
def enter
puts "This is scene three"
end
end
Thanks in advance!
Upvotes: 2
Views: 3311
Reputation: 48599
Just so you know:
@@y = 20
p Object.class_variables
--output:--
1.rb:1: warning: class variable access from toplevel
[:@@y]
And:
class Object
def self.y
@@y
end
end
puts Object.y
--output:--
20
But:
class Dog
@@y = "hello"
def self.y
@@y
end
end
puts Dog.y #=>hello
puts Object.y #=>What do you think?
The output of the last line is the reason that class variables are not used in ruby. Instead of class variables, you should use what are known as class instance variables:
class Object
@y = 10 #class instance variable
def self.y
@y
end
end
puts Object.y
class Dog
@y = "hello"
def self.y
@y
end
end
puts Dog.y #=> hello
puts Object.y #=> 10
A class instance variable is just an @variable that is inside the class, but outside any def. And instead of there being one @@variable that is shared by all the subclasses, each subclass will have its own @variable.
Upvotes: 0
Reputation: 34338
Answer to your first question:
You need to move the class variable definitions inside your Map
class to get rid of these warnings:
Ex45.rb:5: warning: class variable access from toplevel
Ex45.rb:6: warning: class variable access from toplevel
Ex45.rb:7: warning: class variable access from toplevel
So, your Map
class would look like this:
class Map
@@action = SceneOne.new
@@action_two = SceneTwo.new
@@action_three = SceneThree.new
@@scenes = {
'scene_one' => @@action,
'scene_two' => @@action_two,
'scene_three' => @@action_three
}
def initialize(start_scene)
@start_scene = start_scene
end
def next_scene(scene_name)
val = @@scenes[scene_name]
return val
end
def opening_scene()
return next_scene(@start_scene)
end
end
To answer your 2nd question:
You are getting undefined method 'enter' for nil:NilClass (NoMethodError)
because your current_scene
becomes nil
at some point and then you try to call: current_scene.enter()
i.e. nil.enter
and it fails with that error message.
To solve this problem, you have to make sure you always have some value in your current_scene
i.e. make sure it's not nil
.
I think, you can just remove current_scene.enter()
line from the end of your play
method in the Engine
class. So, your Engine
class will look like this:
class Engine
def initialize(scene_map)
@scene_map = scene_map
end
def play()
current_scene = @scene_map.opening_scene()
last_scene = @scene_map.next_scene('finished')
while current_scene != last_scene
next_scene_name = current_scene.enter()
current_scene = @scene_map.next_scene(next_scene_name)
end
# current_scene.enter()
end
end
And, you won't get that error anymore.
Upvotes: 4