Trouble with local variables

Hello everyone!
I’ve been coding with Ruby for about three days, and I just ran into my
first big snag. I’ve been working on a quiz, and the first one had
questions like this:

puts ‘How many classes have a taunt kill?’
puts ‘A - Three’
puts ‘B - Four’
puts ‘C - Five’
puts ‘D - Six’
answer = gets.chomp.downcase
if (answer == ‘d’)
score = score + 1
else
end

But it’s messy, since I have to repeat the code for every question. (I
know, I know, DRY). With this in mind, I began construction on quiz2,
and used this as my prototype:

def check
answer = gets.chomp.downcase
if (answer == c_answer)
score = score + 1
else
puts 'The correct answer was ’ + c_answer + ‘.’
end
end
puts ‘Which is the slowest class?’
puts ‘A - Pyro’
puts ‘B - Demoman’
puts ‘C - Heavy’
puts ‘D - Soldier’
c_answer = ‘c’
check

I understand the problem (mostly), but I’m looking for a workaround.
Most of my ‘fixes’ don’t work because of the ‘A, B, C, D’ system; should
that system be changed?

Sorry for such a long post, I’ve just really gotten into this.

Severin Newsom wrote:

Hello everyone!
I’ve been coding with Ruby for about three days, and I just ran into my
first big snag. I’ve been working on a quiz, and the first one had
questions like this:

puts ‘How many classes have a taunt kill?’
puts ‘A - Three’
puts ‘B - Four’
puts ‘C - Five’
puts ‘D - Six’
answer = gets.chomp.downcase
if (answer == ‘d’)
score = score + 1
else
end

But it’s messy, since I have to repeat the code for every question. (I
know, I know, DRY). With this in mind, I began construction on quiz2,
and used this as my prototype:

def check
answer = gets.chomp.downcase
if (answer == c_answer)
score = score + 1
else
puts 'The correct answer was ’ + c_answer + ‘.’
end
end
puts ‘Which is the slowest class?’
puts ‘A - Pyro’
puts ‘B - Demoman’
puts ‘C - Heavy’
puts ‘D - Soldier’
c_answer = ‘c’
check

I understand the problem (mostly), but I’m looking for a workaround.
Most of my ‘fixes’ don’t work because of the ‘A, B, C, D’ system; should
that system be changed?

Sorry for such a long post, I’ve just really gotten into this.

read about class variables (or global variables)
@score

Also it will be cleaner if you pass the answer to chech() tru its
argument list

def check(answer)

…play around.

Hi,

Am Sonntag, 11. Okt 2009, 00:58:17 +0900 schrieb Severin Newsom:

puts ‘A - Pyro’
puts ‘B - Demoman’
puts ‘C - Heavy’
puts ‘D - Soldier’

Maybe you do want to do something like that:

def menu ary
letter = “A”
ary.each { |entry|
puts “#{letter} - #{entry}”
letter.succ!
}
end

menu %w(Pyro Demoman Heavy Soldier)

Bertram

On Sat, Oct 10, 2009 at 11:58 AM, Severin Newsom
[email protected]wrote:

answer = gets.chomp.downcase
answer = gets.chomp.downcase
puts ‘D - Soldier’

#checker
def correct?(item)
letters = %w(A B C D E F G)
puts item[:question] + ‘?’
item[:choices].each_with_index {|choice,i|
puts “#{letters[i]} - #{choice}”
}
return
item[:solution].call(item[:choices][letters.index(gets.to_s.strip.chomp.upcase)])
rescue false
end

#setup
correct = 0
questions = [
{
:question => “Which is the slowest class”,
:choices => %w(Pyro Demoman Heavy Soldier),
:solution => lambda {|input| input == ‘Heavy’}
},
{
:question => “What is the best programming language ever”,
:choices => %w(whitespace ruby),
:solution => lambda {|input| input == ‘ruby’}
}
]

do work

questions.each {|question|
correct +=1 if correct? question
}

#results

puts “correct: #{correct}, incorrect: #{questions.size-correct} score:
#{correct/questions.size.to_f*100}%”

Hi –

On Sun, 11 Oct 2009, Rodrigo B. wrote:

puts ‘D - Six’
def check
puts ‘C - Heavy’
read about class variables (or global variables)
@score

@score is an instance variable, not a class or global variable. I
would definitely encourage everyone to learn about class and global
variables, but they’re literally about the last thing I would reach
for in almost any situation. You definitely won’t need them here.

David


The Ruby training with D. Black, G. Brown, J.McAnally
Compleat Jan 22-23, 2010, Tampa, FL
Rubyist http://www.thecompleatrubyist.com

David A. Black/Ruby Power and Light, LLC (http://www.rubypal.com)

Severin Newsom wrote:

Hello everyone!
I’ve been coding with Ruby for about three days, and I just ran into my
first big snag. I’ve been working on a quiz, and the first one had
questions like this:

puts ‘How many classes have a taunt kill?’
puts ‘A - Three’
puts ‘B - Four’
puts ‘C - Five’
puts ‘D - Six’
answer = gets.chomp.downcase
if (answer == ‘d’)
score = score + 1
else
end

But it’s messy, since I have to repeat the code for every question. (I
know, I know, DRY). With this in mind, I began construction on quiz2,
and used this as my prototype:

def check
answer = gets.chomp.downcase
if (answer == c_answer)
score = score + 1
else
puts 'The correct answer was ’ + c_answer + ‘.’
end
end
puts ‘Which is the slowest class?’
puts ‘A - Pyro’
puts ‘B - Demoman’
puts ‘C - Heavy’
puts ‘D - Soldier’
c_answer = ‘c’
check

I understand the problem (mostly), but I’m looking for a workaround.
Most of my ‘fixes’ don’t work because of the ‘A, B, C, D’ system; should
that system be changed?

Sorry for such a long post, I’ve just really gotten into this.

Hey! Making a quiz is not something I have ever done, but after reading
your post, this is how I would code it up.

#I’d start by making an array with all the right answers to the quiz
answer_key = [‘a’, ‘a’, ‘b’, ‘d’, ‘c’, ‘c’] # for instance

then after printing the question, as you have done above …

answer = []
answer.push(gets.chomp.downcase)

#The above places the input into a new element in the array

#Once the quiz is complete we can tally the score like this …

answer_key.each_index do |i|
score ||= 0 # initialize the variable unless it it already initialized
score +=1 if answer_key[i] == answer[i]
end

puts “You got #{score} answers correct!”

The each_index method of the array class returns the index number for

each element of the array so that you can iterate through both arrays

at once, which is pretty cool, I think.

Severin Newsom wrote:

def check
answer = gets.chomp.downcase
if (answer == c_answer)
score = score + 1
else
puts 'The correct answer was ’ + c_answer + ‘.’
end
end
puts ‘Which is the slowest class?’
puts ‘A - Pyro’
puts ‘B - Demoman’
puts ‘C - Heavy’
puts ‘D - Soldier’
c_answer = ‘c’
check

I understand the problem (mostly)

I think you’d find it helpful if you post the actual error you get when
you run this program:

quiz.rb:3:in check': undefined local variable or methodc_answer’ for
main:Object (NameError)
from quiz.rb:15

This basically tells you the problem: ruby cannot find anything called
“c_answer”. This is because c_answer is a local variable - but methods
cannot access local variables defined outside them. Every method defined
with ‘def’ starts with a clean slate as far as local variables are
concerned. There are very good reasons for this which I won’t go into
here.

So basically, you either make c_answer a global variable like $c_answer
(bad practice), or pass it in as an argument to the method (good
practice).

def check(c_answer)
answer = gets.chomp.downcase
if (answer == c_answer)
score = score + 1
else
puts 'The correct answer was ’ + c_answer + ‘.’
end
end
puts ‘Which is the slowest class?’
puts ‘A - Pyro’
puts ‘B - Demoman’
puts ‘C - Heavy’
puts ‘D - Soldier’
check(‘c’)

This moves you along, but if you enter a right answer you get a new
error:

quiz.rb:4:in check': undefined method+’ for nil:NilClass
(NoMethodError)
from quiz.rb:14

This is the same problem, in this case ‘score’ is not accessible inside
the method. However because you have an assignment to score (score =
…) a local variable is automatically brought into existence with value
‘nil’, even though the assignment hasn’t actually executed yet. This is
why you get a potentially confusing error message. “Undefined method…
for nil” means you tried to execute nil.something (nil.+ in this case)

Again you have a number of options:

  1. Have a global variable $score (bad practice, makes it hard to re-use
    code, non-thread-safe etc)

  2. Pass in the old score, and return the new score. This is a
    “functional” programming style, meaning that your function doesn’t
    actually modify any state, but just returns new calculated values.

def check(c_answer, score)
answer = gets.chomp.downcase
if (answer == c_answer)
score = score + 1
else
puts 'The correct answer was ’ + c_answer + ‘.’
end
return score
end

score = 0

puts ‘Which is the slowest class?’
puts ‘A - Pyro’
puts ‘B - Demoman’
puts ‘C - Heavy’
puts ‘D - Soldier’
score = check(‘c’, score)

puts “Your score is #{score}”

  1. Build a class which holds the state for your quiz session. This is
    the “object oriented” style.

class QuizSession
attr_accessor :score
def initialize
@score = 0
end
def check(c_answer)
answer = gets.chomp.downcase
if (answer == c_answer)
@score = @score + 1
else
puts 'The correct answer was ’ + c_answer + ‘.’
end
end
end

session = QuizSession.new

puts ‘Which is the slowest class?’
puts ‘A - Pyro’
puts ‘B - Demoman’
puts ‘C - Heavy’
puts ‘D - Soldier’
session.check(‘c’)

puts “Your score is #{session.score}”

I have presented these options making the minimum changes to your
existing code.

You can think of ways of extending the QuizSession object - for example
loading the list of questions from a file, and including the ability to
iterate through the questions-and-answers.

At the moment, QuizSession is just a score counter and answer checker,
but that in itself is a useful function, and a good example of
encapsulation (except that it uses ‘puts’ and so has some user-interface
built in as well)

You could consider making the user interface a separate object, so that
you could plug in a CLI Q&A session, a TK Q&A session, a web-based Q&A
session etc.

HTH,

Brian.