asker-tool 2.6.0 → 2.7.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/README.md +5 -5
- data/lib/asker/ai/ai.rb +6 -6
- data/lib/asker/ai/ai_calculate.rb +3 -3
- data/lib/asker/ai/code/base_code_ai.rb +5 -30
- data/lib/asker/ai/code/code_ai_factory.rb +6 -12
- data/lib/asker/ai/code/javascript_code_ai.rb +33 -34
- data/lib/asker/ai/code/python_code_ai.rb +35 -36
- data/lib/asker/ai/code/ruby_code_ai.rb +33 -33
- data/lib/asker/ai/code/sql_code_ai.rb +20 -21
- data/lib/asker/ai/concept_ai.rb +12 -22
- data/lib/asker/ai/problem/problem_ai.rb +226 -0
- data/lib/asker/ai/question.rb +34 -45
- data/lib/asker/ai/stages/base_stage.rb +7 -7
- data/lib/asker/ai/stages/stage_b.rb +62 -28
- data/lib/asker/ai/stages/stage_d.rb +10 -10
- data/lib/asker/ai/stages/stage_f.rb +17 -17
- data/lib/asker/ai/stages/stage_i.rb +8 -18
- data/lib/asker/ai/stages/stage_s.rb +28 -26
- data/lib/asker/ai/stages/stage_t.rb +40 -51
- data/lib/asker/application.rb +15 -14
- data/lib/asker/check_input/check_haml_data.rb +52 -51
- data/lib/asker/check_input/check_table.rb +17 -20
- data/lib/asker/check_input.rb +10 -23
- data/lib/asker/cli.rb +43 -24
- data/lib/asker/data/code.rb +10 -9
- data/lib/asker/data/column.rb +21 -17
- data/lib/asker/data/concept.rb +24 -37
- data/lib/asker/data/data_field.rb +2 -2
- data/lib/asker/data/problem.rb +112 -0
- data/lib/asker/data/project_data.rb +11 -15
- data/lib/asker/data/row.rb +25 -23
- data/lib/asker/data/table.rb +25 -46
- data/lib/asker/data/template.rb +7 -7
- data/lib/asker/data/world.rb +3 -3
- data/lib/asker/{formatter → deprecated}/question_moodlexml_formatter.rb +19 -21
- data/lib/asker/displayer/code_displayer.rb +10 -10
- data/lib/asker/displayer/concept_ai_displayer.erb +1 -1
- data/lib/asker/displayer/concept_ai_displayer.rb +17 -17
- data/lib/asker/displayer/concept_displayer.rb +4 -2
- data/lib/asker/displayer/problem_displayer.rb +45 -0
- data/lib/asker/displayer/stats_displayer.rb +7 -12
- data/lib/asker/exporter/code_gift_exporter.rb +2 -2
- data/lib/asker/exporter/concept_ai_gift_exporter.rb +4 -4
- data/lib/asker/exporter/concept_ai_yaml_exporter.rb +7 -7
- data/lib/asker/exporter/concept_doc_exporter.rb +5 -5
- data/lib/asker/exporter/data_gift_exporter.rb +14 -15
- data/lib/asker/exporter/data_moodle_exporter.rb +51 -20
- data/lib/asker/exporter/output_file_exporter.rb +9 -8
- data/lib/asker/exporter/problem_gift_exporter.rb +30 -0
- data/lib/asker/files/language/ca/templates.yaml +6 -0
- data/lib/asker/files/language/du/templates.yaml +6 -0
- data/lib/asker/files/language/en/templates.yaml +7 -1
- data/lib/asker/files/language/es/templates.yaml +6 -0
- data/lib/asker/files/language/fr/templates.yaml +6 -0
- data/lib/asker/formatter/code_string_formatter.rb +5 -5
- data/lib/asker/formatter/concept_doc_formatter.rb +3 -3
- data/lib/asker/formatter/concept_string_formatter.rb +6 -6
- data/lib/asker/formatter/moodle/ddmatch.erb +40 -0
- data/lib/asker/formatter/moodle/gapfill.erb +57 -0
- data/lib/asker/formatter/moodle/ordering.erb +41 -0
- data/lib/asker/formatter/question_gift_formatter.rb +41 -14
- data/lib/asker/formatter/question_hash_formatter.rb +5 -6
- data/lib/asker/formatter/question_moodle_formatter.rb +14 -7
- data/lib/asker/formatter/rb2haml_formatter.rb +8 -7
- data/lib/asker/lang/lang.rb +16 -16
- data/lib/asker/lang/lang_factory.rb +13 -16
- data/lib/asker/lang/text_actions.rb +20 -18
- data/lib/asker/loader/code_loader.rb +10 -22
- data/lib/asker/loader/content_loader.rb +42 -49
- data/lib/asker/loader/directory_loader.rb +13 -16
- data/lib/asker/loader/embedded_file.rb +14 -14
- data/lib/asker/loader/file_loader.rb +5 -4
- data/lib/asker/loader/haml_loader.rb +4 -3
- data/lib/asker/loader/image_url_loader.rb +6 -5
- data/lib/asker/loader/input_loader.rb +24 -10
- data/lib/asker/loader/problem_loader.rb +88 -0
- data/lib/asker/loader/project_loader.rb +5 -12
- data/lib/asker/logger.rb +19 -10
- data/lib/asker/skeleton.rb +19 -35
- data/lib/asker/start.rb +44 -0
- data/lib/asker/version.rb +1 -1
- data/lib/asker.rb +7 -52
- metadata +12 -6
- data/lib/asker/ai/code/problem_code_ai.rb +0 -176
- data/lib/asker/exporter/code_moodle_exporter.rb +0 -15
- data/lib/asker/exporter/concept_ai_moodle_exporter.rb +0 -15
@@ -16,29 +16,29 @@ class RubyCodeAI < BaseCodeAI
|
|
16
16
|
questions = []
|
17
17
|
# error_lines = []
|
18
18
|
@lines.each_with_index do |line, index|
|
19
|
-
if line.strip.start_with?(
|
19
|
+
if line.strip.start_with?("#")
|
20
20
|
lines = clone_array @lines
|
21
|
-
lines[index].sub!(
|
21
|
+
lines[index].sub!("#", "").strip!
|
22
22
|
|
23
23
|
q = Question.new(:short)
|
24
24
|
q.name = "#{name}-#{num}-uncomment"
|
25
|
-
q.text = @lang.text_for(:code1,lines_to_html(lines))
|
26
|
-
q.shorts << (index+1)
|
27
|
-
q.feedback =
|
25
|
+
q.text = @lang.text_for(:code1, lines_to_html(lines))
|
26
|
+
q.shorts << (index + 1)
|
27
|
+
q.feedback = "Comment symbol removed"
|
28
28
|
questions << q
|
29
|
-
elsif line.strip.size>0
|
29
|
+
elsif line.strip.size > 0
|
30
30
|
lines = clone_array @lines
|
31
|
-
lines[index]=
|
31
|
+
lines[index] = "# " + lines[index]
|
32
32
|
|
33
33
|
q = Question.new(:short)
|
34
34
|
q.name = "#{name}-#{num}-comment"
|
35
|
-
q.text = @lang.text_for(:code1,lines_to_html(lines))
|
36
|
-
q.shorts << (index+1)
|
37
|
-
q.feedback =
|
35
|
+
q.text = @lang.text_for(:code1, lines_to_html(lines))
|
36
|
+
q.shorts << (index + 1)
|
37
|
+
q.feedback = "Comment symbol added"
|
38
38
|
questions << q
|
39
39
|
end
|
40
40
|
end
|
41
|
-
questions.shuffle[0
|
41
|
+
questions.shuffle[0, @lines.size / @reduce]
|
42
42
|
end
|
43
43
|
|
44
44
|
##
|
@@ -47,7 +47,7 @@ class RubyCodeAI < BaseCodeAI
|
|
47
47
|
questions = []
|
48
48
|
empty_lines = []
|
49
49
|
used_lines = []
|
50
|
-
@lines.each_with_index do |line,index|
|
50
|
+
@lines.each_with_index do |line, index|
|
51
51
|
if line.strip.size.zero?
|
52
52
|
empty_lines << index
|
53
53
|
else
|
@@ -57,28 +57,28 @@ class RubyCodeAI < BaseCodeAI
|
|
57
57
|
|
58
58
|
used_lines.each do |index|
|
59
59
|
lines = clone_array(@lines)
|
60
|
-
lines.insert(index,
|
60
|
+
lines.insert(index, " " * (rand(4).to_i + 1))
|
61
61
|
if @lines.size < 4 || rand(2) == 0
|
62
62
|
q = Question.new(:short)
|
63
63
|
q.name = "#{name}-#{num}-codeok"
|
64
|
-
q.text = @lang.text_for(:code1,lines_to_html(lines))
|
65
|
-
q.shorts <<
|
66
|
-
q.feedback =
|
64
|
+
q.text = @lang.text_for(:code1, lines_to_html(lines))
|
65
|
+
q.shorts << "0"
|
66
|
+
q.feedback = "Code is OK"
|
67
67
|
questions << q
|
68
68
|
else
|
69
69
|
q = Question.new(:choice)
|
70
70
|
q.name = "#{name}-#{num}-codeok"
|
71
|
-
q.text = @lang.text_for(:code2,lines_to_html(lines))
|
71
|
+
q.text = @lang.text_for(:code2, lines_to_html(lines))
|
72
72
|
others = (1..@lines.size).to_a.shuffle!
|
73
|
-
q.good =
|
73
|
+
q.good = "0"
|
74
74
|
q.bads << others[0].to_s
|
75
75
|
q.bads << others[1].to_s
|
76
76
|
q.bads << others[2].to_s
|
77
|
-
q.feedback =
|
77
|
+
q.feedback = "Code is OK"
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
81
|
-
questions.shuffle[0
|
81
|
+
questions.shuffle[0, @lines.size / @reduce]
|
82
82
|
end
|
83
83
|
|
84
84
|
##
|
@@ -86,13 +86,13 @@ class RubyCodeAI < BaseCodeAI
|
|
86
86
|
def make_syntax_error
|
87
87
|
questions = []
|
88
88
|
|
89
|
-
@lang.mistakes.each_pair do |key,values|
|
89
|
+
@lang.mistakes.each_pair do |key, values|
|
90
90
|
error_lines = []
|
91
|
-
@lines.each_with_index do |line,index|
|
91
|
+
@lines.each_with_index do |line, index|
|
92
92
|
error_lines << index if line.include?(key.to_s)
|
93
93
|
end
|
94
94
|
|
95
|
-
v = values.split(
|
95
|
+
v = values.split(",")
|
96
96
|
v.each do |value|
|
97
97
|
error_lines.each do |index|
|
98
98
|
lines = clone_array(@lines)
|
@@ -100,15 +100,15 @@ class RubyCodeAI < BaseCodeAI
|
|
100
100
|
if @lines.size < 4 || rand(2) == 0
|
101
101
|
q = Question.new(:short)
|
102
102
|
q.name = "#{name}-#{num}-syntaxerror"
|
103
|
-
q.text = @lang.text_for(:code1,lines_to_html(lines))
|
104
|
-
q.shorts << (index+1)
|
103
|
+
q.text = @lang.text_for(:code1, lines_to_html(lines))
|
104
|
+
q.shorts << (index + 1)
|
105
105
|
q.feedback = "Syntax error: '#{value}' must be '#{key}'"
|
106
106
|
else
|
107
107
|
q = Question.new(:choice)
|
108
108
|
q.name = "#{name}-#{num}-syntaxerror"
|
109
|
-
q.text = @lang.text_for(:code2,lines_to_html(lines))
|
109
|
+
q.text = @lang.text_for(:code2, lines_to_html(lines))
|
110
110
|
others = (1..@lines.size).to_a.shuffle!
|
111
|
-
others.delete(index+1)
|
111
|
+
others.delete(index + 1)
|
112
112
|
q.good = (index + 1).to_s
|
113
113
|
q.bads << others[0].to_s
|
114
114
|
q.bads << others[1].to_s
|
@@ -119,7 +119,7 @@ class RubyCodeAI < BaseCodeAI
|
|
119
119
|
end
|
120
120
|
end
|
121
121
|
end
|
122
|
-
questions.shuffle[0
|
122
|
+
questions.shuffle[0, @lines.size / @reduce]
|
123
123
|
end
|
124
124
|
|
125
125
|
##
|
@@ -129,7 +129,7 @@ class RubyCodeAI < BaseCodeAI
|
|
129
129
|
# error_lines = []
|
130
130
|
@lines.each_with_index do |line, index|
|
131
131
|
# Search Variable assignment
|
132
|
-
m = /\s*(\w*)\s
|
132
|
+
m = /\s*(\w*)\s*=\w*/.match(line)
|
133
133
|
i = []
|
134
134
|
unless m.nil?
|
135
135
|
varname = (m.values_at 1)[0]
|
@@ -152,22 +152,22 @@ class RubyCodeAI < BaseCodeAI
|
|
152
152
|
q.name = "#{name}-#{num}-variable"
|
153
153
|
q.text = @lang.text_for(:code1, lines_to_html(lines))
|
154
154
|
q.shorts << (index + 1)
|
155
|
-
q.feedback = "Variable error! Swapped lines #{
|
155
|
+
q.feedback = "Variable error! Swapped lines #{index + 1} with #{k + 1}"
|
156
156
|
else
|
157
157
|
q = Question.new(:choice)
|
158
158
|
q.name = "#{name}-#{num}-variable"
|
159
159
|
q.text = @lang.text_for(:code2, lines_to_html(lines))
|
160
160
|
others = (1..@lines.size).to_a.shuffle!
|
161
|
-
others.delete(index+1)
|
161
|
+
others.delete(index + 1)
|
162
162
|
q.good = (index + 1).to_s
|
163
163
|
q.bads << others[0].to_s
|
164
164
|
q.bads << others[1].to_s
|
165
165
|
q.bads << others[2].to_s
|
166
|
-
q.feedback = "Variable error! Swapped lines #{
|
166
|
+
q.feedback = "Variable error! Swapped lines #{index + 1} with #{k + 1}"
|
167
167
|
end
|
168
168
|
questions << q
|
169
169
|
end
|
170
170
|
end
|
171
|
-
questions.shuffle[0
|
171
|
+
questions.shuffle[0, @lines.size / @reduce]
|
172
172
|
end
|
173
173
|
end
|
@@ -1,36 +1,35 @@
|
|
1
|
-
|
2
|
-
require_relative
|
3
|
-
require_relative
|
4
|
-
require_relative 'base_code_ai'
|
1
|
+
require_relative "../../lang/lang_factory"
|
2
|
+
require_relative "../../ai/question"
|
3
|
+
require_relative "base_code_ai"
|
5
4
|
|
6
5
|
class SQLCodeAI < BaseCodeAI
|
7
6
|
def initialize(code)
|
8
|
-
@lang = LangFactory.instance.get(
|
7
|
+
@lang = LangFactory.instance.get("sql")
|
9
8
|
super code
|
10
9
|
end
|
11
10
|
|
12
11
|
def make_comment_error
|
13
12
|
questions = []
|
14
|
-
@lines.each_with_index do |line,index|
|
15
|
-
if line.include?(
|
13
|
+
@lines.each_with_index do |line, index|
|
14
|
+
if line.include?("//")
|
16
15
|
lines = clone_array @lines
|
17
|
-
lines[index].sub!(
|
16
|
+
lines[index].sub!("//", "").strip!
|
18
17
|
|
19
18
|
q = Question.new(:short)
|
20
19
|
q.name = "#{name}-#{num}-code1uncomment"
|
21
|
-
q.text = @lang.text_for(:code1,lines_to_html(lines))
|
22
|
-
q.shorts << (index+1)
|
23
|
-
q.feedback =
|
20
|
+
q.text = @lang.text_for(:code1, lines_to_html(lines))
|
21
|
+
q.shorts << (index + 1)
|
22
|
+
q.feedback = "Comment symbol removed"
|
24
23
|
questions << q
|
25
|
-
elsif line.strip.size>0
|
24
|
+
elsif line.strip.size > 0
|
26
25
|
lines = clone_array @lines
|
27
|
-
lines[index]=
|
26
|
+
lines[index] = "// " + lines[index]
|
28
27
|
|
29
28
|
q = Question.new(:short)
|
30
29
|
q.name = "#{name}-#{num}-code1comment"
|
31
|
-
q.text = @lang.text_for(:code1,lines_to_html(lines))
|
32
|
-
q.shorts << (index+1)
|
33
|
-
q.feedback =
|
30
|
+
q.text = @lang.text_for(:code1, lines_to_html(lines))
|
31
|
+
q.shorts << (index + 1)
|
32
|
+
q.feedback = "Comment symbol added"
|
34
33
|
questions << q
|
35
34
|
end
|
36
35
|
end
|
@@ -41,10 +40,10 @@ class SQLCodeAI < BaseCodeAI
|
|
41
40
|
error_lines = []
|
42
41
|
questions = []
|
43
42
|
|
44
|
-
@lang.mistakes.each_pair do |key,values|
|
45
|
-
v = values.split(
|
43
|
+
@lang.mistakes.each_pair do |key, values|
|
44
|
+
v = values.split(",")
|
46
45
|
v.each do |value|
|
47
|
-
@lines.each_with_index do |line,index|
|
46
|
+
@lines.each_with_index do |line, index|
|
48
47
|
error_lines << index if line.include?(key.to_s)
|
49
48
|
end
|
50
49
|
|
@@ -53,8 +52,8 @@ class SQLCodeAI < BaseCodeAI
|
|
53
52
|
lines[index].sub!(key.to_s, value)
|
54
53
|
q = Question.new(:short)
|
55
54
|
q.name = "#{name}-#{num}-code1keyword"
|
56
|
-
q.text = @lang.text_for(:code1,lines_to_html(lines))
|
57
|
-
q.shorts << (index+1)
|
55
|
+
q.text = @lang.text_for(:code1, lines_to_html(lines))
|
56
|
+
q.shorts << (index + 1)
|
58
57
|
q.feedback = "Keyword error: '#{value}' must be '#{key}'"
|
59
58
|
questions << q
|
60
59
|
end
|
data/lib/asker/ai/concept_ai.rb
CHANGED
@@ -1,15 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
require_relative
|
6
|
-
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# * concept
|
10
|
-
# * questions
|
11
|
-
# * num
|
12
|
-
# * random_image_for
|
3
|
+
require_relative "../lang/lang"
|
4
|
+
require_relative "ai"
|
5
|
+
require_relative "question"
|
6
|
+
|
7
|
+
# Add more info to every Concept instance.
|
8
|
+
# Encapsulating AI data => questions
|
13
9
|
class ConceptAI
|
14
10
|
include AI
|
15
11
|
|
@@ -17,22 +13,17 @@ class ConceptAI
|
|
17
13
|
attr_reader :questions
|
18
14
|
attr_reader :excluded_questions
|
19
15
|
|
20
|
-
##
|
21
|
-
# Initialize ConcepAI
|
22
|
-
# @param concept (Concept)
|
23
|
-
# @param world (World)
|
24
16
|
def initialize(concept, world)
|
25
17
|
@concept = concept
|
26
18
|
@world = world
|
27
|
-
@questions = {
|
28
|
-
@excluded_questions = {
|
19
|
+
@questions = {d: [], b: [], f: [], i: [], s: [], t: []}
|
20
|
+
@excluded_questions = {d: [], b: [], f: [], i: [], s: [], t: []}
|
29
21
|
@num = 0 # Add a unique number to every question
|
30
22
|
make_questions
|
31
23
|
end
|
32
24
|
|
33
|
-
##
|
34
|
-
# Generates and return new "num" value
|
35
25
|
def num
|
26
|
+
# Generates and return new "num" value
|
36
27
|
@num += 1
|
37
28
|
@num.to_s
|
38
29
|
end
|
@@ -43,15 +34,14 @@ class ConceptAI
|
|
43
34
|
@concept.send(method, *args, &block)
|
44
35
|
end
|
45
36
|
|
46
|
-
##
|
47
|
-
# Generates random image URL
|
48
37
|
def random_image_for(_conceptname)
|
49
|
-
|
38
|
+
# Generates random image URL
|
39
|
+
return "" if rand <= ProjectData.instance.get(:threshold)
|
50
40
|
|
51
41
|
keys = @world.image_urls.keys
|
52
42
|
keys.shuffle!
|
53
43
|
values = @world.image_urls[keys[0]] # keys[0] could be conceptname
|
54
|
-
return
|
44
|
+
return "" if values.nil?
|
55
45
|
|
56
46
|
values.shuffle!
|
57
47
|
"<img src=\"#{values[0]}\" alt=\"image\"><br/>"
|
@@ -0,0 +1,226 @@
|
|
1
|
+
require_relative "../../lang/lang_factory"
|
2
|
+
require_relative "../question"
|
3
|
+
|
4
|
+
class ProblemAI
|
5
|
+
attr_accessor :problem
|
6
|
+
|
7
|
+
def call(problem)
|
8
|
+
@problem = problem
|
9
|
+
make_questions
|
10
|
+
@problem
|
11
|
+
end
|
12
|
+
|
13
|
+
def make_questions
|
14
|
+
@counter = 0
|
15
|
+
@questions = []
|
16
|
+
@customs = get_customs(@problem)
|
17
|
+
make_questions_with_aswers
|
18
|
+
make_questions_with_steps
|
19
|
+
@problem.questions = @questions
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def counter
|
25
|
+
@counter += 1
|
26
|
+
end
|
27
|
+
|
28
|
+
def customize(text:, custom:)
|
29
|
+
output = text.clone
|
30
|
+
custom.each_pair { |oldvalue, newvalue| output.gsub!(oldvalue, newvalue) }
|
31
|
+
output
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_customs(problem)
|
35
|
+
customs = []
|
36
|
+
vars = problem.varnames
|
37
|
+
problem.cases.each do |acase|
|
38
|
+
custom = {}
|
39
|
+
vars.each_with_index { |varname, index| custom[varname] = acase[index] }
|
40
|
+
customs << custom
|
41
|
+
end
|
42
|
+
customs
|
43
|
+
end
|
44
|
+
|
45
|
+
def lines_to_s(lines)
|
46
|
+
output = ""
|
47
|
+
lines.each_with_index do |line, index|
|
48
|
+
output << "%2d: #{line}\n" % (index + 1)
|
49
|
+
end
|
50
|
+
output
|
51
|
+
end
|
52
|
+
|
53
|
+
def make_questions_with_aswers
|
54
|
+
name = @problem.name
|
55
|
+
lang = @problem.lang
|
56
|
+
|
57
|
+
@customs.each do |custom|
|
58
|
+
desc = customize(text: @problem.desc, custom: custom)
|
59
|
+
|
60
|
+
@problem.asks.each do |ask|
|
61
|
+
next if ask[:text].nil?
|
62
|
+
asktext = customize(text: ask[:text], custom: custom)
|
63
|
+
next if ask[:answer].nil?
|
64
|
+
correct_answer = customize(text: ask[:answer], custom: custom)
|
65
|
+
|
66
|
+
# Question boolean => true
|
67
|
+
q = Question.new(:boolean)
|
68
|
+
q.name = "#{name}-#{counter}-pa1true1"
|
69
|
+
q.text = lang.text_for(:pa1, desc, asktext, correct_answer)
|
70
|
+
q.good = "TRUE"
|
71
|
+
@questions << q
|
72
|
+
|
73
|
+
# Locate incorrect answers
|
74
|
+
incorrect_answers = []
|
75
|
+
@customs.each do |aux|
|
76
|
+
next if aux == custom
|
77
|
+
incorrect = customize(text: ask[:answer], custom: aux)
|
78
|
+
incorrect_answers << incorrect if incorrect != correct_answer
|
79
|
+
end
|
80
|
+
|
81
|
+
# Question boolean => true
|
82
|
+
if incorrect_answers.size > 0
|
83
|
+
q = Question.new(:boolean)
|
84
|
+
q.name = "#{name}-#{counter}-pa2false2"
|
85
|
+
q.text = lang.text_for(:pa1, desc, asktext, incorrect_answers.first)
|
86
|
+
q.good = "FALSE"
|
87
|
+
@questions << q
|
88
|
+
end
|
89
|
+
|
90
|
+
# Question choice NONE
|
91
|
+
if incorrect_answers.size > 2
|
92
|
+
q = Question.new(:choice)
|
93
|
+
q.name = "#{name}-#{counter}-pa2-choice-none3"
|
94
|
+
q.text = lang.text_for(:pa2, desc, asktext)
|
95
|
+
q.good = lang.text_for(:none)
|
96
|
+
incorrect_answers.shuffle!
|
97
|
+
q.bads << incorrect_answers[0]
|
98
|
+
q.bads << incorrect_answers[1]
|
99
|
+
q.bads << incorrect_answers[2]
|
100
|
+
q.feedback = "Correct answer is #{correct_answer}."
|
101
|
+
@questions << q
|
102
|
+
end
|
103
|
+
|
104
|
+
# Question choice OK
|
105
|
+
if incorrect_answers.size > 2
|
106
|
+
q = Question.new(:choice)
|
107
|
+
q.name = "#{name}-#{counter}-pa2choice4"
|
108
|
+
q.text = lang.text_for(:pa2, desc, asktext)
|
109
|
+
q.good = correct_answer
|
110
|
+
incorrect_answers.shuffle!
|
111
|
+
q.bads << incorrect_answers[0]
|
112
|
+
q.bads << incorrect_answers[1]
|
113
|
+
q.bads << incorrect_answers[2]
|
114
|
+
q.feedback = "Correct answer is #{correct_answer}."
|
115
|
+
@questions << q
|
116
|
+
end
|
117
|
+
|
118
|
+
if incorrect_answers.size > 1
|
119
|
+
q = Question.new(:choice)
|
120
|
+
q.name = "#{name}-#{counter}-pa2choice5"
|
121
|
+
q.text = lang.text_for(:pa2, desc, asktext)
|
122
|
+
q.good = correct_answer
|
123
|
+
incorrect_answers.shuffle!
|
124
|
+
q.bads << incorrect_answers[0]
|
125
|
+
q.bads << incorrect_answers[1]
|
126
|
+
q.bads << lang.text_for(:none)
|
127
|
+
q.feedback = "Correct answer is #{correct_answer}."
|
128
|
+
@questions << q
|
129
|
+
end
|
130
|
+
|
131
|
+
# Question short
|
132
|
+
q = Question.new(:short)
|
133
|
+
q.name = "#{name}-#{counter}-pa2short6"
|
134
|
+
q.text = lang.text_for(:pa2, desc, asktext)
|
135
|
+
q.shorts << correct_answer
|
136
|
+
q.feedback = "Correct answer is #{correct_answer}."
|
137
|
+
@questions << q
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def make_questions_with_steps
|
142
|
+
name = @problem.name
|
143
|
+
lang = @problem.lang
|
144
|
+
|
145
|
+
@customs.each do |custom|
|
146
|
+
desc = customize(text: @problem.desc, custom: custom)
|
147
|
+
|
148
|
+
@problem.asks.each do |ask|
|
149
|
+
next if ask[:text].nil?
|
150
|
+
asktext = customize(text: ask[:text], custom: custom)
|
151
|
+
next if ask[:steps].nil? || ask[:steps].empty?
|
152
|
+
steps = ask[:steps].map { |step| customize(text: step, custom: custom) }
|
153
|
+
|
154
|
+
# Question steps ok
|
155
|
+
q = Question.new(:short)
|
156
|
+
q.name = "#{name}-#{counter}-ps3short7"
|
157
|
+
q.text = lang.text_for(:ps3, desc, asktext, lines_to_s(steps))
|
158
|
+
q.shorts << 0
|
159
|
+
@questions << q
|
160
|
+
|
161
|
+
if steps.size > 3
|
162
|
+
q = Question.new(:ordering)
|
163
|
+
q.name = "#{name}-#{counter}-ps6ordering8"
|
164
|
+
q.text = lang.text_for(:ps6, desc, asktext, lines_to_s(steps))
|
165
|
+
steps.each { |step| q.ordering << step }
|
166
|
+
@questions << q
|
167
|
+
end
|
168
|
+
|
169
|
+
# Using diferents wrong steps sequences
|
170
|
+
max = steps.size - 1
|
171
|
+
(0..max).each do |index|
|
172
|
+
change = rand(max + 1)
|
173
|
+
bads = steps.clone
|
174
|
+
|
175
|
+
minor = index
|
176
|
+
major = change
|
177
|
+
if minor > major
|
178
|
+
minor, major = major, minor
|
179
|
+
elsif minor == major
|
180
|
+
next
|
181
|
+
end
|
182
|
+
bads[minor], bads[major] = bads[major], bads[minor]
|
183
|
+
|
184
|
+
# Question steps error
|
185
|
+
q = Question.new(:short)
|
186
|
+
q.name = "#{name}-#{counter}-ps3short-error9"
|
187
|
+
q.text = lang.text_for(:ps3, desc, asktext, lines_to_s(bads))
|
188
|
+
q.shorts << minor + 1
|
189
|
+
q.feedback = lang.text_for(:ps4, minor + 1, major + 1)
|
190
|
+
@questions << q
|
191
|
+
end
|
192
|
+
|
193
|
+
# Match questions
|
194
|
+
indexes = (0..(steps.size - 1)).to_a.shuffle
|
195
|
+
(0..(steps.size - 4)).each do |first|
|
196
|
+
incomplete_steps = steps.clone
|
197
|
+
incomplete_steps[indexes[first]] = "?"
|
198
|
+
incomplete_steps[indexes[first + 1]] = "?"
|
199
|
+
incomplete_steps[indexes[first + 2]] = "?"
|
200
|
+
incomplete_steps[indexes[first + 3]] = "?"
|
201
|
+
|
202
|
+
q = Question.new(:match)
|
203
|
+
q.name = "#{name}-#{counter}-ps5match10"
|
204
|
+
q.text = lang.text_for(:ps5, desc, asktext, lines_to_s(incomplete_steps))
|
205
|
+
q.matching << [steps[indexes[first]], (indexes[first] + 1).to_s]
|
206
|
+
q.matching << [steps[indexes[first + 1]], (indexes[first + 1] + 1).to_s]
|
207
|
+
q.matching << [steps[indexes[first + 2]], (indexes[first + 2] + 1).to_s]
|
208
|
+
q.matching << [steps[indexes[first + 3]], (indexes[first + 3] + 1).to_s]
|
209
|
+
q.matching << ["", lang.text_for(:error)]
|
210
|
+
@questions << q
|
211
|
+
|
212
|
+
q = Question.new(:ddmatch)
|
213
|
+
q.name = "#{name}-#{counter}-ps5ddmatch11"
|
214
|
+
q.text = lang.text_for(:ps5, desc, asktext, lines_to_s(incomplete_steps))
|
215
|
+
q.matching << [(indexes[first] + 1).to_s, steps[indexes[first]]]
|
216
|
+
q.matching << [(indexes[first + 1] + 1).to_s, steps[indexes[first + 1]]]
|
217
|
+
q.matching << [(indexes[first + 2] + 1).to_s, steps[indexes[first + 2]]]
|
218
|
+
q.matching << [(indexes[first + 3] + 1).to_s, steps[indexes[first + 3]]]
|
219
|
+
q.matching << ["", lang.text_for(:error)]
|
220
|
+
@questions << q
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
data/lib/asker/ai/question.rb
CHANGED
@@ -1,80 +1,69 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "set"
|
4
4
|
|
5
|
-
# Define Question class
|
6
5
|
class Question
|
6
|
+
TYPES = %i[boolean choice ddmatch match ordering short]
|
7
|
+
|
7
8
|
attr_accessor :name # Question name used as identification
|
8
9
|
attr_accessor :comment # Comments asociated
|
9
|
-
attr_accessor :text # The real text of the question
|
10
|
-
attr_accessor :good # The correct answer
|
11
|
-
attr_accessor :bads # Bads answers used by choice type question
|
12
|
-
attr_accessor :matching # Matching answers used by match type question
|
13
|
-
attr_accessor :shorts # Short answers used by short type question
|
14
|
-
attr_accessor :feedback # Question feedbak
|
15
|
-
attr_reader :type # Question type: :choice, :match, :boolean, :short
|
16
10
|
attr_accessor :tags
|
17
11
|
attr_accessor :lang # Info used when export (YAML)
|
18
12
|
attr_accessor :encode # image base64 content used when export Moodle xml
|
19
13
|
|
20
|
-
#
|
21
|
-
|
14
|
+
attr_accessor :text # The real text of the question
|
15
|
+
attr_accessor :feedback # Question feedbak
|
16
|
+
attr_reader :type # Question type: ;boolean, :choice, :match, :short
|
17
|
+
|
18
|
+
attr_accessor :good # The correct answer (types: boolean, choice)
|
19
|
+
attr_accessor :bads # Bads answers (type: choice)
|
20
|
+
attr_accessor :matching # Matching answers (type: match)
|
21
|
+
attr_accessor :ordering # Steps answer (type: ordering)
|
22
|
+
attr_accessor :shorts # Short answers (type: short)
|
23
|
+
|
22
24
|
def initialize(type = :choice)
|
23
25
|
reset(type)
|
24
26
|
end
|
25
27
|
|
26
|
-
# Reset attributes
|
27
28
|
# @param type (Symbol) Question type: choice, match, boolean, short
|
28
|
-
# rubocop:disable Metrics/MethodLength
|
29
29
|
def reset(type = :choice)
|
30
|
-
|
31
|
-
@comment = ''
|
32
|
-
@text = ''
|
30
|
+
validate type
|
33
31
|
@type = type
|
34
|
-
|
35
|
-
@
|
36
|
-
@
|
37
|
-
@shorts = []
|
38
|
-
@feedback = nil
|
39
|
-
shuffle_on
|
32
|
+
|
33
|
+
@name = ""
|
34
|
+
@comment = ""
|
40
35
|
@tags = Set.new
|
41
36
|
@lang = nil
|
42
37
|
@encode = :none
|
43
|
-
end
|
44
|
-
# rubocop:enable Metrics/MethodLength
|
45
38
|
|
46
|
-
|
47
|
-
|
48
|
-
@
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
end
|
55
|
-
|
56
|
-
# Set boolean type
|
57
|
-
def set_boolean
|
58
|
-
@type = :boolean
|
59
|
-
end
|
60
|
-
|
61
|
-
# Set short type
|
62
|
-
def set_short
|
63
|
-
@type = :short
|
39
|
+
@text = ""
|
40
|
+
@feedback = nil
|
41
|
+
@good = ""
|
42
|
+
@bads = []
|
43
|
+
@matching = []
|
44
|
+
@ordering = []
|
45
|
+
@shorts = []
|
46
|
+
shuffle_on
|
64
47
|
end
|
65
48
|
|
66
|
-
# Set shuffle off
|
67
49
|
def shuffle_off
|
68
50
|
@shuffle = false
|
69
51
|
end
|
70
52
|
|
71
|
-
# Set shuffle on
|
72
53
|
def shuffle_on
|
73
54
|
@shuffle = true
|
74
55
|
end
|
75
56
|
|
76
|
-
# Return shuffle value
|
77
57
|
def shuffle?
|
78
58
|
@shuffle
|
79
59
|
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def validate(type)
|
64
|
+
unless TYPES.include? type
|
65
|
+
warn "[ERROR] Question type error (#{type})"
|
66
|
+
exit 1
|
67
|
+
end
|
68
|
+
end
|
80
69
|
end
|