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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +20 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +4 -0
  6. data/Guardfile +30 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +24 -0
  9. data/Rakefile +11 -0
  10. data/lib/minesweeper/core.rb +6 -0
  11. data/lib/minesweeper/core/elements/cell.rb +57 -0
  12. data/lib/minesweeper/core/elements/cell_state.rb +16 -0
  13. data/lib/minesweeper/core/elements/flagged_state.rb +21 -0
  14. data/lib/minesweeper/core/elements/hidden_state.rb +21 -0
  15. data/lib/minesweeper/core/elements/revealed_state.rb +12 -0
  16. data/lib/minesweeper/core/explosives/explosion_error.rb +10 -0
  17. data/lib/minesweeper/core/explosives/mine.rb +14 -0
  18. data/lib/minesweeper/core/explosives/mine_coordinates.rb +27 -0
  19. data/lib/minesweeper/core/explosives/mine_coordinates_factory.rb +21 -0
  20. data/lib/minesweeper/core/explosives/mine_layer.rb +32 -0
  21. data/lib/minesweeper/core/explosives/null_mine.rb +12 -0
  22. data/lib/minesweeper/core/minefield.rb +118 -0
  23. data/lib/minesweeper/core/minefield_solved_error.rb +7 -0
  24. data/lib/minesweeper/core/version.rb +6 -0
  25. data/minesweeper.gemspec +27 -0
  26. data/test/minesweeper/core/elements/cell_state_spy.rb +35 -0
  27. data/test/minesweeper/core/elements/cell_test.rb +82 -0
  28. data/test/minesweeper/core/elements/flagged_state_test.rb +42 -0
  29. data/test/minesweeper/core/elements/hidden_state_test.rb +41 -0
  30. data/test/minesweeper/core/elements/revealed_state_test.rb +34 -0
  31. data/test/minesweeper/core/explosives/mine_coordinates_factory_test.rb +57 -0
  32. data/test/minesweeper/core/explosives/mine_coordinates_test.rb +37 -0
  33. data/test/minesweeper/core/explosives/mine_layer_test.rb +109 -0
  34. data/test/minesweeper/core/explosives/mine_spy.rb +14 -0
  35. data/test/minesweeper/core/explosives/mine_test.rb +16 -0
  36. data/test/minesweeper/core/explosives/null_mine_test.rb +15 -0
  37. data/test/minesweeper/core/minefield_test.rb +121 -0
  38. data/test/test_helper.rb +4 -0
  39. metadata +179 -0
@@ -0,0 +1,7 @@
1
+ module Minesweeper
2
+ module Core
3
+ class MinefieldSolvedError < StandardError
4
+ end
5
+ end
6
+ end
7
+
@@ -0,0 +1,6 @@
1
+ module Minesweeper
2
+ module Core
3
+ VERSION = "2.0.0"
4
+ end
5
+ end
6
+
@@ -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
+