minesweeprb 0.1.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 +7 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/.travis.yml +6 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +134 -0
- data/LICENSE +21 -0
- data/README.md +62 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/exe/minesweeprb +18 -0
- data/lib/minesweeprb.rb +8 -0
- data/lib/minesweeprb/cli.rb +35 -0
- data/lib/minesweeprb/command.rb +127 -0
- data/lib/minesweeprb/commands/.gitkeep +1 -0
- data/lib/minesweeprb/commands/play.rb +185 -0
- data/lib/minesweeprb/game.rb +236 -0
- data/lib/minesweeprb/templates/.gitkeep +1 -0
- data/lib/minesweeprb/templates/play/.gitkeep +1 -0
- data/lib/minesweeprb/version.rb +5 -0
- data/minesweeprb.gemspec +33 -0
- metadata +83 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c0a927b6f66d1e8859c03abc54b13a0cbbc493d6f49363ff64749e7492dca708
|
|
4
|
+
data.tar.gz: 19bcd6ea2b5eba19d15a130e801198380b7d75362bd7a30e4978ae9073eb92f7
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 5dd06487cef1c41d3ace0c74f2faa9d1b221f677f61605dff72b1c4f2bfd298c28a09d26eec4f3bc7a516584c7cb77d863902c1b75813c9ddf80ae232f2e4e7c
|
|
7
|
+
data.tar.gz: 4bba0e734f6140c4ef5e147e713d38ce874ede0b9a3b2c5b2bb3462a7cddc41555ea4aaf3c93ccaf758729293868341ace2ce12f3f0e685abed30118d8bcf5ff
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require: rubocop-rspec
|
|
2
|
+
|
|
3
|
+
Style/Documentation:
|
|
4
|
+
Enabled: false
|
|
5
|
+
|
|
6
|
+
Metrics/BlockLength:
|
|
7
|
+
ExcludedMethods: ['describe']
|
|
8
|
+
|
|
9
|
+
Style/TrailingCommaInArrayLiteral:
|
|
10
|
+
EnforcedStyleForMultiline: comma
|
|
11
|
+
|
|
12
|
+
Style/TrailingCommaInHashLiteral:
|
|
13
|
+
EnforcedStyleForMultiline: comma
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
minesweeprb (0.1.0)
|
|
5
|
+
tty (~> 0.10)
|
|
6
|
+
|
|
7
|
+
GEM
|
|
8
|
+
remote: https://rubygems.org/
|
|
9
|
+
specs:
|
|
10
|
+
diff-lcs (1.3)
|
|
11
|
+
equatable (0.6.1)
|
|
12
|
+
kramdown (1.16.2)
|
|
13
|
+
necromancer (0.5.1)
|
|
14
|
+
pastel (0.7.3)
|
|
15
|
+
equatable (~> 0.6)
|
|
16
|
+
tty-color (~> 0.5)
|
|
17
|
+
rake (12.3.3)
|
|
18
|
+
rouge (3.15.0)
|
|
19
|
+
rspec (3.9.0)
|
|
20
|
+
rspec-core (~> 3.9.0)
|
|
21
|
+
rspec-expectations (~> 3.9.0)
|
|
22
|
+
rspec-mocks (~> 3.9.0)
|
|
23
|
+
rspec-core (3.9.1)
|
|
24
|
+
rspec-support (~> 3.9.1)
|
|
25
|
+
rspec-expectations (3.9.0)
|
|
26
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
27
|
+
rspec-support (~> 3.9.0)
|
|
28
|
+
rspec-mocks (3.9.1)
|
|
29
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
30
|
+
rspec-support (~> 3.9.0)
|
|
31
|
+
rspec-support (3.9.2)
|
|
32
|
+
strings (0.1.8)
|
|
33
|
+
strings-ansi (~> 0.1)
|
|
34
|
+
unicode-display_width (~> 1.5)
|
|
35
|
+
unicode_utils (~> 1.4)
|
|
36
|
+
strings-ansi (0.1.0)
|
|
37
|
+
thor (0.20.3)
|
|
38
|
+
tty (0.10.0)
|
|
39
|
+
bundler (~> 1.16, < 2.0)
|
|
40
|
+
equatable (~> 0.5)
|
|
41
|
+
pastel (~> 0.7.2)
|
|
42
|
+
thor (~> 0.20.0)
|
|
43
|
+
tty-box (~> 0.4.1)
|
|
44
|
+
tty-color (~> 0.5)
|
|
45
|
+
tty-command (~> 0.9.0)
|
|
46
|
+
tty-config (~> 0.3.2)
|
|
47
|
+
tty-cursor (~> 0.7)
|
|
48
|
+
tty-editor (~> 0.5)
|
|
49
|
+
tty-file (~> 0.8.0)
|
|
50
|
+
tty-font (~> 0.4.0)
|
|
51
|
+
tty-logger (~> 0.2.0)
|
|
52
|
+
tty-markdown (~> 0.6.0)
|
|
53
|
+
tty-pager (~> 0.12)
|
|
54
|
+
tty-pie (~> 0.3.0)
|
|
55
|
+
tty-platform (~> 0.2)
|
|
56
|
+
tty-progressbar (~> 0.17)
|
|
57
|
+
tty-prompt (~> 0.19)
|
|
58
|
+
tty-screen (~> 0.7)
|
|
59
|
+
tty-spinner (~> 0.9)
|
|
60
|
+
tty-table (~> 0.11.0)
|
|
61
|
+
tty-tree (~> 0.3)
|
|
62
|
+
tty-which (~> 0.4)
|
|
63
|
+
tty-box (0.4.1)
|
|
64
|
+
pastel (~> 0.7.2)
|
|
65
|
+
strings (~> 0.1.6)
|
|
66
|
+
tty-cursor (~> 0.7)
|
|
67
|
+
tty-color (0.5.1)
|
|
68
|
+
tty-command (0.9.0)
|
|
69
|
+
pastel (~> 0.7.0)
|
|
70
|
+
tty-config (0.3.2)
|
|
71
|
+
tty-cursor (0.7.1)
|
|
72
|
+
tty-editor (0.5.1)
|
|
73
|
+
tty-prompt (~> 0.19)
|
|
74
|
+
tty-which (~> 0.4)
|
|
75
|
+
tty-file (0.8.0)
|
|
76
|
+
diff-lcs (~> 1.3)
|
|
77
|
+
pastel (~> 0.7.2)
|
|
78
|
+
tty-prompt (~> 0.18)
|
|
79
|
+
tty-font (0.4.0)
|
|
80
|
+
tty-logger (0.2.0)
|
|
81
|
+
pastel (~> 0.7.0)
|
|
82
|
+
tty-markdown (0.6.0)
|
|
83
|
+
kramdown (~> 1.16.2)
|
|
84
|
+
pastel (~> 0.7.2)
|
|
85
|
+
rouge (~> 3.3)
|
|
86
|
+
strings (~> 0.1.4)
|
|
87
|
+
tty-color (~> 0.4)
|
|
88
|
+
tty-screen (~> 0.6)
|
|
89
|
+
tty-pager (0.12.1)
|
|
90
|
+
strings (~> 0.1.4)
|
|
91
|
+
tty-screen (~> 0.6)
|
|
92
|
+
tty-which (~> 0.4)
|
|
93
|
+
tty-pie (0.3.0)
|
|
94
|
+
pastel (~> 0.7.3)
|
|
95
|
+
tty-cursor (~> 0.7)
|
|
96
|
+
tty-platform (0.3.0)
|
|
97
|
+
tty-progressbar (0.17.0)
|
|
98
|
+
strings-ansi (~> 0.1.0)
|
|
99
|
+
tty-cursor (~> 0.7)
|
|
100
|
+
tty-screen (~> 0.7)
|
|
101
|
+
unicode-display_width (~> 1.6)
|
|
102
|
+
tty-prompt (0.20.0)
|
|
103
|
+
necromancer (~> 0.5.0)
|
|
104
|
+
pastel (~> 0.7.0)
|
|
105
|
+
tty-reader (~> 0.7.0)
|
|
106
|
+
tty-reader (0.7.0)
|
|
107
|
+
tty-cursor (~> 0.7)
|
|
108
|
+
tty-screen (~> 0.7)
|
|
109
|
+
wisper (~> 2.0.0)
|
|
110
|
+
tty-screen (0.7.1)
|
|
111
|
+
tty-spinner (0.9.3)
|
|
112
|
+
tty-cursor (~> 0.7)
|
|
113
|
+
tty-table (0.11.0)
|
|
114
|
+
equatable (~> 0.6)
|
|
115
|
+
necromancer (~> 0.5)
|
|
116
|
+
pastel (~> 0.7.2)
|
|
117
|
+
strings (~> 0.1.5)
|
|
118
|
+
tty-screen (~> 0.7)
|
|
119
|
+
tty-tree (0.4.0)
|
|
120
|
+
tty-which (0.4.2)
|
|
121
|
+
unicode-display_width (1.6.1)
|
|
122
|
+
unicode_utils (1.4.0)
|
|
123
|
+
wisper (2.0.1)
|
|
124
|
+
|
|
125
|
+
PLATFORMS
|
|
126
|
+
ruby
|
|
127
|
+
|
|
128
|
+
DEPENDENCIES
|
|
129
|
+
minesweeprb!
|
|
130
|
+
rake (~> 12.0)
|
|
131
|
+
rspec (~> 3.0)
|
|
132
|
+
|
|
133
|
+
BUNDLED WITH
|
|
134
|
+
1.17.3
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 scudco
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Minesweeper
|
|
2
|
+
|
|
3
|
+
Use clues on the gameboard to deduce locations of mines. Correctly reveal all non-mine squares to win.
|
|
4
|
+
|
|
5
|
+
## Rules
|
|
6
|
+
A gameboard is composed of a number of squares laid out in a rectangle.
|
|
7
|
+
|
|
8
|
+
A square hold either a Clue or a Mine
|
|
9
|
+
|
|
10
|
+
### Clue Square
|
|
11
|
+
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.
|
|
12
|
+
|
|
13
|
+
For example, a Clue Square containing a "1" will have exactly one mine in one of the spaces that borders itself.
|
|
14
|
+
There is a mine in exactly one of the ? squares.
|
|
15
|
+
```
|
|
16
|
+
◼ ◼ ◼
|
|
17
|
+
◼ 1 ◼
|
|
18
|
+
◼ ◼ ◼
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
There are no mines surrounding an empty square. Note: Revealing an empty square will reveal all neighboring Clue Squares automatically. For example revealing the square where '▣' is placed (B2) could result in the gameboard looking something like:
|
|
22
|
+
```
|
|
23
|
+
◼ ◼ ◼ ◼ ◼ ◻ ◻ 1 ◼ ◼
|
|
24
|
+
◼ ▣ ◼ ◼ ◼ ◻ ◻ 1 ◼ ◼
|
|
25
|
+
◼ ◼ ◼ ◼ ◼ → 1 2 2 ◼ ◼
|
|
26
|
+
◼ ◼ ◼ ◼ ◼ ◼ ◼ ◼ ◼ ◼
|
|
27
|
+
◼ ◼ ◼ ◼ ◼ ◼ ◼ ◼ ◼ ◼
|
|
28
|
+
```
|
|
29
|
+
where '◻' is an empty Clue Square.
|
|
30
|
+
|
|
31
|
+
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.
|
|
32
|
+
```
|
|
33
|
+
3 ◼ ◼ 3 ⚑ ◼
|
|
34
|
+
◼ ◼ ◼ → ⚑ ⚑ ◼
|
|
35
|
+
◼ ◼ ◼ ◼ ◼ ◼
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Mine Square
|
|
39
|
+
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.
|
|
40
|
+
|
|
41
|
+
## Gameboard
|
|
42
|
+
A gameboard contains a Width, Height, and Number of Mines.
|
|
43
|
+
|
|
44
|
+
The first move is always safe which means a gameboard's Mines are not placed until the first square is revealed.
|
|
45
|
+
|
|
46
|
+
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)
|
|
47
|
+
|
|
48
|
+
## How To Play
|
|
49
|
+
Reveal squares you believe do not contain a Mine.
|
|
50
|
+
|
|
51
|
+
Your first move will never reveal a Mine.
|
|
52
|
+
|
|
53
|
+
If you reveal a square that contains a Mine, the game will end.
|
|
54
|
+
|
|
55
|
+
## How To Win
|
|
56
|
+
Reveal all Clue Squares without revealing a Mine.
|
|
57
|
+
|
|
58
|
+
### ASCII Reference
|
|
59
|
+
* Flags `⚑`
|
|
60
|
+
* Squares `◻ ◼ ▣`
|
|
61
|
+
* Numbers `➊ ➋ ➌ ➍ ➎ ➏ ➀ ➁ ➂ ➃ ➄ ➅`
|
|
62
|
+
* Mines `☀ ☼ ✺`
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
require 'minesweeprb'
|
|
6
|
+
|
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
9
|
+
|
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
11
|
+
# require "pry"
|
|
12
|
+
# Pry.start
|
|
13
|
+
|
|
14
|
+
require 'irb'
|
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/exe/minesweeprb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
lib_path = File.expand_path('../lib', __dir__)
|
|
5
|
+
$LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
|
|
6
|
+
require 'minesweeprb/cli'
|
|
7
|
+
|
|
8
|
+
Signal.trap('INT') do
|
|
9
|
+
warn("\n#{caller.join("\n")}: interrupted")
|
|
10
|
+
exit(1)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
begin
|
|
14
|
+
Minesweeprb::CLI.start
|
|
15
|
+
rescue Minesweeprb::CLI::Error => e
|
|
16
|
+
puts "ERROR: #{e.message}"
|
|
17
|
+
exit 1
|
|
18
|
+
end
|
data/lib/minesweeprb.rb
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
4
|
+
|
|
5
|
+
module Minesweeprb
|
|
6
|
+
# Handle the application command line parsing
|
|
7
|
+
# and the dispatch to various command objects
|
|
8
|
+
#
|
|
9
|
+
# @api public
|
|
10
|
+
class CLI < Thor
|
|
11
|
+
# Error raised by this runner
|
|
12
|
+
Error = Class.new(StandardError)
|
|
13
|
+
|
|
14
|
+
default_command 'play'
|
|
15
|
+
|
|
16
|
+
desc 'version', 'minesweeprb version'
|
|
17
|
+
def version
|
|
18
|
+
require_relative 'version'
|
|
19
|
+
puts "v#{Minesweeprb::VERSION}"
|
|
20
|
+
end
|
|
21
|
+
map %w[--version -v] => :version
|
|
22
|
+
|
|
23
|
+
desc 'play', 'Play Minesweeper'
|
|
24
|
+
method_option :help, aliases: '-h', type: :boolean,
|
|
25
|
+
desc: 'Display usage information'
|
|
26
|
+
def play(*)
|
|
27
|
+
if options[:help]
|
|
28
|
+
invoke :help, ['play']
|
|
29
|
+
else
|
|
30
|
+
require_relative 'commands/play'
|
|
31
|
+
Minesweeprb::Commands::Play.new(options).execute
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'forwardable'
|
|
4
|
+
|
|
5
|
+
module Minesweeprb
|
|
6
|
+
class Command
|
|
7
|
+
extend Forwardable
|
|
8
|
+
|
|
9
|
+
def_delegators :command, :run
|
|
10
|
+
|
|
11
|
+
# Execute this command
|
|
12
|
+
#
|
|
13
|
+
# @api public
|
|
14
|
+
def execute(*)
|
|
15
|
+
raise(
|
|
16
|
+
NotImplementedError,
|
|
17
|
+
"#{self.class}##{__method__} must be implemented"
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# The external commands runner
|
|
22
|
+
#
|
|
23
|
+
# @see http://www.rubydoc.info/gems/tty-command
|
|
24
|
+
#
|
|
25
|
+
# @api public
|
|
26
|
+
def command(**options)
|
|
27
|
+
require 'tty-command'
|
|
28
|
+
TTY::Command.new(options)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The cursor movement
|
|
32
|
+
#
|
|
33
|
+
# @see http://www.rubydoc.info/gems/tty-cursor
|
|
34
|
+
#
|
|
35
|
+
# @api public
|
|
36
|
+
def cursor
|
|
37
|
+
require 'tty-cursor'
|
|
38
|
+
TTY::Cursor
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Open a file or text in the user's preferred editor
|
|
42
|
+
#
|
|
43
|
+
# @see http://www.rubydoc.info/gems/tty-editor
|
|
44
|
+
#
|
|
45
|
+
# @api public
|
|
46
|
+
def editor
|
|
47
|
+
require 'tty-editor'
|
|
48
|
+
TTY::Editor
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# File manipulation utility methods
|
|
52
|
+
#
|
|
53
|
+
# @see http://www.rubydoc.info/gems/tty-file
|
|
54
|
+
#
|
|
55
|
+
# @api public
|
|
56
|
+
def generator
|
|
57
|
+
require 'tty-file'
|
|
58
|
+
TTY::File
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Terminal output paging
|
|
62
|
+
#
|
|
63
|
+
# @see http://www.rubydoc.info/gems/tty-pager
|
|
64
|
+
#
|
|
65
|
+
# @api public
|
|
66
|
+
def pager(**options)
|
|
67
|
+
require 'tty-pager'
|
|
68
|
+
TTY::Pager.new(options)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Terminal platform and OS properties
|
|
72
|
+
#
|
|
73
|
+
# @see http://www.rubydoc.info/gems/tty-pager
|
|
74
|
+
#
|
|
75
|
+
# @api public
|
|
76
|
+
def platform
|
|
77
|
+
require 'tty-platform'
|
|
78
|
+
TTY::Platform.new
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# The interactive prompt
|
|
82
|
+
#
|
|
83
|
+
# @see http://www.rubydoc.info/gems/tty-prompt
|
|
84
|
+
#
|
|
85
|
+
# @api public
|
|
86
|
+
def prompt(**options)
|
|
87
|
+
require 'tty-prompt'
|
|
88
|
+
TTY::Prompt.new(options)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Get terminal screen properties
|
|
92
|
+
#
|
|
93
|
+
# @see http://www.rubydoc.info/gems/tty-screen
|
|
94
|
+
#
|
|
95
|
+
# @api public
|
|
96
|
+
def screen
|
|
97
|
+
require 'tty-screen'
|
|
98
|
+
TTY::Screen
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# The unix which utility
|
|
102
|
+
#
|
|
103
|
+
# @see http://www.rubydoc.info/gems/tty-which
|
|
104
|
+
#
|
|
105
|
+
# @api public
|
|
106
|
+
def which(*args)
|
|
107
|
+
require 'tty-which'
|
|
108
|
+
TTY::Which.which(*args)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Check if executable exists
|
|
112
|
+
#
|
|
113
|
+
# @see http://www.rubydoc.info/gems/tty-which
|
|
114
|
+
#
|
|
115
|
+
# @api public
|
|
116
|
+
def exec_exist?(*args)
|
|
117
|
+
require 'tty-which'
|
|
118
|
+
TTY::Which.exist?(*args)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def add_color(str, color)
|
|
122
|
+
return str if @options['no-color'] || color == :none
|
|
123
|
+
|
|
124
|
+
@pastel.decorate(str, color)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pastel'
|
|
4
|
+
require 'tty-reader'
|
|
5
|
+
require 'tty-screen'
|
|
6
|
+
|
|
7
|
+
require_relative '../command'
|
|
8
|
+
require_relative '../game'
|
|
9
|
+
|
|
10
|
+
module Minesweeprb
|
|
11
|
+
module Commands
|
|
12
|
+
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
|
+
def initialize(options)
|
|
23
|
+
@options = options
|
|
24
|
+
@pastel = Pastel.new
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def start_game(output)
|
|
28
|
+
height, width =
|
|
29
|
+
|
|
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
|
|
32
|
+
disabled = '(screen too small)' if too_big
|
|
33
|
+
size.merge(
|
|
34
|
+
value: index,
|
|
35
|
+
disabled: disabled
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
size = prompt(interrupt: -> { exit 1 }).select('Size:', sizes, cycle: true)
|
|
40
|
+
output.print cursor.hide
|
|
41
|
+
output.print cursor.up(1)
|
|
42
|
+
output.print cursor.clear_screen_down
|
|
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
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pastel'
|
|
4
|
+
|
|
5
|
+
module Minesweeprb
|
|
6
|
+
class Game
|
|
7
|
+
DEFAULT_SIZE = 'Tiny'
|
|
8
|
+
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 = '☺'
|
|
19
|
+
SIZES = [
|
|
20
|
+
{
|
|
21
|
+
name: 'Tiny',
|
|
22
|
+
width: 5,
|
|
23
|
+
height: 5,
|
|
24
|
+
mines: 3,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'Small',
|
|
28
|
+
width: 9,
|
|
29
|
+
height: 9,
|
|
30
|
+
mines: 10,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'Medium',
|
|
34
|
+
width: 13,
|
|
35
|
+
height: 13,
|
|
36
|
+
mines: 15,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'Large',
|
|
40
|
+
width: 17,
|
|
41
|
+
height: 17,
|
|
42
|
+
mines: 20,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'Huge',
|
|
46
|
+
width: 21,
|
|
47
|
+
height: 21,
|
|
48
|
+
mines: 25,
|
|
49
|
+
},
|
|
50
|
+
].freeze
|
|
51
|
+
|
|
52
|
+
attr_reader :active_square,
|
|
53
|
+
:flagged_squares,
|
|
54
|
+
:marked_squares,
|
|
55
|
+
:mined_squares,
|
|
56
|
+
:pastel,
|
|
57
|
+
:revealed_squares,
|
|
58
|
+
:size,
|
|
59
|
+
:squares
|
|
60
|
+
|
|
61
|
+
def initialize(size)
|
|
62
|
+
@pastel = Pastel.new
|
|
63
|
+
@size = SIZES[size]
|
|
64
|
+
@active_square = center
|
|
65
|
+
@flagged_squares = []
|
|
66
|
+
@marked_squares = []
|
|
67
|
+
@mined_squares = []
|
|
68
|
+
@revealed_squares = {}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def mines
|
|
72
|
+
size[:mines] - flagged_squares.size
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def width
|
|
76
|
+
size[:width]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def height
|
|
80
|
+
size[:height]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def center
|
|
84
|
+
[(width / 2).floor, (height / 2).floor]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def time
|
|
88
|
+
0
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def move(direction)
|
|
92
|
+
return if over?
|
|
93
|
+
|
|
94
|
+
x, y = @active_square
|
|
95
|
+
|
|
96
|
+
case direction
|
|
97
|
+
when :up then y -= 1
|
|
98
|
+
when :down then y += 1
|
|
99
|
+
when :left then x -= 1
|
|
100
|
+
when :right then x += 1
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
x = x < 0 ? width - 1 : x
|
|
104
|
+
x = x > width - 1 ? 0 : x
|
|
105
|
+
y = y < 0 ? height - 1 : y
|
|
106
|
+
y = y > height - 1 ? 0 : y
|
|
107
|
+
|
|
108
|
+
@active_square = [x,y]
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def face
|
|
112
|
+
if won?
|
|
113
|
+
WON_FACE
|
|
114
|
+
elsif lost?
|
|
115
|
+
LOST_FACE
|
|
116
|
+
else
|
|
117
|
+
PLAYING_FACE
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def header
|
|
122
|
+
"#{MINE} #{mines.to_s.rjust(3, '0')}" \
|
|
123
|
+
" #{face} " \
|
|
124
|
+
"#{CLOCK} #{time.to_s.rjust(3, '0')}"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def cycle_flag
|
|
128
|
+
return if over? || @revealed_squares.empty? || @revealed_squares.include?(active_square)
|
|
129
|
+
|
|
130
|
+
if flagged_squares.include?(active_square)
|
|
131
|
+
@flagged_squares -= [active_square]
|
|
132
|
+
@marked_squares += [active_square]
|
|
133
|
+
elsif marked_squares.include?(active_square)
|
|
134
|
+
@marked_squares -= [active_square]
|
|
135
|
+
elsif flagged_squares.length < size[:mines]
|
|
136
|
+
@flagged_squares += [active_square]
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def reveal_active_square
|
|
141
|
+
return if over? || flagged_squares.include?(active_square)
|
|
142
|
+
|
|
143
|
+
reveal_square(active_square)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def squares
|
|
147
|
+
height.times.map do |y|
|
|
148
|
+
width.times.map do |x|
|
|
149
|
+
pos = [x,y]
|
|
150
|
+
|
|
151
|
+
if mined_squares.include?(pos) && (revealed_squares[pos] || over?)
|
|
152
|
+
MINE
|
|
153
|
+
elsif flagged_squares.include?(pos)
|
|
154
|
+
FLAG
|
|
155
|
+
elsif marked_squares.include?(pos)
|
|
156
|
+
MARK
|
|
157
|
+
elsif revealed_squares[pos]
|
|
158
|
+
CLUES[revealed_squares[pos]]
|
|
159
|
+
else
|
|
160
|
+
SQUARE
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def started?
|
|
167
|
+
!over? && revealed_squares.count > 0
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def won?
|
|
171
|
+
!lost? && revealed_squares.count == width * height - size[:mines]
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def lost?
|
|
175
|
+
(revealed_squares.keys & mined_squares).any?
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def over?
|
|
179
|
+
won? || lost?
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
private
|
|
183
|
+
|
|
184
|
+
def place_mines
|
|
185
|
+
size[:mines].times do
|
|
186
|
+
pos = random_square
|
|
187
|
+
pos = random_square while pos == active_square || mined_squares.include?(pos)
|
|
188
|
+
@mined_squares << pos
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def random_square
|
|
193
|
+
x = (1..width).to_a.sample - 1
|
|
194
|
+
y = (1..height).to_a.sample - 1
|
|
195
|
+
[x, y]
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def reveal_square(square)
|
|
199
|
+
place_mines if revealed_squares.empty?
|
|
200
|
+
return if revealed_squares.keys.include?(square)
|
|
201
|
+
return lose! if mined_squares.include?(square)
|
|
202
|
+
|
|
203
|
+
value = square_value(square)
|
|
204
|
+
@revealed_squares[square] = value
|
|
205
|
+
neighbors(square).each { |neighbor| reveal_square(neighbor) } if value.zero?
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def lose!
|
|
209
|
+
@mined_squares.each { |square| @revealed_squares[square] = -1 }
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def square_value(square)
|
|
213
|
+
(neighbors(square) & mined_squares).size
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def neighbors(square)
|
|
217
|
+
[
|
|
218
|
+
# top
|
|
219
|
+
[square[0] - 1, square[1] - 1],
|
|
220
|
+
[square[0] - 0, square[1] - 1],
|
|
221
|
+
[square[0] + 1, square[1] - 1],
|
|
222
|
+
|
|
223
|
+
# middle
|
|
224
|
+
[square[0] - 1, square[1] - 0],
|
|
225
|
+
[square[0] + 1, square[1] - 0],
|
|
226
|
+
|
|
227
|
+
# bottom
|
|
228
|
+
[square[0] - 1, square[1] + 1],
|
|
229
|
+
[square[0] - 0, square[1] + 1],
|
|
230
|
+
[square[0] + 1, square[1] + 1],
|
|
231
|
+
].select do |x,y|
|
|
232
|
+
(0...width).include?(x) && (0...height).include?(y)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#
|
data/minesweeprb.gemspec
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/minesweeprb/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'minesweeprb'
|
|
7
|
+
spec.license = 'MIT'
|
|
8
|
+
spec.version = Minesweeprb::VERSION
|
|
9
|
+
spec.authors = ['scudco']
|
|
10
|
+
spec.email = ['3806+scudco@users.noreply.github.com']
|
|
11
|
+
|
|
12
|
+
spec.summary = 'Terminal-based Minesweeper'
|
|
13
|
+
spec.homepage = 'https://github.com/scudco/minesweeper'
|
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
|
|
15
|
+
|
|
16
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
17
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
|
18
|
+
spec.metadata['changelog_uri'] = spec.homepage
|
|
19
|
+
|
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added
|
|
22
|
+
# into git.
|
|
23
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
24
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
25
|
+
f.match(%r{^(test|spec|features)/})
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
spec.bindir = 'exe'
|
|
29
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
30
|
+
spec.require_paths = ['lib']
|
|
31
|
+
|
|
32
|
+
spec.add_runtime_dependency 'tty', '~> 0.10'
|
|
33
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: minesweeprb
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- scudco
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2020-02-06 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: tty
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.10'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0.10'
|
|
27
|
+
description:
|
|
28
|
+
email:
|
|
29
|
+
- 3806+scudco@users.noreply.github.com
|
|
30
|
+
executables:
|
|
31
|
+
- minesweeprb
|
|
32
|
+
extensions: []
|
|
33
|
+
extra_rdoc_files: []
|
|
34
|
+
files:
|
|
35
|
+
- ".gitignore"
|
|
36
|
+
- ".rspec"
|
|
37
|
+
- ".rubocop.yml"
|
|
38
|
+
- ".travis.yml"
|
|
39
|
+
- Gemfile
|
|
40
|
+
- Gemfile.lock
|
|
41
|
+
- LICENSE
|
|
42
|
+
- README.md
|
|
43
|
+
- Rakefile
|
|
44
|
+
- bin/console
|
|
45
|
+
- bin/setup
|
|
46
|
+
- exe/minesweeprb
|
|
47
|
+
- lib/minesweeprb.rb
|
|
48
|
+
- lib/minesweeprb/cli.rb
|
|
49
|
+
- lib/minesweeprb/command.rb
|
|
50
|
+
- lib/minesweeprb/commands/.gitkeep
|
|
51
|
+
- lib/minesweeprb/commands/play.rb
|
|
52
|
+
- lib/minesweeprb/game.rb
|
|
53
|
+
- lib/minesweeprb/templates/.gitkeep
|
|
54
|
+
- lib/minesweeprb/templates/play/.gitkeep
|
|
55
|
+
- lib/minesweeprb/version.rb
|
|
56
|
+
- minesweeprb.gemspec
|
|
57
|
+
homepage: https://github.com/scudco/minesweeper
|
|
58
|
+
licenses:
|
|
59
|
+
- MIT
|
|
60
|
+
metadata:
|
|
61
|
+
homepage_uri: https://github.com/scudco/minesweeper
|
|
62
|
+
source_code_uri: https://github.com/scudco/minesweeper
|
|
63
|
+
changelog_uri: https://github.com/scudco/minesweeper
|
|
64
|
+
post_install_message:
|
|
65
|
+
rdoc_options: []
|
|
66
|
+
require_paths:
|
|
67
|
+
- lib
|
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: 2.3.0
|
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '0'
|
|
78
|
+
requirements: []
|
|
79
|
+
rubygems_version: 3.1.2
|
|
80
|
+
signing_key:
|
|
81
|
+
specification_version: 4
|
|
82
|
+
summary: Terminal-based Minesweeper
|
|
83
|
+
test_files: []
|