class Minehunter::Grid
A grid with fields representation
@api private
Attributes
Track the number of flags remaining
@return [Integer]
@api public
Track the number of unmined fields remaining
@return [Integer]
@api public
Public Class Methods
Create a Grid
instance
@param [Integer] width
the number of columns
@param [Integer] height
the number of rows
@param [Integer] mines_limit
the total number of mines
@api public
# File lib/minehunter/grid.rb, line 34 def initialize(width: nil, height: nil, mines_limit: nil) if mines_limit >= width * height raise Error, "cannot have more mines than available fields" end @width = width @height = height @mines_limit = mines_limit @fields = [] reset end
Public Instance Methods
Find field index at a given position
@param [Integer] x
the x coordinate
@param [Integer] y
the y coordinate
@return [Integer]
@api public
# File lib/minehunter/grid.rb, line 122 def at(x, y) y * @width + x end
Check whether or not the grid is cleared
@return [Boolean]
@api public
# File lib/minehunter/grid.rb, line 63 def cleared? @unmined_fields_remaining.zero? end
Total number of flags next to a given position
@param [Integer] x
the x coordinate
@param [Integer] y
the y coordinate
@return [Integer]
@api public
# File lib/minehunter/grid.rb, line 270 def count_flags_next_to(x, y) fields_next_to(x, y).reduce(0) do |acc, cords| acc + (field_at(*cords).flag? ? 1 : 0) end end
Total number of mines next to a given position
@param [Integer] x
the x coordinate
@param [Integer] y
the y coordinate
@return [Integer]
@api public
# File lib/minehunter/grid.rb, line 254 def count_mines_next_to(x, y) fields_next_to(x, y).reduce(0) do |acc, cords| acc + (field_at(*cords).mine? ? 1 : 0) end end
Find a field at a given position
@param [Integer] x
the x coordinate
@param [Integer] y
the y coordinate
@return [Field]
@api public
# File lib/minehunter/grid.rb, line 136 def field_at(x, y) @fields[at(x, y)] end
Enumerate fields next to a given position
@param [Integer] x
the x coordinate
@param [Integer] y
the y coordinate
@return [Enumerator]
the coordinates for nearby fields
@api public
# File lib/minehunter/grid.rb, line 219 def fields_next_to(x, y) return to_enum(:fields_next_to, x, y) unless block_given? -1.upto(1) do |offset_x| -1.upto(1) do |offset_y| close_x = x + offset_x close_y = y + offset_y next if close_x == x && close_y == y next unless within?(close_x, close_y) yield(close_x, close_y) end end end
Fill grid with mines skipping the current position and nearby fields
@param [Integer] x
the x coordinate
@param [Integer] y
the y coordinate
@param [Proc] randomiser
the mine position randomiser
@api public
# File lib/minehunter/grid.rb, line 192 def fill_with_mines(x, y, randomiser: DEFAULT_RANDOMISER) limit = @mines_limit while limit > 0 mine_x = randomiser[@width] mine_y = randomiser[@height] next if mine_x == x && mine_y == y next if fields_next_to(x, y).include?([mine_x, mine_y]) field = field_at(mine_x, mine_y) next if field.mine? field.mine! limit -= 1 end end
Add or remove a flag at a given position
@param [Integer] x
the x coordinate
@param [Integer] y
the y coordinate
@api public
# File lib/minehunter/grid.rb, line 160 def flag(x, y) field = field_at(x, y) return unless field.cover? @flags_remaining += field.flag? ? 1 : -1 field.flag end
Check whether or not there is a flag at a given position
@param [Integer] x
the x coordinate
@param [Integer] y
the y coordinate
@return [Boolean]
@api public
# File lib/minehunter/grid.rb, line 178 def flag?(x, y) field_at(x, y).flag? end
Set a mine at a given position
@param [Integer] x
the x coordinate
@param [Integer] y
the y coordinate
@api public
# File lib/minehunter/grid.rb, line 148 def mine(x, y) field_at(x, y).mine! end
All fields with mines
@return [Array<Field>]
@api public
# File lib/minehunter/grid.rb, line 72 def mines @fields.select(&:mine?) end
Move down on the grid
@return [Integer]
@api public
# File lib/minehunter/grid.rb, line 90 def move_down(y) y == @height - 1 ? 0 : y + 1 end
Move left on the grid
@return [Integer]
@api public
# File lib/minehunter/grid.rb, line 99 def move_left(x) x.zero? ? @width - 1 : x - 1 end
Move right on the grid
@return [Integer]
@api public
# File lib/minehunter/grid.rb, line 108 def move_right(x) x == @width - 1 ? 0 : x + 1 end
Move up on the grid
@return [Integer]
@api public
# File lib/minehunter/grid.rb, line 81 def move_up(y) y.zero? ? @height - 1 : y - 1 end
Render grid
@return [String]
@api public
# File lib/minehunter/grid.rb, line 363 def render(x, y, decorator: DEFAULT_DECORATOR) out = [] @height.times do |field_y| @width.times do |field_x| field = field_at(field_x, field_y) rendered_field = field.render(decorator: decorator) if field_x == x && field_y == y && decorator bg_color = field.mine? && !field.cover? ? :on_red : :on_green rendered_field = decorator[rendered_field, bg_color] end out << rendered_field end out << "\n" end out.join end
Reset all fields to defaults
@api public
# File lib/minehunter/grid.rb, line 50 def reset (@width * @height).times do |i| @fields[i] = Field.new end @unmined_fields_remaining = @width * @height - @mines_limit @flags_remaining = @mines_limit end
Uncover fields surrounding the position
@param [Integer] x
the x coordinate
@param [Integer] y
the y coordinate
@return [Boolean]
whether or not uncovered a mine
@api public
# File lib/minehunter/grid.rb, line 287 def uncover(x, y) field = field_at(x, y) if field.mine? field.uncover uncover_mines return true end return uncover_around(x, y) unless field.cover? mine_count = count_mines_next_to(x, y) field.mine_count = mine_count flag(x, y) if field.flag? field.uncover @unmined_fields_remaining -= 1 if mine_count.zero? fields_next_to(x, y) do |close_x, close_y| close_field = field_at(close_x, close_y) next if !close_field.cover? || close_field.mine? uncover(close_x, close_y) end end false end
Uncover fields around numbered field matching flags count
@param [Integer] x
the x coordinate
@param [Integer] y
the y coordinate
@return [Boolean]
whether or not uncovered a mine
@api public
# File lib/minehunter/grid.rb, line 326 def uncover_around(x, y) field = field_at(x, y) uncovered_mine = false if count_flags_next_to(x, y) != field.mine_count return uncovered_mine end fields_next_to(x, y) do |close_x, close_y| close_field = field_at(close_x, close_y) next if !close_field.cover? || close_field.flag? uncover(close_x, close_y) uncovered_mine = true if close_field.mine? end uncovered_mine end
Uncover all mines without a flag
@api public
# File lib/minehunter/grid.rb, line 349 def uncover_mines @fields.each do |field| if field.mine? && !field.flag? || field.flag? && !field.mine? field.wrong if field.flag? field.uncover end end end
Check whether coordinates are within the grid
return [Boolean]
@api public
# File lib/minehunter/grid.rb, line 240 def within?(x, y) x >= 0 && x < @width && y >= 0 && y < @height end