user2448377
user2448377

Reputation: 69

I have a Fundamental Misunderstanding of How This State Machine Works

I fundamentally don't understand what's going on here. Below play is my state machine. But it doesn't work. It either returns the opposite of the two choices, the same choice over and over (beginning), or 'unknown command'. I've tested it by printing the value of the variable @next_action at various points and the results are inconsistent. Sometimes the result of the case statement is instruct but it prints display. Sometimes vice versa, sometimes unknown command. Yes, I fiddle with the code to produce these different results. But not much. And never has it behaved as expected.

Obviously, I don't understand the logic of what I've written. All I want to do is pass the result of the case statement as a method call and keep everything looping. I'm a ruby newb and the handful of folks who've tried to help have either described things in a way that I don't seem to be understanding, or I've done a poor job of explaining/showing exactly what I'm trying to do.

Any help is greatly appreciated.

class Foo

  def initialize(start_action)
    @start = start_action
    @users = %w(Adam Sarah Kanye)
    @points = %w(100 200 300)
  end

  def play    
    puts @next_action
    while true
      case @next_action
      when beginning
        beginning
      when "instruct"
        instructions
      when "display"
        display_users
      else
        puts "Unknown command."
        play
      end
      puts "\n----------"
    end
  end

  def prompt
    puts "\n----------"
    print "> "
  end

  def beginning
    puts <<-INTROTEXT
      This is intro text.
    INTROTEXT
    prompt; @next_action = gets.chomp.to_s
  end

  def instructions
    puts <<-INSTRUCT
      These are instructions.
    INSTRUCT
    prompt; @next_action = gets.chomp.to_s
  end

  def display_users
    puts "\nYour users now include:"
    puts "\nName\tPoints"
    puts "----\t------"
    @users.each_with_index do |item, index|
      puts "%s\t%s" % [item, @points[index]]
    end
    prompt; @next_action = gets.chomp
  end
end

start = Foo.new(:beginning)
start.play

Upvotes: 1

Views: 135

Answers (2)

damau
damau

Reputation: 334

The beginning in the case statement should be :beginning i.e a symbol otherwise you are assessing next action against a method. Also as you recurse on a case statement that never results in beginning it probably means you end up in infinite recursion. (Apologies if the beginning is just a typo)

You are also mixing symbols and strings (generally should be avoided). In instructions you create @next_action as a string but a symbol via Foo.new. :beginning != "beginning" also. I would suggest next action always be a symbol as symbols (as far as I am aware) are used to indicate an action should be taken based on the symbol when passed as an argument to a function. Although as it's based on user input it does make sense to leave it as a string as well.

Also shouldn't the function call play be outside of the case statement and then remove the while true. This would mean that after @next_action has been created the program continues. Basically I don't think this will ever stop, while true means continue forever, you have to supply a break to stop it. I would imagine you would want it to continue unless there was an unknown action. Or at least reset the action on an unknown action i.e:

class Foo

  def initialize(start_action)
    @start = start_action
    @users = %w(Adam Sarah Kanye)
    @points = %w(100 200 300)
  end

This one would stop if someone entered the wrong instructions but continue otherwise

  def play    
    puts @next_action
    case @next_action
    when :beginning
      beginning
    when :instruct
      instructions
    when :display
      display_users
    else
      puts "Unknown command."
      raise Foo::UnknownCommand
    end
    puts "\n----------"
    play
  end

OR This would continue forever and start the user at the start if they entered the wrong command.

  def play    
    puts @next_action
    while true
      case @next_action
      when :beginning
        beginning
      when :instruct
        instructions
      when :display
        display_users            
      else
        puts "Unknown command."
        @next_action = :beginning
      end
      puts "\n----------"
    end
  end

and the rest

  def prompt
    puts "\n----------"
    print "> "
  end

  def beginning
    puts <<-INTROTEXT
      This is intro text.
    INTROTEXT
    prompt; @next_action = gets.chomp.to_sym
  end

  def instructions
    puts <<-INSTRUCT
      These are instructions.
    INSTRUCT
    prompt; @next_action = gets.chomp.to_sym
  end

  def display_users
    puts "\nYour users now include:"
    puts "\nName\tPoints"
    puts "----\t------"
    @users.each_with_index do |item, index|
      puts "%s\t%s" % [item, @points[index]]
    end
    prompt; @next_action = gets.chomp.to_sym
  end
end

start = Foo.new(:beginning)
start.play

I hope that helps.

Upvotes: 0

Daniel J. Pritchett
Daniel J. Pritchett

Reputation: 1051

You'll be fine, just DRY things up a bit. Also, you have two separate looping constructs in place, and that's leading to undesired behavior. Your :play method contains an infinite loop that itself calls :play as its last step. You really only need one or the other. I've tweaked things a bit to centralize the :prompt functionality and use a loop without recursion (i.e. :play no longer calls itself) to achieve what I think is your expected behavior:

class Foo
  def initialize
    @next_action  = "beginning"
    @users        = %w(Adam Sarah Kanye)
    @points       = [100, 200, 300]
  end

  def play    
    while true
      act
      prompt
    end
  end

  def prompt
    puts "\n----------"
    print "> "
    @next_action = gets.chomp.to_s
  end    

  def act
    case @next_action
    when "beginning"
      beginning
    when "instruct"
      instructions
    when "display"
      display_users
    else
      puts "I don't know how to '#{@next_action}'."
    end
  end

  def beginning
    puts <<-INTROTEXT
      This is intro text.
    INTROTEXT
  end

  def instructions
    puts <<-INSTRUCT
      These are instructions.
    INSTRUCT
  end

  def display_users
    puts "\nYour users now include:"
    puts "\nName\tPoints"
    puts "----\t------"
    @users.each_with_index do |item, index|
      puts "%s\t%s" % [item, @points[index].to_s]
    end
  end
end

Foo.new.play

Upvotes: 1

Related Questions