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 +4 -4
- data/CHANGELOG.md +3 -0
- data/Gemfile.lock +2 -0
- data/README.md +31 -4
- data/lib/minesweeprb/commands/play.rb +15 -152
- data/lib/minesweeprb/game.rb +53 -27
- data/lib/minesweeprb/gameboard.rb +173 -0
- data/lib/minesweeprb/version.rb +1 -1
- data/minesweeprb.gemspec +2 -1
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16bc74524f23df3a1ab558f120560e152b2a1c7551379a6237eefac7cf48962b
|
4
|
+
data.tar.gz: f879f2d59af8c486aa34114fcc77995a7a1951edeac59026d9c54ecbdeda09af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 73cfcc1a3d6e6099f122eb340b567d7517d1853b7654d93ef00df9da1c08ba5ace35a36dbf54d8ec49651c9e5a97f3d09d7cec0255d9f910892aad26589400ec
|
7
|
+
data.tar.gz: aae8f678ed0182f6d76cee2df5491c007a57b91ba1f45625e593a5ffc02d2b1e0d4eb96ea859f5d2b1107d78c1bbbbfe6f2cd4afffecd87a7f8e30cf539fb2ec
|
data/CHANGELOG.md
ADDED
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
|
-
*
|
61
|
-
*
|
62
|
-
*
|
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 '../
|
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
|
28
|
-
|
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
|
data/lib/minesweeprb/game.rb
CHANGED
@@ -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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
125
|
+
SPRITES[:win_face]
|
114
126
|
elsif lost?
|
115
|
-
|
127
|
+
SPRITES[:lose_face]
|
116
128
|
else
|
117
|
-
|
129
|
+
SPRITES[:play_face]
|
118
130
|
end
|
119
131
|
end
|
120
132
|
|
121
133
|
def header
|
122
|
-
"#{
|
134
|
+
"#{SPRITES[:mine]} #{mines.to_s.rjust(3, '0')}" \
|
123
135
|
" #{face} " \
|
124
|
-
"#{
|
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
|
-
|
165
|
+
SPRITES[:mine]
|
153
166
|
elsif flagged_squares.include?(pos)
|
154
|
-
|
167
|
+
SPRITES[:flag]
|
155
168
|
elsif marked_squares.include?(pos)
|
156
|
-
|
169
|
+
SPRITES[:mark]
|
157
170
|
elsif revealed_squares[pos]
|
158
|
-
|
171
|
+
SPRITES[:clues][revealed_squares[pos]]
|
159
172
|
else
|
160
|
-
|
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
|
-
|
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
|
data/lib/minesweeprb/version.rb
CHANGED
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.
|
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-
|
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
|