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
+ ---
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
+