Testing program that runs with user input from the console - ruby

I'm writing tests for a system that models an elevator. For example, I want to test that the elevator can change direction and that it can move to a specified floor.
I have the following methods:
def initialize
#current_floor = 0
#requested_floor = 0
end
def get_value
gets.chomp
end
def arrival
print "Enter floor number: "
#requested_floor = get_value
# only proceed if user entered an integer
if validate_floor_number(#requested_floor)
#requested_floor = #requested_floor.to_i
move
else
arrival
end
end
def move
msg = ""
#current_floor < #requested_floor ? msg = "Going Up!" : msg = "Going Down"
puts msg
#current_floor = #requested_floor
next_move
end
def next_move
puts "Do you want to go to another floor? Y/N"
another_floor = (get_value).upcase
another_floor == 'N' ? final_destination : arrival
end
I start the program by calling Elevator.new.arrival. To check that the elevator has changed directions, I need to store the value of #current_floor in a temporary variable then check it's value has changed after move has been called.
I am testing input from the console using an IO pipe thanks to the answers in this question, but I'm not sure how to apply that knowledge to user interaction that's part of a method.
How can I simulate the program running from the start (Elevator.new.arrival) through the move method and stop it there so I can check the value of #current_floor - all of this without running the program itself and using the IO pipe to simulate user interaction?
I have a feeling that I might have gone about the design of the program in the wrong way. If anyone can even point me in the right direction towards solving this problem I'd appreciate it.
Edit
According to the suggestions from Wand Maker I've written a test as follows:
describe "checks that the elevator can change directions" do
before do
moves = [3, 'Y', 5, 'Y', 2, 'Y', 7, 'N']
def get_value; moves.next end
end
it "should stop on floor 7" do
Elevator.new.arrival
assert_equal(#current_floor, 7)
end
end
Unfortunately, when I run my test file, the program still runs and prompts for user input. Maybe I'm calling arrival incorrectly but I can't think of another way to do it.

As demonstrated in this answer, you can override getvalue to feed in the user input.
Here is complete code that works without actually using gets. I had to add couple of missing methods - validate_floor_number and final_destination:
require 'minitest/autorun'
class Elevator
attr_accessor :current_floor
def initialize
#current_floor = 0
#requested_floor = 0
##last_floor = false
end
def get_value
gets.chomp
end
def validate_floor_number(v)
v.to_i rescue false
end
def arrival
print "Enter floor number: "
#requested_floor = get_value
# only proceed if user entered an integer
if validate_floor_number(#requested_floor)
#requested_floor = #requested_floor.to_i
move
else
arrival
end
end
def move
msg = ""
#current_floor < #requested_floor ? msg = "Going Up!" : msg = "Going Down"
puts msg
#current_floor = #requested_floor
next_move
end
def final_destination
puts "Reached your floor"
end
def next_move
puts "Do you want to go to another floor? Y/N"
another_floor = (get_value).upcase
another_floor == 'N' ? final_destination : arrival
end
end
describe "checks that the elevator can change directions" do
before do
class Elevator
##moves = [3, 'Y', 5, 'Y', 2, 'Y', 7, 'N'].each
def get_value; ##moves.next end
end
end
it "should stop on floor 7" do
e = Elevator.new
e.arrival
assert_equal(e.current_floor, 7)
end
end
Output of above program:
Run options: --seed 2561
# Running:
Enter floor number: Going Up!
Do you want to go to another floor? Y/N
Enter floor number: Going Up!
Do you want to go to another floor? Y/N
Enter floor number: Going Down
Do you want to go to another floor? Y/N
Enter floor number: Going Up!
Do you want to go to another floor? Y/N
Reached your floor
.
Finished in 0.001334s, 749.4982 runs/s, 749.4982 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
[Finished in 0.3s]

This answers this part of your question: How can I simulate the program running from the start (Elevator.new.arrival) through the move method and stop it there so I can check the value of #current_floor?
Install byebug: gem install byebug
Require it in your file: require 'byebug'
Add byebug command where you want to stop your program, for example at start of move (see code at the end of the post)
You are dropped in a shell and can examine everything, for example #current_floor by typing it out, or the instance by using self
If you want to continue, hit CTRL+D and the program will continue (with any modification you might have done)
That should help you debugging it.
def move
byebug # <-- program will stop here
msg = ""
#current_floor < #requested_floor ? msg = "Going Up!" : msg = "Going Down"
puts msg
#current_floor = #requested_floor
next_move
end

Related

How to jump to a line in ruby

I'm doing a pwd generator in ruby and when I get to a certain point of the code I need to return back if the user says that he want to retry to generate the pwd.
print "do you want to retry to generate the password? [y/n]"
retrypwd = gets.chomp
if retrypwd == y
(code to jump to some lines ago)
elsif retrypwd == n
print "Ok, It'll be for the next time"
end
The trick is to use a loop and break it or repeat it according to your expectations:
def try_again?
loop do
print "Would you like to try again? Y/N"
again = gets.chomp.capitalize
case (again)
when 'N'
return false
when 'Y'
return true
else
puts "Huh? I don't know what that means."
end
end
end
Then you can incorporate this into your main program:
begin
try_password
end while try_again?
You will keep trying passwords until try_again? returns false, which happens if you type "N".

Making a time counter while waiting for user input in ruby

I'm trying to create a "tamagoshi" like game to practice ruby. I want the program to wait for user input to do something, but from time to time (right now I am using 10 seconds until I get it right) I want a method to occur (the pet looses energy, and becomes sadder with time). But if the user inputs an action, I want the timer to stop e reset.
I'm having some problems, tried different things and right now, I am only able to do one action. After that, nothing happens. I guess I'm not creating a new thread after doing ".join" but I'm not sure.
I very much welcome all critics on the rest of the code to improve my skills and knowledge as I just started learning ruby.
dog = Dog.new 'Yoshi'
def play pet
user_action = nil
timer_thread = Thread.new do
while !user_action
(1..10).each do |number|
sleep(1)
puts number
end
pet.time_goes_on
user_action = nil
end
end
user_action = gets.chomp
case user_action
when "play"
pet.play_fetch
when "eat"
pet.feed
when "train"
pet.training
when "sleep"
pet.sleep
when "walk"
pet.take_for_a_walk
else
puts "invalid command, try again"
play(pet)
end
timer_thread.join
end
while dog.is_alive
play(dog)
end
Here's the full code at repl.it:
tama'yoshi'
I guess I found a solution!
dog = Dog.new 'Yoshi'
def play pet
while pet.is_alive
user_action = nil
timer_thread = Thread.new do
while user_action == nil
(1..10).each do |number|
sleep(1)
number
if number == 10
pet.time_goes_on
end
break if (user_action != nil)
end
end
end
user_action = gets.chomp
case user_action
when "play"
pet.play_fetch
when "eat"
pet.feed
when "train"
pet.training
when "sleep"
pet.sleep
when "walk"
pet.take_for_a_walk
else
puts "invalid command, try again"
play(pet)
end
timer_thread.join
end
end
play(dog)

Ruby - Secret number game, how to get the game to restart?

im trying to get my secret number game to work properly.
I'm having issues with getting the game to restart when:
you have no more guesses left
you win the game
I used a while loop, but have troubles accessing it via a function/method..
edit:
so basically players are given 3 attempts to guess the secret number. if they win, they will be asked if they want to restart the game. if they lose, they will also be asked if they want to retry.
here is the code:
thanks in advance guys
def get_input
gets.chomp
end
puts "Welcome to the Secret Number Game! Please tell me your name"
player_name = get_input
puts "Welcome #{player_name}!"
puts "Guess a number between 1 - 10. You only have 3 attempts!"
secret_number = 1 + rand(10)
restart = true
while restart
def playAgain ( restart )
puts "Would you like to play again? (y/n)"
answer = get_input
if answer == "n"
restart = false
end
end
def guess_check ( player_input, secret_number )
if player_input > secret_number
puts "Wrong, sorry! Too high!"
elsif player_input < secret_number
puts "Wrong, sorry! Too low!"
else
puts "Congratulations, you've guessed the secret number! #{[secret_number]}"
playAgain ( restart )
end
end
############################## START GAME ###########################################
guesses = []
attempts = 3
while attempts
attempts = attempts - 1
if attempts == -1
puts "Sorry, you have no more tries"
playAgain ( restart )
else
puts "Guess the secret number (You have #{attempts} tries left):"
end
player_input = get_input.to_i
guesses.push ( player_input )
guess_check( player_input, secret_number )
puts "You've guessed - #{guesses}"
end
playAgain ( restart )
end
0 in ruby is truthy, not falsey, contradictory to most languages. To break a while loop one should explicitly check it’s greater than zero:
- while attempts
+ while attempts > 0
or, more rubyish:
3.downto(0) do |attempts|
...
end
UPD
The portion with restarts. You get local restart variable defined in playAgain. Google for ruby scopes, in short: local variables in functions are not visible out of scope of this function; parameters are passed to the function by value. That said, defining restart = false inside playAgain makes no sence at all, since that local variables dies within the function scope. Possible solution would be to declare an instance variable #restart. In this case you won’t need to pass it as parameter. But most reasonable way to get things done would be to return a boolean value from playAgain:
def playAgain
puts "Would you like to play again? (y/n)"
answer = get_input
answer != "n" # return a boolean from the function in last statement
end
And then the whole scope would be looking like:
def playAgain
puts "Would you like to play again? (y/n)"
answer = get_input
answer != "n" # return a boolean from the function in last statement
end
def guess_check ( player_input, secret_number )
if player_input > secret_number
puts "Wrong, sorry! Too high!"
elsif player_input < secret_number
puts "Wrong, sorry! Too low!"
else
puts "Congratulations! Result: #{secret_number}"
end
player_input == secret_number # return true on success
end
# let’s go!
loop do # main loop
guesses = []
3.downto(1) |attempts| # 3 attemts
puts "Guess the secret number (You have #{attempts} tries left):"
player_input = get_input.to_i
guesses.push(player_input)
# you have to return true/false from guess check
# and rerun the topmost loop if success
# see the trick with logical and below:
# on normal execution the loop will return it’s initial
# (3 in this particular case)
# on successful guess is will break, returning false
break false if guess_check(player_input, secret_number)
puts "You've guessed - #{guesses}"
end && puts "Sorry, you have no more tries"
break unless playAgain # will break a topmost loop unless `y` input
end

Catch and throw not working in ruby

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

ruby debugging user prompt

I need a way to pause the program's flow because there are a lot of print statements that I want to check first. is there a way to do this with ruby, stop the program's flow and continue only if the user has entered yes or stop if it has entered no ? thanks
Yes. In your code, put gets. Then the code will pause at that point until the user inputs Enter. You don't need to do anything special to terminate because, if you want to, you can just do Ctrl+C.
Don't forget to chomp off the newline from the return value of gets.
n, m = 0, 1
repeat = 10
loop do
repeat.times do
print "#{m}, "
n, m = m, n + m
end
puts "\nContinue (yes/no)?"
answer = gets.chomp
exit if answer == "no"
end
Also check out Pry.
# test.rb
require 'pry'
class A
def hello() puts "hello world!" end
end
a = A.new
# start a REPL session
binding.pry
# program resumes here (after pry session)
puts "program resumes here."

Resources