minesweeper-core 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/svarlet/minesweeper.svg?branch=master)](https://travis-ci.org/svarlet/minesweeper)
|
2
|
+
[![Coverage Status](https://coveralls.io/repos/svarlet/minesweeper/badge.svg?branch=master)](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
|
+
|