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 +4 -4
- data/.fasterer.yml +1 -1
- data/.gitignore +2 -0
- data/.rubocop.yml +4 -0
- data/Gemfile.lock +5 -1
- data/codebreaker.gemspec +12 -10
- data/config/i18n.rb +3 -0
- data/config/locales/en.yml +7 -0
- data/lib/codebreaker.rb +10 -9
- data/lib/codebreaker/create_stat_service.rb +24 -0
- data/lib/codebreaker/errors/game_over_error.rb +1 -1
- data/lib/codebreaker/errors/game_save_error.rb +9 -0
- data/lib/codebreaker/errors/no_hints_error.rb +1 -1
- data/lib/codebreaker/errors/validation_length_error.rb +1 -1
- data/lib/codebreaker/game.rb +46 -14
- data/lib/codebreaker/mixins/validator.rb +12 -0
- data/lib/codebreaker/player.rb +6 -7
- data/lib/codebreaker/secret_code_matcher.rb +2 -11
- data/lib/codebreaker/stats_storage.rb +22 -0
- data/lib/codebreaker/version.rb +1 -1
- metadata +22 -11
- data/lib/codebreaker/difficulties/base.rb +0 -13
- data/lib/codebreaker/difficulties/easy.rb +0 -13
- data/lib/codebreaker/difficulties/hell.rb +0 -13
- data/lib/codebreaker/difficulties/medium.rb +0 -13
- data/lib/codebreaker/difficulty_factory.rb +0 -16
- data/lib/codebreaker/difficulty_type.rb +0 -7
- data/lib/codebreaker/errors/invalid_difficulty_error.rb +0 -9
- data/lib/codebreaker/hint.rb +0 -14
- data/lib/codebreaker/mixins/validatable.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 177b156f01c5212b23c6bef720dc6962a5103bd09dd79f9df40b84dd3312c57c
|
4
|
+
data.tar.gz: eb3c47dd6140adb217d3e64d4bf0c69446a555dd63944db0c0d09f04d29743c2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9bd350d5b79d20591e5bd3911a9c4f13817f1639af878ec8d2d520a7eb3116c9e8d74800389c2669c5874c9bc55c587c764e6bece85d53385111ba96954f71ca
|
7
|
+
data.tar.gz: 1a0db0f64f89eb34ce758b84080c3b588663500e3199b2644232948bb07270a972a5c6946503769bfcc8f22ea8befb51d3f9f9af0c638128546797fd3dc6009d
|
data/.fasterer.yml
CHANGED
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,18 +1,22 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
cb-core (0.1.
|
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
|
5
|
-
spec.version
|
6
|
-
spec.authors
|
7
|
-
spec.email
|
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
|
10
|
-
spec.description
|
11
|
-
spec.homepage
|
12
|
-
spec.license
|
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
|
24
|
-
spec.executables
|
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,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
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
require_relative '
|
6
|
-
|
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/
|
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/
|
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
|
data/lib/codebreaker/game.rb
CHANGED
@@ -1,31 +1,41 @@
|
|
1
1
|
module Codebreaker
|
2
2
|
class Game
|
3
|
-
attr_reader :
|
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
|
-
@
|
8
|
-
@
|
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
|
-
|
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
|
-
@
|
22
|
-
matching = SecretCodeMatcher.new(
|
23
|
-
check_win(
|
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
|
-
|
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
|
42
|
-
@
|
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(
|
50
|
-
@is_win = true if
|
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
|
data/lib/codebreaker/player.rb
CHANGED
@@ -1,21 +1,20 @@
|
|
1
1
|
module Codebreaker
|
2
2
|
class Player
|
3
|
-
include Mixins::
|
3
|
+
include Mixins::Validator
|
4
4
|
|
5
|
-
|
6
|
-
|
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,
|
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
|
-
|
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
|
data/lib/codebreaker/version.rb
CHANGED
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.
|
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-
|
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/
|
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/
|
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/
|
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,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
|
data/lib/codebreaker/hint.rb
DELETED
@@ -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
|