Module: TextPlayer::Commands

Defined in:
lib/text_player/commands.rb,
lib/text_player/commands/quit.rb,
lib/text_player/commands/save.rb,
lib/text_player/commands/score.rb,
lib/text_player/commands/start.rb,
lib/text_player/commands/action.rb,
lib/text_player/commands/restore.rb

Constant Summary collapse

Quit =

Command for quitting the game

Data.define do
  def input
    "quit"
  end

  def execute(game)
    begin
      game.write(input)
      sleep(0.2)
      game.write("y")
    rescue Errno::EPIPE
      # Expected when process exits - ignore
    ensure
      game.terminate
    end

    CommandResult.new(
      input: input,
      operation: :quit,
      success: true,
      message: "Game quit successfully"
    )
  end
end
Save =

Command for saving game state

Data.define(:savefile) do
  def input
    "save"
  end

  def execute(game)
    # Note: we could check if the file exists and delete it here, but
    # instead we will let dfrotz handle it in case the save fails.
    game.write(input)
    game.read_until(TextPlayer::FILENAME_PROMPT_REGEX)
    game.write(savefile.filename)

    result = game.read_until(/Overwrite existing file\? |Ok\.|Failed\.|>/i)

    if result.include?("Overwrite existing file?")
      game.write("y")
      result += game.read_until(/Ok\.|Failed\.|>/i)
    end

    success = result.include?("Ok.")
    message = if success
      "[#{savefile.slot}] Game saved successfully"
    else
      "Save operation failed"
    end

    CommandResult.new(
      input: input,
      raw_output: result,
      operation: :save,
      success: success,
      message: message,
      slot: savefile.slot,
      filename: savefile.filename
    )
  end
end
Score =

Command for getting game score

Data.define do
  def input
    "score"
  end

  def execute(game)
    game.write(input)
    raw_output = game.read_until(TextPlayer::PROMPT_REGEX)

    score, out_of = nil
    if TextPlayer::SCORE_REGEX =~ raw_output
      score, out_of = $1, $2
    end

    # Some games give dialog instead of score
    # We will return what the game says as a success
    # whether or not we find a score.
    CommandResult.new(
      input:,
      raw_output:,
      operation: :score,
      success: true,
      score:,
      out_of:
    )
  end
end
Start =

Command for starting the game This is used to start the game and is not accessible by the user.

Data.define do
  def input
    nil
  end

  def execute(game)
    raw_output = game.read_until(TextPlayer::PROMPT_REGEX)

    # Handle "Press any key" prompts - be more specific
    max_iterations = 5
    lines = raw_output.lines
    while /\A\W*(Press|Hit|More)\s+.*\z/i.match?(lines.last) # if last line is a continuation prompt
      lines.pop
      game.write(" ")
      lines.concat game.read_until(TextPlayer::PROMPT_REGEX).lines
      max_iterations -= 1
      break if max_iterations.zero?
    end
    raw_output = lines.join

    # Skip introduction if offered
    if raw_output.include?("introduction")
      game.write("no")
      raw_output += game.read_until(TextPlayer::PROMPT_REGEX)
    end

    CommandResult.new(
      input: input,
      raw_output: raw_output,
      operation: :start,
      success: true
    )
  end
end
Action =

Command for generic game actions (look, go north, take sword, etc.)

Data.define(:input) do
  def execute(game)
    game.write(input)
    raw_output = game.read_until(TextPlayer::PROMPT_REGEX)

    CommandResult.new(
      input: input,
      raw_output: raw_output,
      operation: :action,
      success: !failure_detected?(raw_output)
    )
  end

  def failure_detected?(output)
    TextPlayer::FAILURE_PATTERNS.any? { |pattern| output.match?(pattern) }
  end
end
Restore =

Command for restoring game state

Data.define(:savefile) do
  def input
    "restore"
  end

  def execute(game)
    unless savefile.exist?
      return CommandResult.new(
        input: input,
        operation: :restore,
        success: false,
        message: "Restore failed - file not found",
        slot: savefile.slot,
        filename: savefile.filename
      )
    end

    game.write(input)
    game.read_until(TextPlayer::FILENAME_PROMPT_REGEX)
    game.write(savefile.filename)

    result = game.read_until(/Ok\.|Failed\.|not found|>/i)

    success = result.include?("Ok.")
    message = if success
      "Game restored successfully"
    elsif result.include?("Failed") || result.include?("not found")
      "Restore failed - file not found by dfrotz process even though it existed before running this command"
    else
      "Restore operation completed"
    end

    CommandResult.new(
      input: input,
      raw_output: result,
      operation: :restore,
      success: success,
      message: message,
      slot: savefile.slot,
      filename: savefile.filename
    )
  end
end

Class Method Summary collapse

Class Method Details

.create(input, game_name: nil) ⇒ Object



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/text_player/commands.rb', line 12

def self.create(input, game_name: nil)
  case input.strip.downcase
  when "score"
    Commands::Score.new
  when /^save\s*(\S+)/ # no end anchor to catch all save commands that have args
    Commands::Save.new(savefile: Savefile.new(game_name:, slot: Regexp.last_match(1)))
  when "save"
    Commands::Save.new(savefile: Savefile.new(game_name:))
  when /^restore\s*(\S+)/ # no end anchor to catch all restore commands that have args
    Commands::Restore.new(savefile: Savefile.new(game_name:, slot: Regexp.last_match(1)))
  when "restore"
    Commands::Restore.new(savefile: Savefile.new(game_name:))
  when "quit"
    Commands::Quit.new
  else
    Commands::Action.new(input:)
  end
end