Trouble with local variables

S

Severin Newsom

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.
 
R

Rodrigo Bermejo

Severin said:
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.
 
B

Bertram Scharpf

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
 
L

list. rb

[Note: parts of this message were removed to make it a legal post.]

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.

#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} - #{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}%"
 
D

David A. Black

Hi --

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)
 
G

Greg Barozzi

Severin said:
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 == answer
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.
 
B

Brian Candler

Severin said:
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 method `c_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}"

3. 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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,262
Messages
2,571,056
Members
48,769
Latest member
Clifft

Latest Threads

Top