Was completing the pine exercise for Def Grandma and thought would try and take it a little further and abstract it to remove as much duplication as possible.
Hope my logic with this isn't too bizzare just tried to separate things into functions. But now if I type
Bye
The program exits immediately without proceeding to the exitPlan function.
Any advice welcome.
puts 'Say something nice to Grandma.'
puts 'You may need to shout > '
speak = gets.strip
counter = 0
speaks(speak)
def speaks(speak)
if speak != 'Bye'
talk()
else
exitPlan()
end
end
def talk()
if speak == speak.downcase
puts 'Huh Speak up Sonny'
counter -= 1
else
year = rand(1930..1951)
puts 'No not Since ' + year.to_s
counter -= 1
end
if counter < 0
counter = 0 # don't want counter going below zero.
end
puts 'Say something nice to Grandma'
speaks()
end
def exitPlan()
counter += 1
unless counter == 3
puts 'Say something nice to Grandma'
speaks()
else
puts 'good night Sonny'
end
end
Error
renshaw#renshaw-TravelMate-5740G:~/Ruby$ ruby -W dGrand.rb
Say something nice to Grandma.
You may need to shout >
Bye
dGrand.rb:6:in `<main>': undefined method `speaks' for main:Object (NoMethodError)
renshaw#renshaw-TravelMate-5740G:~/Ruby$
You need to move the lines
puts 'Say something nice to Grandma.'
puts 'You may need to shout > '
speak = gets.strip
counter = 0
speaks(speak)
to after your method definition so that the speaks method has been defined when you reach the speaks(speak) line.
Then next problem you will probably encounter is
in `exitPlan': undefined method `+' for nil:NilClass
This is because counter is a local variable so is not shared between your toplevel code and the different methods. You will need to either use a global variable i.e. $counter for this or, better, put the various methods inside a class and then use an instance variable.
I suspect there are still some other issues in your code such as you only seem to be calling gets.strip to get the input once. However, in terms of wrapping the code inside a class it's not the counter that you want to wrap as you would still need to pass that around between the various methods. It's the whole speak/talk interaction so something along the lines of
class Grandma
def initialize
#counter = 0
end
def speaks(speak)
..
end
def talk()
..
end
def exitPlan()
..
end
end
grandma = Grandma.new
grandma.speaks(speak)
and substitute the places where you're currently using the local counter variable with references to #counter
You are calling method speaks before it's defined. See Ruby: How to call function before it is defined?
Look at the other posts explaining the use of methods and globals. Here you have a gist that works with Ruby 1.9.3 because of how you used rand
Related
I'm new to Ruby and programming in general and I'm working on a few practice projects. I have a module for my game that checks to see if my virtual pet is hungry.
module Reaction
def hungry?
if #dragon.hunger > 1
#dragon.hunger -= 1
print 'Mmmmmmm, thanks for the people parts!'
else
print 'I\'m full already!'
end
end
Then on my game file I have a class called StartGame.
require_relative 'dragon'
require_relative 'reaction'
class StartGame
include Reaction
attr_reader :dragon
I'm then attempting to use the module within my user_input method to check if the pet is already hungry.
def user_input
print "How would you like to interact with your dragon?\n"
print "Feed, Sleep, Play, Pet, or Quit\n"
print "Input:\n"
#user_input = gets.capitalize.chomp
if #user_input == 'Feed'
Reaction.hungry?
end
I seem to continuously get this error "undefined method `hungry?' for Reaction:Module (NoMethodError)"
The code within the module works perfectly when used like this
if #user_input == 'Feed'
if #dragon.hunger > 1
#dragon.hunger -= 1
print 'Mmmmmmm, thanks for the people parts!'
else
print 'I\'m full already!'
end
end
I'm just not certain what I'm doing wrong here to search google effectively how to get the method to be defined.
You module really haven't this method because hungry? is instance method
You include it to StartGame, so you can call it on this class instance
start_game.hungry?
But it looks that it is not game method, but dragon's (for example you have other hungry monsters)
So you need rewrite method and include this module to dragon class and call it as #dragon.hungry?
Methods with question marks usually are used to return boolean (true or false)
I am creating an Elevator object with an instance that can only go between floors one and twelve. The code works for the up and down instance methods, but I cannot get the elevator to not go above floor 12 or below floor 1. I tried using unless #floor >= 12, but there was a syntax error. I'm sure it is simple, but I am new to Ruby.
Here is the code that works:
class Elevator
##count = #floor
#The Constructor Method
def initialize(floor) #floor is an instance variable of the Elevator object.
#floor = floor
cheery_greeting
end
def cheery_greeting
puts "Hello my friend! would you like to go up or down?"
end
def self.notify()
"You are now on floor #{##count}"
end
#accessor methods
def go_up
#floor += 1
notify
end
def go_down
#floor -= 1
notify
end
I want to add a break so that it stops iterating when we reach floor twelve, so I wrote this, but it wouldn't even puts.
def floor_limit
if ##count == 12
puts "You're Way Too High!"
end
I also tried:
def go_up
unless #floor >= 12
#floor += 1
notify
end
You're mixing class instance variables with instance variables here and that's going to lead to trouble and confusion. If you're new to Ruby I strongly advise you to avoid using class instance variables, they just lead to a lot of mess. Instead focus on making each instance as self-contained as possible.
To make this more Ruby you can do a few things:
class Elevator
# Define a valid range for this elevator
FLOOR_RANGE_DEFAULT = (1..12)
# Expose the current floor and range as properties
attr_reader :floor
attr_reader :range
def initialize(floor, range = nil)
#floor = floor.to_i
#range = (range || FLOOR_RANGE_DEFAULT).to_a
end
def inspect
#range.map do |floor|
if (#floor == floor)
'[%d]' % floor
else
' %d ' % floor
end
end.join(' ')
end
end
Then your up and down code can check limits and reject if that's an invalid operation. First separate the moving code from the code that interprets up or down:
def up
move(#floor + 1)
end
def down
move(#floor - 1)
end
def move(target)
if (#range.include?(target))
#floor = target
end
#floor
end
Now you have a framework that you can build on. By using simple things like Ruby's range feature you can make a very adaptable class that can handle situations like elevators that have other limits:
e = Elevator.new(1, (1..20))
Or that stop only on odd floors:
e = Elevator.new(1, (1..20).select(&:odd?))
Or skip the 4th, 13th and 14th:
e = Elevator.new(1, (1..20).to_a - [ 4, 13, 14 ])
It doesn't take more code, it just takes the right code.
The following should do the trick.
def go_up
#floor += 1 unless #floor >= 12
notify
end
Or like this:
def go_up
#floor += 1 if #floor < 12
notify
end
They are both pretty intuitive so it's up to you.
This is a pretty detailed explanation on when to use unless.
And here is a standard if/else/unless tutorial.
Ok so I just started learning ruby and I'm making a Yhatzee game, now this is where I'm currently at:
class Yhatzee
def dices
#dices.to_a= [
dice1=rand(1..6),
dice2=rand(1..6),
dice3=rand(1..6),
dice4=rand(1..6),
dice5=rand(1..6)
]
end
def roll_dice
#dices.to_a.each do |dice|
puts dice
end
end
end
x = Yhatzee.new
puts x.roll_dice
Now the reason i typed .to_a after the array is i kept getting a "uninitialized variable #dices" error, and that seemed to fix it, i have no idea why.
anyways on to my question, i currently don't get any errors but my program still won't print anything to the screen. I expected it to print out the value of each dice in the array... any idea what I'm doing wrong? It seems to work when i do it in a procedural style without using classes or methods so i assumed it might work if i made the 'dices' method public. But no luck.
There are a few issues here. Firstly #dices is nil because it is not set anywhere. Thus when you call #dices.to_a you will get []. Also the dices method will not work either because nil does not have a to_a= method and the local variables you are assigning in the array will be ignored.
It seems a little reading is in order but I would do something like the following: (Not the whole game just refactor of your code)
class Yhatzee
def dice
#dice = Array.new(5){rand(1..6)}
end
def roll_dice
puts dice
end
end
x = Yhatzee.new
puts x.roll_dice
There are alot of additional considerations that need to be made here but this should at least get you started. Small Example of how I would recommend expanding your logic: (I did not handle many scenarios here so don't copy paste. Just wanted to give you a more in depth look)
require 'forwardable'
module Yahtzee
module Display
def show_with_index(arr)
print arr.each_index.to_a
print "\n"
print arr
end
end
class Roll
include Display
extend Forwardable
def_delegator :#dice, :values_at
attr_reader :dice
def initialize(dice=5)
#dice = Array.new(dice){rand(1..6)}
end
def show
show_with_index(#dice)
end
end
class Turn
class << self
def start
t = Turn.new
t.show
t
end
end
attr_reader :rolls
include Display
def initialize
#roll = Roll.new
#rolls = 1
#kept = []
end
def show
#roll.show
end
def roll_again
if available_rolls_and_dice
#rolls += 1
#roll = Roll.new(5-#kept.count)
puts "Hand => #{#kept.inspect}"
show
else
puts "No Rolls left" if #rolls == 3
puts "Remove a Die to keep rolling" if #kept.count == 5
show_hand
end
end
def keep(*indices)
#kept += #roll.values_at(*indices)
end
def show_hand
show_with_index(#kept)
end
def remove(*indices)
indices.each do |idx|
#kept.delete_at(idx)
end
show_hand
end
private
def available_rolls_and_dice
#rolls < 3 && #kept.count < 5
end
end
end
The main problem with this code is that you are trying to use the #dices instance variable inside of the roll_dice method, however you are not defining the instance variable anywhere (anywhere that is being used). You have created the dices method but you are not actually instantiating it anywhere. I have outlined a fix below:
class Yhatzee
def initialize
create_dices
end
def roll_dice
#dices.each do |dice|
puts dice
end
end
private
def create_dices
#dices = Array.new(5){rand(1..6)}
end
end
x = Yhatzee.new
x.roll_dice
I have done some simple refactoring:
Created an initialize method, which creates the #dice instance variable on the class initialization.
Made the 'dices' method more descriptive and changed the method visibility to private so only the class itself is able to create the #dice.
Cleaned up the creation of the dices inside of the #dice instance variable
I have omitted the .to_a from the roll_dice method, now that we create the variable from within the class and we know that it is an array and it will be unless we explicitly redefine it.
UPDATE
Although I cleaned up the implementation of the class, it was kindly pointed out by #engineersmnky that I oversaw that the roll would return the same results each time I called the roll_dice function, I have therefore written two functions which will achieve this, one that defines an instance variable for later use and one that literally just returns the results.
class Yhatzee
def roll_dice
#dice = Array.new(5){rand(1..6)} # You will have access to this in other methods defined on the class
#dice.each {|dice| puts dice }
end
def roll_dice_two
Array.new(5){rand(1..6)}.each {|dice| puts dice } # This will return the results but will not be stored for later use
end
end
x = Yhatzee.new
x.roll_dice
x.roll_dice # Will now return a new result
I am having trouble incrementing a variable. This seems very trivial and easy but for some reason I can't get it to work.
I have a program moving a robot about a grid and it's fully working. I would now just like to count how many moves he makes.
Here is my code:
Class Robot
#counter = 0
def move
#counter +=1
end
def print
puts "Hurray the Markov chain has worked in #{#counter}"
end
I get an error saying undefined method '+' operator. I have also tried
#counter = #counter + 1
What am I doing wrong?
your #counter variable is nil because it is not getting set on line 3.
As tomsoft pointed out, the variable is actually defined, but it is defined on the class, and not an instance of the class (an individual Robot).
To define the variable on an instance of the class, you need to initialize the #counter variable in an initializer method.
class Robot
def initialize
#counter = 0
end
def move
#counter +=1
end
def print
puts "Hurray the Markov chain has worked in #{#counter}"
end
end
TL;DR
Classes are executable code. Setting instance variables must generally be done within an instance method. Initialization is invoked by the #new method, which is created when you define "initialize" within your class.
Initializing Instance Variables for Robot#new
For example:
class Robot
def initialize
#counter = 0
end
def move
#counter += 1
end
def print
puts "Hurray the Markov chain has worked in #{#counter}"
end
end
robot = Robot.new
robot.move
robot.print
will print what you expect:
Hurray the Markov chain has worked in 1
Coding Robot Without an Explicit Initializer
Coding is often a matter of style, and how you code something depends not only on what you're trying to do, but also on what you're trying to communicate. In this case, you could rewrite your class without an explicit initializer by ensuring that #counter is set to zero before you attempt to increment it. For example:
def move
#counter.to_i.succ
end
This will ensure that if #counter is nil, it will be converted to an integer (in this case, zero) and then incremented. This might seem a bit "magical" to some folks, so you might also see people being more explicit with nil guards:
def move
#counter ||= 0
#counter += 1
end
If #counter is nil or false, it is assigned the value of zero. This ensures that you'll be able to invoke numeric methods on its value.
The way you define it is that #counter become a class variable, and not an instance variable.
class Robot
#counter=0 # you are in class definition, not instance
def self.print_counter
puts #counter
end
end
Robot.print_counter
returns
0
The only option is to define it in the initializer method
class Robot
def initialize
#counter=0
end
end
I'm learning Ruby and want to be able to do this:
Printer.hi there
and have Ruby output
"hi there"
So far I have the following implementation
class Printer
def method_missing(name, *args)
puts "#{name} #{args.join(',')}"
end
end
But this only lets me do
Printer.hi "there"
If I attempt
Printer.hi there
I get a
NameError: undefined local variable or method `there' for main:Object
which makes sense as I haven't ever defined 'there'. Is there a way to make this work though?
No, this is not possible in the form given (as far as I know).
You aren't looking for method missing, you are looking for the equivalent in the Ruby interpreter to capture when it cannot find a given symbol. So while you cannot intercept it there, you can do it inside of a block:
def hi(&block)
begin
yield
rescue NameError => e
e.message =~ /variable or method `(.+)'/
puts "hi #{$1}"
end
end
hi { there } # => hi there
Please note that I feel like a terrible world citizen for showing you this. Please don't use it anywhere, ever.
Yes, there is a way. When you write there without an explicit receiver, the receiver is the self object of that scope. In this case, it is main. Define methods_missing in the main context.
def method_missing(name, *args)
puts "#{name} was called with arguments: #{args.join(',')}"
end
But if you do so, that would mess up the rest of your code, perhaps. I see not point in doing this.
Since the return value of puts is nil, if you do Printer.hi there, it will evaluate to Printer.hi(nil). So in order for it to output "hi there", you need to define:
class Printer
def self.hi _; puts "hi there" end
end
No because strings need to be quoted, so they are not seen as variables.
Otherwise variables such as there would need some special sort of character to indicate that it is a string. However this still wouldn't work well as spaces would then need to be dealt with.
Use single or double quotes.
It's how the language works. accept this and move on to the next challenge :)
Interestingly you can do this in ruby 1.8.7 with just this code:
def method_missing(*args)
puts args.join ' '
end
I learned about this from Gary Bernhardt's talk, Wat. In 1.9 this gives you a stack level too deep error unless you do it inside a class. Google lead me to this post on Aurthur's tech blog thing, which claims you can do something similar in JRuby 1.9 mode:
def method_missing(*args)
puts [method.to_s, args].flatten.join ' '
end
However when I tried this on MRI 1.9.3 it did not work either. So in 1.9 you can't quite do what you want. Here is the closest I could come:
class Printer
def self.hi(message)
puts "hi #{message}"
end
def self.method_missing(m, *args)
[m.to_s, args].flatten.join ' '
end
def self.bare
hi there
end
end
Printer.bare