Class: Minehunter::Grid Private

Inherits:
Object
  • Object
show all
Defined in:
lib/minehunter/grid.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

A grid with fields representation

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(width: nil, height: nil, mines_limit: nil) ⇒ Grid

Create a Grid instance

Parameters:

  • width (Integer) (defaults to: nil)

    the number of columns

  • height (Integer) (defaults to: nil)

    the number of rows

  • mines_limit (Integer) (defaults to: nil)

    the total number of mines



34
35
36
37
38
39
40
41
42
43
44
45
# 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

Instance Attribute Details

#flags_remainingInteger (readonly)

Track the number of flags remaining

Returns:

  • (Integer)


15
16
17
# File 'lib/minehunter/grid.rb', line 15

def flags_remaining
  @flags_remaining
end

#unmined_fields_remainingInteger (readonly)

Track the number of unmined fields remaining

Returns:

  • (Integer)


22
23
24
# File 'lib/minehunter/grid.rb', line 22

def unmined_fields_remaining
  @unmined_fields_remaining
end

Instance Method Details

#at(x, y) ⇒ Integer

Find field index at a given position

Parameters:

  • x (Integer)

    the x coordinate

  • y (Integer)

    the y coordinate

Returns:

  • (Integer)


122
123
124
# File 'lib/minehunter/grid.rb', line 122

def at(x, y)
  y * @width + x
end

#cleared?Boolean

Check whether or not the grid is cleared

Returns:

  • (Boolean)


63
64
65
# File 'lib/minehunter/grid.rb', line 63

def cleared?
  @unmined_fields_remaining.zero?
end

#count_flags_next_to(x, y) ⇒ Integer

Total number of flags next to a given position

Parameters:

  • x (Integer)

    the x coordinate

  • y (Integer)

    the y coordinate

Returns:

  • (Integer)


270
271
272
273
274
# 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) ⇒ Integer

Total number of mines next to a given position

Parameters:

  • x (Integer)

    the x coordinate

  • y (Integer)

    the y coordinate

Returns:

  • (Integer)


254
255
256
257
258
# 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) ⇒ Field

Find a field at a given position

Parameters:

  • x (Integer)

    the x coordinate

  • y (Integer)

    the y coordinate

Returns:



136
137
138
# File 'lib/minehunter/grid.rb', line 136

def field_at(x, y)
  @fields[at(x, y)]
end

#fields_next_to(x, y) ⇒ Enumerator

Enumerate fields next to a given position

Parameters:

  • x (Integer)

    the x coordinate

  • y (Integer)

    the y coordinate

Returns:

  • (Enumerator)

    the coordinates for nearby fields



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# 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) ⇒ Object

Fill grid with mines skipping the current position and nearby fields

Parameters:

  • x (Integer)

    the x coordinate

  • y (Integer)

    the y coordinate

  • randomiser (Proc) (defaults to: DEFAULT_RANDOMISER)

    the mine position randomiser



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# 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) ⇒ Object

Add or remove a flag at a given position

Parameters:

  • x (Integer)

    the x coordinate

  • y (Integer)

    the y coordinate



160
161
162
163
164
165
166
# 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) ⇒ Boolean

Check whether or not there is a flag at a given position

Parameters:

  • x (Integer)

    the x coordinate

  • y (Integer)

    the y coordinate

Returns:

  • (Boolean)


178
179
180
# File 'lib/minehunter/grid.rb', line 178

def flag?(x, y)
  field_at(x, y).flag?
end

#mine(x, y) ⇒ Object

Set a mine at a given position

Parameters:

  • x (Integer)

    the x coordinate

  • y (Integer)

    the y coordinate



148
149
150
# File 'lib/minehunter/grid.rb', line 148

def mine(x, y)
  field_at(x, y).mine!
end

#minesArray<Field>

All fields with mines

Returns:



72
73
74
# File 'lib/minehunter/grid.rb', line 72

def mines
  @fields.select(&:mine?)
end

#move_down(y) ⇒ Integer

Move down on the grid

Returns:

  • (Integer)


90
91
92
# File 'lib/minehunter/grid.rb', line 90

def move_down(y)
  y == @height - 1 ? 0 : y + 1
end

#move_left(x) ⇒ Integer

Move left on the grid

Returns:

  • (Integer)


99
100
101
# File 'lib/minehunter/grid.rb', line 99

def move_left(x)
  x.zero? ? @width - 1 : x - 1
end

#move_right(x) ⇒ Integer

Move right on the grid

Returns:

  • (Integer)


108
109
110
# File 'lib/minehunter/grid.rb', line 108

def move_right(x)
  x == @width - 1 ? 0 : x + 1
end

#move_up(y) ⇒ Integer

Move up on the grid

Returns:

  • (Integer)


81
82
83
# File 'lib/minehunter/grid.rb', line 81

def move_up(y)
  y.zero? ? @height - 1 : y - 1
end

#render(x, y, decorator: DEFAULT_DECORATOR) ⇒ String

Render grid

Returns:

  • (String)


363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
# 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

#resetObject

Reset all fields to defaults



50
51
52
53
54
55
56
# 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) ⇒ Boolean

Uncover fields surrounding the position

Parameters:

  • x (Integer)

    the x coordinate

  • y (Integer)

    the y coordinate

Returns:

  • (Boolean)

    whether or not uncovered a mine



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# 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) ⇒ Boolean

Uncover fields around numbered field matching flags count

Parameters:

  • x (Integer)

    the x coordinate

  • y (Integer)

    the y coordinate

Returns:

  • (Boolean)

    whether or not uncovered a mine



326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
# 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_minesObject

Uncover all mines without a flag



349
350
351
352
353
354
355
356
# 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) ⇒ Boolean

Check whether coordinates are within the grid

return [Boolean]

Returns:

  • (Boolean)


240
241
242
# File 'lib/minehunter/grid.rb', line 240

def within?(x, y)
  x >= 0 && x < @width && y >= 0 && y < @height
end