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

S

Super Goat

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.

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

Stu

Looks good. You don't need the extra call to_i where it is already an integ=
er:

random =3D rand(100)
choice =3D=3D 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
 
J

Josh Cheek

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

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.

Attachments:
http://www.ruby-forum.com/attachment/6168/100guess.rb
Hi, Super Goat :)

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

John Feminella

There's a small bug:

=3D=3D=3D=3D
puts "Choose a number between 0 and 100"
random =3D rand(100).to_i
=3D=3D=3D=3D

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,

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

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

=3D=3D=3D=3D
until (0..100).include? choice
=3D=3D=3D=3D

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:

=3D=3D=3D=3D
max =3D 100
=3D=3D=3D=3D

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
 
J

Jesús Gabriel y Galán

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. =A0Attached 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 =3D rand(100)
until (choice =3D get_valid_input) =3D=3D target
puts "Wrong, my number is #{choice < target ? 'higher' : 'lower'}"
end

Or something like that.

Jesus.
 
M

Martin Boese

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.

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!"
 
S

Super Goat

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
 
J

Jesús Gabriel y Galán

I am still trying to wrap my head around Methods. =A0I get it, but don't
know how to put down the code. =A0Even when I look at Jesus's example I a= m
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=
 
J

Josh Cheek

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

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]
 
J

Johannes Held

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
 
J

Johnny Morrice

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

That's not functional enough for me ;)

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
 
J

Johnny Morrice

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

Johannes Held

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

You may provide functional code for the machine-solver, too.
 
J

Johnny Morrice

And now, the OP is frightenend of functional programming =E2=80=A6 ;-)

Good point. =20

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

Super Goat

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?

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

Josh Cheek

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

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.



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!"
 
S

Stu

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)? =A0Or
is the way my code is written make this impossible?

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

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 =3D (first_num + second_num)
return( answer)
end

x =3D 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 =3D 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 =3D add_num 42, 100
y =3D add_num( x, 16)
z =3D add_num( y, x)
puts z
=3D> 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
 
S

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
 
S

Super Goat

Josh Cheek wrote in post #997327:
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.
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:
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
 
S

Stu

Hi Goat,

The basic skeletal structure of a ruby method is so:

def method_name( arg1, arg2, *args) # and so on
#.. method body
return #.. or implicit return( i.e. the keyword is optional)
end

In traditional languages you have a return keyword which returns a
value to the caller. In ruby everything is a caller and everything is
a sender. This is another paradigm you'll come to understand the more
you work with ruby.

very contrived example:

def return_42( )
return( 42)
end

is equivalent to

def return_42
42
end

Here is another conceptual example with my style please try it in irb.

I will create a adder method and then create a square method using the
the previous methods in effort to show you how methods can be reused:

def adder( x,y)
x + y
end

def square_num( x)
y = 0
count = 0

while count != x
y+=adder( x,0)
count += 1
end

return( y)
end

irb session:=> 34

starting with the adder method I did not use a return statement. I
could have rewritten the last line as return( x+y) but I didn't. It's
up to you to figure out if it's more readable with or without it.
Either way if I write this code:

var = adder( 41, 100) # the variable 'var' will be populated with the
return value from the function adder.

The square_num( ) method reuses the adder method in a conditional
while loop. Though contrived for this example it's a good way to
understand methods to be composed for reuse. When you get to get to
OOPS programming there is a concept of composite objects. The concept
is the same but in a less linear fashion.

You'll also notice I used the return statement in the square_num( )
method. I did this because I personally feel the freestanding variable
'y' looks awkward and less readable. Others opinion on the subject may
vary. It's up to you to decide what works best and use those
conventions.

A more succinct way to write the square_num( ) method would be this:

def square_ruby_way( x)
y = 0
x.times { y+=adder(x,0) }
y
end

The right ruby way( and quickest) with the most most brevity would be
to use ruby's built in power of operator method:

def best_ruby_square( x)
x**2
end

The first example of course removes the while loop and follows one of
ruby's object oriented conventions. I don't want to get to deep into
it as it is a abstraction of a while loop and begins to expose some of
ruby's built in iterator design patterns. The times method should be
saved for another discussion after you get your bearings with method
creation and definition.

~Stu
 

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
473,755
Messages
2,569,536
Members
45,013
Latest member
KatriceSwa

Latest Threads

Top