Karthik Soravanahalli
Karthik Soravanahalli

Reputation: 123

Class variable access from top level

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

Answers (2)

7stud
7stud

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

K M Rakibul Islam
K M Rakibul Islam

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

Related Questions