I'm currently learning ruby from the Learn Ruby the hard way tutorial. And in that exercise, the author ask us to add things to a simple game. However, I was trying this to improve the bear_room method by doing something like this:
while true
print "> "
choice = gets.chomp.downcase!
if choice.include? ("taunt")
dead("The bear looks at you then slaps your face off.")
elsif choice.include? "taunt" && !bear_moved
puts "The bear has moved from the door. You can go through it now."
bear_moved = true
elsif choice.include? "taunt" && bear_moved
dead("The bear gets pissed off and chews your leg off.")
elsif choice.include? "open" && bear_moved
However, when I write this:
choice = gets.chomp.downcase!
It gives me this error when executing:
ex35.rb:44:in `bear_room': undefined method `include?' for nil:NilClass (NoMethodError)
from ex35.rb:99:in `start'
from ex35.rb:109:in `<main>'
But if do something like this:
choice = gets.chomp.downcase
or this:
choice = gets.chomp
choice.downcase!
It works. Why is that? I would appreciate any kind of help. Also, how works the while true bit? That really gets me confused.
Here is the rest of the program in case that you need it. I'm going to leave "separated" the mentioned method to make it easier to read.
# Creates the 'gold_room' method, so it can be called later.
def gold_room
puts "This room is full of gold. How much do you take?"
print "> "
choice = gets.chomp
# Converts the 'choice' variable to integer type.
choice.to_i
# Checks if 'choice' is equals to 0, OR greater or equal to 1.
if choice == '0' || choice >= '1'
# Saves the integer 'choice' variable in the 'how_much' variable.
how_much = choice.to_i
else
dead("Man, learn to type a number.")
end
# Checks if the 'how_much' variable is lesser than 50, and executes the code below if so.
if how_much < 50
puts "Nice, you're not greedy, you win!"
exit(0)
elsif how_much >= 50 && how_much < 100
puts "Mmm, ok that's enough. Get out!"
else
dead("You greedy bastard!")
end
end
################### bear_room method ###################
# Creates the 'bear_room' method.
def bear_room
puts "There is a bear here."
puts "The bear has a bunch of honey."
puts "The fat bear is in front of another door."
puts "How are you going to move the bear?"
puts "1. Taunt the bear."
puts "2. Steal the bear's honey. "
# Declares the 'bear_moved' variable as a boolean, initialize it to false.
bear_moved = false
while true
print "> "
choice = gets.chomp.downcase
if choice.include? ("taunt")
dead("The bear looks at you then slaps your face off.")
elsif choice.include? "taunt" && !bear_moved
puts "The bear has moved from the door. You can go through it now."
bear_moved = true
elsif choice.include? "taunt" && bear_moved
dead("The bear gets pissed off and chews your leg off.")
elsif choice.include? "open" && bear_moved
gold_room
else
puts "I got no idea what that means."
end
end
end
############### end of method ###############
# Defines the 'cthulhu_room' method.
def cthulhu_room
puts "Here you see the great evil Cthulhu."
puts "He, it, whatever stares at you and you go insane."
puts "Do you flee for your life or eat your head?"
print "> "
choice = gets.chomp
# Checks if the user's input contains the word 'flee'. If so, executes the code below.
if choice.include? "flee"
start
# Checks if the user's input contains the word 'head'. If so, executes the code below instead.
elsif choice.include? "head"
dead("Well that was tasty!")
else
# Otherwise, calls the 'cthulhu_room' method again.
cthulhu_room
end
end
# Defines the 'dead' method. It takes one argument (why). Example: dead("Well that was tasty!")
def dead(why)
puts why, "Nice."
# Succesfully finish the program.
exit(0)
end
# Defines the 'start' method, wich is where the game begins. Duh.
def start
puts "You are in a dark room."
puts "There is a door to your right and left."
puts "Which one do you take?"
print "> "
choice = gets.chomp
# Start the branching. It checks the user's input, and saves that on the 'choice' variable, which is used along the whole program in the other methods.
if choice == "left"
# Calls the 'bear_room' method.
bear_room
elsif choice == "right"
# Calls the 'cthulhu_room' method.
cthulhu_room
else
dead("You stumble around the room until you starve.")
end
end
# Start the game.
start
---------------------------------------------------------------------------
TL;DR: What's the difference between choice = gets.chomp.downcase! and
choice = gets.chomp
choice.downcase!
PS: The comments are part of the exercise. Please, if you have any type of correction (about the comments, how I made the question, code in general, etc) please tell me so I can improve. Thanks and sorry for the length!
-------------------------------------------------------------------------
That is because if the string did not change upon calling downcase! it returns nil. Thus when you try and call include? it says that nil does not have such a method.
Thus, it is safest to use the "non-destructive" version of downcase. The downcase! method mutates the string in place if it can.
Check the docs for further reading.
Related
I am just starting with ruby and tried to create a multiple choice game. I can't seem to see where i get something wrong which makes it so that it either repeats the generic line for a room instead of showing the resulting option.
just for info, the options in the hall are either "north", "look" or "quit"
then in the study, options are "look", "look at desk", "south", "quit", "enter combination 2451"
code below:
def hall_begin
#first line you see
puts "you can either look around or move north"
gets.chomp
end
def look_hall
# first option to look around in the hall
puts "You are standing in a hall with a marble floor. You see a door."
hall_begin
end
def onwards_study
# second option to go forwards into the next room from the hall
puts "You are in the study, you can either look around of move back south"
gets.chomp
end
def back_to_hall
# moving back into the hall from the study
puts "You are back in the hall, either look around or go north"
gets.chomp
end
def look_study
# looking around the study to find the desk and safe
puts "You are in a warm and cosy study. You see a safe. You see a desk."
onwards_study
end
def study_desk
# looking on the study desk to find the combination
puts "You see a piece of paper that reads, The combination is 2451."
onwards_study
end
def study_safe
# if you open the safe with combination
puts "You see some diamonds in the safe, pick them up and make your escape"
end
def first_choice
# all the choices whilst in the hall
while true
direction_1 = hall_begin
if direction_1 == "look"
look_hall
elsif direction_1 == "north"
onwards_study
elsif direction_1 == "quit"
break
end
end
end
while true
# start of the game
first_choice
while true
# all choices you face whilst in the study
direction_2 = onwards_study
if direction_2 == "look"
look_study
elsif direction_2 == "south"
back_to_hall
elsif direction_2 == "look at desk"
study_desk
elsif direction_2 == "enter combination 2451"
study_safe
break
elsif direction_2 == "quit"
break
end
break
end
end
This seems to be a beginner level program. In case if you haven't studied the functions yet. This simple code can help you.
won_condition = false
breaking_condition = false
puts "You are standing in the hall, either look around or go north"
while true
if won_condition == true
break
end
if breaking_condition == true
break
end
user_input = gets.chomp
if user_input == 'quit'
break
elsif user_input == "look"
puts "You are standing in a hall with a marble floor. You see a door."
elsif user_input == 'north'
puts "You are in the study, you can either look around or move back south"
while true
study_input = gets.chomp
if study_input == 'look'
puts "You are in a warm and cosy study. You see a safe. You see a desk."
elsif study_input == 'look at desk'
puts "You see a piece of paper that reads, The combination is 2451."
elsif study_input == 'south'
puts "You are standing in the hall, either look around or go north"
break
elsif study_input == '2451'
puts "You see some diamonds in the safe, pick them up and make your escape."
won_condition = true
break
elsif study_input == 'quit'
breaking_condition = true
break
end
end
end
end
As Chandan pointed out, having this many loops is unnecessary. I think what you are after can be achieved by having one main loop that gets the user input and then handles it.
I don't want to make too complicated suggestions to begin with since you are just starting out, but I think keeping track of a "current_room" variable is helpful (which can later transition into coordinates on a 2D room array or something).
Too give you a few examples, this is how something similar could be achieved.
def describe_room(current_room)
if current_room == "hall"
puts "You are standing in the hall, either look around or go north"
elsif current_room == "study"
puts "You are in the study, you can either look around of move back south"
end
end
def examine_room(current_room)
if current_room == "hall"
puts "You are standing in a hall with a marble floor. You see a door."
elsif current_room == "study"
puts "You are in a warm and cosy study. You see a safe. You see a desk."
end
end
def move(current_room, direction)
if current_room == "hall" and direction == "north"
"study"
elsif current_room == "study" and direction == "south"
"hall"
else
puts "You cannot go that way"
current_room
end
end
def hall_commands(command)
# No hall specific commands at this points
puts "Unknown command"
# Return "hall" since we are never moving anywhere else
"hall"
end
def study_commands(command)
if command == "look at desk"
puts "You see a piece of paper that reads, The combination is 2451."
elsif command == "enter combination 2451"
puts "You see some diamonds in the safe, pick them up and make your escape"
return nil # Use explicit return statement to avoid default return at the end of the method
else
puts "Unknown command"
end
# Return "study" as the default
"study"
end
# Starting position
current_room = "hall"
while true
break if current_room == nil
# Start each loop by a brief description of the current room.
describe_room(current_room)
# Get the user input in the main loop
command = gets.chomp
# Check for global commands (i.e. movements, look, etc.) first
# and then move on to check for room specific commands.
if command.in?(["north", "east", "south", "west"])
current_room = move(current_room, command)
elsif command == "look"
examine_room(current_room)
elsif command == "quit"
break
elsif current_room == "hall"
current_room = hall_commands(command)
elsif current_room == "study"
current_room = study_commands(command)
else
puts "Unknown command"
end
end
Basically I have simplified it into one loop as mentioned earlier, then split up the "global commands" that could be used regardless which room your are in, and the "room specific commands" that only apply in certain rooms.
I hope this helps you getting into Ruby. When you feel more comfortable, I would recommend looking into case/when statements as an alternative to if/elsif/else statements and also Arrays to keep track of rooms and positions.
So I tried to follow this exercise to get it to work and well, see for yourself...
def gold_room
puts "This room is full of gold. How much do you take?"
print ">"
choice = $stdin.gets.chomp
#This line has a bug, so fix it
if choice.include? ("0") || if choice.include? ("1")
how_much = choice.to_i
else
dead("Man, learn how to type a number")
end
if how_much < 50
puts "Nice, you're not greedy. YOU WIN!!!"
exit(0)
else
dead("YOU GREEDY BASTARD!!")
end
end
def bear_room
puts "There is a bear here."
puts "The bear has a bunch of honey."
puts "The fat bear is in front of the door."
puts "How are you going to move the bear?"
bear_moved = false
while true
print ">"
choice = $stdin.gets.chomp
if choice == "take honey"
dead("The bear looks at you, then slaps your face off.")
elsif choice == "taunt the bear" && !bear_moved
puts "The bear has moved from the door. You can go through now."
bear_moved = true
elsif choice == "taunt the bear" && bear_moved
dead("The bear gets pissed off and chews your legs off!")
elsif choice == "open door" && bear_moved
gold_room
else
puts "I got no idea what that means."
end
end
end
def cthulhu_room
puts "Here you see the great evil known as Cthulhu."
puts "He, it, whatever stares at you and you go insane!"
puts "Do you flee for your life or eat your head?"
print ">"
choice = $stdin.gets.chomp
if choice.include? "flee"
start
elsif choice.include? "head"
dead("Well that was tasty!")
else
cthulhu_room
end
end
def dead(why)
puts why, "Good job!"
exit(0)
end
def start
puts "You are in a dark room."
puts "there is a door to your right and left."
puts "Which one do you take?"
print ">"
choice = $stdin.gets.chomp
if choice == "left"
bear_room
elsif choice == "right"
cthulhu_room
else
dead("You stumble around the room until you die of hunger.")
end
end
start
and got these errors:
“ex35.rb:90: syntax error, unexpected end-of-input, expecting `end’”
“ex35.rb:90:in <main>': undefined local variable or methodstart’ for main:Object (NameError)” (added extra end)
I'm not sure what happened.
There's one if too many in your code. You only need one.
replace
if choice.include?("0") || if choice.include?("1")
with
if choice.include?("0") || choice.include?("1")
or more pretty
if %w[0 1].include?(choice)
How does the program continue beyond ''bear_moved = false''? If the while statement only runs while true how can the program continue to run? Or is the while true unrelated to the 'bear_moved'? If so what does it relate too?
def bear_room
puts "There is a bear here."
puts "The bear has a bunch of honey."
puts "The fat bear is in front of another door."
puts "How are you going to move the bear?"
bear_moved = false
while true
print "> "
choice = $stdin.gets.chomp
if choice == "take honey"
dead("The bear looks at you then slaps your face off.")
elsif choice == "taunt bear" && !bear_moved
puts "The bear has moved from the door. You can go through it now."
bear_moved = true
elsif choice == "taunt bear" && bear_moved
dead("The bear gets annoyed and chews your leg off.")
elsif choice == "open door" && bear_moved
gold_room
else
puts "I got no idea what that means."
end
end
end
It appears to me that you want something like the following:
def bear_room
puts "There is a bear here."
puts "The bear has a bunch of honey."
puts "The fat bear is in front of another door."
puts "How are you going to move the bear?"
bear_moved = false
until bear_moved
print "> "
case gets.chomp
when "take honey"
dead("The bear looks at you then slaps your face off.")
when "taunt bear"
puts "The bear has moved from the door. You can go through it now."
bear_moved = true
when "open door"
gold_room
else
puts "I have no idea what that means."
end
end
puts "I'm outta' here!"
end
def dead(str) puts(str) end
def gold_room; puts "So this is the famous gold room. Nice."; end
Some points to consider:
since you have while true, you need a break statement to exit the loop. Alternatively, you can use until bear_moved, as I have done. If you want to use while true (or more idiomatic, loop do), just replace until bear_moved with break. I've written it differently just to show you the choice available.
you don't need an if bear_moved...else...end under when "taunt bear", because bear_moved is always false at the point.
there is no method or local variable dead or gold_room, so you need to do something about that. I've added methods for those.
I've used a case statement instead of all the if-elsif's because it is easier to read. Also, you see you don't need the variable choice when using a case statement. I've also included only the outcomes of choice, leaving the secondary choices in if-then-else's for two of the case outcomes.
(I initially had while bear_moved == false. I changed that to until bear_moved after reading #SteveTurczyn's answer. Thanks, Steve.)
while true
is another way of saying
while true == true
which of course, is always and perpetually true. You can only come out of a while true loop via an explicit break command.
What you want to do (I think) is...
until bear_moved
Meaning you will continue to loop until you've executed the bear_moved == true
I think you're failing to grasp the concept of "state", the status that things are in. Initially the bear is (implicitly, according to text) in front of the door. The state of the bear is "unmoved from its initial position", and is captured by the boolean variable bear_moved which is set to the value false. The while true will continue to repeat the contained logic indefinitely until you give a command which results in the bear moving or the bear killing you. It looks like the correct sequence of actions is to taunt the bear - if it hasn't moved, as indicated by the bear_moved state variable, it will move, but it if has already moved and you taunt it again the bear will get annoyed and chew your leg off.
Basically, the chain of logic that gets followed depends on the state, i.e., in this case the interaction between the status of the bear (moved or not) and your action choice.
I'm writing a text-adventure game for exercise 36 of Learn Ruby the hard Way: http://ruby.learncodethehardway.org/book/ex36.html
I want to include 'instructions' as an option players can use anytime, but once the player exits the Room() function and enters the intructions() function, I am unsure how to return them to the appropriate room. I can have the instructions() always return player to the start after, but is there a way to get them back to the same location?
Here is a quick example, sorry it's incomplete...I'm still in the middle of building it:
puts
puts <<INTRO
"Welcome to the Cave of Indifference.
It doesn't much care for you. Beware!
There are deadly areas of this cave, but if you seek
it you may find the secret treasure and escape with your life."
INTRO
puts
puts "Type \'Instructions\' at any time for direction"
puts
sword = false
monster = true
treasure = false
def Command()
puts ">>> "
end
def dead(how)
puts how.to_s
puts "PLAYER DEAD!"
Process.exit(0)
end
def instruction()
puts "Rooms will have individual instructions"
puts "but here are some general items."
puts "west, east, north, south: goes that direction"
puts "look: look around the room"
puts "take: to take item or object"
end
def Room1()
puts "You are now at the cave entrance."
puts "You may go west or east. OR exit with your life!"
Command(); choice = gets.chomp()
if choice.downcase == "exit" && treasure = true
puts "Congratulations! You win!"
Process.break
elsif choice.downcase == "exit" && treasure = false
puts "Seriously?! Giving up already?"
puts "Fine. Here is what happens:"
dead("You stumble on your exit from the cave and trip
on a rock. The fall cracks your skull and you bleed
to death. Bye bye!")
elsif choice.downcase.include? "right"
#INPUT
elsif choice.downcase.include? "left"
#INPUT
elsif choice.downcase.include? "instructions"
instructions()
else
"That command makes no sense, try again."
end
end
Room1()
I also assume there are many issues with the code and would be very appreciative of your help, but no worries I am going to keep working on this and make it really fun to play :)
You can give the instruction method the last location.
def instruction(last_room)
#do stuff
last_room.call()
end
You would call that function like so:
instructions(method(:Room1)), where Room1 is the name of the method you want to return to.
The problem you have isn't having instructions return to Room1 (or RoomX). It'll do that without you doing anything special. What you need is something like:
#room = :Room1
while true
send(#room)
end
and then set the variable #room to control which room you're in.
It's not the greatest way in the world to do that, but it'll get you started.
I am trying to make a number guessing game in Ruby but the program exits after I type in yes when I want to play again. I tried using the catch and throw but it would not work. Could I please get some help.
Here is my code.
class Game
def Play
catch (:start) do
$a=rand(11)
puts ($a)
until $g==$a
puts "Guess the number between 0-10."
$g=gets.to_i
if $g>$a
puts "The number you guessed is too high."
elsif $g==$a
puts "Correct you won!!!"
puts "Would you like to play again?"
$s=gets()
if $s=="yes"
$c=true
end
if $c==true
throw (:start)
end
elsif $g<$a
puts "The number you guessed is too low."
end
end
end
end
end
Game.new.Play
Edit: Here's my new code after trying suggestions:
class Game
def Play
catch (:start) do
$a=rand(11)
puts ($a)
while $s=="yes"
until $g==$a
puts "Guess the number between 0-10."
$g=gets.chomp.to_i
if $g>$a
puts "The number you guessed is too high."
elsif $g==$a
puts "Correct you won!!!"
puts "Would you like to play again?"
$s=gets.chomp
if $s=="yes"
throw (:start)
end
elsif $g<$a
puts "The number you guessed is too low."
end
end
end
end
end
end
Game.new.Play
Your first problem is here:
$s=gets()
if $s=="yes"
$c=true
end
The gets method will read the next line including the new line character '\n', and you compare it to only "yes":
> gets
=> "yes\n"
The idiomatic way to fix this in Ruby is the chomp method:
> gets.chomp
=> "yes"
That said, your code has two other deficiencies.
You may come from a language such as PHP, Perl, or even just Bash scripting, but Ruby doesn't require the dollar sign before variables. Using a $ gives a variable global scope, which is likely not what you want. In fact, you almost never want a variable to have global scope.
Ruby uses three types of symbol prefixes to indicate scope - # for instance, ## for class, and $ for global. However the most common type of variable is just local which doesn't need any prefix, and what I would suggest for your code.
I have always been told that it is very bad practice to use exceptions for control structure. Your code would be better served with a while/break structure.
When you do gets(), it retrieves the full line with a '\n' in the end. You need to trim the new line character by using:
$g=gets.chomp.to_i
Same for other gets
Based on your updated code (where you fixed the newline problem shown by others), your new problem is that you have wrapped all your game inside while $s=="true". The very first time your code is run, $s is nil (it has never been set), and so you never get to play. If you used local variables instead of global variables (s instead of $s) this would have become more obvious, because the code would not even have run.
Here's one working way that I would re-write your game.
class Game
def play
keep_playing = true
while keep_playing
answer = rand(11) # Make a new answer each time
puts answer if $DEBUG # we don't normally let the user cheat
loop do # keep going until I break from the loop
puts "Guess the number between 0-10."
guess = gets.to_i # no need for chomp here
if guess>answer
puts "The number you guessed is too high."
elsif guess<answer
puts "The number you guessed is too low."
else
puts "Correct you won!!!",
"Would you like to play again?"
keep_playing = gets.chomp.downcase=="yes"
break
end
end
end
end
end
Game.new.play
I know this doesn't really answer your question about why your code isn't working, but after seeing the code you posted I just had to refactor it. Here you go:
class Game
def initialize
#answer = rand(11)
end
def play
loop do
guess = get_guess
display_feedback guess
break if guess == #answer
end
end
def self.play_loop
loop do
Game.new.play
break unless play_again?
end
end
private
def get_guess
puts "Guess the number between 0-10."
return gets.chomp.to_i
end
def display_feedback(guess)
if guess > #answer
puts "The number you guessed is too high."
elsif guess < #answer
puts "The number you guessed is too low."
elsif guess == #answer
puts "Correct you won!!!"
end
end
def self.play_again?
puts "Would you like to play again?"
return gets.chomp == "yes"
end
end
Game.play_loop