cb-core 0.1.6 → 0.1.7

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