cb-core 0.1.2 → 0.1.8

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