minesweeprb 0.1.0 → 0.2.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: c0a927b6f66d1e8859c03abc54b13a0cbbc493d6f49363ff64749e7492dca708
4
- data.tar.gz: 19bcd6ea2b5eba19d15a130e801198380b7d75362bd7a30e4978ae9073eb92f7
3
+ metadata.gz: 16bc74524f23df3a1ab558f120560e152b2a1c7551379a6237eefac7cf48962b
4
+ data.tar.gz: f879f2d59af8c486aa34114fcc77995a7a1951edeac59026d9c54ecbdeda09af
5
5
  SHA512:
6
- metadata.gz: 5dd06487cef1c41d3ace0c74f2faa9d1b221f677f61605dff72b1c4f2bfd298c28a09d26eec4f3bc7a516584c7cb77d863902c1b75813c9ddf80ae232f2e4e7c
7
- data.tar.gz: 4bba0e734f6140c4ef5e147e713d38ce874ede0b9a3b2c5b2bb3462a7cddc41555ea4aaf3c93ccaf758729293868341ace2ce12f3f0e685abed30118d8bcf5ff
6
+ metadata.gz: 73cfcc1a3d6e6099f122eb340b567d7517d1853b7654d93ef00df9da1c08ba5ace35a36dbf54d8ec49651c9e5a97f3d09d7cec0255d9f910892aad26589400ec
7
+ data.tar.gz: aae8f678ed0182f6d76cee2df5491c007a57b91ba1f45625e593a5ffc02d2b1e0d4eb96ea859f5d2b1107d78c1bbbbfe6f2cd4afffecd87a7f8e30cf539fb2ec
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ### 2020-02-08 0.2.0
2
+
3
+ * Use curses for gameboard to reduce flickering
data/Gemfile.lock CHANGED
@@ -2,6 +2,7 @@ PATH
2
2
  remote: .
3
3
  specs:
4
4
  minesweeprb (0.1.0)
5
+ timers (~> 4.3)
5
6
  tty (~> 0.10)
6
7
 
7
8
  GEM
@@ -35,6 +36,7 @@ GEM
35
36
  unicode_utils (~> 1.4)
36
37
  strings-ansi (0.1.0)
37
38
  thor (0.20.3)
39
+ timers (4.3.0)
38
40
  tty (0.10.0)
39
41
  bundler (~> 1.16, < 2.0)
40
42
  equatable (~> 0.5)
data/README.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  Use clues on the gameboard to deduce locations of mines. Correctly reveal all non-mine squares to win.
4
4
 
5
+ <p align="center">
6
+ <img src="screenshots/play.png" width="940" />
7
+ <img src="screenshots/win.png" width="394" />
8
+ </p>
9
+
10
+ ## Install
11
+
12
+ ```
13
+ gem install minesweerb
14
+ ```
15
+
16
+ ## Run
17
+
18
+ ```
19
+ minesweeprb
20
+ ```
21
+
5
22
  ## Rules
6
23
  A gameboard is composed of a number of squares laid out in a rectangle.
7
24
 
@@ -56,7 +73,17 @@ If you reveal a square that contains a Mine, the game will end.
56
73
  Reveal all Clue Squares without revealing a Mine.
57
74
 
58
75
  ### ASCII Reference
59
- * Flags `⚑`
60
- * Squares `◻ ◼ ▣`
61
- * Numbers `➊ ➋ ➌ ➍ ➎ ➏ ➁ ➂ ➃ ➄ ➅`
62
- * Mines `☀ ☼ ✺`
76
+ * Flags `⚑ ⍰`
77
+ * Square `◼`
78
+ * Clues `◻ ➋ ➌ ➍ ➎ ➏ ➑`
79
+ * Mine `☀`
80
+
81
+
82
+ ## TODO
83
+ * Extract Gameboard
84
+ * Simplify logic
85
+ * Repaint only what's necessary
86
+ * Implement timer
87
+ * Add Leaderboard
88
+ * Add custom games (set width, height, and number of mines)
89
+ * Add peek mode, undo, or lives to help users learn
@@ -1,32 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'pastel'
4
3
  require 'tty-reader'
5
4
  require 'tty-screen'
6
5
 
7
6
  require_relative '../command'
8
- require_relative '../game'
7
+ require_relative '../gameboard'
9
8
 
10
9
  module Minesweeprb
11
10
  module Commands
12
11
  class Play < Minesweeprb::Command
13
- VIM_MAPPING = {
14
- 'k' => :up,
15
- 'j' => :down,
16
- 'h' => :left,
17
- 'l' => :right,
18
- }.freeze
19
-
20
- attr_reader :game, :pastel
21
-
22
12
  def initialize(options)
23
13
  @options = options
24
- @pastel = Pastel.new
25
14
  end
26
15
 
27
- def start_game(output)
28
- height, width =
16
+ def execute(input: $stdin, output: $stdout)
17
+ size = prompt_size(output)
18
+ gameboard = Gameboard.new(size)
19
+
20
+ begin
21
+ gameboard.draw
22
+ ensure
23
+ gameboard.clear
24
+ end
25
+ end
26
+
27
+ private
29
28
 
29
+ def prompt_size(output)
30
30
  sizes = Game::SIZES.map.with_index do |size, index|
31
31
  too_big = size[:height] * 2 > TTY::Screen.height || size[:width] * 2 > TTY::Screen.width
32
32
  disabled = '(screen too small)' if too_big
@@ -41,144 +41,7 @@ module Minesweeprb
41
41
  output.print cursor.up(1)
42
42
  output.print cursor.clear_screen_down
43
43
  output.puts
44
-
45
- @game = Minesweeprb::Game.new(size)
46
-
47
- print_gameboard(output)
48
- end
49
-
50
- def how_to_play
51
- instructions = []
52
- instructions << '(←↓↑→ or hjkl) Move' unless game.over?
53
- instructions << '(f or ␣)Flag/Mark' if game.started?
54
- instructions << '(↵)Reveal' unless game.over?
55
- instructions << '(r)Restart'
56
- instructions << '(q)Quit'
57
- instructions.join(' ')
58
- end
59
-
60
- def execute(input: $stdin, output: $stdout)
61
- start_game(output)
62
-
63
- reader
64
- .on(:keyescape) { exit }
65
- .on(:keyalpha) do |event|
66
- key = event.value.downcase
67
- case key
68
- when 'q' then exit
69
- when 'r'
70
- output.print cursor.clear_screen_down
71
- start_game(output)
72
- when 'f'
73
- unless game.over?
74
- game.cycle_flag
75
- print_gameboard(output)
76
- end
77
- when 'j', 'k', 'h', 'l'
78
- unless game.over?
79
- game.move(VIM_MAPPING[key])
80
- print_gameboard(output)
81
- end
82
- end
83
-
84
- end.on(:keypress) do |event|
85
- unless game.over?
86
- case event.key.name
87
- when :up, :down, :left, :right
88
- game.move(event.key.name)
89
- when :space
90
- game.cycle_flag
91
- when :return
92
- game.reveal_active_square
93
- end
94
-
95
- print_gameboard(output)
96
- end
97
- end
98
-
99
- loop { reader.read_keypress }
100
- end
101
-
102
- def reader
103
- @reader ||= TTY::Reader.new
104
- end
105
-
106
- def print_gameboard(output)
107
- total_height = game.height + game.header.lines.length + 1
108
- output.print cursor.clear_lines(total_height, :down)
109
- output.print cursor.up(total_height - 2)
110
-
111
- game.header.each_line do |line|
112
- chars = line.chars.map do |char|
113
- case char
114
- when Game::WON_FACE then pastel.bright_yellow(char)
115
- when Game::LOST_FACE then pastel.bright_red(char)
116
- when Game::PLAYING_FACE then pastel.bright_cyan(char)
117
- when Game::MINE then pastel.bright_red(char)
118
- when Game::CLOCK then pastel.bright_cyan(char)
119
- else
120
- char
121
- end
122
- end
123
-
124
- center(output, chars.join)
125
- end
126
-
127
- game.squares.each.with_index do |row, y|
128
- line = row.map.with_index do |char, x|
129
- char = case char
130
- when Game::FLAG then pastel.bright_red(char)
131
- when Game::MINE
132
- if game.won?
133
- pastel.bright_green(char)
134
- elsif game.active_square == [x,y]
135
- pastel.black.on_bright_red(char)
136
- else
137
- pastel.bright_red(char)
138
- end
139
- when Game::MARK then pastel.bright_magenta(char)
140
- when Game::CLUES[0] then pastel.dim(char)
141
- when Game::CLUES[1] then pastel.blue(char)
142
- when Game::CLUES[2] then pastel.green(char)
143
- when Game::CLUES[3] then pastel.red(char)
144
- when Game::CLUES[4] then pastel.magenta(char)
145
- when Game::CLUES[5] then pastel.black(char)
146
- when Game::CLUES[6] then pastel.bright_red(char)
147
- when Game::CLUES[7] then pastel.bright_white(char)
148
- when Game::CLUES[8] then pastel.bright_cyan(char)
149
- else
150
- char
151
- end
152
-
153
- !game.over? && game.active_square == [x,y] ? pastel.inverse(char): char
154
- end.join(' ')
155
-
156
- center(output, line)
157
- end
158
-
159
-
160
- output.print cursor.clear_screen_down
161
- output.puts
162
-
163
- if game.won?
164
- center(output, pastel.bright_green.bold('☻ YOU WON ☻'))
165
- output.puts
166
- center(output, how_to_play)
167
- elsif game.lost?
168
- center(output, pastel.bright_magenta.bold('☹ GAME OVER ☹'))
169
- output.puts
170
- center(output, how_to_play)
171
- else
172
- center(output, how_to_play)
173
- output.print cursor.up(total_height + 2)
174
- end
175
- end
176
-
177
- def center(output, line)
178
- width = TTY::Screen.width
179
- padding = (width - (pastel.strip(line.chomp.strip).length)) / 2
180
- output.print(' ' * [padding, 0].max)
181
- output.puts line
44
+ size
182
45
  end
183
46
  end
184
47
  end
@@ -1,21 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'pastel'
4
-
5
3
  module Minesweeprb
6
4
  class Game
7
5
  DEFAULT_SIZE = 'Tiny'
8
6
  DEFAULT_MINE_COUNT = 1
9
- FLAG = '⚑'
10
- SQUARE = ''
11
- ACTIVE_SQUARE = ''
12
- MARK = ''
13
- CLUES = '◻➊➋➌➍➎➏➐➑'.chars.freeze
14
- CLOCK = ''
15
- MINE = '☀'
16
- WON_FACE = ''
17
- LOST_FACE = ''
18
- PLAYING_FACE = ''
7
+ SPRITES = {
8
+ clock: '',
9
+ clues: '◻➊➋➌➍➎➏➐➑'.chars.freeze,
10
+ flag: '', # ⚑ Flag does not work in curses?
11
+ lose_face: '',
12
+ mark: '',
13
+ mine: '☀',
14
+ play_face: '',
15
+ square: '',
16
+ win_face: '',
17
+ }.freeze
18
+ WIN = "#{SPRITES[:win_face]} YOU WON #{SPRITES[:win_face]}"
19
+ LOSE = "#{SPRITES[:lose_face]} GAME OVER #{SPRITES[:lose_face]}"
19
20
  SIZES = [
20
21
  {
21
22
  name: 'Tiny',
@@ -53,19 +54,24 @@ module Minesweeprb
53
54
  :flagged_squares,
54
55
  :marked_squares,
55
56
  :mined_squares,
56
- :pastel,
57
57
  :revealed_squares,
58
58
  :size,
59
- :squares
59
+ :squares,
60
+ :start_time
60
61
 
61
62
  def initialize(size)
62
- @pastel = Pastel.new
63
63
  @size = SIZES[size]
64
+ restart
65
+ end
66
+
67
+ def restart
64
68
  @active_square = center
65
69
  @flagged_squares = []
66
70
  @marked_squares = []
67
71
  @mined_squares = []
68
72
  @revealed_squares = {}
73
+ @start_time = nil
74
+ @end_time = nil
69
75
  end
70
76
 
71
77
  def mines
@@ -84,8 +90,14 @@ module Minesweeprb
84
90
  [(width / 2).floor, (height / 2).floor]
85
91
  end
86
92
 
93
+ def end_time
94
+ @end_time || now
95
+ end
96
+
87
97
  def time
88
- 0
98
+ return 0 unless start_time
99
+
100
+ (end_time - start_time).round
89
101
  end
90
102
 
91
103
  def move(direction)
@@ -110,18 +122,18 @@ module Minesweeprb
110
122
 
111
123
  def face
112
124
  if won?
113
- WON_FACE
125
+ SPRITES[:win_face]
114
126
  elsif lost?
115
- LOST_FACE
127
+ SPRITES[:lose_face]
116
128
  else
117
- PLAYING_FACE
129
+ SPRITES[:play_face]
118
130
  end
119
131
  end
120
132
 
121
133
  def header
122
- "#{MINE} #{mines.to_s.rjust(3, '0')}" \
134
+ "#{SPRITES[:mine]} #{mines.to_s.rjust(3, '0')}" \
123
135
  " #{face} " \
124
- "#{CLOCK} #{time.to_s.rjust(3, '0')}"
136
+ "#{SPRITES[:clock]} #{time.to_s.rjust(3, '0')}"
125
137
  end
126
138
 
127
139
  def cycle_flag
@@ -141,6 +153,7 @@ module Minesweeprb
141
153
  return if over? || flagged_squares.include?(active_square)
142
154
 
143
155
  reveal_square(active_square)
156
+ @end_time = now if over?
144
157
  end
145
158
 
146
159
  def squares
@@ -149,15 +162,15 @@ module Minesweeprb
149
162
  pos = [x,y]
150
163
 
151
164
  if mined_squares.include?(pos) && (revealed_squares[pos] || over?)
152
- MINE
165
+ SPRITES[:mine]
153
166
  elsif flagged_squares.include?(pos)
154
- FLAG
167
+ SPRITES[:flag]
155
168
  elsif marked_squares.include?(pos)
156
- MARK
169
+ SPRITES[:mark]
157
170
  elsif revealed_squares[pos]
158
- CLUES[revealed_squares[pos]]
171
+ SPRITES[:clues][revealed_squares[pos]]
159
172
  else
160
- SQUARE
173
+ SPRITES[:square]
161
174
  end
162
175
  end
163
176
  end
@@ -179,8 +192,21 @@ module Minesweeprb
179
192
  won? || lost?
180
193
  end
181
194
 
195
+ def game_over_message
196
+ won? ? WIN : LOSE
197
+ end
198
+
182
199
  private
183
200
 
201
+ def now
202
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
203
+ end
204
+
205
+ def start_game
206
+ place_mines
207
+ @start_time = now
208
+ end
209
+
184
210
  def place_mines
185
211
  size[:mines].times do
186
212
  pos = random_square
@@ -196,7 +222,7 @@ module Minesweeprb
196
222
  end
197
223
 
198
224
  def reveal_square(square)
199
- place_mines if revealed_squares.empty?
225
+ start_game if revealed_squares.empty?
200
226
  return if revealed_squares.keys.include?(square)
201
227
  return lose! if mined_squares.include?(square)
202
228
 
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'curses'
4
+ require 'timers'
5
+ require_relative './game'
6
+
7
+ module Minesweeprb
8
+ class Gameboard
9
+ RESTART = ['r'].freeze
10
+ REVEAL = [10, Curses::KEY_ENTER].freeze
11
+ FLAG = ['f', ' '].freeze
12
+ QUIT = ['q', 27].freeze
13
+
14
+ MOVE = {
15
+ Curses::KEY_UP => :up,
16
+ Curses::KEY_DOWN => :down,
17
+ Curses::KEY_LEFT => :left,
18
+ Curses::KEY_RIGHT => :right,
19
+ 'k' => :up,
20
+ 'j' => :down,
21
+ 'h' => :left,
22
+ 'l' => :right,
23
+ }.freeze
24
+
25
+ COLORS = {
26
+ win: [Curses::A_BOLD | Curses::COLOR_GREEN],
27
+ lose: [Curses::A_BOLD | Curses::COLOR_MAGENTA],
28
+ Game::SPRITES[:clock] => [Curses::A_BOLD | Curses::COLOR_CYAN],
29
+ Game::SPRITES[:win_face] => [Curses::A_BOLD | Curses::COLOR_YELLOW],
30
+ Game::SPRITES[:lose_face] => [Curses::A_BOLD | Curses::COLOR_RED],
31
+ Game::SPRITES[:play_face] => [Curses::A_BOLD | Curses::COLOR_CYAN],
32
+
33
+ Game::SPRITES[:mine] => [Curses::A_BOLD | Curses::COLOR_RED],
34
+ Game::SPRITES[:flag] => [Curses::A_BOLD | Curses::COLOR_RED],
35
+ Game::SPRITES[:mark] => [Curses::A_BOLD | Curses::COLOR_MAGENTA],
36
+ Game::SPRITES[:clues][0] => [Curses::COLOR_BLACK],
37
+ Game::SPRITES[:clues][1] => [Curses::COLOR_BLUE],
38
+ Game::SPRITES[:clues][2] => [Curses::COLOR_GREEN],
39
+ Game::SPRITES[:clues][3] => [Curses::COLOR_MAGENTA],
40
+ Game::SPRITES[:clues][4] => [Curses::COLOR_CYAN],
41
+ Game::SPRITES[:clues][5] => [Curses::COLOR_RED],
42
+ Game::SPRITES[:clues][6] => [Curses::COLOR_YELLOW],
43
+ Game::SPRITES[:clues][7] => [Curses::A_BOLD | Curses::COLOR_MAGENTA],
44
+ Game::SPRITES[:clues][8] => [Curses::A_BOLD | Curses::COLOR_RED],
45
+ }.freeze
46
+
47
+ COLOR_PAIRS = COLORS.keys.freeze
48
+
49
+ attr_reader :game, :pastel, :window
50
+
51
+ def initialize(size)
52
+ @pastel = Pastel.new
53
+ @game = Game.new(size)
54
+ @timers = Timers::Group.new
55
+ @game_timer = @timers.every(0.5) { paint }
56
+ Thread.new { loop { @timers.wait } }
57
+ setup_curses
58
+ end
59
+
60
+ def draw
61
+ paint
62
+ draw if process_input(window.getch)
63
+ end
64
+
65
+ def clear
66
+ Curses.close_screen
67
+ end
68
+
69
+ private
70
+
71
+ def setup_curses
72
+ Curses.init_screen
73
+ Curses.use_default_colors
74
+ Curses.start_color
75
+ Curses.curs_set(0)
76
+ Curses.noecho
77
+ Curses.ESCDELAY = 1;
78
+ @window = Curses::Window.new(0, 0, 0, 0)
79
+ @window.keypad(true)
80
+ COLOR_PAIRS.each.with_index do |char, index|
81
+ fg, bg = COLORS[char]
82
+ Curses.init_pair(index + 1, fg, bg || -1)
83
+ end
84
+ end
85
+
86
+ def process_input(key)
87
+ case key
88
+ when *MOVE.keys then game.move(MOVE[key])
89
+ when *REVEAL then game.reveal_active_square
90
+ when *FLAG then game.cycle_flag
91
+ when *RESTART then game.restart
92
+ when *QUIT then return false
93
+ end
94
+
95
+ true
96
+ end
97
+
98
+ def how_to_play
99
+ instructions = []
100
+ instructions << '(←↓↑→ or hjkl)Move' unless game.over?
101
+ instructions << '(f or ␣)Flag/Mark' if game.started?
102
+ instructions << '(↵)Reveal' unless game.over?
103
+ instructions << '(r)Restart'
104
+ instructions << '(q or ⎋)Quit'
105
+ instructions.join(' ')
106
+ end
107
+
108
+ def paint
109
+ window.clear
110
+ window << "\n"
111
+ game.header.center(window.maxx - 1).chars.each do |char|
112
+ window.attron(color_for(char)) { window << char }
113
+ end
114
+ Curses.clrtoeol
115
+ window << "\n"
116
+
117
+ # COLOR_PAIRS.each do |char|
118
+ # window.attron(color_for(char)) { window << char }
119
+ # end
120
+ # Curses.clrtoeol
121
+ # window << "\n"
122
+
123
+ padding = (window.maxx - game.width * 2) / 2
124
+ game.squares.each.with_index do |line, row|
125
+ window << ' ' * padding
126
+ line.each.with_index do |char, col|
127
+ if game.active_square == [col, row]
128
+ window.attron(color_for(char) | Curses::A_REVERSE) { window << char }
129
+ else
130
+ window.attron(color_for(char)) { window << char }
131
+ end
132
+ window << ' ' if col < line.length
133
+ end
134
+ Curses.clrtoeol
135
+ window << "\n"
136
+ end
137
+
138
+ if game.over?
139
+ window << "\n"
140
+ outcome = game.won? ? :win : :lose
141
+ message = game.game_over_message.center(window.maxx - 1)
142
+ message.chars.each do |char|
143
+ char_color = color_for(char)
144
+
145
+ if char_color.zero?
146
+ window.attron(color_for(outcome)) { window << char }
147
+ else
148
+ window.attron(char_color) { window << char }
149
+ end
150
+ end
151
+ Curses.clrtoeol
152
+ window << "\n"
153
+ end
154
+
155
+ window << "\n"
156
+ window << how_to_play.center(window.maxx - 1)
157
+ Curses.clrtoeol
158
+ window << "\n"
159
+
160
+ window.refresh
161
+ end
162
+
163
+ def color_for(char)
164
+ pair = COLOR_PAIRS.index(char)
165
+
166
+ if pair
167
+ Curses.color_pair(pair + 1)
168
+ else
169
+ 0
170
+ end
171
+ end
172
+ end
173
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Minesweeprb
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
data/minesweeprb.gemspec CHANGED
@@ -22,12 +22,13 @@ Gem::Specification.new do |spec|
22
22
  # into git.
23
23
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
24
  `git ls-files -z`.split("\x0").reject do |f|
25
- f.match(%r{^(test|spec|features)/})
25
+ f.match(%r{^(test|spec|features|screenshots)/})
26
26
  end
27
27
  end
28
28
  spec.bindir = 'exe'
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 'timers', '~> 4.3'
32
33
  spec.add_runtime_dependency 'tty', '~> 0.10'
33
34
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minesweeprb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - scudco
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-02-06 00:00:00.000000000 Z
11
+ date: 2020-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: timers
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.3'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: tty
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -36,6 +50,7 @@ files:
36
50
  - ".rspec"
37
51
  - ".rubocop.yml"
38
52
  - ".travis.yml"
53
+ - CHANGELOG.md
39
54
  - Gemfile
40
55
  - Gemfile.lock
41
56
  - LICENSE
@@ -50,6 +65,7 @@ files:
50
65
  - lib/minesweeprb/commands/.gitkeep
51
66
  - lib/minesweeprb/commands/play.rb
52
67
  - lib/minesweeprb/game.rb
68
+ - lib/minesweeprb/gameboard.rb
53
69
  - lib/minesweeprb/templates/.gitkeep
54
70
  - lib/minesweeprb/templates/play/.gitkeep
55
71
  - lib/minesweeprb/version.rb