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
data/minesweeper.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'minesweeper/core/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "minesweeper-core"
|
8
|
+
spec.version = Minesweeper::Core::VERSION
|
9
|
+
spec.authors = ["Sebastien Varlet"]
|
10
|
+
spec.email = ["sebastien.varlet@gmail.com"]
|
11
|
+
spec.summary = %q{Core model of the minesweeper game.}
|
12
|
+
spec.description = %q{This gem defines the core classes of a minesweeper game such as Minefield, Cell, and Mine.}
|
13
|
+
spec.homepage = "https://github.com/svarlet/minesweeper"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "coveralls"
|
24
|
+
spec.add_development_dependency "byebug"
|
25
|
+
spec.add_development_dependency "guard"
|
26
|
+
spec.add_development_dependency "guard-test"
|
27
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'minesweeper/core/elements/cell_state'
|
2
|
+
|
3
|
+
module Minesweeper
|
4
|
+
module Core
|
5
|
+
module Elements
|
6
|
+
class CellStateSpy
|
7
|
+
attr_reader :flag_called, :reveal_called, :unflag_called, :to_s_called
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@flag_called = false
|
11
|
+
@reveal_called = false
|
12
|
+
@unflag_called = false
|
13
|
+
@to_s_called = false
|
14
|
+
end
|
15
|
+
|
16
|
+
def flag(cell)
|
17
|
+
@flag_called = true
|
18
|
+
end
|
19
|
+
|
20
|
+
def reveal(cell)
|
21
|
+
@reveal_called = true
|
22
|
+
end
|
23
|
+
|
24
|
+
def unflag(cell)
|
25
|
+
@unflag_called = true
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
@to_s_called = true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require_relative '../../../test_helper.rb'
|
2
|
+
require 'minesweeper/core/elements/cell'
|
3
|
+
require 'minesweeper/core/elements/hidden_state'
|
4
|
+
require 'minesweeper/core/explosives/null_mine'
|
5
|
+
require_relative '../elements/cell_state_spy'
|
6
|
+
require_relative '../explosives/mine_spy'
|
7
|
+
|
8
|
+
module Minesweeper
|
9
|
+
module Core
|
10
|
+
module Elements
|
11
|
+
class CellTest < Test::Unit::TestCase
|
12
|
+
def setup
|
13
|
+
@cell = Cell.new(Explosives::MineSpy.new)
|
14
|
+
@state_spy = CellStateSpy.new
|
15
|
+
@cell.current_state = @state_spy
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_initialize_should_set_current_state_to_hidden
|
19
|
+
assert_equal(CellState::HIDDEN_STATE, Cell.new(Minesweeper::Core::Explosives::MineSpy.new).current_state)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_flag_should_call_the_flag_method_of_the_current_state
|
23
|
+
@cell.flag
|
24
|
+
assert @state_spy.flag_called
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_reveal_should_call_the_reveal_method_of_the_current_state
|
28
|
+
@cell.reveal
|
29
|
+
assert @state_spy.reveal_called
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_unflag_should_call_the_unflag_method_of_the_current_state
|
33
|
+
@cell.unflag
|
34
|
+
assert @state_spy.unflag_called
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_to_s_should_delegate_to_state
|
38
|
+
@cell.to_s
|
39
|
+
assert @state_spy.to_s_called
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_trigger_should_delegate_to_underlying_mine
|
43
|
+
a_mine_spy = Explosives::MineSpy.new
|
44
|
+
@cell = Cell.new(a_mine_spy)
|
45
|
+
@cell.trigger
|
46
|
+
assert(a_mine_spy.trigger_called)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_given_a_cell_with_no_mines_when_revealed_then_to_s_returns_mines_around
|
50
|
+
safe_cell = Cell.new(Explosives::NullMine.new, 1)
|
51
|
+
safe_cell.reveal
|
52
|
+
assert_equal('1', safe_cell.to_s)
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_when_a_cell_is_revealed_then_revealed_should_be_true
|
56
|
+
cell = Cell.new(Explosives::NullMine.new, 0)
|
57
|
+
cell.reveal
|
58
|
+
assert_true(cell.revealed?)
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_flagged_is_false_until_cell_is_flagged
|
62
|
+
cell = Cell.new(Explosives::NullMine.new, 0)
|
63
|
+
assert_false(cell.flagged?)
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_flagged_is_false_after_cell_is_unflagged
|
67
|
+
cell = Cell.new(Explosives::NullMine.new, 0)
|
68
|
+
cell.flag
|
69
|
+
cell.unflag
|
70
|
+
assert_false(cell.flagged?)
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_flagged_is_true_after_cell_is_flagged
|
74
|
+
cell = Cell.new(Explosives::NullMine.new, 0)
|
75
|
+
cell.flag
|
76
|
+
assert_true(cell.flagged?)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require_relative '../../../test_helper.rb'
|
2
|
+
require 'minesweeper/core/elements/cell'
|
3
|
+
require_relative '../explosives/mine_spy'
|
4
|
+
|
5
|
+
module Minesweeper
|
6
|
+
module Core
|
7
|
+
module Elements
|
8
|
+
class FlaggedStateTest < Test::Unit::TestCase
|
9
|
+
def setup
|
10
|
+
@mine_spy = Minesweeper::Core::Explosives::MineSpy.new
|
11
|
+
@cell = Cell.new(@mine_spy)
|
12
|
+
@cell.flag
|
13
|
+
@state = CellState::FLAGGED_STATE
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_flag_should_raise_NoMethodError
|
17
|
+
assert_raise(NoMethodError) { @state.flag(@cell) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_unflag_should_change_state_of_cell_to_hidden
|
21
|
+
@state.unflag(@cell)
|
22
|
+
assert_equal(CellState::HIDDEN_STATE, @cell.current_state)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_reveal_should_change_state_of_cell_to_revealed
|
26
|
+
@state.reveal(@cell)
|
27
|
+
assert_equal(CellState::REVEALED_STATE, @cell.current_state)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_reveal_should_trigger_the_underlying_mine
|
31
|
+
@state.reveal(@cell)
|
32
|
+
assert(@mine_spy.trigger_called)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_to_s_should_return_F
|
36
|
+
assert_equal("F", @state.to_s)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require_relative '../../../test_helper.rb'
|
2
|
+
require 'minesweeper/core/elements/cell'
|
3
|
+
require_relative '../explosives/mine_spy'
|
4
|
+
|
5
|
+
module Minesweeper
|
6
|
+
module Core
|
7
|
+
module Elements
|
8
|
+
class HiddenStateTest < Test::Unit::TestCase
|
9
|
+
def setup
|
10
|
+
@state = CellState::HIDDEN_STATE
|
11
|
+
@mine_spy = Minesweeper::Core::Explosives::MineSpy.new
|
12
|
+
@cell = Cell.new(@mine_spy)
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_unflag_should_raise_NoMethodError
|
16
|
+
assert_raise(NoMethodError) { @state.unflag(@cell) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_flag_should_change_state_of_cell_to_flagged
|
20
|
+
@state.flag(@cell)
|
21
|
+
assert_equal(CellState::FLAGGED_STATE, @cell.current_state)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_reveal_should_change_state_of_cell_to_revealed
|
25
|
+
@state.reveal(@cell)
|
26
|
+
assert_equal(CellState::REVEALED_STATE, @cell.current_state)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_reveal_should_trigger_the_underlying_mine
|
30
|
+
@state.reveal(@cell)
|
31
|
+
assert(@mine_spy.trigger_called)
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_to_s_should_return_H
|
35
|
+
assert_equal("H", @state.to_s)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative '../../../test_helper.rb'
|
2
|
+
require 'minesweeper/core/elements/cell'
|
3
|
+
require_relative '../explosives/mine_spy'
|
4
|
+
|
5
|
+
module Minesweeper
|
6
|
+
module Core
|
7
|
+
module Elements
|
8
|
+
class RevealedStateTest < Test::Unit::TestCase
|
9
|
+
def setup
|
10
|
+
@cell = Cell.new(Minesweeper::Core::Explosives::MineSpy.new)
|
11
|
+
@cell.reveal
|
12
|
+
@state = CellState::REVEALED_STATE
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_flag_should_raise_NoMethodError
|
16
|
+
assert_raise(NoMethodError) { @state.flag(@cell) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_reveal_should_raise_cell_state_error
|
20
|
+
assert_raise(NoMethodError) { @state.reveal(@cell) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_unflag_should_raise_cell_state_error
|
24
|
+
assert_raise(NoMethodError) { @state.unflag(@cell) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_to_s_should_return_R
|
28
|
+
assert_equal("R", @state.to_s)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require_relative '../../../test_helper'
|
2
|
+
require 'minesweeper/core/explosives/mine_coordinates_factory'
|
3
|
+
require 'minesweeper/core/explosives/mine_coordinates'
|
4
|
+
|
5
|
+
module Minesweeper
|
6
|
+
module Core
|
7
|
+
module Explosives
|
8
|
+
class MineCoordinatesFactoryTest < Test::Unit::TestCase
|
9
|
+
def test_create_should_return_a_pair_of_mine_coordinates
|
10
|
+
sut = MineCoordinatesFactory.new(Random.new)
|
11
|
+
mc = sut.create(1)
|
12
|
+
assert_instance_of(MineCoordinates, mc)
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_create_should_use_the_random_number_generator_to_produce_the_coordinates
|
16
|
+
spy = RandomNumberGeneratorSpy.new
|
17
|
+
sut = MineCoordinatesFactory.new(spy)
|
18
|
+
upper_bound = 20
|
19
|
+
|
20
|
+
mc = sut.create(upper_bound)
|
21
|
+
|
22
|
+
assert_equal(upper_bound, spy.generated_numbers[0].max)
|
23
|
+
assert_equal(mc.row_index, spy.generated_numbers[0].value)
|
24
|
+
|
25
|
+
assert_equal(upper_bound, spy.generated_numbers[1].max)
|
26
|
+
assert_equal(mc.col_index, spy.generated_numbers[1].value)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class RandomNumberGeneratorSpy
|
31
|
+
attr_reader :generated_numbers
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
@generated_numbers = []
|
35
|
+
@random_number_generator = Random.new
|
36
|
+
end
|
37
|
+
|
38
|
+
def rand(max)
|
39
|
+
a_generated_number = RandomNumber.new(max, @random_number_generator.rand(max))
|
40
|
+
@generated_numbers << a_generated_number
|
41
|
+
a_generated_number.value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class RandomNumber
|
46
|
+
attr_reader :max, :value
|
47
|
+
|
48
|
+
def initialize(max, value)
|
49
|
+
@max = max
|
50
|
+
@value = value
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'minesweeper/core/explosives/mine_coordinates'
|
2
|
+
require_relative '../../../test_helper'
|
3
|
+
|
4
|
+
module Minesweeper
|
5
|
+
module Core
|
6
|
+
module Explosives
|
7
|
+
class MineCoordinatesTest < Test::Unit::TestCase
|
8
|
+
def test_is_initializable
|
9
|
+
assert_nothing_raised { MineCoordinates.new(0,0) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_properties_are_initialized_by_the_constructor
|
13
|
+
mc = MineCoordinates.new(12, 17)
|
14
|
+
assert_equal(12, mc.row_index)
|
15
|
+
assert_equal(17, mc.col_index)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_immutability
|
19
|
+
mc = MineCoordinates.new(0, 0)
|
20
|
+
assert_raise(NoMethodError) { mc.row_index = 1 }
|
21
|
+
assert_equal(0, mc.row_index)
|
22
|
+
assert_raise(NoMethodError) { mc.col_index = 1 }
|
23
|
+
assert_equal(0, mc.col_index)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_2_different_objects_with_same_properties_are_equal
|
27
|
+
a = MineCoordinates.new(0, 0)
|
28
|
+
b = MineCoordinates.new(0, 0)
|
29
|
+
assert_equal(a, b)
|
30
|
+
assert_true(a == b)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require_relative '../../../test_helper'
|
2
|
+
require 'minesweeper/core/explosives/mine_layer'
|
3
|
+
require 'minesweeper/core/minefield'
|
4
|
+
require 'minesweeper/core/explosives/mine_coordinates_factory'
|
5
|
+
|
6
|
+
module Minesweeper
|
7
|
+
module Core
|
8
|
+
module Explosives
|
9
|
+
class MineLayerTest < Test::Unit::TestCase
|
10
|
+
MINEFIELD_SIZE = 19
|
11
|
+
|
12
|
+
def setup
|
13
|
+
@minefield = MinefieldSpy.new(MINEFIELD_SIZE)
|
14
|
+
@mine_generator = MineGeneratorSpy.new
|
15
|
+
@sut = MineLayer.new(@minefield, @mine_generator)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_initialize_requires_a_minefield_and_a_mine_factory
|
19
|
+
assert_raise(ArgumentError) { MineLayer.new(Object.new, nil)}
|
20
|
+
assert_raise(ArgumentError) { MineLayer.new(nil, Object.new)}
|
21
|
+
assert_raise(ArgumentError) { MineLayer.new(nil, nil)}
|
22
|
+
assert_nothing_raised { MineLayer.new(@minefield, MineCoordinatesFactory.new(Random.new)) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_lay_1_should_generate_1_mine_within_the_minefield_bounds
|
26
|
+
@sut.lay 1
|
27
|
+
assert_equal(MINEFIELD_SIZE, @mine_generator.create_call_params.pop)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_lay_2_should_generate_2_mines_within_the_minefield_bounds
|
31
|
+
@sut.lay 2
|
32
|
+
2.times { assert_equal(MINEFIELD_SIZE, @mine_generator.create_call_params.pop) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_lay_1_should_lay_a_single_mine_at_random_coordinates
|
36
|
+
@sut.lay 1
|
37
|
+
assert_generated_mines_were_laid(1)
|
38
|
+
end
|
39
|
+
|
40
|
+
def assert_generated_mines_were_laid(quantity)
|
41
|
+
assert_equal(quantity, @mine_generator.generated_mines.length)
|
42
|
+
assert_equal(quantity, @minefield.mines_laid.length)
|
43
|
+
quantity.times { assert_equal(@mine_generator.generated_mines.pop, @minefield.mines_laid.pop) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_lay_2_mines_should_lay_exactly_2_mines_at_random_coordinates
|
47
|
+
@sut.lay 2
|
48
|
+
assert_generated_mines_were_laid(2)
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_lay_should_never_lay_2_mine_at_the_same_coordinates
|
52
|
+
a_mine = MineCoordinates.new(1, 1)
|
53
|
+
same_mine = MineCoordinates.new(1, 1)
|
54
|
+
different_mine = MineCoordinates.new(1, 2)
|
55
|
+
dummy_mine_generator = DummyMineGenerator.new([a_mine, same_mine, different_mine])
|
56
|
+
sut = MineLayer.new(@minefield, dummy_mine_generator)
|
57
|
+
|
58
|
+
sut.lay(2)
|
59
|
+
|
60
|
+
assert_equal(@minefield.mines_laid, [a_mine, different_mine])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class MinefieldSpy
|
65
|
+
attr_reader :mines_laid
|
66
|
+
attr_reader :row_count
|
67
|
+
|
68
|
+
def initialize(row_count)
|
69
|
+
@mines_laid = []
|
70
|
+
@row_count = row_count
|
71
|
+
end
|
72
|
+
|
73
|
+
def hide_mine_at(x, y)
|
74
|
+
@mines_laid << MineCoordinates.new(x, y)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class MineGeneratorSpy < MineCoordinatesFactory
|
79
|
+
attr_reader :generated_mines
|
80
|
+
attr_reader :create_call_params
|
81
|
+
|
82
|
+
def initialize
|
83
|
+
super Random.new
|
84
|
+
@generated_mines = []
|
85
|
+
@create_call_params = []
|
86
|
+
end
|
87
|
+
|
88
|
+
def create(max)
|
89
|
+
@create_call_params << max
|
90
|
+
a_mine = super(max)
|
91
|
+
@generated_mines << a_mine
|
92
|
+
a_mine
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class DummyMineGenerator < MineCoordinatesFactory
|
97
|
+
def initialize(preconstructed_mines)
|
98
|
+
@preconstructed_mines = preconstructed_mines
|
99
|
+
end
|
100
|
+
|
101
|
+
def create(max)
|
102
|
+
@preconstructed_mines.shift
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|