cb-core 0.1.2 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a8c84ec479f860091201255deb7c8204b624fd76fbb810b9f37d3109a439a3e
4
- data.tar.gz: 74fd4f6be8e760df7ae607b90ce26289c691adedb7c1e46d48a16637f158f3d4
3
+ metadata.gz: 10d77f2204399e688b8fdc7682608c67a20635e2081089bb19ccb67e937ab8e5
4
+ data.tar.gz: 8b1beda09989c6570219bd385d4f280772c62990471367b3d561a36357cae59d
5
5
  SHA512:
6
- metadata.gz: eecef01c72a243ab57bc55462fc5a8656dabd6e4fdb51da7311a933325d266c3e19f4f1f1e500503ef7b8f79f5e6f981378911fd237398e46614aebc4f39c36b
7
- data.tar.gz: 0b92ac7fef950bc1931575c6d53f29ccf36d0d8a0cd8ee2405efb41114de90c161813fd90424482cae7d5df51132c06187a8f4e1676f62d123fdb8335417f708
6
+ metadata.gz: cc43320fc01a5bd588122b13ef482017b8a067efeccd37d53fa1e530477cea056e99e00b349ecfdfa9a0ba12b22b11ad4b99f0e957fbf0f05b204fb7974506b3
7
+ data.tar.gz: 62b52ad66559cfbb93e49d3269ef87e20db0673cf3aff4885d88ab81aac26829ccb3876d1728b43b668f1326f3253050ba202da2cefbc0979bc359de01531aae
data/.fasterer.yml CHANGED
@@ -19,4 +19,4 @@ speedups:
19
19
  setter_vs_attr_writer: true
20
20
 
21
21
  exclude_paths:
22
- - 'vendor/**/*.rb'
22
+ - 'vendor/**/*.rb'
data/.gitignore CHANGED
@@ -14,3 +14,5 @@
14
14
  *.gem
15
15
 
16
16
  .idea
17
+
18
+ db
data/.rubocop.yml CHANGED
@@ -12,6 +12,10 @@ Metrics/BlockLength:
12
12
  Exclude:
13
13
  - 'spec/**/*.rb'
14
14
 
15
+ Metrics/ModuleLength:
16
+ Exclude:
17
+ - 'spec/**/*.rb'
18
+
15
19
  Style/Documentation:
16
20
  Enabled: false
17
21
 
data/Gemfile.lock CHANGED
@@ -1,18 +1,22 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cb-core (0.1.1)
4
+ cb-core (0.1.8)
5
+ i18n (~> 1.8.9)
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
8
9
  specs:
9
10
  ast (2.4.2)
10
11
  colorize (0.8.1)
12
+ concurrent-ruby (1.1.8)
11
13
  diff-lcs (1.4.4)
12
14
  docile (1.3.5)
13
15
  fasterer (0.8.3)
14
16
  colorize (~> 0.7)
15
17
  ruby_parser (>= 3.14.1)
18
+ i18n (1.8.10)
19
+ concurrent-ruby (~> 1.0)
16
20
  parallel (1.20.1)
17
21
  parser (3.0.0.0)
18
22
  ast (~> 2.4.1)
data/codebreaker.gemspec CHANGED
@@ -1,15 +1,15 @@
1
1
  require_relative 'lib/codebreaker/version'
2
2
 
3
3
  Gem::Specification.new do |spec|
4
- spec.name = 'cb-core'
5
- spec.version = Codebreaker::VERSION
6
- spec.authors = ['Dmytro Yakupov']
7
- spec.email = ['yakupov.dmitrij@gmail.com']
4
+ spec.name = 'cb-core'
5
+ spec.version = Codebreaker::VERSION
6
+ spec.authors = ['Dmytro Yakupov']
7
+ spec.email = ['yakupov.dmitrij@gmail.com']
8
8
 
9
- spec.summary = 'Core'
10
- spec.description = 'Contains a core logic.'
11
- spec.homepage = 'https://github.com/YakupovDima/rubygarage-codebreaker-core'
12
- spec.license = 'MIT'
9
+ spec.summary = 'Core'
10
+ spec.description = 'Contains a core logic.'
11
+ spec.homepage = 'https://github.com/YakupovDima/rubygarage-codebreaker-core'
12
+ spec.license = 'MIT'
13
13
  spec.required_ruby_version = Gem::Requirement.new('>= 2.7.0')
14
14
 
15
15
  spec.metadata['homepage_uri'] = spec.homepage
@@ -20,10 +20,12 @@ Gem::Specification.new do |spec|
20
20
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
21
21
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
22
22
  end
23
- spec.bindir = 'exe'
24
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
23
+ spec.bindir = 'exe'
24
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
25
25
  spec.require_paths = ['lib']
26
26
 
27
+ spec.add_dependency 'i18n', '~> 1.8.9'
28
+
27
29
  spec.add_development_dependency 'fasterer', '~> 0.8.3'
28
30
  spec.add_development_dependency 'rspec', '~> 3.0'
29
31
  spec.add_development_dependency 'rspec_junit_formatter', '~> 0.4'
data/config/i18n.rb ADDED
@@ -0,0 +1,3 @@
1
+ I18n.load_path << Dir["#{File.expand_path('config/locales')}/*.yml"]
2
+ I18n.config.available_locales = :en
3
+ I18n.default_locale = :en
@@ -0,0 +1,4 @@
1
+ en:
2
+ errors:
3
+ invalid_difficulty: "unknown difficulty type %{type}"
4
+ validation_length: "invalid length '%{value}'. min=%{min}, max=%{max}"
data/lib/codebreaker.rb CHANGED
@@ -1,19 +1,24 @@
1
+ require 'fileutils'
2
+ require 'yaml'
3
+ require 'i18n'
4
+
5
+ require_relative '../config/i18n'
6
+
7
+ require_relative 'codebreaker/difficulty'
1
8
  require_relative 'codebreaker/difficulty_factory'
2
9
  require_relative 'codebreaker/difficulty_type'
3
- require_relative 'codebreaker/difficulties/base'
4
- require_relative 'codebreaker/difficulties/easy'
5
- require_relative 'codebreaker/difficulties/medium'
6
- require_relative 'codebreaker/difficulties/hell'
7
10
  require_relative 'codebreaker/errors/game_over_error'
11
+ require_relative 'codebreaker/errors/game_save_error'
8
12
  require_relative 'codebreaker/errors/invalid_difficulty_error'
9
13
  require_relative 'codebreaker/errors/no_hints_error'
10
14
  require_relative 'codebreaker/errors/validation_length_error'
15
+ require_relative 'codebreaker/create_stat_service'
11
16
  require_relative 'codebreaker/game'
12
- require_relative 'codebreaker/hint'
13
- require_relative 'codebreaker/mixins/validatable'
17
+ require_relative 'codebreaker/mixins/validator'
14
18
  require_relative 'codebreaker/player'
15
19
  require_relative 'codebreaker/secret_code_generator'
16
20
  require_relative 'codebreaker/secret_code_matcher'
21
+ require_relative 'codebreaker/stats_storage'
17
22
  require_relative 'codebreaker/version'
18
23
 
19
24
  module Codebreaker
@@ -0,0 +1,24 @@
1
+ module Codebreaker
2
+ class CreateStatService
3
+ def initialize(game)
4
+ @game = game
5
+ end
6
+
7
+ def call
8
+ StatsStorage.new.add(stat_data)
9
+ end
10
+
11
+ private
12
+
13
+ def stat_data
14
+ {
15
+ name: @game.player.name,
16
+ difficulty: @game.difficulty.type,
17
+ attempts_total: @game.difficulty.attempts,
18
+ attempts_used: @game.difficulty.attempts - @game.attempts_left,
19
+ hints_total: @game.difficulty.hints,
20
+ hints_used: @game.difficulty.hints - @game.hints_left
21
+ }
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,11 @@
1
+ module Codebreaker
2
+ class Difficulty
3
+ attr_reader :type, :attempts, :hints
4
+
5
+ def initialize(type, attempts, hints)
6
+ @type = type
7
+ @attempts = attempts
8
+ @hints = hints
9
+ end
10
+ end
11
+ end
@@ -3,13 +3,13 @@ module Codebreaker
3
3
  def self.build(type)
4
4
  case type
5
5
  when DifficultyType::EASY
6
- Difficulties::Easy.new
6
+ Difficulty.new(DifficultyType::EASY, 15, 2)
7
7
  when DifficultyType::MEDIUM
8
- Difficulties::Medium.new
8
+ Difficulty.new(DifficultyType::MEDIUM, 10, 1)
9
9
  when DifficultyType::HELL
10
- Difficulties::Hell.new
10
+ Difficulty.new(DifficultyType::HELL, 5, 1)
11
11
  else
12
- raise Errors::InvalidDifficultyError, "unknown difficulty type #{type}"
12
+ raise Errors::InvalidDifficultyError, I18n.t('errors.invalid_difficulty', type: type)
13
13
  end
14
14
  end
15
15
  end
@@ -0,0 +1,9 @@
1
+ module Codebreaker
2
+ module Errors
3
+ class GameSaveError < StandardError
4
+ def initialize(msg = 'Only won game could be saved')
5
+ super
6
+ end
7
+ end
8
+ end
9
+ end
@@ -1,26 +1,34 @@
1
1
  module Codebreaker
2
2
  class Game
3
- attr_reader :secret_code
3
+ attr_reader :player, :attempts_left
4
4
 
5
5
  def initialize(player, difficulty_type)
6
6
  @player = player
7
- @secret_code = SecretCodeGenerator.new.call
8
- @difficulty = DifficultyFactory.build(difficulty_type)
9
- @hint = Hint.new(@difficulty, @secret_code)
10
- @attempts_used = 0
7
+ @difficulty_type = difficulty_type
8
+ @attempts_left = difficulty.attempts
11
9
  @is_win = false
12
10
  end
13
11
 
12
+ def secret_code
13
+ @secret_code ||= SecretCodeGenerator.new.call
14
+ end
15
+
16
+ def difficulty
17
+ @difficulty ||= DifficultyFactory.build(@difficulty_type)
18
+ end
19
+
14
20
  def take_hint
15
21
  ensure_game_is_not_over
16
- @hint.call
22
+ raise Errors::NoHintsError if hints.empty?
23
+
24
+ hints.pop
17
25
  end
18
26
 
19
27
  def guess_secret_code(guess)
20
28
  ensure_game_is_not_over
21
- @attempts_used += 1
22
- matching = SecretCodeMatcher.new(@secret_code, guess).call
23
- check_win(matching)
29
+ @attempts_left -= 1
30
+ matching = SecretCodeMatcher.new(secret_code, guess).call
31
+ check_win(guess)
24
32
  matching
25
33
  end
26
34
 
@@ -32,22 +40,36 @@ module Codebreaker
32
40
  @is_win
33
41
  end
34
42
 
43
+ def over?
44
+ win? || lose?
45
+ end
46
+
47
+ def hints_left
48
+ hints.count
49
+ end
50
+
51
+ def save_result
52
+ raise Errors::GameSaveError unless win?
53
+
54
+ CreateStatService.new(self).call
55
+ end
56
+
35
57
  private
36
58
 
37
- def game_over?
38
- win? || lose?
59
+ def hints
60
+ @hints ||= secret_code.sample(difficulty.hints)
39
61
  end
40
62
 
41
63
  def no_attempts?
42
- @difficulty.attempts == @attempts_used
64
+ attempts_left.zero?
43
65
  end
44
66
 
45
67
  def ensure_game_is_not_over
46
- raise Errors::GameOverError if game_over?
68
+ raise Errors::GameOverError if over?
47
69
  end
48
70
 
49
- def check_win(combination)
50
- @is_win = true if SecretCodeMatcher.win_combination?(combination)
71
+ def check_win(guess)
72
+ @is_win = true if guess == secret_code
51
73
  end
52
74
  end
53
75
  end
@@ -0,0 +1,12 @@
1
+ module Codebreaker
2
+ module Mixins
3
+ module Validator
4
+ def validate_length(value, min, max)
5
+ return if value.instance_of?(String) && value.size.between?(min, max)
6
+
7
+ raise Codebreaker::Errors::ValidationLengthError,
8
+ I18n.t('errors.validation_length', value: value, min: min, max: max)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,21 +1,20 @@
1
1
  module Codebreaker
2
2
  class Player
3
- include Mixins::Validatable
3
+ include Mixins::Validator
4
4
 
5
- NAME_MIN_SIZE = 3
6
- NAME_MAX_SIZE = 20
5
+ NAME_SIZE = {
6
+ min: 3,
7
+ max: 20
8
+ }.freeze
7
9
 
8
10
  attr_reader :name
9
11
 
10
12
  def initialize(name)
11
13
  @name = name
12
- validate
13
14
  end
14
15
 
15
- private
16
-
17
16
  def validate
18
- validate_length(@name, NAME_MIN_SIZE, NAME_MAX_SIZE)
17
+ validate_length(@name, NAME_SIZE[:min], NAME_SIZE[:max])
19
18
  end
20
19
  end
21
20
  end
@@ -2,11 +2,6 @@ module Codebreaker
2
2
  class SecretCodeMatcher
3
3
  EXACT_MATCH_SYMBOL = '+'.freeze
4
4
  DIGIT_MATCH_SYMBOL = '-'.freeze
5
- WIN_COMBINATION = '++++'.freeze
6
-
7
- def self.win_combination?(combination)
8
- combination == WIN_COMBINATION
9
- end
10
5
 
11
6
  def initialize(secret_code, guess_code)
12
7
  @secret_code = secret_code.dup
@@ -20,20 +15,17 @@ module Codebreaker
20
15
  private
21
16
 
22
17
  def exact_match
23
- result = ''
24
- @guess_code.each_with_index do |digit, index|
18
+ @guess_code.each_with_object('').with_index do |(digit, result), index|
25
19
  next unless digit == @secret_code[index]
26
20
 
27
21
  result << EXACT_MATCH_SYMBOL
28
22
  @guess_code[index] = nil
29
23
  @secret_code[index] = nil
30
24
  end
31
- result
32
25
  end
33
26
 
34
27
  def digit_match
35
- result = ''
36
- @guess_code.compact.each do |digit|
28
+ @guess_code.compact.each_with_object('') do |digit, result|
37
29
  found_index = @secret_code.index(digit)
38
30
 
39
31
  next unless found_index
@@ -41,7 +33,6 @@ module Codebreaker
41
33
  result << DIGIT_MATCH_SYMBOL
42
34
  @secret_code[found_index] = nil
43
35
  end
44
- result
45
36
  end
46
37
  end
47
38
  end
@@ -0,0 +1,22 @@
1
+ module Codebreaker
2
+ class StatsStorage
3
+ PATH = './db/data.yml'.freeze
4
+
5
+ def load
6
+ File.file?(PATH) ? YAML.load_file(PATH) : []
7
+ end
8
+
9
+ def add(stat)
10
+ stats = load
11
+ stats << stat
12
+ save(stats)
13
+ end
14
+
15
+ private
16
+
17
+ def save(stats)
18
+ Pathname(PATH).dirname.mkpath
19
+ File.write(PATH, stats.to_yaml)
20
+ end
21
+ end
22
+ end
@@ -1,3 +1,3 @@
1
1
  module Codebreaker
2
- VERSION = '0.1.2'.freeze
2
+ VERSION = '0.1.8'.freeze
3
3
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cb-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmytro Yakupov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-28 00:00:00.000000000 Z
11
+ date: 2021-04-23 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: i18n
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.8.9
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.8.9
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: fasterer
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -129,23 +143,24 @@ files:
129
143
  - bin/console
130
144
  - bin/setup
131
145
  - codebreaker.gemspec
146
+ - config/i18n.rb
147
+ - config/locales/en.yml
132
148
  - lib/codebreaker.rb
133
- - lib/codebreaker/difficulties/base.rb
134
- - lib/codebreaker/difficulties/easy.rb
135
- - lib/codebreaker/difficulties/hell.rb
136
- - lib/codebreaker/difficulties/medium.rb
149
+ - lib/codebreaker/create_stat_service.rb
150
+ - lib/codebreaker/difficulty.rb
137
151
  - lib/codebreaker/difficulty_factory.rb
138
152
  - lib/codebreaker/difficulty_type.rb
139
153
  - lib/codebreaker/errors/game_over_error.rb
154
+ - lib/codebreaker/errors/game_save_error.rb
140
155
  - lib/codebreaker/errors/invalid_difficulty_error.rb
141
156
  - lib/codebreaker/errors/no_hints_error.rb
142
157
  - lib/codebreaker/errors/validation_length_error.rb
143
158
  - lib/codebreaker/game.rb
144
- - lib/codebreaker/hint.rb
145
- - lib/codebreaker/mixins/validatable.rb
159
+ - lib/codebreaker/mixins/validator.rb
146
160
  - lib/codebreaker/player.rb
147
161
  - lib/codebreaker/secret_code_generator.rb
148
162
  - lib/codebreaker/secret_code_matcher.rb
163
+ - lib/codebreaker/stats_storage.rb
149
164
  - lib/codebreaker/version.rb
150
165
  homepage: https://github.com/YakupovDima/rubygarage-codebreaker-core
151
166
  licenses:
@@ -1,13 +0,0 @@
1
- module Codebreaker
2
- module Difficulties
3
- class Base
4
- def attempts
5
- raise NotImplementedError
6
- end
7
-
8
- def hints
9
- raise NotImplementedError
10
- end
11
- end
12
- end
13
- end
@@ -1,13 +0,0 @@
1
- module Codebreaker
2
- module Difficulties
3
- class Easy < Base
4
- def attempts
5
- 15
6
- end
7
-
8
- def hints
9
- 2
10
- end
11
- end
12
- end
13
- end
@@ -1,13 +0,0 @@
1
- module Codebreaker
2
- module Difficulties
3
- class Hell < Base
4
- def attempts
5
- 5
6
- end
7
-
8
- def hints
9
- 1
10
- end
11
- end
12
- end
13
- end
@@ -1,13 +0,0 @@
1
- module Codebreaker
2
- module Difficulties
3
- class Medium < Base
4
- def attempts
5
- 10
6
- end
7
-
8
- def hints
9
- 1
10
- end
11
- end
12
- end
13
- end
@@ -1,14 +0,0 @@
1
- module Codebreaker
2
- class Hint
3
- def initialize(difficulty, secret_code)
4
- @difficulty = difficulty
5
- @hints = secret_code.sample(difficulty.hints)
6
- end
7
-
8
- def call
9
- raise Errors::NoHintsError if @hints.empty?
10
-
11
- @hints.pop
12
- end
13
- end
14
- end
@@ -1,15 +0,0 @@
1
- module Codebreaker
2
- module Mixins
3
- module Validatable
4
- def validate
5
- raise NotImplementedError
6
- end
7
-
8
- def validate_length(value, min, max)
9
- return if value.instance_of?(String) && value.size >= min && value.size <= max
10
-
11
- raise Codebreaker::Errors::ValidationLengthError, "invalid length '#{value}'. min=#{min}, max=#{max}"
12
- end
13
- end
14
- end
15
- end