cb-core 0.1.3 → 0.1.9

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