minesweeper-curses 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2039f5bcd5de35121f6f1d4018769ab714ddb044
4
+ data.tar.gz: 3e225649c8b9be84ea534de481f4acd24fea5843
5
+ SHA512:
6
+ metadata.gz: d564d8a4a4ce20bfc6f9896e7b64aad95cf39d379c2798808febb4c41b94644a13b330c40e51a0659cd4d03fed5928dcb36aed077052d66f0b0da2a3bcba9179
7
+ data.tar.gz: df398eded885b498bd6f0c2c80ea2815aa14b11a2a8ce4f03dde0d3350455d1bf4531d12985abeb14a6aa44036027be6cc9db7e150291074e5acc5166ee050e7
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /coverage
2
+ /.bundle/
3
+ /.yardoc
4
+ /Gemfile.lock
5
+ /_yardoc/
6
+ /coverage/
7
+ /doc/
8
+ /pkg/
9
+ /spec/reports/
10
+ /tmp/
11
+ *.gem
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.4.1
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.4.1
4
+ cache: bundler
5
+ script:
6
+ - bundle exec rspec
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at mykhailo.rybak.if@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ gem 'pry'
8
+ gem 'simplecov', '~> 0.11.2'
data/Gemfile.lock ADDED
@@ -0,0 +1,59 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ minesweeper-curses (0.1.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ coderay (1.1.1)
10
+ curses (1.0.2)
11
+ diff-lcs (1.3)
12
+ docile (1.1.5)
13
+ json (1.8.6)
14
+ method_source (0.8.2)
15
+ minitest (5.10.2)
16
+ pry (0.10.4)
17
+ coderay (~> 1.1.0)
18
+ method_source (~> 0.8.1)
19
+ slop (~> 3.4)
20
+ rake (10.5.0)
21
+ rspec (3.6.0)
22
+ rspec-core (~> 3.6.0)
23
+ rspec-expectations (~> 3.6.0)
24
+ rspec-mocks (~> 3.6.0)
25
+ rspec-core (3.6.0)
26
+ rspec-support (~> 3.6.0)
27
+ rspec-expectations (3.6.0)
28
+ diff-lcs (>= 1.2.0, < 2.0)
29
+ rspec-support (~> 3.6.0)
30
+ rspec-mocks (3.6.0)
31
+ diff-lcs (>= 1.2.0, < 2.0)
32
+ rspec-support (~> 3.6.0)
33
+ rspec-support (3.6.0)
34
+ sequel (4.46.0)
35
+ simplecov (0.11.2)
36
+ docile (~> 1.1.0)
37
+ json (~> 1.8)
38
+ simplecov-html (~> 0.10.0)
39
+ simplecov-html (0.10.0)
40
+ slop (3.6.0)
41
+ sqlite3 (1.3.13)
42
+
43
+ PLATFORMS
44
+ ruby
45
+
46
+ DEPENDENCIES
47
+ bundler (~> 1.14)
48
+ curses (~> 1.0.2)
49
+ minesweeper-curses!
50
+ minitest (~> 5.0)
51
+ pry
52
+ rake (~> 10.0)
53
+ rspec (~> 3.6.0)
54
+ sequel
55
+ simplecov (~> 0.11.2)
56
+ sqlite3
57
+
58
+ BUNDLED WITH
59
+ 1.14.6
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 TODO: Write your name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'test'
6
+ t.libs << 'lib'
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
11
+
12
+ task :build_and_install do
13
+ sh('bundle install')
14
+ sh("gem install ./pkg/minesweeper-#{Minesweeper::VERSION}.gem")
15
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'minesweeper'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require 'pry'
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
5
+
6
+ load './lib/minesweeper.rb'
7
+
8
+ Minesweeper::GameInitializer.new.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,234 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minesweeper
4
+ class GameOver < StandardError
5
+ def message
6
+ 'You Lost'
7
+ end
8
+ end
9
+
10
+ class GameWon < StandardError
11
+ def message
12
+ 'You Won'
13
+ end
14
+ end
15
+
16
+ class Board
17
+ extend Forwardable
18
+
19
+ ENTER = 10
20
+ STEP = 3
21
+ LEFT_RIGHT_PADDING = 2
22
+ SPACE = ' '
23
+
24
+ def_delegators :@window, :curx, :cury
25
+
26
+ attr_reader :height, :width, :cells, :window
27
+
28
+ def initialize(height:, width:, window:)
29
+ @height = height
30
+ @width = width
31
+ @window = window
32
+ @cells = []
33
+ end
34
+
35
+ def play(stdy = nil, stdx = nil)
36
+ window.keypad = true
37
+ draw_board
38
+ window.setpos(stdy, stdx) if stdx && stdy
39
+ noecho
40
+
41
+ loop do
42
+ ch = window.getch
43
+ case ch
44
+ when KEY_UP
45
+ move_up
46
+ when KEY_DOWN
47
+ move_down
48
+ when KEY_LEFT
49
+ move_left
50
+ when KEY_RIGHT
51
+ move_right
52
+ when ENTER
53
+ open_cell(cury, curx)
54
+ play(cury, curx)
55
+ when SPACE
56
+ trigger_bomb_flag(cury, curx)
57
+ play(cury, curx)
58
+ end
59
+ end
60
+ rescue GameWon, GameOver => e
61
+ end_game(e)
62
+ exit
63
+ end
64
+
65
+ private
66
+
67
+ def draw_board
68
+ window.clear
69
+ cells.each_index do |row_index|
70
+ (0..(width * 3 - 1)).each_slice(3).with_index do |cell_ary, index|
71
+ cell = cells[row_index][index]
72
+ window.setpos(row_index, cell_ary[0])
73
+ window.attron(color_pair(COLOR_BLUE)) { window.addstr '[' }
74
+ window.attron(color_pair(cell.color)) { window.addstr cell.draw }
75
+ window.attron(color_pair(COLOR_BLUE)) { window.addstr ']' }
76
+ end
77
+ end
78
+ window.refresh
79
+ window.setpos(0, 1)
80
+ end
81
+
82
+ def debug(args = {})
83
+ window.setpos(20, 20)
84
+ str = ''
85
+ args.each { |k, a| str << "| #{k} #{a} |" }
86
+ window.addstr(str)
87
+ window.setpos(cury, curx)
88
+ end
89
+
90
+ def open_cell(y, x)
91
+ cell_x = x / 3 # / 3 because rendered cell 3 chars
92
+ open_original(y, cell_x)
93
+ end
94
+
95
+ def trigger_bomb_flag(y, x)
96
+ cell_x = x / 3 # / 3 because rendered cell 3 chars
97
+ cell = cells[y][cell_x]
98
+ cell.toggle_bomb_flag!
99
+ raise GameWon if game_won?
100
+ end
101
+
102
+ def open_original(y, x)
103
+ cell = cells[y][x]
104
+ return if cell.opened? || cell.marked_as_bomb?
105
+ surrounding_bombs = number_of_boms_nearby(y, x)
106
+ cell.open!(surrounding_bombs)
107
+ raise GameOver if cell.bomb?
108
+ raise GameWon if game_won?
109
+ open_zero_cells(y, x) if surrounding_bombs.zero?
110
+ end
111
+
112
+ def open_zero_cells(y, x)
113
+ # open left
114
+ open_original(y, x - 1) if x.positive?
115
+ # open right
116
+ open_original(y, x + 1) if x < cells[y].index(cells[y].last)
117
+ # open top
118
+ open_original(y - 1, x) if y.positive?
119
+ # open bottom
120
+ open_original(y + 1, x) if y < cells.index(cells.last)
121
+ end
122
+
123
+ def game_won?
124
+ not_bombs.all?(&:opened?) &&
125
+ bombs.all?(&:marked_as_bomb?)
126
+ end
127
+
128
+ def reveal_bombs
129
+ bombs.each(&:open!)
130
+ end
131
+
132
+ def move_up
133
+ return if cury.zero?
134
+ window.setpos(cury - 1, curx)
135
+ end
136
+
137
+ def move_down
138
+ return if cury + 1 >= height
139
+ window.setpos(cury + 1, curx)
140
+ end
141
+
142
+ def move_left
143
+ return if curx <= LEFT_RIGHT_PADDING
144
+ window.setpos(cury, curx - STEP)
145
+ end
146
+
147
+ def move_right
148
+ return if curx >= (width * 3 - LEFT_RIGHT_PADDING)
149
+ window.setpos(cury, curx + STEP)
150
+ end
151
+
152
+ def end_game(e)
153
+ found_bombs_count = found_bombs.count
154
+ reveal_bombs
155
+ draw_board
156
+ window.setpos(height + 3, 0)
157
+ window.addstr e.message
158
+ window.setpos(height + 5, 0)
159
+ window.addstr <<-STR
160
+ Game Stats: you have found #{found_bombs_count} out of #{bombs.count} bombs
161
+
162
+ - `R` to replay with same parameters
163
+ - `Enter` to start a new game
164
+ - any other key to exit
165
+ STR
166
+ case window.getch
167
+ when ENTER
168
+ echo
169
+ Minesweeper::GameInitializer.new.start
170
+ when 'r', 'R'
171
+ Minesweeper::BoardBuilder.from_board(self).build.play
172
+ end
173
+ end
174
+
175
+ def number_of_boms_nearby(y, x)
176
+ bombs_around(y, x) + top_row_bombs(y, x) + bottom_row_bombs(y, x)
177
+ end
178
+
179
+ def bombs_around(y, x)
180
+ row = cells[y]
181
+ working_cell = row[x]
182
+ if working_cell == row.first
183
+ row[1..1]
184
+ elsif working_cell == row.last
185
+ row[-2..-2]
186
+ else
187
+ [row[x - 1], row[x + 1]]
188
+ end.count(&:bomb?)
189
+ end
190
+
191
+ def top_row_bombs(y, x)
192
+ return 0 if y < 1
193
+ row = cells[y - 1]
194
+ bombs_around_in_row(row, x)
195
+ end
196
+
197
+ def bottom_row_bombs(y, x)
198
+ return 0 if y >= height - 1 # normalized height
199
+ row = cells[y + 1]
200
+ bombs_around_in_row(row, x)
201
+ end
202
+
203
+ def bombs_around_in_row(row, x)
204
+ working_cell = row[x]
205
+ if working_cell == row.first
206
+ row[0..1]
207
+ elsif working_cell == row.last
208
+ row[-2..-1]
209
+ else
210
+ row[x - 1..x + 1]
211
+ end.count(&:bomb?)
212
+ end
213
+
214
+ def not_bombs
215
+ cells.flatten(1).reject(&:bomb?)
216
+ end
217
+
218
+ public
219
+
220
+ # stats
221
+ def bombs
222
+ cells.flatten(1).select(&:bomb?)
223
+ end
224
+
225
+ def found_bombs
226
+ bombs.select(&:marked_as_bomb?)
227
+ end
228
+ # stats
229
+
230
+ def number_of_cells
231
+ height * width
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,38 @@
1
+ module Minesweeper
2
+ class BoardBuilder
3
+ extend Forwardable
4
+
5
+ def self.from_board(board)
6
+ new(board.window, flush_params: false)
7
+ end
8
+
9
+ def initialize(window, flush_params:)
10
+ @window = window
11
+ @params_builder = Minesweeper::BoardParams.new(window, flush_params)
12
+ @bomb_injector = BombInjector.new(level: level)
13
+ end
14
+
15
+ def build
16
+ board = Minesweeper::Board.new(height: height, width: width, window: window)
17
+ fill_with_cells(board)
18
+ inject_bombs(board)
19
+ board
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :window, :bomb_injector, :params_builder
25
+
26
+ def_delegators :params_builder, :height, :width, :level
27
+
28
+ def fill_with_cells(board)
29
+ height.times do
30
+ board.cells << Array.new(width) { Cell.new }
31
+ end
32
+ end
33
+
34
+ def inject_bombs(board)
35
+ bomb_injector.inject(board)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,86 @@
1
+ module Minesweeper
2
+ class BoardParams
3
+
4
+ # how about more sophisticated solution
5
+ DB = Sequel.sqlite
6
+ DB.execute <<-SQL
7
+ CREATE TABLE IF NOT EXISTS board_params (
8
+ height integer,
9
+ width integer,
10
+ level varchar(255)
11
+ );
12
+ SQL
13
+
14
+ DEFAULTS = { height: 10, width: 10, level: 'advanced' }.freeze
15
+
16
+ attr_reader :window, :flush_params
17
+
18
+ def initialize(window, flush_params)
19
+ @window = window
20
+ @flush_params = flush_params
21
+ prepare
22
+ end
23
+
24
+ def height
25
+ read[:height]
26
+ end
27
+
28
+ def width
29
+ read[:width]
30
+ end
31
+
32
+ def level
33
+ read[:level]
34
+ end
35
+
36
+ def prepare
37
+ flush_storage if flush_params
38
+ read || build
39
+ end
40
+
41
+ private
42
+
43
+ def table
44
+ DB[:board_params]
45
+ end
46
+
47
+ def read
48
+ table.first
49
+ end
50
+
51
+ def flush_storage
52
+ DB[:board_params].delete
53
+ end
54
+
55
+ def build
56
+ window.addstr('Start game with default parameters? (Y/N)')
57
+ window.setpos(4, 42)
58
+ str = window.getstr
59
+ if str.casecmp('y').zero?
60
+ table.insert(DEFAULTS)
61
+ else
62
+ table.insert(height: ask_y, width: ask_x, level: ask_level)
63
+ end
64
+ read
65
+ end
66
+
67
+ def ask_x
68
+ window.addstr('Number of columns: ')
69
+ x = window.getstr
70
+ x = window.maxx - 1 if x.to_i > window.maxx
71
+ x
72
+ end
73
+
74
+ def ask_y
75
+ window.addstr('Number of rows: ')
76
+ y = window.getstr
77
+ y = window.maxy - 1 if y.to_i > window.maxy
78
+ y
79
+ end
80
+
81
+ def ask_level
82
+ window.addstr('Your Level (beginner/advanced/expert): ')
83
+ window.getstr
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,63 @@
1
+ module Minesweeper
2
+ class BombCell
3
+ attr_accessor :status, :pointer, :force_color
4
+
5
+ def initialize(status: :initial)
6
+ @status = status
7
+ @pointer = nil
8
+ @force_color = nil
9
+ end
10
+
11
+ def bomb?
12
+ true
13
+ end
14
+
15
+ def draw
16
+ case status
17
+ when :initial
18
+ ' '
19
+ when :marked_as_bomb
20
+ '*'
21
+ when :opened
22
+ '*'
23
+ end
24
+ end
25
+
26
+ def toggle_bomb_flag!
27
+ if marked_as_bomb?
28
+ self.force_color = nil
29
+ self.status = :initial
30
+ else
31
+ self.force_color = COLOR_WHITE
32
+ self.status = :marked_as_bomb
33
+ end
34
+ end
35
+
36
+ def open!(*)
37
+ self.status = :opened
38
+ end
39
+
40
+ %i[initial marked_as_bomb opened].each do |method|
41
+ define_method("#{method}?") do
42
+ status == method
43
+ end
44
+ end
45
+
46
+ def color
47
+ case status
48
+ when :initial
49
+ COLOR_MAGENTA
50
+ when :marked_as_bomb
51
+ COLOR_MAGENTA
52
+ when :opened
53
+ color_when_opened
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def color_when_opened
60
+ force_color.nil? ? COLOR_RED : force_color
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,49 @@
1
+ module Minesweeper
2
+ class BombInjector
3
+ attr_reader :board, :level
4
+
5
+ def initialize(level:)
6
+ @level = level
7
+ end
8
+
9
+ def inject(board)
10
+ @board = board
11
+ add_bombs
12
+ end
13
+
14
+ private
15
+
16
+ def add_bombs(number = number_of_bombs)
17
+ return if number.zero?
18
+ row_index = random_row
19
+ cell_index = random_cell
20
+ cell = board.cells[row_index][cell_index]
21
+ unless cell.bomb?
22
+ board.cells[row_index][cell_index] = BombCell.new
23
+ number -= 1
24
+ end
25
+ add_bombs(number)
26
+ end
27
+
28
+ def number_of_bombs
29
+ bomb_percent =
30
+ case level.to_sym
31
+ when :beginner
32
+ 0.05
33
+ when :advanced
34
+ 0.15
35
+ when :expert
36
+ 0.25
37
+ end
38
+ (bomb_percent * board.number_of_cells).ceil
39
+ end
40
+
41
+ def random_row
42
+ Random.rand(board.height)
43
+ end
44
+
45
+ def random_cell
46
+ Random.rand(board.width)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,52 @@
1
+ module Minesweeper
2
+ class Cell
3
+ attr_accessor :status, :pointer
4
+
5
+ def initialize(status: :initial)
6
+ @status = status
7
+ @pointer = nil
8
+ end
9
+
10
+ def bomb?
11
+ false
12
+ end
13
+
14
+ def draw
15
+ case status
16
+ when :initial
17
+ ' '
18
+ when :marked_as_bomb
19
+ '*'
20
+ when :opened
21
+ pointer.to_s
22
+ end
23
+ end
24
+
25
+ def toggle_bomb_flag!
26
+ return if opened?
27
+ self.status = marked_as_bomb? ? :initial : :marked_as_bomb
28
+ end
29
+
30
+ def open!(number_of_boms_nearby)
31
+ self.pointer = number_of_boms_nearby
32
+ self.status = :opened
33
+ end
34
+
35
+ %i[initial marked_as_bomb opened].each do |method|
36
+ define_method("#{method}?") do
37
+ status == method
38
+ end
39
+ end
40
+
41
+ def color
42
+ case status
43
+ when :initial
44
+ COLOR_MAGENTA
45
+ when :marked_as_bomb
46
+ COLOR_MAGENTA
47
+ when :opened
48
+ COLOR_CYAN
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minesweeper
4
+ class GameInitializer
5
+ def initialize
6
+ @window = Window.new(0, 0, 0, 0)
7
+ end
8
+
9
+ def start
10
+ init_screen
11
+ init_colors
12
+ cbreak
13
+ window.keypad = true
14
+ window.refresh
15
+ window.addch("\n")
16
+ window.addstr("Welcome to Minesweeper \n")
17
+ window.addstr("screen size: rows: #{window.maxy}; columns: #{window.maxx} \n")
18
+ window.addstr("controls: `space` to mark/unmark cell as bomb, `arrow keys` to navigate \n")
19
+ board = Minesweeper::BoardBuilder.new(window, flush_params: true).build
20
+ board.play
21
+ ensure
22
+ close_screen
23
+ at_exit { puts 'good bye' }
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :window
29
+
30
+ def init_colors
31
+ Curses.start_color
32
+ Curses.init_pair(COLOR_RED, COLOR_RED, COLOR_BLACK)
33
+ Curses.init_pair(COLOR_BLUE, COLOR_BLUE, COLOR_BLACK)
34
+ Curses.init_pair(COLOR_CYAN, COLOR_CYAN, COLOR_BLACK)
35
+ Curses.init_pair(COLOR_WHITE, COLOR_WHITE, COLOR_BLACK)
36
+ Curses.init_pair(COLOR_MAGENTA, COLOR_MAGENTA, COLOR_BLACK)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minesweeper
4
+ VERSION = "0.1.1"
5
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'curses'
4
+ include Curses
5
+
6
+ require 'pry'
7
+ require 'forwardable'
8
+ require 'sequel'
9
+ require_relative 'minesweeper/board'
10
+ require_relative 'minesweeper/board_builder'
11
+ require_relative 'minesweeper/board_params'
12
+ require_relative 'minesweeper/bomb_cell'
13
+ require_relative 'minesweeper/bomb_injector'
14
+ require_relative 'minesweeper/cell'
15
+ require_relative 'minesweeper/game_initializer'
16
+
17
+ module Minesweeper
18
+ end
@@ -0,0 +1,41 @@
1
+ # coding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'minesweeper/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'minesweeper-curses'
9
+ spec.version = Minesweeper::VERSION
10
+ spec.authors = ['Mykhailo Rybak']
11
+ spec.email = ['mykhailo.rybak.if@gmail.com']
12
+
13
+ spec.summary = %q{Minesweeper CLI game using curses}
14
+ spec.description = %q{Minesweeper CLI game using curses}
15
+ spec.homepage = "https://github.com/ruba-ruba/minesweeper"
16
+ spec.license = "MIT"
17
+
18
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
19
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
20
+ if spec.respond_to?(:metadata)
21
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
22
+ else
23
+ raise "RubyGems 2.0 or newer is required to protect against " \
24
+ "public gem pushes."
25
+ end
26
+
27
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
28
+ f.match(%r{^(test|spec|features)/})
29
+ end
30
+ spec.bindir = 'bin'
31
+ spec.executables = ['minesweeper-curses']
32
+ spec.require_paths = ['lib']
33
+
34
+ spec.add_development_dependency 'bundler', '~> 1.14'
35
+ spec.add_development_dependency 'rake', '~> 10.0'
36
+ spec.add_development_dependency 'minitest', '~> 5.0'
37
+ spec.add_development_dependency 'curses', '~> 1.0.2'
38
+ spec.add_development_dependency 'sequel'
39
+ spec.add_development_dependency 'sqlite3'
40
+ spec.add_development_dependency 'rspec', '~> 3.6.0'
41
+ end
data/readme.md ADDED
@@ -0,0 +1,32 @@
1
+ [![Build Status](https://travis-ci.org/ruba-ruba/minesweeper.svg?branch=master)](https://travis-ci.org/ruba-ruba/minesweeper)
2
+
3
+ ### Minesweeper Terminal
4
+ > Console version of [minesweeper game](https://en.wikipedia.org/wiki/Microsoft_Minesweeper) written in [ruby](https://www.ruby-lang.org/en/) & [curses gem](https://github.com/ruby/curses)
5
+
6
+ #### Requirements
7
+
8
+ - ruby 2.4.1
9
+
10
+ #### Setup
11
+
12
+ - `gem install minesweeper-curses`
13
+
14
+ #### Local installation
15
+
16
+ - `git clone git@github.com:ruba-ruba/minesweeper.git`
17
+ - `cd minesweeper`
18
+ - `rake build_and_install`
19
+
20
+ #### Launching
21
+
22
+ - minesweeper-curses
23
+
24
+ #### Controls
25
+
26
+ - use arrow keys to move over the mineboard
27
+ - use space to mark cell as a bomb
28
+ - use enter to open the cell
29
+
30
+ #### License
31
+
32
+ [MIT](https://opensource.org/licenses/MIT)
metadata ADDED
@@ -0,0 +1,167 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: minesweeper-curses
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Mykhailo Rybak
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-06-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.14'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: curses
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 1.0.2
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 1.0.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: sequel
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sqlite3
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 3.6.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 3.6.0
111
+ description: Minesweeper CLI game using curses
112
+ email:
113
+ - mykhailo.rybak.if@gmail.com
114
+ executables:
115
+ - minesweeper-curses
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - ".gitignore"
120
+ - ".rspec"
121
+ - ".ruby-version"
122
+ - ".travis.yml"
123
+ - CODE_OF_CONDUCT.md
124
+ - Gemfile
125
+ - Gemfile.lock
126
+ - LICENSE.txt
127
+ - Rakefile
128
+ - bin/console
129
+ - bin/minesweeper-curses
130
+ - bin/setup
131
+ - lib/minesweeper.rb
132
+ - lib/minesweeper/board.rb
133
+ - lib/minesweeper/board_builder.rb
134
+ - lib/minesweeper/board_params.rb
135
+ - lib/minesweeper/bomb_cell.rb
136
+ - lib/minesweeper/bomb_injector.rb
137
+ - lib/minesweeper/cell.rb
138
+ - lib/minesweeper/game_initializer.rb
139
+ - lib/minesweeper/version.rb
140
+ - minesweeper.gemspec
141
+ - readme.md
142
+ homepage: https://github.com/ruba-ruba/minesweeper
143
+ licenses:
144
+ - MIT
145
+ metadata:
146
+ allowed_push_host: https://rubygems.org
147
+ post_install_message:
148
+ rdoc_options: []
149
+ require_paths:
150
+ - lib
151
+ required_ruby_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ required_rubygems_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ requirements: []
162
+ rubyforge_project:
163
+ rubygems_version: 2.6.11
164
+ signing_key:
165
+ specification_version: 4
166
+ summary: Minesweeper CLI game using curses
167
+ test_files: []