Search code examples
ruby

Ruby loop isn't working as I thought it would


I am new to programming and have just begun learning Ruby. I wrote the program below to practice about loops, variables etc. Essentially, the program mimics a laundry card machine where the user can get a new card or load value into an existing card. How it works is that it randomly selects a value for the card as either needing to buy a new card or have an existing card with an X amount of $s.

It seems to work, but where I'm having a problem is when the user tries to go back to the welcome display and then types in a value, regardless of what the user enters it just goes directly to the menu display. Ideally, it should display "INVALID INPUT" if the user enters anything but "enter". My hunch is I've written my loop incorrectly.

What am I doing wrong?

Thank you in advance for taking the time to read and help out :)

# Method that randomly generates either need for a card or existing card with balance between $1-25
def card_value
  # "NO CARD"
  arr = [nil, (1..25)]
  value = arr.sample
  if value == nil
    "NO CARD"
  else
    rand(value)
  end
end

# Variable assigned to return value of method card_value
balance = card_value

# Method that outputs a string when user input is incorrect
def invalid
  puts "INVALID RESPONSE"
end

# Method for main screen, prompts user to start
def welcome
  puts ""
  puts "*******************************"
  puts "  HERCULES ADD VALUE STATION"
  puts "          WELCOME"
  puts ""
  puts "        | Enter? |"
  puts "*******************************"
  puts "    Type Enter to continue:"
  puts "-------------------------------"
  puts ""
  start = gets.chomp
end

# Method that displays the need of a card or the card balance
def card_balance(balance)
  puts ""
  puts "-------------------------------"
  puts ""
  if balance == "NO CARD"
    puts "       |  #{balance}  |"
  else
    puts "    | BALANCE IS $#{balance}  |"
  end
  puts ""
  puts "-------------------------------"
  balance
end

# Method that displays the option screen, prompts user to get a card or add balance to card
def menu(balance)
  loop do
    card_balance(balance)
    puts ""
    puts "*******************************"
    puts "| NEW CARD? | or | ADD VALUE? |"
    puts "*******************************"
    puts "   Type '1' to buy new card"
    puts "   Type '2' to add value"
    puts "   Type 'X' to exit"
    puts "-------------------------------"
    puts ""

    buy_add = gets.chomp
    if buy_add == "1" && balance == "NO CARD"
      buy_card(balance)
    elsif buy_add == "2" && (balance.is_a? Integer)
      add_value(balance)
    elsif buy_add.upcase == "X"
      welcome
    else
      invalid
    end
  end
end

# Method that displays buy new card, prompts user to add money to buy card
def buy_card(balance)
  loop do
    puts ""
    puts "*******************************"
    puts "        |NEW CARD - $5|"
    puts "*******************************"
    puts "  Type '5' to purchase card"
    puts "  Type 'X' to exit"
    puts "-------------------------------"
    puts ""
    new_card = gets.chomp
    if new_card == "5" && balance == "NO CARD"
      puts ""
      puts "*******************************"
      puts "     |NEW CARD PURCHASED!|"
      puts "*******************************"
      balance = 0
      puts "BALANCE IS $#{balance}"
      balance
    elsif new_card == "5" && balance == 0
      puts "YOU ALREADY HAVE A CARD"
    elsif new_card.upcase == "X"
      menu(balance)
    else
      invalid
    end
  end
end

# Method that displays the options for loading card value, prompts user for amount and displays
# the updated balance
def add_value(balance)
  loop do
    puts ""
    puts "*******************************"
    puts "          |ADD VALUE|"
    puts "    |$15| |$35| |$50| |$75|"
    puts "*******************************"
    puts "    Type '15' to add $15"
    puts "    Type '35' to add $35"
    puts "    Type '50' to add $50"
    puts "    Type '75' to add $75"
    puts "    Type 'X' to exit"
    puts "-------------------------------"
    puts ""
    puts "BALANCE IS $#{balance}"
    add_more = gets.chomp
    if add_more == "15"
      new_balance = balance += 15
    elsif add_more == "35"
      new_balance = balance += 35
    elsif add_more == "50"
      new_balance = balance += 50
    elsif add_more == "75"
      new_balance = balance += 75
    elsif add_more.upcase == "X"
      menu(balance)
    else
      invalid
    end
    puts "NEW BALANCE = #{balance}"
  end 
end

loop do
  if welcome.downcase == "enter"
    menu(balance)
  else
    invalid
  end
end

I think where the problem lies is in the :menu(balance).. but can't seem to figure out what to do.


Solution

  • After entering the menu loop, it'll stay there forever since you never break out of it.

    One quick way of fixing it would be to use break after calling the welcome screen, that way it returns to the main loop at the end of the file:

    elsif buy_add.upcase == "X"
      welcome
      break
    

    For that matter, whenever you need to return to the parent/outer loop, just use break again. I can see another two other cases in your code in buy_card and add_value. If you add a break in the "X" condition in those screens, it should fix all your stuck loops.

    And if you analyze the code a bit more, you'll also find out that calling a "parent" screen and breaking after it, doesn't fix another hidden bug, which is, funnily enough, a stack overflow. If you keep entering and exiting screens for a long time, after a while it'll overflow the stack because you keep entering loops and calling new functions that create new loops and never actually finish the previous loops. To fix this once and for all, you should be able to do something like:

    elsif buy_add.upcase == "X"
      break
    

    Which should also be replicated in the other "X" conditions for other functions as well. This way, after the user inputs "X", it'll just end the function and return to the parent/caller function.

    In the cases where you call menu(balance), of course replacing that with only a break wouldn't update the user balance, so you need to take advantage of how functions work to actually update the balance correctly. I would suggest you always return balance at the end of any submenu instead of using break in those cases, and update the balance in the menu function like balance = buy_card(balance). I won't give out an entire fixed piece of code for you but this should be enough hints to get you started in debugging and fixing some of the problems you're facing.