minesweeprb 0.1.0 → 0.2.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.
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