cb-core 0.1.3 → 0.1.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b6333078ada26df3d788b6f0b3e45321943328818cec72debe9788771dbba4a9
4
- data.tar.gz: 39aace72bf77dc72e6f69182579566f249af4d26a14da016a61efdc6fb5b73d2
3
+ metadata.gz: 177b156f01c5212b23c6bef720dc6962a5103bd09dd79f9df40b84dd3312c57c
4
+ data.tar.gz: eb3c47dd6140adb217d3e64d4bf0c69446a555dd63944db0c0d09f04d29743c2
5
5
  SHA512:
6
- metadata.gz: e1af830b3b9450073b8e09cc1d52f97645cb480441afe4cf1c237907d1858103f07979338665f6b7bcdd6a82774221a47b749b07b4c63d84e566a8c9a7edd853
7
- data.tar.gz: 672b6396c2bb761f28312f416e137d1763dda623751ff6f58aeb5e8e9b687e2c052732124fadd86af7ede8d1b7ddac757c12a35ab5b061c081ed2fc0e6fedaef
6
+ metadata.gz: 9bd350d5b79d20591e5bd3911a9c4f13817f1639af878ec8d2d520a7eb3116c9e8d74800389c2669c5874c9bc55c587c764e6bece85d53385111ba96954f71ca
7
+ data.tar.gz: 1a0db0f64f89eb34ce758b84080c3b588663500e3199b2644232948bb07270a972a5c6946503769bfcc8f22ea8befb51d3f9f9af0c638128546797fd3dc6009d
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.2)
4
+ cb-core (0.1.9)
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,7 @@
1
+ en:
2
+ errors:
3
+ validation_length_common: Invalid string length
4
+ validation_length_detailed: "Invalid length '%{value}'. min=%{min}, max=%{max}"
5
+ game_over: No attempts available or a player won the game
6
+ game_save: Only won game could be saved
7
+ no_hints: No hints available
data/lib/codebreaker.rb CHANGED
@@ -1,19 +1,20 @@
1
- require_relative 'codebreaker/difficulty_factory'
2
- 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'
1
+ require 'fileutils'
2
+ require 'yaml'
3
+ require 'i18n'
4
+
5
+ require_relative '../config/i18n'
6
+
7
7
  require_relative 'codebreaker/errors/game_over_error'
8
- require_relative 'codebreaker/errors/invalid_difficulty_error'
8
+ require_relative 'codebreaker/errors/game_save_error'
9
9
  require_relative 'codebreaker/errors/no_hints_error'
10
10
  require_relative 'codebreaker/errors/validation_length_error'
11
+ require_relative 'codebreaker/create_stat_service'
11
12
  require_relative 'codebreaker/game'
12
- require_relative 'codebreaker/hint'
13
- require_relative 'codebreaker/mixins/validatable'
13
+ require_relative 'codebreaker/mixins/validator'
14
14
  require_relative 'codebreaker/player'
15
15
  require_relative 'codebreaker/secret_code_generator'
16
16
  require_relative 'codebreaker/secret_code_matcher'
17
+ require_relative 'codebreaker/stats_storage'
17
18
  require_relative 'codebreaker/version'
18
19
 
19
20
  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.attempts_total,
18
+ attempts_used: @game.attempts_total - @game.attempts_left,
19
+ hints_total: @game.hints_total,
20
+ hints_used: @game.hints_total - @game.hints_left
21
+ }
22
+ end
23
+ end
24
+ end
@@ -1,7 +1,7 @@
1
1
  module Codebreaker
2
2
  module Errors
3
3
  class GameOverError < StandardError
4
- def initialize(msg = 'No attempts available or a player won the game')
4
+ def initialize(msg = I18n.t('errors.game_over'))
5
5
  super
6
6
  end
7
7
  end
@@ -0,0 +1,9 @@
1
+ module Codebreaker
2
+ module Errors
3
+ class GameSaveError < StandardError
4
+ def initialize(msg = I18n.t('errors.game_save'))
5
+ super
6
+ end
7
+ end
8
+ end
9
+ end
@@ -1,7 +1,7 @@
1
1
  module Codebreaker
2
2
  module Errors
3
3
  class NoHintsError < StandardError
4
- def initialize(msg = 'No hints available')
4
+ def initialize(msg = I18n.t('errors.no_hints'))
5
5
  super
6
6
  end
7
7
  end
@@ -1,7 +1,7 @@
1
1
  module Codebreaker
2
2
  module Errors
3
3
  class ValidationLengthError < StandardError
4
- def initialize(msg = 'Invalid string length')
4
+ def initialize(msg = I18n.t('errors.validation_length_common'))
5
5
  super
6
6
  end
7
7
  end
@@ -1,31 +1,41 @@
1
1
  module Codebreaker
2
2
  class Game
3
- attr_reader :secret_code
3
+ attr_reader :player, :difficulty_type, :attempts_left
4
+
5
+ DIFFICULTIES = {
6
+ easy: { attempts: 15, hints: 2, hardness: 1 },
7
+ medium: { attempts: 10, hints: 1, hardness: 2 },
8
+ hell: { attempts: 5, hints: 1, hardness: 3 }
9
+ }.freeze
4
10
 
5
11
  def initialize(player, difficulty_type)
6
12
  @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
13
+ @difficulty_type = difficulty_type
14
+ @attempts_left = difficulty[:attempts]
11
15
  @is_win = false
12
16
  end
13
17
 
18
+ def secret_code
19
+ @secret_code ||= SecretCodeGenerator.new.call
20
+ end
21
+
14
22
  def take_hint
15
23
  ensure_game_is_not_over
16
- @hint.call
24
+ raise Errors::NoHintsError if hints.empty?
25
+
26
+ hints.pop
17
27
  end
18
28
 
19
29
  def guess_secret_code(guess)
20
30
  ensure_game_is_not_over
21
- @attempts_used += 1
22
- matching = SecretCodeMatcher.new(@secret_code, guess).call
23
- check_win(matching)
31
+ @attempts_left -= 1
32
+ matching = SecretCodeMatcher.new(secret_code, guess).call
33
+ check_win(guess)
24
34
  matching
25
35
  end
26
36
 
27
37
  def lose?
28
- no_attempts? && !win?
38
+ attempts_left.zero? && !win?
29
39
  end
30
40
 
31
41
  def win?
@@ -36,18 +46,40 @@ module Codebreaker
36
46
  win? || lose?
37
47
  end
38
48
 
49
+ def attempts_total
50
+ difficulty[:attempts]
51
+ end
52
+
53
+ def hints_total
54
+ difficulty[:hints]
55
+ end
56
+
57
+ def hints_left
58
+ hints.count
59
+ end
60
+
61
+ def save_result
62
+ raise Errors::GameSaveError unless win?
63
+
64
+ CreateStatService.new(self).call
65
+ end
66
+
39
67
  private
40
68
 
41
- def no_attempts?
42
- @difficulty.attempts == @attempts_used
69
+ def difficulty
70
+ DIFFICULTIES[@difficulty_type]
71
+ end
72
+
73
+ def hints
74
+ @hints ||= secret_code.sample(difficulty[:hints])
43
75
  end
44
76
 
45
77
  def ensure_game_is_not_over
46
78
  raise Errors::GameOverError if over?
47
79
  end
48
80
 
49
- def check_win(combination)
50
- @is_win = true if SecretCodeMatcher.win_combination?(combination)
81
+ def check_win(guess)
82
+ @is_win = true if guess == secret_code
51
83
  end
52
84
  end
53
85
  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_detailed', 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.3'.freeze
2
+ VERSION = '0.1.9'.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.3
4
+ version: 0.1.9
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-05-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,20 @@ 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
137
- - lib/codebreaker/difficulty_factory.rb
138
- - lib/codebreaker/difficulty_type.rb
149
+ - lib/codebreaker/create_stat_service.rb
139
150
  - lib/codebreaker/errors/game_over_error.rb
140
- - lib/codebreaker/errors/invalid_difficulty_error.rb
151
+ - lib/codebreaker/errors/game_save_error.rb
141
152
  - lib/codebreaker/errors/no_hints_error.rb
142
153
  - lib/codebreaker/errors/validation_length_error.rb
143
154
  - lib/codebreaker/game.rb
144
- - lib/codebreaker/hint.rb
145
- - lib/codebreaker/mixins/validatable.rb
155
+ - lib/codebreaker/mixins/validator.rb
146
156
  - lib/codebreaker/player.rb
147
157
  - lib/codebreaker/secret_code_generator.rb
148
158
  - lib/codebreaker/secret_code_matcher.rb
159
+ - lib/codebreaker/stats_storage.rb
149
160
  - lib/codebreaker/version.rb
150
161
  homepage: https://github.com/YakupovDima/rubygarage-codebreaker-core
151
162
  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,16 +0,0 @@
1
- module Codebreaker
2
- class DifficultyFactory
3
- def self.build(type)
4
- case type
5
- when DifficultyType::EASY
6
- Difficulties::Easy.new
7
- when DifficultyType::MEDIUM
8
- Difficulties::Medium.new
9
- when DifficultyType::HELL
10
- Difficulties::Hell.new
11
- else
12
- raise Errors::InvalidDifficultyError, "unknown difficulty type #{type}"
13
- end
14
- end
15
- end
16
- end
@@ -1,7 +0,0 @@
1
- module Codebreaker
2
- module DifficultyType
3
- EASY = :easy
4
- MEDIUM = :medium
5
- HELL = :hell
6
- end
7
- end
@@ -1,9 +0,0 @@
1
- module Codebreaker
2
- module Errors
3
- class InvalidDifficultyError < StandardError
4
- def initialize(msg = 'Invalid difficulty type')
5
- super
6
- end
7
- end
8
- end
9
- 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