codebreker_manfly 0.1.0 → 0.1.1

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