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 +4 -4
- data/Gemfile.lock +117 -0
- data/codebreker_manfly-0.1.0.gem +0 -0
- data/config/locales/errors.yml +8 -0
- data/lib/codebreker_manfly/autoload.rb +19 -0
- data/lib/codebreker_manfly/config/i18n_config.rb +3 -0
- data/lib/codebreker_manfly/entities/code_generator.rb +27 -0
- data/lib/codebreker_manfly/entities/code_matcher.rb +62 -0
- data/lib/codebreker_manfly/entities/codebreaker_store.rb +48 -0
- data/lib/codebreker_manfly/entities/difficulty.rb +56 -0
- data/lib/codebreker_manfly/entities/game.rb +73 -0
- data/lib/codebreker_manfly/entities/guess.rb +38 -0
- data/lib/codebreker_manfly/entities/user.rb +24 -0
- data/lib/codebreker_manfly/entities/user_statistics.rb +39 -0
- data/lib/codebreker_manfly/entities/validatable_entity.rb +26 -0
- data/lib/codebreker_manfly/modules/validator.rb +38 -0
- data/lib/codebreker_manfly/version.rb +1 -1
- metadata +16 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b780227d68e66b178bd1c3250207c3123f569ba0a8b9e42d105471181ed314e5
|
|
4
|
+
data.tar.gz: 2109e9aa4fcda81c129b4fd4ab1016177b9bb20fb0d307961b5bc1921d7b6867
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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,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
|
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.
|
|
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:
|