codebreker_manfly 0.1.0 → 0.1.1

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: 3b4b8e740c8829e5bc0f3356d56f39509e1283eec9c5d336248d8b68a33e01ac
4
- data.tar.gz: 5b1e41c437bc790388e712fb80f0f607db13334819a3c3cc04edaeadd7076923
3
+ metadata.gz: b780227d68e66b178bd1c3250207c3123f569ba0a8b9e42d105471181ed314e5
4
+ data.tar.gz: 2109e9aa4fcda81c129b4fd4ab1016177b9bb20fb0d307961b5bc1921d7b6867
5
5
  SHA512:
6
- metadata.gz: 3830612519d9d4dbba120cd9684c19b138b0a033e6e340ea95e6330a7015c7b2c70a15153acbfb63ff3afb136a0639af8488e56989cb504bea75e3838fba67fc
7
- data.tar.gz: 3b1002781ecdc3ebcc76cc0cbf32400f9a9c4d44b998e1e6cfcfa9ad352d1acf52abb6439e309b505a061926e8ba0255f99a209f62d6f45f8e81f9d1dfac0b02
6
+ metadata.gz: 230708b78d9578af1432eeae02e0af6574ff908a6ad63e530215937c1fbf92ff6499273ab735fc9e10721b434ceaf03bacbc1e3b499acb67acc260c563383bbe
7
+ data.tar.gz: 59a978e957a7e2486c9f4ebef56e7c81df44e82e765fb047f2c81bf6f7f8cd5183bbf57cf2b1d1168828594df457148a9f4d6b8f0d1f84976718e6f5bae54e59
data/Gemfile.lock ADDED
@@ -0,0 +1,117 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ codebreker_manfly (0.1.0)
5
+ i18n
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ ast (2.4.2)
11
+ backport (1.2.0)
12
+ benchmark (0.2.0)
13
+ colorize (0.8.1)
14
+ concurrent-ruby (1.1.9)
15
+ diff-lcs (1.4.4)
16
+ docile (1.4.0)
17
+ e2mmap (0.1.0)
18
+ faker (2.19.0)
19
+ i18n (>= 1.6, < 2)
20
+ fasterer (0.9.0)
21
+ colorize (~> 0.7)
22
+ ruby_parser (>= 3.14.1)
23
+ i18n (1.8.11)
24
+ concurrent-ruby (~> 1.0)
25
+ jaro_winkler (1.5.4)
26
+ kramdown (2.3.1)
27
+ rexml
28
+ kramdown-parser-gfm (1.1.0)
29
+ kramdown (~> 2.0)
30
+ lefthook (0.7.7)
31
+ nokogiri (1.12.5-x86_64-linux)
32
+ racc (~> 1.4)
33
+ parallel (1.21.0)
34
+ parser (3.0.3.2)
35
+ ast (~> 2.4.1)
36
+ racc (1.6.0)
37
+ rainbow (3.0.0)
38
+ rake (13.0.6)
39
+ regexp_parser (2.2.0)
40
+ reverse_markdown (2.1.1)
41
+ nokogiri
42
+ rexml (3.2.5)
43
+ rspec (3.10.0)
44
+ rspec-core (~> 3.10.0)
45
+ rspec-expectations (~> 3.10.0)
46
+ rspec-mocks (~> 3.10.0)
47
+ rspec-core (3.10.1)
48
+ rspec-support (~> 3.10.0)
49
+ rspec-expectations (3.10.1)
50
+ diff-lcs (>= 1.2.0, < 2.0)
51
+ rspec-support (~> 3.10.0)
52
+ rspec-mocks (3.10.2)
53
+ diff-lcs (>= 1.2.0, < 2.0)
54
+ rspec-support (~> 3.10.0)
55
+ rspec-support (3.10.3)
56
+ rubocop (1.23.0)
57
+ parallel (~> 1.10)
58
+ parser (>= 3.0.0.0)
59
+ rainbow (>= 2.2.2, < 4.0)
60
+ regexp_parser (>= 1.8, < 3.0)
61
+ rexml
62
+ rubocop-ast (>= 1.12.0, < 2.0)
63
+ ruby-progressbar (~> 1.7)
64
+ unicode-display_width (>= 1.4.0, < 3.0)
65
+ rubocop-ast (1.15.0)
66
+ parser (>= 3.0.1.1)
67
+ rubocop-rspec (2.6.0)
68
+ rubocop (~> 1.19)
69
+ ruby-progressbar (1.11.0)
70
+ ruby_parser (3.18.1)
71
+ sexp_processor (~> 4.16)
72
+ sexp_processor (4.16.0)
73
+ simplecov (0.21.2)
74
+ docile (~> 1.1)
75
+ simplecov-html (~> 0.11)
76
+ simplecov_json_formatter (~> 0.1)
77
+ simplecov-html (0.12.3)
78
+ simplecov_json_formatter (0.1.3)
79
+ solargraph (0.44.2)
80
+ backport (~> 1.2)
81
+ benchmark
82
+ bundler (>= 1.17.2)
83
+ diff-lcs (~> 1.4)
84
+ e2mmap
85
+ jaro_winkler (~> 1.5)
86
+ kramdown (~> 2.3)
87
+ kramdown-parser-gfm (~> 1.1)
88
+ parser (~> 3.0)
89
+ reverse_markdown (>= 1.0.5, < 3)
90
+ rubocop (>= 0.52)
91
+ thor (~> 1.0)
92
+ tilt (~> 2.0)
93
+ yard (~> 0.9, >= 0.9.24)
94
+ thor (1.1.0)
95
+ tilt (2.0.10)
96
+ unicode-display_width (2.1.0)
97
+ webrick (1.7.0)
98
+ yard (0.9.27)
99
+ webrick (~> 1.7.0)
100
+
101
+ PLATFORMS
102
+ x86_64-linux
103
+
104
+ DEPENDENCIES
105
+ codebreker_manfly!
106
+ faker
107
+ fasterer
108
+ lefthook
109
+ rake (~> 13.0)
110
+ rspec (~> 3.0)
111
+ rubocop (~> 1.21)
112
+ rubocop-rspec
113
+ simplecov
114
+ solargraph
115
+
116
+ BUNDLED WITH
117
+ 2.2.33
Binary file
@@ -0,0 +1,8 @@
1
+ en:
2
+ empty_string_error: "can't be blank"
3
+ negative_integer_error: "can't be negative number"
4
+ no_numeric_string_error: "can contain only digits"
5
+ non_positive_integer_error: "can't be less than 1"
6
+ string_max_length_error: 'is too long'
7
+ string_min_length_error: 'is too short'
8
+ unexpected_class_error: 'contains unexpeted type'
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml/store'
4
+ require 'date'
5
+
6
+ require 'i18n'
7
+ require_relative 'config/i18n_config'
8
+
9
+ require_relative 'version'
10
+ require_relative 'modules/validator'
11
+ require_relative 'entities/validatable_entity'
12
+ require_relative 'entities/code_generator'
13
+ require_relative 'entities/difficulty'
14
+ require_relative 'entities/user'
15
+ require_relative 'entities/guess'
16
+ require_relative 'entities/user_statistics'
17
+ require_relative 'entities/codebreaker_store'
18
+ require_relative 'entities/code_matcher'
19
+ require_relative 'entities/game'
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ I18n.load_path << Dir["#{File.expand_path('config/locales')}/*.yml"]
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CodebrekerManfly
4
+ class CodeGenerator < ValidatableEntity
5
+ DEFAULT_CODE_RANGE = (1..6).freeze
6
+ DEFAULT_CODE_LENGTH = 4
7
+
8
+ def initialize(range: DEFAULT_CODE_RANGE, amount: DEFAULT_CODE_LENGTH)
9
+ super()
10
+ @range = range
11
+ @amount = amount
12
+ end
13
+
14
+ def generate
15
+ Array.new(@amount) { rand(@range) }
16
+ end
17
+
18
+ private
19
+
20
+ def validate
21
+ add_error(:range, I18n.t(:unexpected_class_error)) unless valid_class?(Range, @range)
22
+ return add_error(:amount, I18n.t(:unexpected_class_error)) unless valid_class?(Integer, @amount)
23
+
24
+ add_error(:amount, I18n.t(:non_positive_integer_error)) unless valid_positive_integer?(@amount)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CodebrekerManfly
4
+ class CodeMatcher < ValidatableEntity
5
+ attr_reader :answer
6
+
7
+ STRONG_MATCH_SYMBOL = '+'
8
+ SOFT_MATCH_SYMBOL = '-'
9
+
10
+ def initialize(secret_code, guess_code)
11
+ super()
12
+ @secret_code = secret_code
13
+ @guess_code = guess_code
14
+ end
15
+
16
+ def match_codes
17
+ secret_code = @secret_code.clone
18
+ guess_code = @guess_code.clone
19
+
20
+ strong_match(secret_code, guess_code) + soft_match(secret_code, guess_code)
21
+ end
22
+
23
+ def codes_match?
24
+ @secret_code == @guess_code
25
+ end
26
+
27
+ private
28
+
29
+ def strong_match(secret_code, guess_code)
30
+ guess_answer = ''
31
+
32
+ guess_code.count.times do |index|
33
+ next unless secret_code[index] == guess_code[index]
34
+
35
+ guess_answer += STRONG_MATCH_SYMBOL
36
+ secret_code[index] = nil
37
+ guess_code[index] = nil
38
+ end
39
+
40
+ guess_answer
41
+ end
42
+
43
+ def soft_match(secret_code, guess_code)
44
+ guess_answer = ''
45
+
46
+ guess_code.count.times do |index|
47
+ next unless guess_code[index] && secret_code.include?(guess_code[index])
48
+
49
+ guess_answer += SOFT_MATCH_SYMBOL
50
+ secret_code.delete_at(secret_code.index(guess_code[index]))
51
+ guess_code[index] = nil
52
+ end
53
+
54
+ guess_answer
55
+ end
56
+
57
+ def validate
58
+ add_error(:secret_code, I18n.t(:unexpected_class_error)) unless valid_class?(Array, @secret_code)
59
+ add_error(:guess_code, I18n.t(:unexpected_class_error)) unless valid_class?(Array, @guess_code)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CodebrekerManfly
4
+ class CodebreakerStore
5
+ STORAGE_DIRECTORY = 'db'
6
+ STORAGE_FILE = 'db.yml'
7
+ attr_accessor :data
8
+
9
+ def initialize
10
+ @data = db_initialized? ? load : initialize_db
11
+ end
12
+
13
+ def save
14
+ store = YAML::Store.new(storage_path)
15
+ store.transaction { @data.each { |key, value| store[key] = value } }
16
+ end
17
+
18
+ private
19
+
20
+ def db_initialized?
21
+ Dir.exist?(STORAGE_DIRECTORY) && File.file?(File.join(STORAGE_DIRECTORY, STORAGE_FILE))
22
+ end
23
+
24
+ def initialize_db
25
+ Dir.mkdir(STORAGE_DIRECTORY)
26
+
27
+ store = YAML::Store.new(File.join(STORAGE_DIRECTORY, STORAGE_FILE))
28
+ store.transaction { default_data.each { |key, value| store[key] = value } }
29
+
30
+ default_data
31
+ end
32
+
33
+ def default_data
34
+ {
35
+ user_statistics: []
36
+ }
37
+ end
38
+
39
+ def storage_path
40
+ File.join(STORAGE_DIRECTORY, STORAGE_FILE)
41
+ end
42
+
43
+ def load
44
+ store = YAML::Store.new(storage_path)
45
+ store.transaction { store.roots.to_h { |key| [key, store[key]] } }
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CodebrekerManfly
4
+ class Difficulty < ValidatableEntity
5
+ attr_reader :name, :attempts, :hints
6
+
7
+ EASY = :easy
8
+ MEDIUM = :medium
9
+ HELL = :hell
10
+
11
+ def initialize(name:, attempts:, hints:)
12
+ super()
13
+ @name = name
14
+ @attempts = attempts
15
+ @hints = hints
16
+ end
17
+
18
+ def <=>(other)
19
+ [attempts, hints] <=> [other.attempts, other.hints]
20
+ end
21
+
22
+ def self.difficulties(keyword)
23
+ case keyword
24
+ when EASY then CodebrekerManfly::Difficulty.new(name: 'Easy', attempts: 15, hints: 2)
25
+ when MEDIUM then CodebrekerManfly::Difficulty.new(name: 'Medium', attempts: 10, hints: 1)
26
+ when HELL then CodebrekerManfly::Difficulty.new(name: 'Hell', attempts: 5, hints: 1)
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def validate
33
+ validate_name
34
+ validate_attempts
35
+ validate_hints
36
+ end
37
+
38
+ def validate_name
39
+ return add_error(:name, I18n.t(:unexpected_class_error)) unless valid_class?(String, name)
40
+
41
+ add_error(:name, I18n.t(:empty_string_error)) unless valid_non_empty_string?(name)
42
+ end
43
+
44
+ def validate_attempts
45
+ return add_error(:attempts, I18n.t(:unexpected_class_error)) unless valid_class?(Integer, attempts)
46
+
47
+ add_error(:attempts, I18n.t(:non_positive_integer_error)) unless valid_positive_integer?(attempts)
48
+ end
49
+
50
+ def validate_hints
51
+ return add_error(:hints, I18n.t(:unexpected_class_error)) unless valid_class?(Integer, hints)
52
+
53
+ add_error(:hints, I18n.t(:negative_integer_error)) unless valid_non_negative_integer?(hints)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CodebrekerManfly
4
+ class Game < ValidatableEntity
5
+ attr_reader :difficulty, :user, :attempts_amount, :hints_amount, :code
6
+
7
+ def initialize(difficulty, user)
8
+ super()
9
+ @difficulty = difficulty
10
+ @user = user
11
+ end
12
+
13
+ def start
14
+ prepare_game
15
+ end
16
+
17
+ def self.user_statistic
18
+ store = CodebrekerManfly::CodebreakerStore.new
19
+ store.data[:user_statistics].sort_by { |stats| [stats.difficulty, stats.attempts, stats.hints] }
20
+ end
21
+
22
+ def save_statistic
23
+ store = CodebrekerManfly::CodebreakerStore.new
24
+ store.data[:user_statistics] << current_statistic
25
+ store.save
26
+ end
27
+
28
+ def take_hint
29
+ @hints_amount -= 1
30
+ @hints.pop
31
+ end
32
+
33
+ def make_turn(guess)
34
+ @guess = guess
35
+ @attempts_amount -= 1
36
+
37
+ matcher = CodebrekerManfly::CodeMatcher.new(code, guess.code)
38
+ matcher.match_codes
39
+ end
40
+
41
+ def win?
42
+ code == @guess&.code
43
+ end
44
+
45
+ def lose?
46
+ attempts_amount < 1 && !win?
47
+ end
48
+
49
+ def restart
50
+ prepare_game
51
+ end
52
+
53
+ private
54
+
55
+ def current_statistic
56
+ CodebrekerManfly::UserStatistics.new(user: user, difficulty: difficulty,
57
+ attempts: difficulty.attempts - attempts_amount,
58
+ hints: difficulty.hints - hints_amount)
59
+ end
60
+
61
+ def validate
62
+ add_error(:user, I18n.t(:unexpected_class_error)) unless valid_class?(User, user)
63
+ add_error(:difficulty, I18n.t(:unexpected_class_error)) unless valid_class?(Difficulty, difficulty)
64
+ end
65
+
66
+ def prepare_game
67
+ @attempts_amount = difficulty.attempts
68
+ @hints_amount = difficulty.hints
69
+ @code = CodebrekerManfly::CodeGenerator.new.generate
70
+ @hints = code.sample(hints_amount)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CodebrekerManfly
4
+ class Guess < ValidatableEntity
5
+ CODE_LENGTH = 4
6
+
7
+ def initialize(string_code)
8
+ super()
9
+ @string_code = string_code
10
+ end
11
+
12
+ def code
13
+ @code ||= parse_code
14
+ end
15
+
16
+ private
17
+
18
+ def parse_code
19
+ @string_code.chars.map { |number| Integer(number) }
20
+ end
21
+
22
+ def validate
23
+ return add_error(:code, I18n.t(:unexpected_class_error)) unless valid_class?(String, @string_code)
24
+
25
+ validate_code_length
26
+ return add_error(:code, I18n.t(:non_numeric_string_error)) unless valid_only_numeric_string?(@string_code)
27
+
28
+ add_error(:code, I18n.t(:invalid_range)) unless valid_range?(
29
+ CodebrekerManfly::CodeGenerator::DEFAULT_CODE_RANGE, parse_code
30
+ )
31
+ end
32
+
33
+ def validate_code_length
34
+ add_error(:code, I18n.t(:string_min_length_error)) unless valid_string_min_length?(@string_code, CODE_LENGTH)
35
+ add_error(:code, I18n.t(:string_max_length_error)) unless valid_string_max_length?(@string_code, CODE_LENGTH)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CodebrekerManfly
4
+ class User < ValidatableEntity
5
+ attr_reader :name
6
+
7
+ USERNAME_MIN_LENGTH = 3
8
+ USERNAME_MAX_LENGTH = 20
9
+
10
+ def initialize(name)
11
+ super()
12
+ @name = name
13
+ end
14
+
15
+ private
16
+
17
+ def validate
18
+ return add_error(:name, I18n.t(:unexpected_class_error)) unless valid_class?(String, name)
19
+
20
+ add_error(:name, I18n.t(:string_min_length_error)) unless valid_string_min_length?(name, USERNAME_MIN_LENGTH)
21
+ add_error(:name, I18n.t(:string_max_length_error)) unless valid_string_max_length?(name, USERNAME_MAX_LENGTH)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CodebreakerManflu
4
+ class UserStatistics < ValidatableEntity
5
+ include Validator
6
+ attr_reader :user, :difficulty, :attempts, :hints, :date
7
+
8
+ def initialize(user:, difficulty:, attempts:, hints:)
9
+ super()
10
+ @user = user
11
+ @difficulty = difficulty
12
+ @attempts = attempts
13
+ @hints = hints
14
+ @date = DateTime.now
15
+ end
16
+
17
+ private
18
+
19
+ def validate
20
+ add_error(:user, I18n.t(:unexpected_class_error)) unless valid_class?(CodebreakerManflu::User, user)
21
+ add_error(:difficulty, I18n.t(:unexpected_class_error)) unless valid_class?(CodebreakerManflu::Difficulty,
22
+ difficulty)
23
+ validate_attempts
24
+ validate_hints
25
+ end
26
+
27
+ def validate_attempts
28
+ return add_error(:attempts, I18n.t(:unexpected_class_error)) unless valid_class?(Integer, attempts)
29
+
30
+ add_error(:attempts, I18n.t(:negative_integer_error)) unless valid_non_negative_integer?(attempts)
31
+ end
32
+
33
+ def validate_hints
34
+ return add_error(:hints, I18n.t(:unexpected_class_error)) unless valid_class?(Integer, hints)
35
+
36
+ add_error(:hints, I18n.t(:negative_integer_error)) unless valid_non_negative_integer?(hints)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CodebrekerManfly
4
+ class ValidatableEntity
5
+ include Validator
6
+
7
+ attr_reader :errors
8
+
9
+ def initialize
10
+ @errors = {}
11
+ end
12
+
13
+ def valid?
14
+ validate
15
+ errors.empty?
16
+ end
17
+
18
+ private
19
+
20
+ def validate; end
21
+
22
+ def add_error(attribute, error)
23
+ @errors[attribute] = @errors[attribute].nil? ? [error] : @errors[attribute] << error
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CodebrekerManfly
4
+ module Validator
5
+ def valid_class?(expected_class, instance)
6
+ instance.is_a?(expected_class)
7
+ end
8
+
9
+ def valid_non_empty_string?(string)
10
+ !string.empty?
11
+ end
12
+
13
+ def valid_positive_integer?(number)
14
+ number.positive?
15
+ end
16
+
17
+ def valid_non_negative_integer?(number)
18
+ !number.negative?
19
+ end
20
+
21
+ def valid_string_min_length?(string, min_length)
22
+ string.length >= min_length
23
+ end
24
+
25
+ def valid_string_max_length?(string, max_length)
26
+ string.length <= max_length
27
+ end
28
+
29
+ def valid_only_numeric_string?(string)
30
+ /\A\d+\Z/.match?(string)
31
+ end
32
+
33
+ def valid_range?(range, code)
34
+ code.each { |digit| return false unless range.cover?(digit) }
35
+ true
36
+ end
37
+ end
38
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CodebrekerManfly
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: codebreker_manfly
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Manfly1
@@ -161,12 +161,27 @@ files:
161
161
  - CHANGELOG.md
162
162
  - CODE_OF_CONDUCT.md
163
163
  - Gemfile
164
+ - Gemfile.lock
164
165
  - LICENSE.txt
165
166
  - README.md
166
167
  - Rakefile
167
168
  - bin/console
168
169
  - bin/setup
170
+ - codebreker_manfly-0.1.0.gem
171
+ - config/locales/errors.yml
169
172
  - lib/codebreker_manfly.rb
173
+ - lib/codebreker_manfly/autoload.rb
174
+ - lib/codebreker_manfly/config/i18n_config.rb
175
+ - lib/codebreker_manfly/entities/code_generator.rb
176
+ - lib/codebreker_manfly/entities/code_matcher.rb
177
+ - lib/codebreker_manfly/entities/codebreaker_store.rb
178
+ - lib/codebreker_manfly/entities/difficulty.rb
179
+ - lib/codebreker_manfly/entities/game.rb
180
+ - lib/codebreker_manfly/entities/guess.rb
181
+ - lib/codebreker_manfly/entities/user.rb
182
+ - lib/codebreker_manfly/entities/user_statistics.rb
183
+ - lib/codebreker_manfly/entities/validatable_entity.rb
184
+ - lib/codebreker_manfly/modules/validator.rb
170
185
  - lib/codebreker_manfly/version.rb
171
186
  - sig/codebreker_manfly.rbs
172
187
  homepage: