class Minehunter::Grid

A grid with fields representation

@api private

Attributes

flags_remaining[R]

Track the number of flags remaining

@return [Integer]

@api public

unmined_fields_remaining[R]

Track the number of unmined fields remaining

@return [Integer]

@api public

Public Class Methods

new(width: nil, height: nil, mines_limit: nil) click to toggle source

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

at(x, y) click to toggle source

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
cleared?() click to toggle source

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
count_flags_next_to(x, y) click to toggle source

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
count_mines_next_to(x, y) click to toggle source

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
field_at(x, y) click to toggle source

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
fields_next_to(x, y) { |close_x, close_y| ... } click to toggle source

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_with_mines(x, y, randomiser: DEFAULT_RANDOMISER) click to toggle source

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
flag(x, y) click to toggle source

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
flag?(x, y) click to toggle source

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
mine(x, y) click to toggle source

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
mines() click to toggle source

All fields with mines

@return [Array<Field>]

@api public

# File lib/minehunter/grid.rb, line 72
def mines
  @fields.select(&:mine?)
end
move_down(y) click to toggle source

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(x) click to toggle source

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(x) click to toggle source

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(y) click to toggle source

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(x, y, decorator: DEFAULT_DECORATOR) click to toggle source

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() click to toggle source

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(x, y) click to toggle source

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_around(x, y) click to toggle source

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_mines() click to toggle source

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
within?(x, y) click to toggle source

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