Foundations course | Battleship "Outside of range" question

So, I’ve just finished my battleship project and, while I passed all of the specs and the program is running relatively fine, I’ve noticed an edge case where if the input from the player is outside of the size of the board (too large) it throws an error and shuts itself down.

I’ve made a few attempts to alter the code so that it can catch and raises an exception if the player input is outside of the board but to no avail. I guess I’m not sure what class to put it in –

Initially I thought it would belong in Battleship’s #turn method. I’ve also tried the Player method as well, trying to reference back to the battleship.board.grid.length value to compare it to the gets.chomp.split(" ").map(&:to_i).any?{|num| num > battleship.board.grid.length}

I feel like I’m getting lost in the weeds with this problem, which seems like it should be pretty simple to create an unless or while loop for, but I’m struggling with it.

1 Like

Creating a while loop is how I addressed this as well.

I thought it made the most sense to put this inside of the players #get_move method. That way the #get_move method will only get called once per turn, and run through a while loop while staying within the same method, rather than jumping back and forth. Try for the cleanest solution based on how your classes are already structured and interact with each other.

You’ll need a board#exist? or board#valid_bounds? method that takes a pos as an argument (make sure pos is properly formatted) and returns a boolean. It’s purpose is just to make sure that position exists. It’s been awhile since I looked at the rspec (and I haven’t looked to see if it’s changed at all from Alpha to Foundations) so forgive me if one of those method names is already spoken for and utilized differently.

Your loop could look something like this:

pos = nil
until pos && board.valid_bounds?(pos)
…pos = gets.chomp
…pos = convert(pos) # if needed
end
pos

pos could = nil so you jump straight into the loop, or you could get a position first and jump into the loop only if it’s out of bounds. Try a few things and see what works. I hope this helps!

2 Likes

Thanks Stephanie.

For anyone else in the Foundations course, I put a similar loop in the #turn method of the Battleship class.

def turn
move = @player.get_move
until move.all? {|num| num < @board.grid.length}
puts “Move was outside of the battlefield! Try again.”
move = @player.get_move
end
if @board.attack(move) == false
@remaining_misses -= 1
puts “”
@board.print
puts “”
puts “remaining misses: #{@remaining_misses}”
else
puts “”
@board.print
puts “”
puts “remaining misses: #{@remaining_misses}”
end
end

1 Like

Yeah, I just got to the Foundations version of Battleship. It’s specs are very different from the Alpha version. The only real choice is to put the loop inside of Battleship#turn since the Player class can’t reference the board instance and still pass rspec. Looks like it works!
And the “outside of the battlefield!” is informative to the player. :slight_smile:

But hey, just a neat tip looking at your code up above.
If you want to use puts as a spacer, no content, you can omit the “”
like so:

puts

or, since it’s immediately followed by another puts “string” you can also add the newline character at the beginning and have the same effect.

This:
puts "\nremaining misses: #{@remaining_misses}”

is equivalent to this:
puts “”
puts “remaining misses: #{@remaining_misses}”

I’m still getting used to and learning the shorthand way to write things, but it’s making my code look a lot cleaner the more I learn.

1 Like

You can also handle the “out of bounds” case with a raise/rescue.

Here is the general syntax of the begin, raise, rescue, else, end structure:

def some_method
  begin #some exception case may occur in the following code
    
    if  # if exception, raise error
      raise ArgumentError, "This is my error message" #you can specify the error type and the message with a comma. The error type has a default value of RuntimeError but can be specified otherwise.
    end

  rescue ArgumentError => error #rescue code - will run when raised error occurs
    puts
    puts "ERROR: " + error.message # => "ERROR: This is my error message"
    puts
    some_method #in my rescue, I begin the entire method again

  else # else no raise/rescue was encountered
    #proceed how you would if no edge case is encountered
  end
end

Here was my specific solution:

  def turn
    begin
      user_input = @player.get_move
      if user_input.any? { |element| element > @square_size }
        raise ArgumentError, "One or more of your coordinates was off the grid"
      end

    rescue ArgumentError => off_grid
      puts
      puts "ERROR: " + off_grid.message
      puts
      turn

    else
      unless @board.attack(user_input)
        @remaining_misses -= 1
      end
      puts
      @board.print
      puts "#{@remaining_misses} misses remaining"
    end
  end