minesweeper-core 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +20 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/Guardfile +30 -0
- data/LICENSE.txt +22 -0
- data/README.md +24 -0
- data/Rakefile +11 -0
- data/lib/minesweeper/core.rb +6 -0
- data/lib/minesweeper/core/elements/cell.rb +57 -0
- data/lib/minesweeper/core/elements/cell_state.rb +16 -0
- data/lib/minesweeper/core/elements/flagged_state.rb +21 -0
- data/lib/minesweeper/core/elements/hidden_state.rb +21 -0
- data/lib/minesweeper/core/elements/revealed_state.rb +12 -0
- data/lib/minesweeper/core/explosives/explosion_error.rb +10 -0
- data/lib/minesweeper/core/explosives/mine.rb +14 -0
- data/lib/minesweeper/core/explosives/mine_coordinates.rb +27 -0
- data/lib/minesweeper/core/explosives/mine_coordinates_factory.rb +21 -0
- data/lib/minesweeper/core/explosives/mine_layer.rb +32 -0
- data/lib/minesweeper/core/explosives/null_mine.rb +12 -0
- data/lib/minesweeper/core/minefield.rb +118 -0
- data/lib/minesweeper/core/minefield_solved_error.rb +7 -0
- data/lib/minesweeper/core/version.rb +6 -0
- data/minesweeper.gemspec +27 -0
- data/test/minesweeper/core/elements/cell_state_spy.rb +35 -0
- data/test/minesweeper/core/elements/cell_test.rb +82 -0
- data/test/minesweeper/core/elements/flagged_state_test.rb +42 -0
- data/test/minesweeper/core/elements/hidden_state_test.rb +41 -0
- data/test/minesweeper/core/elements/revealed_state_test.rb +34 -0
- data/test/minesweeper/core/explosives/mine_coordinates_factory_test.rb +57 -0
- data/test/minesweeper/core/explosives/mine_coordinates_test.rb +37 -0
- data/test/minesweeper/core/explosives/mine_layer_test.rb +109 -0
- data/test/minesweeper/core/explosives/mine_spy.rb +14 -0
- data/test/minesweeper/core/explosives/mine_test.rb +16 -0
- data/test/minesweeper/core/explosives/null_mine_test.rb +15 -0
- data/test/minesweeper/core/minefield_test.rb +121 -0
- data/test/test_helper.rb +4 -0
- metadata +179 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 996b180c564f2f0c5e1ae87b8e5d2c4664cebbae
|
4
|
+
data.tar.gz: cd87e6240b920aec8d036937b5debf00bdd70e5c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dd39810b52861ad5c8ab311c8fd88eb3d6d868ffcacf5ab42b4397cdd391b3c5b5a77ceaf7a2f02e45f14153ea01ca490ab4a1373b926a7652935172733a8601
|
7
|
+
data.tar.gz: d3202184311226d28973e6a7a5a18aa9d8729f8a3ddc5e2bce04a8960b0debf7c0aab802eaca19dcabb8bbc3cc60fc08ea578d1c45f976cb0083dc2eb6fe239c
|
data/.coveralls.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
service_name: travis-ci
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec features)
|
6
|
+
|
7
|
+
## Uncomment to clear the screen before every task
|
8
|
+
clearing :on
|
9
|
+
|
10
|
+
## Guard internally checks for changes in the Guardfile and exits.
|
11
|
+
## If you want Guard to automatically start up again, run guard in a
|
12
|
+
## shell loop, e.g.:
|
13
|
+
##
|
14
|
+
## $ while bundle exec guard; do echo "Restarting Guard..."; done
|
15
|
+
##
|
16
|
+
## Note: if you are using the `directories` clause above and you are not
|
17
|
+
## watching the project directory ('.'), then you will want to move
|
18
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
19
|
+
#
|
20
|
+
# $ mkdir config
|
21
|
+
# $ mv Guardfile config/
|
22
|
+
# $ ln -s config/Guardfile .
|
23
|
+
#
|
24
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
25
|
+
|
26
|
+
guard :test do
|
27
|
+
watch(%r{^test/.+_test\.rb$})
|
28
|
+
watch('test/../test_helper.rb') { 'test' }
|
29
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" }
|
30
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Sebastien Varlet
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
[](https://travis-ci.org/svarlet/minesweeper)
|
2
|
+
[](https://coveralls.io/r/svarlet/minesweeper?branch=master)
|
3
|
+
|
4
|
+
# Minesweeper
|
5
|
+
|
6
|
+
The rules of the minesweeper game are explained there: http://en.wikipedia.org/wiki/Minesweeper_(video_game).
|
7
|
+
|
8
|
+
This repository contains a ruby gem that defines the core concepts of a 2D minesweeper game: minefield, cells, mines and the user interactions with the minefield (reveal, flag, unflag).
|
9
|
+
|
10
|
+
Therefore, this gem is not a playable version of the game but if you want to try it, I also built a [terminal frontend](https://github.com/svarlet/minesweeper-console) which uses it.
|
11
|
+
|
12
|
+
## Install
|
13
|
+
|
14
|
+
Sadly, this gem can't be pushed to rubygems as there are already a pair of gems with the same name. I may or may not rename my gem to fix this. Meanwhile, one can simply do:
|
15
|
+
```
|
16
|
+
git clone https://github.com/svarlet/minesweeper.git
|
17
|
+
cd minesweeper
|
18
|
+
rake build install
|
19
|
+
```
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
An example of the usage of this library can be found in my terminal based implementation of this game: [svarlet/minesweeper-console](https://github.com/svarlet/minesweeper-console).
|
24
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require_relative 'cell_state'
|
2
|
+
|
3
|
+
module Minesweeper
|
4
|
+
module Core
|
5
|
+
module Elements
|
6
|
+
class Cell
|
7
|
+
# STATES
|
8
|
+
# ---------------------------------------------------------
|
9
|
+
# CURRENT | ACTION | STATES | ACTION
|
10
|
+
# ---------------------------------------------------------
|
11
|
+
# hidden | reveal | revealed | trigger
|
12
|
+
# hidden | flag | flagged |
|
13
|
+
# flagged | reveal | revealed | trigger
|
14
|
+
# flagged | unflag | hidden |
|
15
|
+
# ---------------------------------------------------------
|
16
|
+
|
17
|
+
attr_accessor :current_state
|
18
|
+
attr_accessor :mines_around
|
19
|
+
|
20
|
+
def initialize(mine, mines_around = 0)
|
21
|
+
@current_state = CellState::HIDDEN_STATE
|
22
|
+
@mine = mine
|
23
|
+
@mines_around = mines_around
|
24
|
+
end
|
25
|
+
|
26
|
+
def flag
|
27
|
+
current_state.flag(self)
|
28
|
+
end
|
29
|
+
|
30
|
+
def reveal
|
31
|
+
current_state.reveal(self)
|
32
|
+
end
|
33
|
+
|
34
|
+
def unflag
|
35
|
+
current_state.unflag(self)
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
@current_state == CellState::REVEALED_STATE ? @mines_around.to_s : @current_state.to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
def trigger
|
43
|
+
@mine.trigger
|
44
|
+
end
|
45
|
+
|
46
|
+
def revealed?
|
47
|
+
@current_state == CellState::REVEALED_STATE
|
48
|
+
end
|
49
|
+
|
50
|
+
def flagged?
|
51
|
+
@current_state == CellState::FLAGGED_STATE
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative 'hidden_state'
|
2
|
+
require_relative 'revealed_state'
|
3
|
+
require_relative 'flagged_state'
|
4
|
+
|
5
|
+
module Minesweeper
|
6
|
+
module Core
|
7
|
+
module Elements
|
8
|
+
module CellState
|
9
|
+
HIDDEN_STATE = HiddenState.new
|
10
|
+
FLAGGED_STATE = FlaggedState.new
|
11
|
+
REVEALED_STATE = RevealedState.new
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Minesweeper
|
2
|
+
module Core
|
3
|
+
module Elements
|
4
|
+
class FlaggedState
|
5
|
+
def reveal(cell)
|
6
|
+
cell.current_state = CellState::REVEALED_STATE
|
7
|
+
cell.trigger
|
8
|
+
end
|
9
|
+
|
10
|
+
def unflag(cell)
|
11
|
+
cell.current_state = CellState::HIDDEN_STATE
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
"F"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Minesweeper
|
2
|
+
module Core
|
3
|
+
module Elements
|
4
|
+
class HiddenState
|
5
|
+
def reveal(cell)
|
6
|
+
cell.current_state = CellState::REVEALED_STATE
|
7
|
+
cell.trigger
|
8
|
+
end
|
9
|
+
|
10
|
+
def flag(cell)
|
11
|
+
cell.current_state = CellState::FLAGGED_STATE
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
"H"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Minesweeper
|
2
|
+
module Core
|
3
|
+
module Explosives
|
4
|
+
class MineCoordinates
|
5
|
+
attr_reader :row_index, :col_index
|
6
|
+
|
7
|
+
def initialize(row_index, col_index)
|
8
|
+
@row_index = row_index
|
9
|
+
@col_index = col_index
|
10
|
+
end
|
11
|
+
|
12
|
+
def ==(other)
|
13
|
+
self.class == other.class &&
|
14
|
+
self.row_index == other.row_index &&
|
15
|
+
self.col_index == other.col_index
|
16
|
+
end
|
17
|
+
alias eql? :==
|
18
|
+
|
19
|
+
def hash
|
20
|
+
[@row_index, @col_index].hash
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'mine_coordinates'
|
2
|
+
|
3
|
+
module Minesweeper
|
4
|
+
module Core
|
5
|
+
module Explosives
|
6
|
+
class MineCoordinatesFactory
|
7
|
+
def initialize(random_number_generator)
|
8
|
+
@rng = random_number_generator
|
9
|
+
end
|
10
|
+
|
11
|
+
def create(coordinates_upper_bound)
|
12
|
+
row_index = @rng.rand(coordinates_upper_bound)
|
13
|
+
col_index = @rng.rand(coordinates_upper_bound)
|
14
|
+
MineCoordinates.new(row_index, col_index)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Minesweeper
|
2
|
+
module Core
|
3
|
+
module Explosives
|
4
|
+
class MineLayer
|
5
|
+
def initialize(a_minefield, a_mine_generator)
|
6
|
+
raise ArgumentError if a_minefield.nil?
|
7
|
+
raise ArgumentError if a_mine_generator.nil?
|
8
|
+
@generator = a_mine_generator
|
9
|
+
@minefield = a_minefield
|
10
|
+
end
|
11
|
+
|
12
|
+
def lay(quantity)
|
13
|
+
laid_mines = []
|
14
|
+
quantity.times do
|
15
|
+
loop do
|
16
|
+
coords = @generator.create(@minefield.row_count)
|
17
|
+
if laid_mines.include?(coords)
|
18
|
+
next
|
19
|
+
else
|
20
|
+
laid_mines << coords
|
21
|
+
@minefield.hide_mine_at(coords.row_index, coords.col_index)
|
22
|
+
break
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require_relative 'elements/cell'
|
2
|
+
require_relative 'explosives/null_mine'
|
3
|
+
require_relative 'explosives/explosion_error'
|
4
|
+
require_relative 'explosives/mine'
|
5
|
+
require_relative 'minefield_solved_error'
|
6
|
+
|
7
|
+
module Minesweeper
|
8
|
+
module Core
|
9
|
+
class Minefield
|
10
|
+
attr_reader :row_count
|
11
|
+
|
12
|
+
OFFSETS = [[-1, -1], [-1, 0], [-1, 1], [0, 1], [0, -1], [1, 1], [1, 0], [1, -1]]
|
13
|
+
|
14
|
+
def initialize(row_count)
|
15
|
+
raise ArgumentError unless row_count > 0
|
16
|
+
@row_count = row_count
|
17
|
+
@cells = Array.new(row_count) { Array.new(row_count) { create_non_explosive_cell } }
|
18
|
+
@all_mine_coords = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_non_explosive_cell
|
22
|
+
Elements::Cell.new(Explosives::NullMine.new)
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
@cells.flatten.join
|
27
|
+
end
|
28
|
+
|
29
|
+
def hide_mine_at(row_index, col_index)
|
30
|
+
verify_coordinates_existance(row_index, col_index)
|
31
|
+
create_explosive_cell_at(row_index, col_index)
|
32
|
+
register_mine_at(row_index, col_index)
|
33
|
+
increase_mine_counters_around(row_index, col_index)
|
34
|
+
end
|
35
|
+
|
36
|
+
def verify_coordinates_existance(row_index, col_index)
|
37
|
+
raise RangeError unless cell_exists_at?(row_index, col_index)
|
38
|
+
end
|
39
|
+
|
40
|
+
def create_explosive_cell_at(row_index, col_index)
|
41
|
+
mines_around = mines_adjacent_to(row_index, col_index)
|
42
|
+
@cells[row_index][col_index] = Elements::Cell.new(Explosives::Mine.new, mines_around)
|
43
|
+
end
|
44
|
+
|
45
|
+
def mines_adjacent_to(row_index, col_index)
|
46
|
+
cells_coords_around(row_index, col_index).count { |coords| @all_mine_coords.include?(coords) }
|
47
|
+
end
|
48
|
+
|
49
|
+
def cells_coords_around(row_index, col_index)
|
50
|
+
coords_around = OFFSETS.map { |coords| [row_index + coords[0], col_index + coords[1]] }
|
51
|
+
coords_around.select { |coords| cell_exists_at?(coords[0], coords[1]) }
|
52
|
+
end
|
53
|
+
|
54
|
+
def cell_exists_at?(row_index, col_index)
|
55
|
+
(0...@row_count).include?(row_index) && (0...@row_count).include?(col_index)
|
56
|
+
end
|
57
|
+
|
58
|
+
def register_mine_at(row_index, col_index)
|
59
|
+
@all_mine_coords << [row_index, col_index]
|
60
|
+
end
|
61
|
+
|
62
|
+
def increase_mine_counters_around(row_index, col_index)
|
63
|
+
cells_around(row_index, col_index).each { |cell| cell.mines_around += 1 }
|
64
|
+
end
|
65
|
+
|
66
|
+
def cells_around(row_index, col_index)
|
67
|
+
cells_coords_around(row_index, col_index).map { |coords| cell_at(coords[0], coords[1])}
|
68
|
+
end
|
69
|
+
|
70
|
+
def cell_at(row_index, col_index)
|
71
|
+
@cells[row_index][col_index]
|
72
|
+
end
|
73
|
+
|
74
|
+
def reveal_at(row_index, col_index)
|
75
|
+
verify_coordinates_existance(row_index, col_index)
|
76
|
+
return if cell_at(row_index, col_index).revealed?
|
77
|
+
cell_at(row_index, col_index).reveal
|
78
|
+
if cell_at(row_index, col_index).mines_around.zero?
|
79
|
+
cells_coords_around(row_index, col_index).each do |coords|
|
80
|
+
reveal_at(coords[0], coords[1]) unless cell_at(coords[0], coords[1]).revealed?
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def flag_at(row_index, col_index)
|
86
|
+
verify_coordinates_existance(row_index, col_index)
|
87
|
+
cell_at(row_index, col_index).flag
|
88
|
+
raise MinefieldSolvedError if !@all_mine_coords.empty? && all_mines_flagged?
|
89
|
+
end
|
90
|
+
|
91
|
+
def all_mines_flagged?
|
92
|
+
mined_cells = @all_mine_coords.map { |coords| cell_at(*coords) }
|
93
|
+
mined_cells.all?(&:flagged?)
|
94
|
+
end
|
95
|
+
|
96
|
+
def unflag_at(row_index, col_index)
|
97
|
+
verify_coordinates_existance(row_index, col_index)
|
98
|
+
cell_at(row_index, col_index).unflag
|
99
|
+
end
|
100
|
+
|
101
|
+
def mines_around(row_index, col_index)
|
102
|
+
cell_at(row_index, col_index).mines_around
|
103
|
+
end
|
104
|
+
|
105
|
+
private :create_non_explosive_cell
|
106
|
+
private :create_explosive_cell_at
|
107
|
+
private :cell_exists_at?
|
108
|
+
private :increase_mine_counters_around
|
109
|
+
private :mines_adjacent_to
|
110
|
+
private :cells_coords_around
|
111
|
+
private :register_mine_at
|
112
|
+
private :cells_around
|
113
|
+
private :cell_at
|
114
|
+
private :all_mines_flagged?
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|