minesweeper-console 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +23 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +4 -0
  6. data/Guardfile +36 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +31 -0
  9. data/Rakefile +11 -0
  10. data/bin/minesweeper +17 -0
  11. data/lib/minesweeper/console.rb +4 -0
  12. data/lib/minesweeper/console/game_loop.rb +58 -0
  13. data/lib/minesweeper/console/parser/command_parser.rb +47 -0
  14. data/lib/minesweeper/console/parser/flag_command.rb +18 -0
  15. data/lib/minesweeper/console/parser/invalid_command_parameters_error.rb +9 -0
  16. data/lib/minesweeper/console/parser/invalid_input_error.rb +11 -0
  17. data/lib/minesweeper/console/parser/null_command.rb +14 -0
  18. data/lib/minesweeper/console/parser/reveal_command.rb +18 -0
  19. data/lib/minesweeper/console/parser/unflag_command.rb +18 -0
  20. data/lib/minesweeper/console/parser/unsupported_command_error.rb +9 -0
  21. data/lib/minesweeper/console/prettyprinter/header_printer.rb +32 -0
  22. data/lib/minesweeper/console/prettyprinter/minefield_pretty_printer.rb +47 -0
  23. data/lib/minesweeper/console/prettyprinter/row_printer.rb +41 -0
  24. data/lib/minesweeper/console/prettyprinter/theme/default_theme.rb +38 -0
  25. data/lib/minesweeper/console/prettyprinter/theme/null_theme.rb +25 -0
  26. data/lib/minesweeper/console/version.rb +5 -0
  27. data/minesweeper-console.gemspec +31 -0
  28. data/test/minesweeper/console/parser/command_parser_test.rb +59 -0
  29. data/test/minesweeper/console/parser/flag_command_test.rb +24 -0
  30. data/test/minesweeper/console/parser/minefield_spy.rb +26 -0
  31. data/test/minesweeper/console/parser/null_command_test.rb +19 -0
  32. data/test/minesweeper/console/parser/reveal_command_test.rb +24 -0
  33. data/test/minesweeper/console/parser/unflag_command_test.rb +24 -0
  34. data/test/minesweeper/console/prettyprinter/header_printer_test.rb +62 -0
  35. data/test/minesweeper/console/prettyprinter/minefield_pretty_printer_test.rb +42 -0
  36. data/test/minesweeper/console/prettyprinter/row_printer_test.rb +60 -0
  37. data/test/minesweeper/console/prettyprinter/theme/default_theme_test.rb +40 -0
  38. data/test/minesweeper/console/prettyprinter/theme/null_theme_test.rb +30 -0
  39. data/test/test_helper.rb +4 -0
  40. metadata +221 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 239fd63509b9da5143a0824db67bcf5b69054d6d
4
+ data.tar.gz: 132a852bac982542d6a17ad15b816cfe8c278000
5
+ SHA512:
6
+ metadata.gz: b01640dc5dfce28e72330a7a8250505918595c7a91d320a013752f957308c80d0944c751468c054f5e03cf2c870a572d38e54d5c7b92f0703448e0b1efda6b3d
7
+ data.tar.gz: e7fc80dfeecda6f82856c2601bddbe9f5f1b9742080d46a50a71385b2dd0daa137175e1e790d5fe7f5036d426542af3e3a995599dd3139b8742bd0db32d96f4e
@@ -0,0 +1 @@
1
+ service_name: travis-ci
@@ -0,0 +1,23 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ *.gem
16
+ .idea/
17
+ .rakeTasks
18
+ *.iml
19
+ <<<<<<< HEAD
20
+ tags
21
+ gems.tags
22
+ *.swp
23
+
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in minesweeper-console.gemspec
4
+ gemspec
@@ -0,0 +1,36 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features)
6
+
7
+ ## Uncomment to clear the screen before every task
8
+ clearing :on
9
+
10
+ ## Guard internally checks for changes in the Guardfile and exits.
11
+ ## If you want Guard to automatically start up again, run guard in a
12
+ ## shell loop, e.g.:
13
+ ##
14
+ ## $ while bundle exec guard; do echo "Restarting Guard..."; done
15
+ ##
16
+ ## Note: if you are using the `directories` clause above and you are not
17
+ ## watching the project directory ('.'), then you will want to move
18
+ ## the Guardfile to a watched dir and symlink it back, e.g.
19
+ #
20
+ # $ mkdir config
21
+ # $ mv Guardfile config/
22
+ # $ ln -s config/Guardfile .
23
+ #
24
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
25
+
26
+ guard :test do
27
+ watch(%r{^test/.+_test\.rb$})
28
+ watch('test/test_helper.rb') { 'test' }
29
+ watch(%r{^lib/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" }
30
+ end
31
+
32
+
33
+ guard 'ctags-bundler', :src_path => ["lib", "test"] do
34
+ watch(/^(lib|test)\/.*\.rb$/)
35
+ watch('Gemfile.lock')
36
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Sébastien Varlet
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,31 @@
1
+ # Minesweeper::Console
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'minesweeper-console'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install minesweeper-console
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Contributing
26
+
27
+ 1. Fork it ( https://github.com/[my-github-username]/minesweeper-console/fork )
28
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
29
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
30
+ 4. Push to the branch (`git push origin my-new-feature`)
31
+ 5. Create a new Pull Request
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.test_files = FileList['test/**/*_test.rb']
7
+ t.verbose = true
8
+ t.warning = true
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'minesweeper/console'
4
+
5
+ args = ARGV.dup
6
+ begin
7
+ row_count = args.shift.strip.to_i
8
+ rescue
9
+ puts "This command requires one argument to set the size of the minefield."
10
+ end
11
+
12
+ trap('INT') do
13
+ puts 'Bye bye !'
14
+ exit
15
+ end
16
+ Minesweeper::Console::GameLoop.new(row_count).start
17
+
@@ -0,0 +1,4 @@
1
+ require 'minesweeper/core'
2
+ require 'minesweeper/console/version'
3
+ require 'minesweeper/console/game_loop'
4
+
@@ -0,0 +1,58 @@
1
+ require 'readline'
2
+ require 'minesweeper/core'
3
+ require 'rainbow'
4
+ require_relative 'prettyprinter/minefield_pretty_printer'
5
+ require_relative 'prettyprinter/theme/default_theme'
6
+ require_relative 'parser/command_parser'
7
+ require_relative 'parser/unsupported_command_error'
8
+ require_relative 'parser/invalid_command_parameters_error'
9
+
10
+ module Minesweeper
11
+ module Console
12
+ class GameLoop
13
+ PROMPT = "(R)eveal, (F)lag, (U)nflag <x> <y>\n>"
14
+ def initialize(size)
15
+ @row_count = size
16
+ @minefield = Minefield.new(@row_count)
17
+ @pretty_printer = Console::PrettyPrinter::MinefieldPrettyPrinter.new(@minefield, Console::PrettyPrinter::Theme::DefaultTheme.new(Rainbow.new))
18
+ @command_parser = Console::Parser::CommandParser.new(@minefield)
19
+ mine_generator = Explosives::MineCoordinatesFactory.new(Random.new)
20
+ @mine_layer = Explosives::MineLayer.new(@minefield, mine_generator)
21
+ end
22
+
23
+ def start
24
+ @mine_layer.lay(@row_count)
25
+ loop do
26
+ begin
27
+ puts @pretty_printer.print
28
+ user_input = Readline.readline(PROMPT, true)
29
+ @command_parser.parse(user_input).execute
30
+ rescue RangeError => e
31
+ print_error('Please type coordinates within the minefield range.')
32
+ rescue Parser::UnsupportedCommandError, Parser::InvalidCommandParametersError => e
33
+ print_error(e.message)
34
+ rescue Minesweeper::Explosives::ExplosionError => e
35
+ print_error(e.message)
36
+ exit
37
+ rescue MinefieldSolvedError
38
+ print_victory
39
+ exit
40
+ end
41
+ end
42
+ end
43
+
44
+ def print_error(message)
45
+ wrap_with_pretty_lines Rainbow(message).yellow.bright
46
+ end
47
+
48
+ def wrap_with_pretty_lines(message)
49
+ puts '-' * 79, message, '-' * 79
50
+ end
51
+
52
+ def print_victory
53
+ wrap_with_pretty_lines Rainbow('Congratulations! You flagged all mines.').green.bright
54
+ end
55
+ end
56
+ end
57
+ end
58
+
@@ -0,0 +1,47 @@
1
+ require_relative 'invalid_command_parameters_error'
2
+ require_relative 'unsupported_command_error'
3
+ require_relative 'null_command'
4
+ require_relative 'reveal_command'
5
+ require_relative 'flag_command'
6
+ require_relative 'unflag_command'
7
+
8
+ module Minesweeper
9
+ module Console
10
+ module Parser
11
+ class CommandParser
12
+ def initialize(a_minefield)
13
+ raise ArgumentError if a_minefield.nil?
14
+ @minefield = a_minefield
15
+ end
16
+
17
+ def parse(user_input)
18
+ if user_input.nil? || user_input =~ /^\s*$/
19
+ return NullCommand.new(@minefield)
20
+ end
21
+ validate_user_input(user_input)
22
+ tokens = user_input.split(/\s+/)
23
+ case tokens[0].upcase
24
+ when 'R', 'REVEAL'
25
+ RevealCommand.new(@minefield, *tokens[1,2].map(&:to_i))
26
+ when 'F', 'FLAG'
27
+ FlagCommand.new(@minefield, *tokens[1,2].map(&:to_i))
28
+ when 'U', 'UNFLAG'
29
+ UnflagCommand.new(@minefield, *tokens[1,2].map(&:to_i))
30
+ end
31
+ end
32
+
33
+ def validate_user_input(user_input)
34
+ if user_input !~ /^[RUFruf] .*$/
35
+ raise UnsupportedCommandError, 'Supported commands are r,u,f,R,U,F.'
36
+ end
37
+ if user_input !~ /^.*\d+ \d+$/
38
+ raise InvalidCommandParametersError, 'A command must be followed by two integers.'
39
+ end
40
+ end
41
+
42
+ private :validate_user_input
43
+ end
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,18 @@
1
+ module Minesweeper
2
+ module Console
3
+ module Parser
4
+ class FlagCommand
5
+ def initialize(a_minefield, x, y)
6
+ @minefield = a_minefield
7
+ @row_index = x
8
+ @column_index = y
9
+ end
10
+
11
+ def execute
12
+ @minefield.flag_at(@row_index, @column_index)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,9 @@
1
+ module Minesweeper
2
+ module Console
3
+ module Parser
4
+ class InvalidCommandParametersError < StandardError
5
+ end
6
+ end
7
+ end
8
+ end
9
+
@@ -0,0 +1,11 @@
1
+
2
+
3
+ module Minesweeper
4
+ module Console
5
+ module Parser
6
+ class InvalidInputError < StandardError
7
+ end
8
+ end
9
+ end
10
+ end
11
+
@@ -0,0 +1,14 @@
1
+ module Minesweeper
2
+ module Console
3
+ module Parser
4
+ class NullCommand
5
+ def initialize(a_minefield)
6
+ end
7
+
8
+ def execute
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,18 @@
1
+ module Minesweeper
2
+ module Console
3
+ module Parser
4
+ class RevealCommand
5
+ def initialize(a_minefield, x, y)
6
+ @minefield = a_minefield
7
+ @row_index = x
8
+ @column_index = y
9
+ end
10
+
11
+ def execute
12
+ @minefield.reveal_at(@row_index, @column_index)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,18 @@
1
+ module Minesweeper
2
+ module Console
3
+ module Parser
4
+ class UnflagCommand
5
+ def initialize(a_minefield, x, y)
6
+ @minefield = a_minefield
7
+ @row_index = x
8
+ @column_index = y
9
+ end
10
+
11
+ def execute
12
+ @minefield.unflag_at(@row_index, @column_index)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,9 @@
1
+ module Minesweeper
2
+ module Console
3
+ module Parser
4
+ class UnsupportedCommandError < StandardError
5
+ end
6
+ end
7
+ end
8
+ end
9
+
@@ -0,0 +1,32 @@
1
+ module Minesweeper
2
+ module Console
3
+ module PrettyPrinter
4
+ class HeaderPrinter
5
+ def initialize(separator, theme)
6
+ raise ArgumentError if separator.nil? || theme.nil?
7
+ @separator = separator
8
+ @theme = theme
9
+ end
10
+
11
+ def print(number_of_columns)
12
+ raise ArgumentError if number_of_columns <= 0
13
+ column_width = compute_column_width_for(number_of_columns)
14
+ result = ' ' * column_width
15
+ result << @theme.colorize_separator(@separator)
16
+ number_of_columns.times do |i|
17
+ column_header = @theme.colorize_header(i.to_s)
18
+ column_header.prepend(' ' * (column_width - i.to_s.length))
19
+ result << column_header
20
+ result << @theme.colorize_separator(@separator)
21
+ end
22
+ result
23
+ end
24
+
25
+ def compute_column_width_for(number_of_columns)
26
+ (number_of_columns - 1).to_s.length
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
@@ -0,0 +1,47 @@
1
+ require 'minesweeper/core'
2
+ require 'rainbow'
3
+ require_relative 'header_printer'
4
+ require_relative 'row_printer'
5
+ require_relative 'theme/default_theme'
6
+
7
+ module Minesweeper
8
+ module Console
9
+ module PrettyPrinter
10
+ class MinefieldPrettyPrinter
11
+ def initialize(a_minefield, a_theme)
12
+ raise ArgumentError if a_minefield.nil?
13
+ @minefield = a_minefield
14
+ @theme = a_theme
15
+ @header_printer = HeaderPrinter.new('|', @theme)
16
+ @row_printer = RowPrinter.new('|', @theme)
17
+ end
18
+
19
+ def print
20
+ cells = @minefield.to_s
21
+ nb_columns = @minefield.row_count
22
+ generate_header(nb_columns) + generate_rows(cells, nb_columns)
23
+ end
24
+
25
+ def generate_header(nb_columns)
26
+ @header_printer.print(nb_columns) + "\n"
27
+ end
28
+
29
+ def generate_rows(cells, nb_columns)
30
+ max_column_width = (nb_columns - 1).to_s.length
31
+ result = ''
32
+ split_string_in_chunks(cells, nb_columns).each_with_index do |row, i|
33
+ result << @row_printer.print(i, row, max_column_width) + "\n"
34
+ end
35
+ result
36
+ end
37
+
38
+ def split_string_in_chunks(a_string, chunk_size)
39
+ a_string.scan(/.{#{chunk_size}}/)
40
+ end
41
+
42
+ private :split_string_in_chunks, :generate_header, :generate_rows
43
+ end
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,41 @@
1
+ module Minesweeper
2
+ module Console
3
+ module PrettyPrinter
4
+ class RowPrinter
5
+ def initialize(separator, theme)
6
+ raise ArgumentError if separator.nil? || theme.nil?
7
+ @separator = separator
8
+ @theme = theme
9
+ end
10
+
11
+ def print(row_number, raw_row, column_width)
12
+ raise ArgumentError if column_width.nil?
13
+ raise ArgumentError if column_width < 1
14
+ raise ArgumentError if column_width < row_number.to_s.length
15
+ result = build_row_header(row_number, column_width)
16
+ raw_row.each_char do |c|
17
+ result << left_padding_for(c, column_width)
18
+ if (c =~ /\d+/)
19
+ result << @theme.colorize_mine_quantity(c)
20
+ else
21
+ result << @theme.colorize_cell_status(c)
22
+ end
23
+ result << @theme.colorize_separator(@separator)
24
+ end
25
+ result
26
+ end
27
+
28
+ def build_row_header(row_number, desired_length)
29
+ result = left_padding_for(row_number.to_s, desired_length)
30
+ result << @theme.colorize_header(row_number.to_s)
31
+ result << @theme.colorize_separator(@separator)
32
+ end
33
+
34
+ def left_padding_for(string, desired_length)
35
+ ' ' * (desired_length - string.length)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+
@@ -0,0 +1,38 @@
1
+ module Minesweeper
2
+ module Console
3
+ module PrettyPrinter
4
+ module Theme
5
+ class DefaultTheme
6
+ def initialize(a_colorizer)
7
+ @colorizer = a_colorizer
8
+ end
9
+
10
+ def colorize_header(a_string)
11
+ @colorizer.wrap(a_string).white
12
+ end
13
+
14
+ def colorize_separator(a_string)
15
+ a_string
16
+ end
17
+
18
+ def colorize_mine_quantity(a_string)
19
+ if (a_string == '0')
20
+ ' '
21
+ else
22
+ @colorizer.wrap(a_string).red
23
+ end
24
+ end
25
+
26
+ def colorize_cell_status(a_string)
27
+ if (a_string == 'F')
28
+ @colorizer.wrap(a_string).green
29
+ else
30
+ @colorizer.wrap(a_string).yellow
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
@@ -0,0 +1,25 @@
1
+ module Minesweeper
2
+ module Console
3
+ module PrettyPrinter
4
+ module Theme
5
+ class NullTheme
6
+ def colorize_header(a_string)
7
+ a_string
8
+ end
9
+
10
+ def colorize_separator(a_string)
11
+ a_string
12
+ end
13
+
14
+ def colorize_mine_quantity(a_string)
15
+ a_string
16
+ end
17
+
18
+ def colorize_cell_status(a_string)
19
+ a_string
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,5 @@
1
+ module Minesweeper
2
+ module Console
3
+ VERSION = "1.0.0"
4
+ end
5
+ end
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'minesweeper/console/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "minesweeper-console"
8
+ spec.version = Minesweeper::Console::VERSION
9
+ spec.authors = ["Sébastien Varlet"]
10
+ spec.email = ["sebastien.varlet@plumbee.co.uk"]
11
+ spec.summary = %q{Console UI for the minesweeper game.}
12
+ spec.description = %q{Console UI for the minesweeper game.}
13
+ spec.homepage = "https://github.com/svarlet/minesweeper-console"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "coveralls"
24
+ spec.add_development_dependency "guard"
25
+ spec.add_development_dependency "guard-test"
26
+ spec.add_development_dependency "guard-ctags-bundler"
27
+ spec.add_development_dependency "byebug"
28
+ spec.add_development_dependency "rainbow", "~> 2.0.0"
29
+
30
+ spec.add_runtime_dependency "minesweeper-core", "~> 2.0.0"
31
+ end
@@ -0,0 +1,59 @@
1
+ require_relative '../../../test_helper'
2
+ require 'minesweeper/console/parser/command_parser'
3
+ require 'minesweeper/console/parser/invalid_command_parameters_error'
4
+ require 'minesweeper/console/parser/invalid_input_error'
5
+ require 'minesweeper/console/parser/reveal_command'
6
+ require 'minesweeper/console/parser/flag_command'
7
+ require 'minesweeper/console/parser/unflag_command'
8
+
9
+ module Minesweeper
10
+ module Console
11
+ module Parser
12
+ class CommandParserTest < Test::Unit::TestCase
13
+ def setup
14
+ @minefield = nil
15
+ @sut = CommandParser.new('')
16
+ end
17
+
18
+ def test_raise_argument_error_when_initialize_parameter_is_nil
19
+ assert_raise(ArgumentError) { CommandParser.new }
20
+ end
21
+
22
+ def test_parse_returns_a_null_command_when_user_input_is_nil_or_empty_or_newline
23
+ assert_instance_of(NullCommand, @sut.parse(nil))
24
+ assert_instance_of(NullCommand, @sut.parse(''))
25
+ assert_instance_of(NullCommand, @sut.parse("\n"))
26
+ assert_instance_of(NullCommand, @sut.parse(' '))
27
+ assert_instance_of(NullCommand, @sut.parse("\r"))
28
+ assert_instance_of(NullCommand, @sut.parse("\t"))
29
+ end
30
+
31
+ def test_raises_error_when_the_first_token_is_not_a_supported_command
32
+ assert_raise(UnsupportedCommandError) { @sut.parse('? 0 0')}
33
+ end
34
+
35
+ def test_raises_error_when_the_last_2_tokens_of_user_input_are_not_integers
36
+ assert_raise(InvalidCommandParametersError) { @sut.parse("R str str") }
37
+ assert_raise(InvalidCommandParametersError) { @sut.parse("R 0 str") }
38
+ assert_raise(InvalidCommandParametersError) { @sut.parse("R str 0") }
39
+ end
40
+
41
+ def test_returns_a_reveal_command_when_first_token_of_user_input_is_R_or_r
42
+ assert_instance_of(RevealCommand, @sut.parse('r 0 0'))
43
+ assert_instance_of(RevealCommand, @sut.parse('R 0 0'))
44
+ end
45
+
46
+ def test_returns_a_flag_command_when_first_token_of_user_input_is_f_or_F
47
+ assert_instance_of(FlagCommand, @sut.parse('f 0 0') )
48
+ assert_instance_of(FlagCommand, @sut.parse('F 0 0') )
49
+ end
50
+
51
+ def test_parse_returns_an_unflag_command_when_first_token_of_user_input_is_u_or_U
52
+ assert_instance_of(UnflagCommand, @sut.parse('u 0 0') )
53
+ assert_instance_of(UnflagCommand, @sut.parse('U 0 0') )
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+
@@ -0,0 +1,24 @@
1
+ require_relative '../../../test_helper'
2
+ require 'minesweeper/console/parser/flag_command'
3
+ require_relative 'minefield_spy'
4
+
5
+ module Minesweeper
6
+ module Console
7
+ module Parser
8
+ class FlagCommandTest < Test::Unit::TestCase
9
+ def test_initialize_requires_a_minefield_and_2_coordinates
10
+ assert_nothing_raised { FlagCommand.new(nil, 4, 19) }
11
+ end
12
+
13
+ def test_execute_should_call_flag_at_on_the_minefield_object
14
+ spy = MinefieldSpy.new
15
+ sut = FlagCommand.new(spy, 4, 19)
16
+ sut.execute
17
+ assert_equal(1, spy.methods_called.length)
18
+ assert_equal([:flag_at, 4, 19], spy.methods_called.pop)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,26 @@
1
+ module Minesweeper
2
+ module Console
3
+ module Parser
4
+ class MinefieldSpy
5
+ attr_reader :methods_called
6
+
7
+ def initialize
8
+ @methods_called = []
9
+ end
10
+
11
+ def reveal_at(x, y)
12
+ @methods_called << [:reveal_at, x, y]
13
+ end
14
+
15
+ def flag_at(x, y)
16
+ @methods_called << [:flag_at, x, y]
17
+ end
18
+
19
+ def unflag_at(x, y)
20
+ @methods_called << [:unflag_at, x, y]
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,19 @@
1
+ require_relative '../../../test_helper'
2
+ require 'minesweeper/console/parser/null_command'
3
+
4
+ module Minesweeper
5
+ module Console
6
+ module Parser
7
+ class NullCommandTest < Test::Unit::TestCase
8
+ def test_initializes_with_one_parameter
9
+ NullCommand.new(nil)
10
+ end
11
+
12
+ def test_execute_does_nothing
13
+ assert_nothing_raised(StandardError) { NullCommand.new(nil).execute }
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+
@@ -0,0 +1,24 @@
1
+ require_relative '../../../test_helper'
2
+ require_relative 'minefield_spy'
3
+ require 'minesweeper/console/parser/reveal_command'
4
+
5
+ module Minesweeper
6
+ module Console
7
+ module Parser
8
+ class RevealCommandTest < Test::Unit::TestCase
9
+ def test_initializes_requires_a_minefield_parameter_and_2_coordinates
10
+ assert_nothing_raised { RevealCommand.new(nil, 4, 19) }
11
+ end
12
+
13
+ def test_execute_only_delegate_to_the_reveal_method_of_the_minefield_object
14
+ spy = MinefieldSpy.new
15
+ sut = RevealCommand.new(spy, 4, 19)
16
+ sut.execute
17
+ assert_equal(1, spy.methods_called.length)
18
+ assert_equal([:reveal_at, 4, 19], spy.methods_called.pop)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,24 @@
1
+ require_relative '../../../test_helper'
2
+ require 'minesweeper/console/parser/unflag_command'
3
+ require_relative 'minefield_spy'
4
+
5
+ module Minesweeper
6
+ module Console
7
+ module Parser
8
+ class UnflagCommandTest < Test::Unit::TestCase
9
+ def test_initialize_requires_a_minefield_and_2_coordinates
10
+ assert_nothing_raised { UnflagCommand.new(nil, 0, 0) }
11
+ end
12
+
13
+ def test_execute_should_call_unflag_at_on_the_minefield_object
14
+ spy = MinefieldSpy.new
15
+ sut = UnflagCommand.new(spy, 4, 19)
16
+ sut.execute
17
+ assert_equal(1, spy.methods_called.length)
18
+ assert_equal([:unflag_at, 4, 19], spy.methods_called.pop)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,62 @@
1
+ require 'minesweeper/console/prettyprinter/header_printer'
2
+ require_relative '../../../test_helper'
3
+ require 'minesweeper/console/prettyprinter/theme/null_theme'
4
+
5
+ module Minesweeper
6
+ module Console
7
+ module PrettyPrinter
8
+ class HeaderPrinterTest < Test::Unit::TestCase
9
+ SEP = '§'
10
+
11
+ def setup
12
+ @printer = HeaderPrinter.new(SEP, Theme::NullTheme.new)
13
+ end
14
+
15
+ def test_raises_error_when_initialized_with_nil_separator
16
+ assert_raise(ArgumentError) { HeaderPrinter.new(nil, Theme::NullTheme.new) }
17
+ end
18
+
19
+ def test_raises_error_when_initialized_with_nil_theme
20
+ assert_raise(ArgumentError) { HeaderPrinter.new('.', nil) }
21
+ end
22
+
23
+ def test_raises_error_when_number_of_columns_is_zero
24
+ assert_raise(ArgumentError) { @printer.print(0) }
25
+ end
26
+
27
+ def test_raises_error_when_number_of_columns_is_negative
28
+ assert_raise(ArgumentError) { @printer.print(-1) }
29
+ end
30
+
31
+ def test_makes_room_for_the_greatest_row_number
32
+ assert_match(/^ .*$/, @printer.print(1))
33
+ assert_match(/^ .*$/, @printer.print(11))
34
+ assert_match(/^ .*$/, @printer.print(101))
35
+ end
36
+
37
+ def test_inserts_separator_after_first_column
38
+ assert_match(/^\s+#{SEP}.*$/, @printer.print(1))
39
+ end
40
+
41
+ def test_first_column_number_is_zero
42
+ assert_match(/^\s#{SEP}0.*$/, @printer.print(1))
43
+ end
44
+
45
+ def test_first_column_number_is_followed_by_separator
46
+ assert_match(/^.*#{SEP}$/, @printer.print(1))
47
+ end
48
+
49
+ def test_prints_all_columns_number_upto_but_not_including_the_number_of_columns
50
+ assert_equal(" #{SEP}0#{SEP}1#{SEP}", @printer.print(2))
51
+ end
52
+
53
+ def test_each_column_is_printed_with_the_width_of_the_greatest_column_number
54
+ result = @printer.print(100)
55
+ headers = result.split(SEP)
56
+ headers.each { |header| assert_equal(2, header.length) }
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+
@@ -0,0 +1,42 @@
1
+ require 'minesweeper/console/prettyprinter/minefield_pretty_printer'
2
+ require 'minesweeper/console/prettyprinter/theme/null_theme'
3
+ require_relative '../../../test_helper'
4
+
5
+ module Minesweeper
6
+ module Console
7
+ module PrettyPrinter
8
+ class MinefieldPrettyPrinterTest < Test::Unit::TestCase
9
+ def test_raises_error_when_initialized_with_a_nil_minefield
10
+ assert_raise(ArgumentError) { MinefieldPrettyPrinter.new(nil) }
11
+ end
12
+
13
+ def test_output
14
+ verify_printing_of(" |0|1|\n0|1|2|\n1|H|F|\n", '12HF')
15
+ verify_printing_of(" |0|1|2|3|\n0|A|A|A|A|\n1|B|B|B|B|\n2|C|C|C|C|\n3|D|D|D|D|\n", 'AAAABBBBCCCCDDDD')
16
+ end
17
+
18
+ def verify_printing_of(expected, str_representation)
19
+ minefield = MinefieldMock.new(str_representation)
20
+ theme = Theme::NullTheme.new
21
+ printer = MinefieldPrettyPrinter.new(minefield, theme)
22
+ assert_equal(expected, printer.print)
23
+ end
24
+ end
25
+
26
+ class MinefieldMock
27
+ def initialize(string_representation)
28
+ @string_representation = string_representation
29
+ end
30
+
31
+ def to_s
32
+ @string_representation
33
+ end
34
+
35
+ def row_count
36
+ Math.sqrt(@string_representation.length).to_i
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+
@@ -0,0 +1,60 @@
1
+ require 'minesweeper/console/prettyprinter/row_printer'
2
+ require_relative '../../../test_helper'
3
+ require 'minesweeper/console/prettyprinter/theme/null_theme'
4
+
5
+ module Minesweeper
6
+ module Console
7
+ module PrettyPrinter
8
+ class RowPrinterTest < Test::Unit::TestCase
9
+ SEP = '§'
10
+
11
+ def setup
12
+ @printer = RowPrinter.new(SEP, Theme::NullTheme.new)
13
+ end
14
+
15
+ def test_raise_error_when_initialized_with_a_nil_separator
16
+ assert_raise(ArgumentError) { RowPrinter.new(nil, Theme::NullTheme.new) }
17
+ end
18
+
19
+ def test_raise_error_when_initialized_with_a_nil_theme
20
+ assert_raise(ArgumentError) { RowPrinter.new('.', nil) }
21
+ end
22
+ def test_print_raises_error_when_column_width_is_nil
23
+ assert_raise(ArgumentError) { @printer.print(0, 'XYZ', nil) }
24
+ end
25
+
26
+ def test_print_raises_error_when_columns_width_is_not_strictly_positive
27
+ assert_raise(ArgumentError) { @printer.print(0, 'XYZ', 0) }
28
+ assert_raise(ArgumentError) { @printer.print(0, 'XYZ', -1) }
29
+ end
30
+
31
+ def test_print_raises_error_when_desired_column_width_is_smaller_than_row_number
32
+ assert_raise(ArgumentError) { @printer.print(10, 'XYZ', 1) }
33
+ end
34
+
35
+ def test_prints_row_number_first
36
+ assert_match(/^0.*$/, @printer.print(0, 'HHHH', 1))
37
+ end
38
+
39
+ def test_prints_separator_after_row_number
40
+ assert_match(/^\d+#{SEP}.*$/, @printer.print(0, 'HHHH', 1))
41
+ end
42
+
43
+ def test_prints_each_character_of_the_sequence_separated_by_separator
44
+ assert_match(/^\d+#{SEP}X#{SEP}Y#{SEP}Z.*$/, @printer.print(0, 'XYZ', 1))
45
+ end
46
+
47
+ def test_print_appends_separator_at_end_of_line
48
+ assert_match(/^.*#{SEP}$/, @printer.print(0, 'XYZ', 1))
49
+ end
50
+
51
+ def test_enforces_column_width_on_each_column
52
+ result = @printer.print(0, 'XYZ', 3)
53
+ cells = result.split(SEP)
54
+ cells.each { |cell| assert_equal(3, cell.length) }
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+
@@ -0,0 +1,40 @@
1
+ require_relative '../../../../test_helper'
2
+ require 'minesweeper/console/prettyprinter/theme/default_theme'
3
+ require 'rainbow'
4
+
5
+ module Minesweeper
6
+ module Console
7
+ module PrettyPrinter
8
+ module Theme
9
+ class DefaultThemeTest < Test::Unit::TestCase
10
+ def test_is_initializable
11
+ DefaultTheme.new(@colorizer)
12
+ end
13
+
14
+ def setup
15
+ @colorizer = Rainbow.new
16
+ @theme = DefaultTheme.new(@colorizer)
17
+ end
18
+
19
+ def test_class_implementation
20
+ class_specs = [:colorize_header, :colorize_separator, :colorize_mine_quantity, :colorize_cell_status]
21
+ class_specs.each do |spec|
22
+ assert_respond_to @theme, spec
23
+ assert_raise(ArgumentError) { @theme.send(spec) }
24
+ assert_equal('efhfh', do_with_colorizer_disabled { @theme.send(spec, 'efhfh') } )
25
+ end
26
+ end
27
+
28
+ def do_with_colorizer_disabled(&block)
29
+ @colorizer.enabled = false
30
+ result = yield
31
+ @colorizer.enabled = true
32
+ result
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+
@@ -0,0 +1,30 @@
1
+ require_relative '../../../../test_helper'
2
+ require 'minesweeper/console/prettyprinter/theme/null_theme'
3
+
4
+ module Minesweeper
5
+ module Console
6
+ module PrettyPrinter
7
+ module Theme
8
+ class NullThemeTest < Test::Unit::TestCase
9
+ def test_is_initializable
10
+ NullTheme.new
11
+ end
12
+
13
+ def setup
14
+ @theme = NullTheme.new
15
+ end
16
+
17
+ def test_class_implementation
18
+ class_specs = [:colorize_header, :colorize_separator, :colorize_mine_quantity, :colorize_cell_status]
19
+ class_specs.each do |spec|
20
+ assert_respond_to @theme, spec
21
+ assert_raise(ArgumentError) { @theme.send(spec) }
22
+ assert_equal('efhfh', @theme.send(spec, 'efhfh'))
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,4 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
3
+
4
+ require 'test/unit'
metadata ADDED
@@ -0,0 +1,221 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: minesweeper-console
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Sébastien Varlet
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-06-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: coveralls
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: guard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-test
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: guard-ctags-bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: byebug
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rainbow
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: 2.0.0
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ~>
123
+ - !ruby/object:Gem::Version
124
+ version: 2.0.0
125
+ - !ruby/object:Gem::Dependency
126
+ name: minesweeper-core
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ~>
130
+ - !ruby/object:Gem::Version
131
+ version: 2.0.0
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ~>
137
+ - !ruby/object:Gem::Version
138
+ version: 2.0.0
139
+ description: Console UI for the minesweeper game.
140
+ email:
141
+ - sebastien.varlet@plumbee.co.uk
142
+ executables:
143
+ - minesweeper
144
+ extensions: []
145
+ extra_rdoc_files: []
146
+ files:
147
+ - .coveralls.yml
148
+ - .gitignore
149
+ - .travis.yml
150
+ - Gemfile
151
+ - Guardfile
152
+ - LICENSE.txt
153
+ - README.md
154
+ - Rakefile
155
+ - bin/minesweeper
156
+ - lib/minesweeper/console.rb
157
+ - lib/minesweeper/console/game_loop.rb
158
+ - lib/minesweeper/console/parser/command_parser.rb
159
+ - lib/minesweeper/console/parser/flag_command.rb
160
+ - lib/minesweeper/console/parser/invalid_command_parameters_error.rb
161
+ - lib/minesweeper/console/parser/invalid_input_error.rb
162
+ - lib/minesweeper/console/parser/null_command.rb
163
+ - lib/minesweeper/console/parser/reveal_command.rb
164
+ - lib/minesweeper/console/parser/unflag_command.rb
165
+ - lib/minesweeper/console/parser/unsupported_command_error.rb
166
+ - lib/minesweeper/console/prettyprinter/header_printer.rb
167
+ - lib/minesweeper/console/prettyprinter/minefield_pretty_printer.rb
168
+ - lib/minesweeper/console/prettyprinter/row_printer.rb
169
+ - lib/minesweeper/console/prettyprinter/theme/default_theme.rb
170
+ - lib/minesweeper/console/prettyprinter/theme/null_theme.rb
171
+ - lib/minesweeper/console/version.rb
172
+ - minesweeper-console.gemspec
173
+ - test/minesweeper/console/parser/command_parser_test.rb
174
+ - test/minesweeper/console/parser/flag_command_test.rb
175
+ - test/minesweeper/console/parser/minefield_spy.rb
176
+ - test/minesweeper/console/parser/null_command_test.rb
177
+ - test/minesweeper/console/parser/reveal_command_test.rb
178
+ - test/minesweeper/console/parser/unflag_command_test.rb
179
+ - test/minesweeper/console/prettyprinter/header_printer_test.rb
180
+ - test/minesweeper/console/prettyprinter/minefield_pretty_printer_test.rb
181
+ - test/minesweeper/console/prettyprinter/row_printer_test.rb
182
+ - test/minesweeper/console/prettyprinter/theme/default_theme_test.rb
183
+ - test/minesweeper/console/prettyprinter/theme/null_theme_test.rb
184
+ - test/test_helper.rb
185
+ homepage: https://github.com/svarlet/minesweeper-console
186
+ licenses:
187
+ - MIT
188
+ metadata: {}
189
+ post_install_message:
190
+ rdoc_options: []
191
+ require_paths:
192
+ - lib
193
+ required_ruby_version: !ruby/object:Gem::Requirement
194
+ requirements:
195
+ - - '>='
196
+ - !ruby/object:Gem::Version
197
+ version: '0'
198
+ required_rubygems_version: !ruby/object:Gem::Requirement
199
+ requirements:
200
+ - - '>='
201
+ - !ruby/object:Gem::Version
202
+ version: '0'
203
+ requirements: []
204
+ rubyforge_project:
205
+ rubygems_version: 2.4.5
206
+ signing_key:
207
+ specification_version: 4
208
+ summary: Console UI for the minesweeper game.
209
+ test_files:
210
+ - test/minesweeper/console/parser/command_parser_test.rb
211
+ - test/minesweeper/console/parser/flag_command_test.rb
212
+ - test/minesweeper/console/parser/minefield_spy.rb
213
+ - test/minesweeper/console/parser/null_command_test.rb
214
+ - test/minesweeper/console/parser/reveal_command_test.rb
215
+ - test/minesweeper/console/parser/unflag_command_test.rb
216
+ - test/minesweeper/console/prettyprinter/header_printer_test.rb
217
+ - test/minesweeper/console/prettyprinter/minefield_pretty_printer_test.rb
218
+ - test/minesweeper/console/prettyprinter/row_printer_test.rb
219
+ - test/minesweeper/console/prettyprinter/theme/default_theme_test.rb
220
+ - test/minesweeper/console/prettyprinter/theme/null_theme_test.rb
221
+ - test/test_helper.rb