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 +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
|