Lets play a guessing game. (how to code this better?)

I am a new Rubyist. I told my friend that I was learning Ruby. He asked
me how that was going and then gave me a little challenge. His
challenge, “Write a text game that guesses numbers 0-100”. My reply,
“you mean it picks a number at random, and you guess the number, it
tells you higher or lower until you get it?”. Up to this point I had not
coded anything in Ruby on my own (aside from the examples in the book)
and I saw this as a great first challenge. So here is what I got. It
took me some time. I ran into trouble because I forgot to take into
consideration the Class of the variables and couldn’t figure out why the
loops weren’t working.
My question to you all… how could I have done this better or do you
seeing anything that is wrong. Attached is the code. Thanks for the
feedback.

Looks good. You don’t need the extra call to_i where it is already an
integer:

random = rand(100)
choice == random

Both expressions are fine without any castiing.

small nit pick. ruby is on my machine in /usr/local/bin … to make
this portable use:

#!/usr/bin/env ruby

I suggest your next task is to break everything down to methods. When
you get comfortable with the concept of methods make your user input
more strict as to only acquire a number. You did a very good job,
Keep it going.

~Stu

On Thu, May 5, 2011 at 1:45 AM, Super G.
[email protected]wrote:

My question to you all… how could I have done this better or do you
seeing anything that is wrong. Attached is the code. Thanks for the
feedback.

Attachments:
http://www.ruby-forum.com/attachment/6168/100guess.rb

Hi, Super G. :slight_smile:

Congratulations on your first program! Hope you’re enjoying Ruby.
Here are some thoughts, that might be helpful:

  • On line1 your shebang is #!/usr/bin/ruby, but the best shebang is
    #!/usr/bin/env ruby because Ruby can be located in lots of different
    places, but env is always (I think) located in /usr/bin/env, and it is
    smart
    enough to figure out where your real Ruby is and then invoke it.

  • On line 4, you have rand(100).to_i, rand already returns an integer
    when
    you pass it a parameter, so you don’t need to use to_i on it (
    http://www.ruby-doc.org/core/classes/Kernel.html#M001388)

  • For that variable, I would have probably named it “target” rather than
    “random”, I think this clearer.

  • On line 11, if choice > random.to_i again you are converting it to
    an
    integer, but it is already an integer. (not harmful, but redundant and
    thus
    confusing as it makes reader say “wait, I thought it was an integer
    already,
    what did I miss?”)

  • Lines 13-14 look like this:
    if choice > random.to_i
    puts “\nIncorrect, please choose a lower number.”
    else puts “\nIncorrect, please choose a higher number.”
    end

Your indenting is off, the if/else/end should be at the same level, and
for
multiline conditionals, the code would go between them, so it should
look
like this:

if choice > target
puts “\nIncorrect, please choose a lower number.”
else
puts “\nIncorrect, please choose a higher number.”
end

I would personally probably save space by putting the conditional into
the
string like this:
puts “\nIncorrect, please choose a #{if choice > target then ‘higher’
else
‘lower’ end} number”

I’m not sure whether many other Rubyists would do that or not, but I
like it
much better.

  • On line 16, you have gets.chomp.to_i The chomp here is unnecessary,
    because to_i will disregard anything after the digit.

Anyway, nice job. Keep it up, after you get comfortable with the
language,
you can play with gems, which is where all the fun stuff is!

There’s a small bug:

====
puts “Choose a number between 0 and 100”
random = rand(100).to_i

This actually produces numbers between 0 and 99 inclusive, not 0 and
100. Also the name “random” doesn’t describe the point of the variable
in the program, which is to serve as the answer. So it might be better
to call it “answer”, for instance. Finally, rand(100) already
produces an integer, so you don’t need the .to_i method.

Also, for this condition,

====
until choice <= 100 and choice >= 0

a more succinct way of writing this is to check if it’s included in
the range 0…100:

====
until (0…100).include? choice

Next, notice that “100” appears a lot, so it might be better to
describe what that number is – it’s the maximum value that your
random number can take on. So, w emight write:

====
max = 100

and then use max everywhere instead of the 100 literal.

Otherwise, looks good overall for your first attempt at this, minus a
few rough spots. For another challenge, you might try writing a
program that can guess a number between 0 and 100 in as few guesses as
possible (hint: it should take around 7 guesses).

~ jf

John F.
Principal Consultant, BitsBuilder
LI: http://www.linkedin.com/in/johnxf
SO: http://stackoverflow.com/users/75170/

On Thu, May 5, 2011 at 8:45 AM, Super G. [email protected]
wrote:

My question to you all… how could I have done this better or do you
seeing anything that is wrong. Attached is the code. Thanks for the
feedback.

Attachments:
http://www.ruby-forum.com/attachment/6168/100guess.rb

Apart from the other comments, I would like to point out that the
check about the choice being a valid number (between 0 and 100) is
made only for the first choice. After the first incorrect choice, you
already inside the second loop, and don’t check again.
My advice is to create a method that contains that logic: outputting a
prompt, getting the input from the user and validating it, the code
would get much cleaner:

def get_valid_input

the prompt and validation logic here

end

target = rand(100)
until (choice = get_valid_input) == target
puts “Wrong, my number is #{choice < target ? ‘higher’ : ‘lower’}”
end

Or something like that.

Jesus.

On Thu, 5 May 2011 15:45:52 +0900
Super G. [email protected] wrote:

My question to you all… how could I have done this better or do you
seeing anything that is wrong. Attached is the code. Thanks for the
feedback.

Hey, ruby-quiz is back?!
This is how I would have done it… One loop, break-out once you got the
number. Maybe it helps… Cheers, Martin

magic = rand(101).to_i
puts “Guess the magic number!”

loop do
print "Choose 0-100> "
n = gets.to_i
if !(0…100).include?(n)
puts “0-100!”
next
end
puts “Higher!” if n<magic
puts “Lower!” if n>magic
break if n==magic
end

puts “You won: #{magic} is the number!”

Thanks everyone for taking the time to reply!

The reason I had ‘rand(100).to_i’ and ‘random.to_i’ is because in IRB it
told me it was a Fixnum. Also, I had just figured out why my operators
weren’t working so I wanted everything to be an integer. Obviously, in
retrospect, I see now that I didn’t have to do it that way.

After I submitted to Ruby-Forum I did notice that when I was checking to
see if the input was within range that it would only do this at the
beginning. Putting that code in was an afterthought. I also thought
that I should really check to see if it was a number but I didn’t know
how to do that.

I was unaware that I didn’t need the chomp in ‘gets.chomp.to_i’.

I am still trying to wrap my head around Methods. I get it, but don’t
know how to put down the code. Even when I look at Jesus’s example I am
a bit confused but I have a feeling that using Methods is a very Rubyist
thing and the way Ruby was intended to be written.

I really like Martin’s example.

I am going to try to use a Method and make this work and see if I can
figure out how to reject any input that isn’t a number (probably isn’t
hard just don’t know how yet).

I want to thank everyone again for their help. I definitely feel
welcomed and will be back.

Goat

On Fri, May 6, 2011 at 1:18 AM, Super G.
[email protected]wrote:

Thanks everyone for taking the time to reply!

The reason I had ‘rand(100).to_i’ and ‘random.to_i’ is because in IRB it
told me it was a Fixnum. Also, I had just figured out why my operators
weren’t working so I wanted everything to be an integer. Obviously, in
retrospect, I see now that I didn’t have to do it that way.

In OOP, classes can inherit functionality from other classes. There are
several kinds of Integers, Fixnum is one of them, Bignum is another
(okay,
actually, those are the only two).

1.class # => Fixnum
1.kind_of? Integer # => true
Fixnum.ancestors # => [Fixnum, Integer, Numeric, Comparable, Object,
Kernel, BasicObject]

n = 10**1000
n.class # => Bignum
n.kind_of? Integer # => true
Bignum.ancestors # => [Bignum, Integer, Numeric, Comparable, Object,
Kernel, BasicObject]

On 05.05.2011 10:10, John F. wrote:

For another challenge, you might try writing a program that can guess
a number between 0 and 100 in as few guesses as possible (hint: it
should take around 7 guesses).
Ah, that’s a nice challenge.
You could even try to solve it with the functional pradigm. In this
case: recursion!

http://pastie.org/1870939

On Fri, May 6, 2011 at 8:18 AM, Super G. [email protected]
wrote:

I am still trying to wrap my head around Methods. I get it, but don’t
know how to put down the code. Even when I look at Jesus’s example I am
a bit confused but I have a feeling that using Methods is a very Rubyist
thing and the way Ruby was intended to be written.

A method is just a block of code with a name that can be invoked from
other points in the code.
It can receive parameters, which are like local variables within the
method.
For example:

def say what, who
puts “Hi, #{who}, did you know that #{what}?”
end

say “the sky is blue”, “Mike”
say “my name is Jesus”, “Tom”

and so on. When you need to reuse a piece of logic, it’s usually a
good idea to put it in a method.

I am going to try to use a Method and make this work and see if I can
figure out how to reject any input that isn’t a number (probably isn’t
hard just don’t know how yet).

You already know how to use to_i. If you use it on a string that isn’t
a number it will return 0.
If you need to check if the full string is a valid number, you can use
Kernel#Integer, which will raise an exception:

ruby-1.8.7-p334 :001 > “abc”.to_i
=> 0
ruby-1.8.7-p334 :002 > Integer(“abc”)
ArgumentError: invalid value for Integer: “abc”
from (irb):2:in `Integer’
from (irb):2

You can do:

def get_valid_input
loop do
puts “Input a number between 0 and 100”
choice = Integer(gets.chomp) rescue nil # this will evaluate to
nil if the string isn’t a number
return choice if choice && (0…100).include?(choice)
end
end

As others have said, I recommend you get familiar with IRB
(Interactive Ruby), it’s a wonderful tool to explore Ruby, test little
pieces of code, explore which methods an object has, etc. You can also
take a look here: http://ruby-doc.org/core/ to explore the core
classes (start with String, Array and Hash).

Jesus.

You could even try to solve it with the functional pradigm.

That’s not functional enough for me :wink:

In case the syntax gets chewed: http://pastie.org/1871414

Z combinator defines explicit recursion

Z = lambda { |f| (lambda {|x| f.call(lambda {|*ys|
x.call(x).call(*ys)})}).call(lambda {|x| f.call(lambda {|*ys|
x.call(x).call(*ys)})})}

The guess combinator holds a reference to itself in the ‘again’

variable Guess = lambda do |again|
lambda do |magic|
print "Choose 0-100> "
n = gets.to_i
if !(0…100).include?(n)
puts “0-100!”
next
end
puts “Higher!” if n<magic
puts “Lower!” if n>magic
if n==magic
puts “You won: #{magic} is the number!”
else
again.call magic
end
end
end

magic = rand(101).to_i
puts “Guess the magic number!”

Z.call(Guess).call(magic)

Clearly this is the most obvious solution.

Cheers,
Johnny

On 06.05.2011 14:01, Johnny M. wrote:

That’s not functional enough for me;)
And now, the OP is frightenend of functional programming

Actually I left a bug in that due to side effects. (When will lazy
functional ruby be out?)

Here you go:

I have defined the combinators as constants to reflect the TRUTH and

PURITY of COMPUTER SCIENCE

Z combinator defines explicit recursion

Z = lambda { |f| (lambda {|x| f.call(lambda {|*ys|
x.call(x).call(*ys)})}).call(lambda {|x| f.call(lambda {|*ys|
x.call(x).call(*ys)})})}

The guess combinator holds a reference to itself in the ‘again’

variable Guess = lambda do |again|
lambda do |magic|
print "Choose 0-100> "
n = gets.to_i
if (0…100).include?(n)
puts “Higher!” if n<magic
puts “Lower!” if n>magic
if n==magic
puts “You won: #{magic} is the number!”
else
again.call magic
end
else
puts “0-100!”
again.call magic
end
end
end

magic = rand(101).to_i
puts “Guess the magic number!”

Z.call(Guess).call(magic)

And now, the OP is frightenend of functional programming … :wink:

Good point.

Please OP, for the love of all things computery do not code
like me.

On Fri, May 06, 2011 at 04:27:53PM +0900, Jess Gabriel y Galn wrote:

If you need to check if the full string is a valid number, you can use
Kernel#Integer, which will raise an exception:

Wow. Somehow I never found that. All this time, I’ve been doing this:

if foo.to_i.to_s == foo

. . . or, if appropriate:

if foo.to_i.to_s == foo.chomp

Thanks for mentioning the Integer() approach.

Thanks again for everyones input and thanks to Jesus for explaining and
providing examples for using a method in my code.

And now, the OP is frightenend of functional programming
No, this sounds like fun. I’ll work my way there eventually.

So attached is my updated version.

As you can see I also added a # of ‘guesses’ variable that counts the
number of attempts and then exits when you hit the max. That took me a
little while to figure out, but I am pleased that I got it.

I was thinking of asking if the player would like to play again and then
figuring out how to restart the whole thing but I wasn’t sure how to do
this. How can I make prompt to “Play again?” and when answered “Yes”
restart, whether the person wins or loses (or if “No” just exits)? Or
is the way my code is written make this impossible?

On Sun, May 8, 2011 at 12:08 AM, Super G.
[email protected]wrote:

Attachments:
http://www.ruby-forum.com/attachment/6181/meth_100guess.rb

attempts = “0”.to_i

In the same way you have string literals (the “0”), you also have
integer
literals (just 0).
0 == “0”.to_i # => true

So just make it attempts = 0, same for flag.

flag = “8”.to_i

This seems to record the maximum number of times you can guess. I would
call
it something like “max_attempts”, “flag” is not a descriptive name.

On Sun, May 8, 2011 at 12:08 AM, Super G.
[email protected]wrote:

I was thinking of asking if the player would like to play again and then
figuring out how to restart the whole thing but I wasn’t sure how to do
this. How can I make prompt to “Play again?” and when answered “Yes”
restart, whether the person wins or loses (or if “No” just exits)? Or
is the way my code is written make this impossible?

Right now there is no mechanism to get back to the beginning where
variables
are set and such. What if you had a method that did everything involved
with
a game, then you could play a game by calling that method. That would
allow
you to just have a little piece of code that is responsible for handling
whether you should play another game or not.

def play_game

end

def play_again?

prompt user, return true or false

end

play_game
play_game while play_again?
puts “goodbye!”

On Sun, May 8, 2011 at 12:08 AM, Super G.
[email protected] wrote:


Posted via http://www.ruby-forum.com/.

You can do it with more methods. In fact you should break as much down
as possible. The concept is to keep every method as small as possible.
Does one thing and does it well. Ruby allows you to add ? at the end
of a method name which should return true/false so you could almost
have it kind of tell a story.

Ruby is also liberal with it’s parens and has implied return statements.

so for a contrived example a traditional language would look like this:

def add_num( first_num, second_num)
answer = (first_num + second_num)
return( answer)
end

x = add_num( 42, 100)

where in ruby it could look like this:

def add_num first_num, second_num
first_num + second_num
end

x = add_num 42, 100

methods always return a value (this is contrast to functions which do
one thing and do it well but may only return success or error) Ruby
has only methods.

Methods give the user the ability to reuse code using the example above:

x = add_num 42, 100
y = add_num( x, 16)
z = add_num( y, x)
puts z
=> 300

using good names for your methods make it easier for others to read
them as well as making them easier to read years later if you ever
have to revisit.

Method definitions start with the def keyword, the method name, then
there are optional arguments which are variables passed as copies into
the scope of the method. The method body can contain the same code
logic you have which reads much like a fall through shell script right
now. Every method closes with the end keyword.

The last statement returns regardless in ruby.

My personal preference in style is to keep the parens in the
definition but only use the return when I need to force it:

def add_num( first_num, second_num)
first_num + second_num
end

Once you have normalized the script in pure methods we can then begin
to talk about how user created methods can relate to user created
classes and start talking about object oriented programming.

~Stu

I also wanted to add that you can embed methods directly into the
argument area.

following my previous example:

x = add_num( 42, 100)
add_num( x, add_num(16, x))
=> 300

in this case add_num(16, x) is evaluating 16+142 before sending it
through.

My last piece of advice when you begin to figure out names for your
variables and methods is to separate as much of the game logic as you
can from the peripheral data. This will make it easier to change
things in the long run if you need.

puts “\nLets play a guessing game! You have 8 guesses before you lose.”
target = rand(101) #creates a random number from 0 - 100
attempts = “0”.to_i #
flag = “8”.to_i

can be rewritten like so

max_user_attempts = 8
@attempt_counter = 0
directions = “\nLets play a guessing game! You have
#{max_user_attempts.to_s} guesses before you lose.”
winning_response = rand(101)

def game_loop
print directions
#… start loop and call many methods
end

If you want to change max_user_attempts later you have just one place
to change it. or better yet create a method that ask the user if they
want ‘easy’, ‘medium’, ‘hard’, ‘elite’ and set it based on the user
response. ( good way to learn about hashes though you can program it
other ways. Make easy infinite for responses so you can have the
chance to test the functionality of your game without really playing
it. sneaking in an easter egg cheat to reveal winning_response is also
a good way for testing =)

Last note. notice the @ in front of attempt_counter. This allows for
methods to see the variable outside their own scope. it’s optional if
you want to use it but allows for direct manipulation:

def increment_attempt
@attempt_counter += 1
end

vs a more generic approach:

counter = 0
def inc_counter( arg)
arg += 1
end
counter = inc_counter( counter)

I hope I haven’t confused you. I do not want to make it
overcomplicated or obfuscate it so please ask many questions. Methods(
and functions) are a very important concept to grok in every
programming language in every paradigm.

~Stu

Josh C. wrote in post #997327:

attempts = “0”.to_i

In the same way you have string literals (the “0”), you also have
integer
literals (just 0).
0 == “0”.to_i # => true

So just make it attempts = 0, same for flag.

Josh: I originally had the variables like that way you described but I
changed it because I was getting an error when I tried to add 1
‘attempts += 1’. I just now changed it back and of course there was no
error so I must have mistyped something. Who knows now but thanks for
pointing that out.

On Sun, May 8, 2011 at 12:08 AM, Super G.
[email protected]wrote:

I was thinking of asking if the player would like to play again and then
figuring out how to restart the whole thing but I wasn’t sure how to do
this. How can I make prompt to “Play again?” and when answered “Yes”
restart, whether the person wins or loses (or if “No” just exits)? Or
is the way my code is written make this impossible?

Right now there is no mechanism to get back to the beginning where
variables
are set and such. What if you had a method that did everything involved
with
a game, then you could play a game by calling that method. That would
allow
you to just have a little piece of code that is responsible for handling
whether you should play another game or not.

def play_game

end

def play_again?

prompt user, return true or false

end

play_game
play_game while play_again?
puts “goodbye!”

I like this idea and will give this a go on my next version.
Thanks again for your insight.

==============================

Stu wrote in post #997331:

On Sun, May 8, 2011 at 12:08 AM, Super G.
[email protected] wrote:


Posted via http://www.ruby-forum.com/.

Every method closes with the end keyword.

The last statement returns regardless in ruby.

Can you explain these two lines more thoroughly and when you say ‘the
last statement returns’, where does it return to.

Sorry for my ignorance, this is my first true attempt to learn a
language (I took 2 classes C++ 15 years ago in highschool)

Stu wrote in post #997333:

My last piece of advice when you begin to figure out names for your
variables and methods is to separate as much of the game logic as you
can from the peripheral data. This will make it easier to change
things in the long run if you need.

puts “\nLets play a guessing game! You have 8 guesses before you lose.”
target = rand(101) #creates a random number from 0 - 100
attempts = “0”.to_i #
flag = “8”.to_i

can be rewritten like so

max_user_attempts = 8
@attempt_counter = 0
directions = “\nLets play a guessing game! You have
#{max_user_attempts.to_s} guesses before you lose.”
winning_response = rand(101)

def game_loop
print directions
#… start loop and call many methods
end

Stu, this make complete sense. I probably would have read this or
sortof figured this out eventually but now I will really consider it as
I start to code each project.

Last note. notice the @ in front of attempt_counter. This allows for
methods to see the variable outside their own scope. it’s optional if
you want to use it but allows for direct manipulation:

def increment_attempt
@attempt_counter += 1
end

So can I just refer to increment_attempt each time i want the
@attempt_counter to increase by one? for instance, replacing the way i
did it here.

until (choice = get_valid_input) == target
puts “\nWrong, my number is #{choice < target ? ‘higher’ : ‘lower’}”
attempts += 1

with

until (choice = get_valid_input) == target
puts “\nWrong, my number is #{choice < target ? ‘higher’ : ‘lower’}”
@attempt_counter

and if there were any other places where I would want to increase the
attempts just refer to @attempt_counter again?

I hope I haven’t confused you. I do not want to make it
overcomplicated or obfuscate it so please ask many questions. Methods(
and functions) are a very important concept to grok in every
programming language in every paradigm.

~Stu

Not at all Stu. I think you are really helping me establish some solid
Ruby coding processes and techniques. (I’m sure a lot of this applies to
more than just Ruby) One thing I have learned, which may sound obvious
but I never realized, is that there are so many ways to code something
and get the same end result. That coding can even be someones style.
Thanks for taking your time to explain everything.

Goat

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs