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
+ ---
2
+ SHA1:
3
+ metadata.gz: 996b180c564f2f0c5e1ae87b8e5d2c4664cebbae
4
+ data.tar.gz: cd87e6240b920aec8d036937b5debf00bdd70e5c
5
+ SHA512:
6
+ metadata.gz: dd39810b52861ad5c8ab311c8fd88eb3d6d868ffcacf5ab42b4397cdd391b3c5b5a77ceaf7a2f02e45f14153ea01ca490ab4a1373b926a7652935172733a8601
7
+ data.tar.gz: d3202184311226d28973e6a7a5a18aa9d8729f8a3ddc5e2bce04a8960b0debf7c0aab802eaca19dcabb8bbc3cc60fc08ea578d1c45f976cb0083dc2eb6fe239c
@@ -0,0 +1 @@
1
+ service_name: travis-ci
@@ -0,0 +1,20 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ .idea/
16
+ *.gem
17
+ *.iml
18
+ *.ipr
19
+ *.iws
20
+
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in minesweeper.gemspec
4
+ gemspec
@@ -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
@@ -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.
@@ -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
+
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.test_files = FileList['test/**/*_test.rb']
7
+ t.verbose = true
8
+ t.warning = true
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,6 @@
1
+ require 'minesweeper/core/version'
2
+ require 'minesweeper/core/minefield'
3
+ require 'minesweeper/core/explosives/explosion_error'
4
+ require 'minesweeper/core/explosives/mine_coordinates_factory'
5
+ require 'minesweeper/core/explosives/mine_layer'
6
+
@@ -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,12 @@
1
+ module Minesweeper
2
+ module Core
3
+ module Elements
4
+ class RevealedState
5
+ def to_s
6
+ "R"
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,10 @@
1
+ module Minesweeper
2
+ module Core
3
+ module Explosives
4
+ class ExplosionError < StandardError
5
+ end
6
+ end
7
+ end
8
+
9
+ end
10
+
@@ -0,0 +1,14 @@
1
+ require_relative 'explosion_error'
2
+
3
+ module Minesweeper
4
+ module Core
5
+ module Explosives
6
+ class Mine
7
+ def trigger
8
+ raise ExplosionError, 'You triggered a mine! Game over.'
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+
@@ -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,12 @@
1
+ module Minesweeper
2
+ module Core
3
+ module Explosives
4
+ class NullMine
5
+ def trigger
6
+ #do nothing, it's a null mine
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
12
+
@@ -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
+