cb-core 0.1.6 → 0.1.7

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: eaa6a62438cc6336ddc9329d0c56f9b070f52d66bdee469d0bd2ec5f16ed6a72
4
- data.tar.gz: 3ef241b273c84403f66ff74521e3b6f234f7af9758f3ad96e40473f40815d561
3
+ metadata.gz: fe28b7f9cb8b365bef09a5e66f7e7e4e8bc3613e5fd040582a574374f9fb8e3b
4
+ data.tar.gz: 9c21dfafcfc866bba9447dd4dfafd40627874e4fd6ad798ffbce0c8c85661036
5
5
  SHA512:
6
- metadata.gz: acaed6517f2d08384eac662e39ae0ca174822572ca9efe6d19861b71c42403b402f9674605ddde7a0fde82cc6516632867fabc0936966f4f39808624d7b44435
7
- data.tar.gz: e4da06e1b72430b70a7b3a669da965a65795dc6a0f620030e7aeb5dfd819b8423a44b9ed52ab89a30431ef298db370e48ad277a0db1112a3c28c2357e78a2d55
6
+ metadata.gz: e8daed2a5f0e44e76836444198c77059277b4bb040f4cdbc1db51ad24e7353fe70376d8f63c4c1fbd4c74944e6fe460216770c419015417276ba39c8ae9d395b
7
+ data.tar.gz: e41b6b32135c06ff547ba9dd4f7eea8ccf19490ed2c1249e44760c608b9e77412ad9cae6f90b7c62e24cbcae8fc41b2444cfd41e98321b7b7f869919a20f6a0b
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/Gemfile.lock CHANGED
@@ -1,18 +1,22 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cb-core (0.1.6)
4
+ cb-core (0.1.7)
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,25 +1,24 @@
1
1
  require 'fileutils'
2
2
  require 'yaml'
3
+ require 'i18n'
3
4
 
5
+ require_relative '../config/i18n'
6
+
7
+ require_relative 'codebreaker/difficulty'
4
8
  require_relative 'codebreaker/difficulty_factory'
5
9
  require_relative 'codebreaker/difficulty_type'
6
- require_relative 'codebreaker/difficulties/base'
7
- require_relative 'codebreaker/difficulties/easy'
8
- require_relative 'codebreaker/difficulties/medium'
9
- require_relative 'codebreaker/difficulties/hell'
10
10
  require_relative 'codebreaker/errors/game_over_error'
11
11
  require_relative 'codebreaker/errors/game_save_error'
12
12
  require_relative 'codebreaker/errors/invalid_difficulty_error'
13
13
  require_relative 'codebreaker/errors/no_hints_error'
14
14
  require_relative 'codebreaker/errors/validation_length_error'
15
- require_relative 'codebreaker/file_storage'
15
+ require_relative 'codebreaker/create_stat_service'
16
16
  require_relative 'codebreaker/game'
17
- require_relative 'codebreaker/hint'
18
- require_relative 'codebreaker/mixins/validatable'
17
+ require_relative 'codebreaker/mixins/validator'
19
18
  require_relative 'codebreaker/player'
20
19
  require_relative 'codebreaker/secret_code_generator'
21
20
  require_relative 'codebreaker/secret_code_matcher'
22
- require_relative 'codebreaker/stats'
21
+ require_relative 'codebreaker/stats_storage'
23
22
  require_relative 'codebreaker/version'
24
23
 
25
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
@@ -1,26 +1,34 @@
1
1
  module Codebreaker
2
2
  class Game
3
- attr_reader :secret_code, :player, :difficulty
3
+ attr_reader :player
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)
7
+ @difficulty_type = difficulty_type
10
8
  @attempts_used = 0
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.take
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
29
  @attempts_used += 1
22
- matching = SecretCodeMatcher.new(@secret_code, guess).call
23
- check_win(matching)
30
+ matching = SecretCodeMatcher.new(secret_code, guess).call
31
+ check_win(guess)
24
32
  matching
25
33
  end
26
34
 
@@ -37,29 +45,35 @@ module Codebreaker
37
45
  end
38
46
 
39
47
  def hints_left
40
- @hint.count
48
+ hints.count
41
49
  end
42
50
 
43
51
  def attempts_left
44
- @difficulty.attempts - @attempts_used
52
+ difficulty.attempts - @attempts_used
45
53
  end
46
54
 
47
55
  def save_result
48
- Stats.new.save_game(self)
56
+ raise Errors::GameSaveError unless win?
57
+
58
+ CreateStatService.new(self).call
49
59
  end
50
60
 
51
61
  private
52
62
 
63
+ def hints
64
+ @hints ||= secret_code.sample(difficulty.hints)
65
+ end
66
+
53
67
  def no_attempts?
54
- @difficulty.attempts == @attempts_used
68
+ difficulty.attempts == @attempts_used
55
69
  end
56
70
 
57
71
  def ensure_game_is_not_over
58
72
  raise Errors::GameOverError if over?
59
73
  end
60
74
 
61
- def check_win(combination)
62
- @is_win = true if SecretCodeMatcher.win_combination?(combination)
75
+ def check_win(guess)
76
+ @is_win = true if guess == secret_code
63
77
  end
64
78
  end
65
79
  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.6'.freeze
2
+ VERSION = '0.1.7'.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.6
4
+ version: 0.1.7
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-29 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,11 +143,11 @@ 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
@@ -141,14 +155,12 @@ files:
141
155
  - lib/codebreaker/errors/invalid_difficulty_error.rb
142
156
  - lib/codebreaker/errors/no_hints_error.rb
143
157
  - lib/codebreaker/errors/validation_length_error.rb
144
- - lib/codebreaker/file_storage.rb
145
158
  - lib/codebreaker/game.rb
146
- - lib/codebreaker/hint.rb
147
- - lib/codebreaker/mixins/validatable.rb
159
+ - lib/codebreaker/mixins/validator.rb
148
160
  - lib/codebreaker/player.rb
149
161
  - lib/codebreaker/secret_code_generator.rb
150
162
  - lib/codebreaker/secret_code_matcher.rb
151
- - lib/codebreaker/stats.rb
163
+ - lib/codebreaker/stats_storage.rb
152
164
  - lib/codebreaker/version.rb
153
165
  homepage: https://github.com/YakupovDima/rubygarage-codebreaker-core
154
166
  licenses:
@@ -1,17 +0,0 @@
1
- module Codebreaker
2
- module Difficulties
3
- class Base
4
- def type
5
- raise NotImplementedError
6
- end
7
-
8
- def attempts
9
- raise NotImplementedError
10
- end
11
-
12
- def hints
13
- raise NotImplementedError
14
- end
15
- end
16
- end
17
- end
@@ -1,17 +0,0 @@
1
- module Codebreaker
2
- module Difficulties
3
- class Easy < Base
4
- def type
5
- DifficultyType::EASY
6
- end
7
-
8
- def attempts
9
- 15
10
- end
11
-
12
- def hints
13
- 2
14
- end
15
- end
16
- end
17
- end
@@ -1,17 +0,0 @@
1
- module Codebreaker
2
- module Difficulties
3
- class Hell < Base
4
- def type
5
- DifficultyType::HELL
6
- end
7
-
8
- def attempts
9
- 5
10
- end
11
-
12
- def hints
13
- 1
14
- end
15
- end
16
- end
17
- end
@@ -1,17 +0,0 @@
1
- module Codebreaker
2
- module Difficulties
3
- class Medium < Base
4
- def type
5
- DifficultyType::MEDIUM
6
- end
7
-
8
- def attempts
9
- 10
10
- end
11
-
12
- def hints
13
- 1
14
- end
15
- end
16
- end
17
- end
@@ -1,16 +0,0 @@
1
- module Codebreaker
2
- class FileStorage
3
- def initialize(path)
4
- @path = path
5
- end
6
-
7
- def load
8
- File.file?(@path) ? YAML.load_file(@path) : nil
9
- end
10
-
11
- def save(data)
12
- Pathname(@path).dirname.mkpath
13
- File.write(@path, data.to_yaml)
14
- end
15
- end
16
- end
@@ -1,18 +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 take
9
- raise Errors::NoHintsError if @hints.empty?
10
-
11
- @hints.pop
12
- end
13
-
14
- def count
15
- @hints.count
16
- end
17
- end
18
- 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
@@ -1,37 +0,0 @@
1
- module Codebreaker
2
- class Stats
3
- PATH = './db/data.yml'.freeze
4
-
5
- def initialize
6
- @storage = FileStorage.new(PATH)
7
- end
8
-
9
- def load
10
- @storage.load || []
11
- end
12
-
13
- def save_game(game)
14
- validate_game_is_win(game)
15
- data = load
16
- data << game_data(game)
17
- @storage.save(data)
18
- end
19
-
20
- private
21
-
22
- def game_data(game)
23
- {
24
- name: game.player.name,
25
- difficulty: game.difficulty.type,
26
- attempts_total: game.difficulty.attempts,
27
- attempts_used: game.difficulty.attempts - game.attempts_left,
28
- hints_total: game.difficulty.hints,
29
- hints_used: game.difficulty.hints - game.hints_left
30
- }
31
- end
32
-
33
- def validate_game_is_win(game)
34
- raise Errors::GameSaveError unless game.win?
35
- end
36
- end
37
- end