minesweeprb 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d8570b88e453c2b2831d7b4d155ece9b35191aa989b38c3df409cde3258af522
4
- data.tar.gz: b4d5c63637d268285cc2a0a08d9b3011b9ceed0aaeabfb36fb63004b90938f82
3
+ metadata.gz: 1a4af15df91c5d1f7b09134c5effe760ae2c16ab6cf1b568599cacd7ae0c648b
4
+ data.tar.gz: e8b3bc60828dfd5f93e526a0ec8bdcecba7c4b7adf811fab6efb9a1e66b8ad97
5
5
  SHA512:
6
- metadata.gz: 0250dbc2b118b525aad974cf1ab17001cff0484abdf4b2c49961d02e7f040a5f9dfdcb687473e3514f030899369ef650d61011cd68b9bf17c0ca0c36afa9ef7b
7
- data.tar.gz: ecdb3eacfb7288c3f8383ac6936d782947c7c1fb06fcc57c80dba1e9b404508ff51526bebec7a793beee00e6d2e55e469354d3cae95cfebf5afce23c7727f990
6
+ metadata.gz: 950340b2ad509214d3a29b9ce6bfb150afd0e5c5bd51bed672d6ad995a9960b447be27ab45d20bc45b41d0496bd53b2171ed527164d92b4e4d51ec9631e15b9c
7
+ data.tar.gz: 0f4eda9e575449989c208b4d32430591f697b63728b5c4a7697c48f5f8b46a0f32cc0d79348f8f7ab61d9293984f4382f6155f194f21c1bcb4899265ba621de2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ### 2020-02-08 0.4.0
2
+
3
+ * Add custom boards
4
+ * Fix gemspec
5
+
1
6
  ### 2020-02-08 0.3.0
2
7
 
3
8
  * Add mouse support
data/Gemfile.lock CHANGED
@@ -1,13 +1,15 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- minesweeprb (0.1.0)
4
+ minesweeprb (0.3.0)
5
+ curses (~> 1.3)
5
6
  timers (~> 4.3)
6
7
  tty (~> 0.10)
7
8
 
8
9
  GEM
9
10
  remote: https://rubygems.org/
10
11
  specs:
12
+ curses (1.3.2)
11
13
  diff-lcs (1.3)
12
14
  equatable (0.6.1)
13
15
  kramdown (1.16.2)
data/README.md CHANGED
@@ -10,7 +10,7 @@ Use clues on the gameboard to deduce locations of mines. Correctly reveal all no
10
10
  ## Install
11
11
 
12
12
  ```
13
- gem install minesweerb
13
+ gem install minesweeprb
14
14
  ```
15
15
 
16
16
  ## Run
@@ -22,13 +22,13 @@ minesweeprb
22
22
  ## Rules
23
23
  A gameboard is composed of a number of squares laid out in a rectangle.
24
24
 
25
- A square hold either a Clue or a Mine
25
+ A square holds either a Clue or a Mine
26
26
 
27
27
  ### Clue Square
28
28
  A Clue Square will contain a number representing the numbers of mines that border itself. If the Clue Square has no neighboring mines then it will be blank.
29
29
 
30
30
  For example, a Clue Square containing a "1" will have exactly one mine in one of the spaces that borders itself.
31
- There is a mine in exactly one of the ? squares.
31
+ There is a mine in exactly one of the squares.
32
32
  ```
33
33
  ◼ ◼ ◼
34
34
  ◼ 1 ◼
@@ -45,7 +45,7 @@ There are no mines surrounding an empty square. Note: Revealing an empty square
45
45
  ```
46
46
  where '◻' is an empty Clue Square.
47
47
 
48
- In the example below, there is a mine in exactly 3 of the ? squares. Because the "3" Clue Square only has three unrevealed spaces bordering itself, it is correct to assume that there is mine in each space.
48
+ In the example below, there is a mine in exactly 3 of the squares. Because the "3" Clue Square only has three unrevealed spaces bordering itself, it is correct to assume that there is mine in each space.
49
49
  ```
50
50
  3 ◼ ◼ 3 ⚑ ◼
51
51
  ◼ ◼ ◼ → ⚑ ⚑ ◼
@@ -53,14 +53,14 @@ In the example below, there is a mine in exactly 3 of the ? squares. Because the
53
53
  ```
54
54
 
55
55
  ### Mine Square
56
- Mine Squares should not be revealed. If you believe you have found the location of a Mine then you can mark that square to prevent accidentally revealing it.
56
+ Mine Squares should not be revealed. If you believe you have found the location of a Mine then you can flag that square to prevent accidentally revealing it.
57
57
 
58
58
  ## Gameboard
59
59
  A gameboard contains a Width, Height, and Number of Mines.
60
60
 
61
61
  The first move is always safe which means a gameboard's Mines are not placed until the first square is revealed.
62
62
 
63
- Since the first is always safe, a gameboard is only valid if the number of mines is less than the total number of squares. A valid gameboard must have more than one square. (i.e., 0 < # of Mines < Width * Height)
63
+ Since the first move is always safe, a gameboard is only valid if the number of mines is less than the total number of squares. A valid gameboard must have more than one square. (i.e., 0 < # of Mines < Width * Height)
64
64
 
65
65
  ## How To Play
66
66
  Reveal squares you believe do not contain a Mine.
@@ -80,10 +80,12 @@ Reveal all Clue Squares without revealing a Mine.
80
80
 
81
81
 
82
82
  ## TODO
83
- * Extract Gameboard
83
+ * ~Extract Gameboard~
84
84
  * Simplify logic
85
- * Repaint only what's necessary
86
- * Implement timer
85
+ * ~Repaint only what's necessary~
86
+ * Separate squares and timer into separate window?
87
+ * ~Implement timer~
87
88
  * Add Leaderboard
88
- * Add custom games (set width, height, and number of mines)
89
+ * ~Add custom games (set width, height, and number of mines)~
89
90
  * Add peek mode, undo, or lives to help users learn
91
+ * restarting a game brings back to prompt instead of generating a new board of the same dimensions
@@ -1,22 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'tty-reader'
4
3
  require 'tty-screen'
5
4
 
6
5
  require_relative '../command'
7
- require_relative '../gameboard'
6
+ require_relative '../../minesweeprb'
8
7
 
9
8
  module Minesweeprb
10
9
  module Commands
11
10
  class Play < Minesweeprb::Command
11
+ SIZES = [
12
+ # [ label, width, height, # of mines ]
13
+ ['Tiny', 5, 5, 3],
14
+ ['Small', 9, 9, 10],
15
+ ['Medium', 13, 13, 15],
16
+ ['Large', 17, 17, 20],
17
+ ['Huge', 21, 21, 25],
18
+ ].map { |options| GameTemplate.new(*options) }.freeze
19
+
12
20
  def initialize(options)
13
21
  @options = options
14
22
  end
15
23
 
16
24
  def execute(input: $stdin, output: $stdout)
17
- size = prompt_size(output)
18
- gameboard = Gameboard.new(size)
25
+ template = prompt_size(output)
26
+ template = prompt_custom(output) if template == :custom
19
27
 
28
+ game = Game.new(**template.to_h)
29
+ gameboard = Gameboard.new(game)
20
30
  begin
21
31
  gameboard.draw
22
32
  ensure
@@ -27,21 +37,63 @@ module Minesweeprb
27
37
  private
28
38
 
29
39
  def prompt_size(output)
30
- sizes = Game::SIZES.map.with_index do |size, index|
31
- too_big = size[:height] * 2 > TTY::Screen.height || size[:width] * 2 > TTY::Screen.width
40
+ options = SIZES.map do |option|
41
+ too_big = option.height > TTY::Screen.height || option.width * 2 - 1 > TTY::Screen.width
32
42
  disabled = '(screen too small)' if too_big
33
- size.merge(
34
- value: index,
35
- disabled: disabled
36
- )
43
+ {
44
+ disabled: disabled,
45
+ name: option.label,
46
+ value: option,
47
+ }
48
+ end
49
+
50
+ options << {
51
+ name: 'Custom',
52
+ value: :custom
53
+ }
54
+
55
+ prompt(interrupt: -> { exit 1 }).select('Size:', options, cycle: true)
56
+ end
57
+
58
+ def prompt_custom(output)
59
+ min_width = 1
60
+ max_width = TTY::Screen.width / 2 - 1
61
+ width = prompt.ask("Width (#{min_width}-#{max_width})") do |q|
62
+ q.required true
63
+ q.convert :int
64
+ q.validate do |val|
65
+ val =~ /\d+/ && (min_width..max_width).include?(val.to_i)
66
+ end
37
67
  end
38
68
 
39
- size = prompt(interrupt: -> { exit 1 }).select('Size:', sizes, cycle: true)
69
+ min_height = width == 1 ? 2 : 1
70
+ max_height = TTY::Screen.height - 10 # leave room for interface
71
+ height = prompt.ask("Height (#{min_height}-#{max_height})") do |q|
72
+ q.required true
73
+ q.convert :int
74
+ q.validate do |val|
75
+ val =~ /\d+/ && (min_height..max_height).include?(val.to_i)
76
+ end
77
+ end
78
+
79
+ min_mines = 1
80
+ max_mines = width * height - 1
81
+ mines = prompt.ask("Mines (#{min_mines}-#{max_mines})") do |q|
82
+ q.required true
83
+ q.convert :int
84
+ q.validate do |val|
85
+ val =~ /\d+/ && (min_mines..max_mines).include?(val.to_i)
86
+ end
87
+ end
88
+
89
+ GameTemplate.new('Custom', width, height, mines)
90
+ end
91
+
92
+ def clear_output(output)
40
93
  output.print cursor.hide
41
94
  output.print cursor.up(1)
42
95
  output.print cursor.clear_screen_down
43
96
  output.puts
44
- size
45
97
  end
46
98
  end
47
99
  end
@@ -2,8 +2,6 @@
2
2
 
3
3
  module Minesweeprb
4
4
  class Game
5
- DEFAULT_SIZE = 'Tiny'
6
- DEFAULT_MINE_COUNT = 1
7
5
  SPRITES = {
8
6
  clock: '◷',
9
7
  clues: '◻➊➋➌➍➎➏➐➑'.chars.freeze,
@@ -17,73 +15,36 @@ module Minesweeprb
17
15
  }.freeze
18
16
  WIN = "#{SPRITES[:win_face]} YOU WON #{SPRITES[:win_face]}"
19
17
  LOSE = "#{SPRITES[:lose_face]} GAME OVER #{SPRITES[:lose_face]}"
20
- SIZES = [
21
- {
22
- name: 'Tiny',
23
- width: 5,
24
- height: 5,
25
- mines: 3,
26
- },
27
- {
28
- name: 'Small',
29
- width: 9,
30
- height: 9,
31
- mines: 10,
32
- },
33
- {
34
- name: 'Medium',
35
- width: 13,
36
- height: 13,
37
- mines: 15,
38
- },
39
- {
40
- name: 'Large',
41
- width: 17,
42
- height: 17,
43
- mines: 20,
44
- },
45
- {
46
- name: 'Huge',
47
- width: 21,
48
- height: 21,
49
- mines: 25,
50
- },
51
- ].freeze
52
18
 
53
19
  attr_accessor :active_square
54
20
  attr_reader :flagged_squares,
21
+ :height,
55
22
  :marked_squares,
56
- :mined_squares,
23
+ :mines,
57
24
  :revealed_squares,
58
- :size,
59
- :squares,
60
- :start_time
25
+ :start_time,
26
+ :width
61
27
 
62
- def initialize(size)
63
- @size = SIZES[size]
28
+ def initialize(label:, width:, height:, mines:)
29
+ @width = width
30
+ @height = height
31
+ @mines = mines
64
32
  restart
65
33
  end
66
34
 
67
35
  def restart
68
36
  @active_square = center
69
- @flagged_squares = []
70
- @marked_squares = []
71
- @mined_squares = []
72
- @revealed_squares = {}
37
+ @flagged_squares = Set[]
38
+ @marked_squares = Set[]
39
+ @mined_squares = Set[]
40
+ @revealed_squares = Set[]
41
+ @grid = Array.new(height) { Array.new(width) }
73
42
  @start_time = nil
74
43
  @end_time = nil
75
44
  end
76
45
 
77
- def mines
78
- size[:mines] - flagged_squares.size
79
- end
80
-
81
- def width
82
- size[:width]
83
- end
84
-
85
- def height
86
- size[:height]
46
+ def remaining_mines
47
+ mines - flagged_squares.length
87
48
  end
88
49
 
89
50
  def center
@@ -97,13 +58,13 @@ module Minesweeprb
97
58
  def time
98
59
  return 0 unless start_time
99
60
 
100
- (end_time - start_time).round
61
+ end_time - start_time
101
62
  end
102
63
 
103
64
  def move(direction)
104
65
  return if over?
105
66
 
106
- x, y = @active_square
67
+ x, y = active_square
107
68
 
108
69
  case direction
109
70
  when :up then y -= 1
@@ -112,6 +73,11 @@ module Minesweeprb
112
73
  when :right then x += 1
113
74
  end
114
75
 
76
+ self.active_square = [x,y]
77
+ end
78
+
79
+ def active_square=(pos)
80
+ x, y = pos
115
81
  x = x < 0 ? width - 1 : x
116
82
  x = x > width - 1 ? 0 : x
117
83
  y = y < 0 ? height - 1 : y
@@ -131,20 +97,20 @@ module Minesweeprb
131
97
  end
132
98
 
133
99
  def header
134
- "#{SPRITES[:mine]} #{mines.to_s.rjust(3, '0')}" \
100
+ "#{SPRITES[:mine]} #{remaining_mines.to_s.rjust(3, '0')}" \
135
101
  " #{face} " \
136
- "#{SPRITES[:clock]} #{time.to_s.rjust(3, '0')}"
102
+ "#{SPRITES[:clock]} #{time.round.to_s.rjust(3, '0')}"
137
103
  end
138
104
 
139
105
  def cycle_flag
140
- return if over? || @revealed_squares.empty? || @revealed_squares.include?(active_square)
106
+ return if over? || revealed_squares.empty? || revealed_squares.include?(active_square)
141
107
 
142
108
  if flagged_squares.include?(active_square)
143
109
  @flagged_squares -= [active_square]
144
110
  @marked_squares += [active_square]
145
111
  elsif marked_squares.include?(active_square)
146
112
  @marked_squares -= [active_square]
147
- elsif flagged_squares.length < size[:mines]
113
+ elsif flagged_squares.length < mines
148
114
  @flagged_squares += [active_square]
149
115
  end
150
116
  end
@@ -152,22 +118,23 @@ module Minesweeprb
152
118
  def reveal_active_square
153
119
  return if over? || flagged_squares.include?(active_square)
154
120
 
155
- reveal_square(active_square)
121
+ x, y = active_square
122
+ reveal_square(x, y)
156
123
  @end_time = now if over?
157
124
  end
158
125
 
159
- def squares
126
+ def play_grid
160
127
  height.times.map do |y|
161
128
  width.times.map do |x|
162
- pos = [x,y]
129
+ square = [x,y]
163
130
 
164
- if mined_squares.include?(pos) && (revealed_squares[pos] || over?)
131
+ if @mined_squares.include?(square) && (revealed_squares.include?(square) || over?)
165
132
  SPRITES[:mine]
166
- elsif flagged_squares.include?(pos)
133
+ elsif revealed_squares.include?(square) || over?
134
+ SPRITES[:clues][@grid[y][x]]
135
+ elsif flagged_squares.include?(square)
167
136
  SPRITES[:flag]
168
- elsif revealed_squares[pos]
169
- SPRITES[:clues][revealed_squares[pos]]
170
- elsif marked_squares.include?(pos)
137
+ elsif marked_squares.include?(square)
171
138
  SPRITES[:mark]
172
139
  else
173
140
  SPRITES[:square]
@@ -181,11 +148,11 @@ module Minesweeprb
181
148
  end
182
149
 
183
150
  def won?
184
- !lost? && revealed_squares.count == width * height - size[:mines]
151
+ !lost? && revealed_squares.count == width * height - mines
185
152
  end
186
153
 
187
154
  def lost?
188
- (revealed_squares.keys & mined_squares).any?
155
+ (revealed_squares & @mined_squares).any?
189
156
  end
190
157
 
191
158
  def over?
@@ -204,13 +171,14 @@ module Minesweeprb
204
171
 
205
172
  def start_game
206
173
  place_mines
174
+ place_clues
207
175
  @start_time = now
208
176
  end
209
177
 
210
178
  def place_mines
211
- size[:mines].times do
179
+ mines.times do
212
180
  pos = random_square
213
- pos = random_square while pos == active_square || mined_squares.include?(pos)
181
+ pos = random_square while pos == active_square || @mined_squares.include?(pos)
214
182
  @mined_squares << pos
215
183
  end
216
184
  end
@@ -221,43 +189,54 @@ module Minesweeprb
221
189
  [x, y]
222
190
  end
223
191
 
224
- def reveal_square(square)
192
+ def place_clues
193
+ width.times do |x|
194
+ height.times do |y|
195
+ @grid[y][x] = square_value(x,y)
196
+ end
197
+ end
198
+ end
199
+
200
+ def square_value(x,y)
201
+ return if @mined_squares.include?([x,y])
202
+
203
+ (neighbors(x,y) & @mined_squares).length
204
+ end
205
+
206
+ def reveal_square(x,y)
207
+ square = [x,y]
225
208
  return if over? || flagged_squares.include?(active_square)
226
209
  start_game if revealed_squares.empty?
227
- return if revealed_squares.keys.include?(square)
228
- return lose! if mined_squares.include?(square)
210
+ return if revealed_squares.include?(square)
211
+ return lose! if @mined_squares.include?(square)
229
212
 
230
- value = square_value(square)
231
- @revealed_squares[square] = value
232
- neighbors(square).each { |neighbor| reveal_square(neighbor) } if value.zero?
213
+ @revealed_squares << [x,y]
214
+ value = @grid[y][x]
215
+ neighbors(x,y).each { |x,y| reveal_square(x,y) } if value == 0
233
216
  end
234
217
 
235
218
  def lose!
236
- @mined_squares.each { |square| @revealed_squares[square] = -1 }
237
- end
238
-
239
- def square_value(square)
240
- (neighbors(square) & mined_squares).size
219
+ @revealed_squares |= @mined_squares
241
220
  end
242
221
 
243
- def neighbors(square)
222
+ def neighbors(x,y)
244
223
  [
245
224
  # top
246
- [square[0] - 1, square[1] - 1],
247
- [square[0] - 0, square[1] - 1],
248
- [square[0] + 1, square[1] - 1],
225
+ [x - 1, y - 1],
226
+ [x - 0, y - 1],
227
+ [x + 1, y - 1],
249
228
 
250
- # middle
251
- [square[0] - 1, square[1] - 0],
252
- [square[0] + 1, square[1] - 0],
229
+ # sides
230
+ [x - 1, y - 0],
231
+ [x + 1, y - 0],
253
232
 
254
233
  # bottom
255
- [square[0] - 1, square[1] + 1],
256
- [square[0] - 0, square[1] + 1],
257
- [square[0] + 1, square[1] + 1],
234
+ [x - 1, y + 1],
235
+ [x - 0, y + 1],
236
+ [x + 1, y + 1],
258
237
  ].select do |x,y|
259
- (0...width).include?(x) && (0...height).include?(y)
260
- end
238
+ x.between?(0, width-1) && y.between?(0, height-1)
239
+ end.to_set
261
240
  end
262
241
  end
263
242
  end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minesweeprb
4
+ class GameTemplate < Struct.new(:label, :width, :height, :mines)
5
+ end
6
+ end
@@ -2,7 +2,6 @@
2
2
 
3
3
  require 'curses'
4
4
  require 'timers'
5
- require_relative './game'
6
5
 
7
6
  module Minesweeprb
8
7
  class Gameboard
@@ -48,20 +47,39 @@ module Minesweeprb
48
47
 
49
48
  COLOR_PAIRS = COLORS.keys.freeze
50
49
 
51
- attr_reader :game, :pastel, :window, :game_x, :game_y
50
+ attr_reader :game, :windows, :game_x, :game_y
52
51
 
53
- def initialize(size)
54
- @pastel = Pastel.new
55
- @game = Game.new(size)
56
- @timers = Timers::Group.new
57
- @game_timer = @timers.every(0.5) { paint }
52
+ def initialize(game)
53
+ @game = game
58
54
  setup_curses
59
- Thread.new { loop { @timers.wait } }
55
+ end
56
+
57
+ def w_header
58
+ windows[:header]
59
+ end
60
+
61
+ def w_grid
62
+ windows[:grid]
63
+ end
64
+
65
+ def w_status
66
+ windows[:status]
67
+ end
68
+
69
+ def w_instructions
70
+ windows[:instructions]
71
+ end
72
+
73
+ def w_debug
74
+ windows[:debug]
60
75
  end
61
76
 
62
77
  def draw
63
- paint
64
- draw if process_input(window.getch)
78
+ # paint_debug
79
+ Thread.new { loop { paint_header && sleep(0.5) } }
80
+
81
+ paint_grid
82
+ paint_grid while process_input(w_grid.getch)
65
83
  end
66
84
 
67
85
  def clear
@@ -71,21 +89,63 @@ module Minesweeprb
71
89
  private
72
90
 
73
91
  def setup_curses
74
- init_screen
92
+ screen = init_screen
75
93
  use_default_colors
76
94
  start_color
77
95
  curs_set(0)
78
96
  noecho
79
97
  self.ESCDELAY = 1;
80
98
  mousemask(BUTTON1_CLICKED|BUTTON2_CLICKED|BUTTON3_CLICKED|BUTTON4_CLICKED)
81
- @window = Window.new(0, 0, 0, 0)
82
- @window.keypad(true)
99
+
100
+ header = {
101
+ top: 1,
102
+ left: (screen.maxx - game.header.length) / 2,
103
+ cols: game.header.length,
104
+ rows: 1,
105
+ }
106
+ grid = {
107
+ left: (screen.maxx - (game.width * 2 - 1)) / 2,
108
+ top: header[:top] + header[:rows] + 1,
109
+ cols: game.width * 2 - 1, # leave room for spaces between squares
110
+ rows: game.height, # leave room for win/lose status and instructions
111
+ }
112
+ status = {
113
+ left: 0,
114
+ top: grid[:top] + grid[:rows] + 1,
115
+ cols: 0,
116
+ rows: 1,
117
+ }
118
+ instructions = {
119
+ left: 0,
120
+ top: status[:top] + status[:rows] + 1,
121
+ cols: 0,
122
+ rows: 1,
123
+ }
124
+ debug = {
125
+ left: 0,
126
+ top: screen.maxy - 1,
127
+ cols: 0,
128
+ rows: 1,
129
+ }
130
+
131
+ @windows = {}
132
+ @windows[:header] = build_window(**header)
133
+ @windows[:grid] = build_window(**grid)
134
+ @windows[:status] = build_window(**status)
135
+ @windows[:instructions] = build_window(**instructions)
136
+ @windows[:debug] = build_window(**debug)
137
+ @windows[:grid].keypad(true)
138
+
83
139
  COLOR_PAIRS.each.with_index do |char, index|
84
140
  fg, bg = COLORS[char]
85
141
  init_pair(index + 1, fg, bg || -1)
86
142
  end
87
143
  end
88
144
 
145
+ def build_window(rows:, cols:, top:, left:)
146
+ Window.new(rows, cols, top, left)
147
+ end
148
+
89
149
  def process_input(key)
90
150
  case key
91
151
  when KEY_MOUSE then process_mouse(getmouse)
@@ -100,17 +160,17 @@ module Minesweeprb
100
160
  end
101
161
 
102
162
  def process_mouse(m)
103
- top = game_y
104
- left = game_x
105
- bottom = game_y + game.height
106
- right = game_x + game.width * 2 - 1
163
+ top = w_grid.begy
164
+ left = w_grid.begx
165
+ bottom = top + game.height
166
+ right = left + game.width * 2 - 1
107
167
  on_board = (top..bottom).include?(m.y) &&
108
168
  (left..right).include?(m.x) &&
109
- (m.x - game_x).even?
169
+ (m.x - w_grid.begx).even?
110
170
 
111
171
  return if !on_board && !game.over?
112
172
 
113
- game.active_square = [(m.x - game_x) / 2, m.y - game_y]
173
+ game.active_square = [(m.x - w_grid.begx) / 2, m.y - w_grid.begy]
114
174
 
115
175
  case m.bstate
116
176
  when BUTTON1_CLICKED then game.reveal_active_square
@@ -118,70 +178,75 @@ module Minesweeprb
118
178
  end
119
179
  end
120
180
 
121
- def how_to_play
122
- instructions = []
123
- instructions << '(←↓↑→ or hjkl)Move' unless game.over?
124
- instructions << '(f or ␣)Flag/Mark' if game.started?
125
- instructions << '(↵)Reveal' unless game.over?
126
- instructions << '(r)Restart'
127
- instructions << '(q or ⎋)Quit'
128
- instructions.join(' ')
181
+ def paint_header
182
+ w_header.setpos(0,0)
183
+
184
+ game.header.chars.each do |char|
185
+ w_header.attron(color_for(char)) { w_header << char }
186
+ end
187
+
188
+ w_header.refresh
129
189
  end
130
190
 
131
- def paint
132
- window.clear
133
- window << "\n"
134
- game.header.center(window.maxx - 1).chars.each do |char|
135
- window.attron(color_for(char)) { window << char }
191
+ def paint_debug
192
+ COLORS.keys.each do |char|
193
+ w_debug.attron(color_for(char)) { w_debug << char.to_s }
136
194
  end
137
- clrtoeol
138
- window << "\n"
139
-
140
- # COLOR_PAIRS.each do |char|
141
- # window.attron(color_for(char)) { window << char }
142
- # end
143
- # clrtoeol
144
- # window << "\n"
145
-
146
- padding = (window.maxx - game.width * 2) / 2
147
- game.squares.each.with_index do |line, row|
148
- window << ' ' * padding
149
- @game_x, @game_y = window.curx, window.cury if row.zero?
195
+ w_debug.refresh
196
+ end
197
+
198
+ def paint_grid
199
+ w_grid.setpos(0,0)
200
+
201
+ game.play_grid.each.with_index do |line, row|
150
202
  line.each.with_index do |char, col|
203
+ w_grid.setpos(row, col * 2) if col < line.length
204
+
151
205
  if game.active_square == [col, row]
152
- window.attron(color_for(char) | A_REVERSE) { window << char }
206
+ w_grid.attron(color_for(char) | A_REVERSE) { w_grid << char }
153
207
  else
154
- window.attron(color_for(char)) { window << char }
208
+ w_grid.attron(color_for(char)) { w_grid << char }
155
209
  end
156
- window << ' ' if col < line.length
157
210
  end
158
- clrtoeol
159
- window << "\n"
160
211
  end
161
212
 
213
+ paint_status
214
+ paint_instructions
215
+
216
+ w_grid.refresh
217
+ w_status.refresh
218
+ w_instructions.refresh
219
+ end
220
+
221
+ def paint_status
162
222
  if game.over?
163
- window << "\n"
223
+ w_status.setpos(0,0)
164
224
  outcome = game.won? ? :win : :lose
165
- message = game.game_over_message.center(window.maxx - 1)
225
+ message = game.game_over_message.center(w_status.maxx - 1)
166
226
  message.chars.each do |char|
167
227
  char_color = color_for(char)
168
228
 
169
229
  if char_color.zero?
170
- window.attron(color_for(outcome)) { window << char }
230
+ w_status.attron(color_for(outcome)) { w_status << char }
171
231
  else
172
- window.attron(char_color) { window << char }
232
+ w_status.attron(char_color) { w_status << char }
173
233
  end
174
234
  end
175
- clrtoeol
176
- window << "\n"
235
+ else
236
+ w_status.clear
177
237
  end
238
+ end
178
239
 
179
- window << "\n"
180
- window << how_to_play.center(window.maxx - 1)
181
- clrtoeol
182
- window << "\n"
240
+ def paint_instructions
241
+ instructions = []
242
+ instructions << '(←↓↑→ or hjkl)Move' unless game.over?
243
+ instructions << '(f or ␣)Flag/Mark' if game.started?
244
+ instructions << '(↵)Reveal' unless game.over?
245
+ instructions << '(r)Restart'
246
+ instructions << '(q or ⎋)Quit'
183
247
 
184
- window.refresh
248
+ w_instructions.setpos(0,0)
249
+ w_instructions << instructions.join(' ').center(w_instructions.maxx - 1)
185
250
  end
186
251
 
187
252
  def color_for(char)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Minesweeprb
4
- VERSION = '0.3.0'
4
+ VERSION = '0.4.0'
5
5
  end
data/lib/minesweeprb.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'minesweeprb/version'
4
+ require 'minesweeprb/game'
5
+ require 'minesweeprb/game_template'
6
+ require 'minesweeprb/gameboard'
4
7
 
5
8
  module Minesweeprb
6
9
  class Error < StandardError; end
data/minesweeprb.gemspec CHANGED
@@ -29,6 +29,7 @@ Gem::Specification.new do |spec|
29
29
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
30
  spec.require_paths = ['lib']
31
31
 
32
+ spec.add_runtime_dependency 'curses', '~> 1.3'
32
33
  spec.add_runtime_dependency 'timers', '~> 4.3'
33
34
  spec.add_runtime_dependency 'tty', '~> 0.10'
34
35
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minesweeprb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - scudco
@@ -10,6 +10,20 @@ bindir: exe
10
10
  cert_chain: []
11
11
  date: 2020-02-09 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: curses
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: timers
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -65,6 +79,7 @@ files:
65
79
  - lib/minesweeprb/commands/.gitkeep
66
80
  - lib/minesweeprb/commands/play.rb
67
81
  - lib/minesweeprb/game.rb
82
+ - lib/minesweeprb/game_template.rb
68
83
  - lib/minesweeprb/gameboard.rb
69
84
  - lib/minesweeprb/templates/.gitkeep
70
85
  - lib/minesweeprb/templates/play/.gitkeep