minesweeper-core 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+