-
-
Notifications
You must be signed in to change notification settings - Fork 79
Expand file tree
/
Copy pathanswerHints.pl
More file actions
209 lines (175 loc) · 6.44 KB
/
answerHints.pl
File metadata and controls
209 lines (175 loc) · 6.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
=head1 NAME
answerHints.pl - Provides methods for answer hints.
=head1 DESCRIPTION
This macro provides an answer-checker post-filter that allows you to produce
additional error messages for incorrect answers. You can trigger
a message for a single answer, a collection of answers, or via a
subroutine that determines the condition for the message.
=head1 FUNCTIONS
=head2 AnswerHints
The answer hints are given as a pair using C<< => >> with the right-hand
side being the answer message and the left-hand side being one of
three possibilities: 1) the value that triggers the message,
2) a reference to an array of values that trigger the message, or
3) a code reference to a subtroutine that accepts the correct
answer, the student's answer, and the answer hash, and returns
1 or 0 depending on whether the message should or should not be
displayed. (See the examples below.)
The right-hand side can be either the message string itself, or
a referrence to an array where the first element is the message
string, and the remaining elements are name-value pairs that
set options for the message. These can include:
=over
=item C<S<< checkCorrect => 0 or 1 >>>
1 means check for messages even
if the answer is correct.
Default: 0
=item C<S<< replaceMessage => 0 or 1 >>>
1 means it's OK to repalce any
message that is already in place
in the answer hash.
Default: 0
=item C<S<< checkTypes => 0 or 1 >>>
1 means only perform the test
if the student answer is the
same type as the correct one.
Default: 1
=item C<S<< processPreview => 0 or 1 >>>
1 means process student answers even
during answer previews. Usually, no
hints are given durring previews, but
only when answers are checked or submitted.
The default can be controlled on an individual
message basis, or by adding
C<answerHintsProcessPreview> to the C<cmp()>
arguments, or in the context's flags.
Default: 0
=item C<S<< score => number >>>
Specifies the score to use if
the message is triggered (so that
partial credit can be given).
Default: keep original score
=item C<S<< cmp_options => [...] >>>
provides options for the cmp routine
used to check if the student answer
matches these answers.
Default: []
=back
If more than one message matches the student's answer, the first
one in the list is used.
Example:
ANS(Vector(1,2,3)->cmp(showCoordinateHints=>0)->withPostFilter(AnswerHints(
Vector(0,0,0) => "The zero vector is not a valid solution",
"-<1,2,3>" => "Try the opposite direction",
"<1,2,3>" => "Well done!",
["<1,1,1>","<2,2,2>","<3,3,3>"] => "Don't just guess!",
sub {
my ($correct,$student,$ans) = @_;
return $correct . $student == 0;
} => "Your answer is perpendicular to the correct one",
Vector(1,2,3) => [
"You have the right direction, but not length",
cmp_options => [parallel=>1],
],
0 => ["Careful, your answer should be a vector!", checkTypes => 0, replaceMessage => 1],
sub {
my ($correct,$student,$ans) = @_;
return norm($correct-$student) < .1;
} => ["Close! Keep trying.", score => .25],
)));
=cut
sub _answerHints_init { }
sub AnswerHints {
return (
sub {
my $ans = shift;
$ans->{_filter_name} = "Answer Hints Post Filter";
my $correct = $ans->{correct_value};
my $student = $ans->{student_value};
Value::Error("AnswerHints can only be used with MathObjects answer checkers") unless ref($correct);
return $ans unless ref($student);
my $context = $correct->context;
my $hash = $context->{answerHash};
$context->{answerHash} = $ans;
my $processPreview = $correct->getFlag('answerHintsProcessPreview', 0);
$context->{answerHash} = $hash;
while (@_) {
my $wrongList = shift;
my $message = shift;
my @options;
($message, @options) = @{$message} if ref($message) eq 'ARRAY';
my %options = (
checkCorrect => 0,
replaceMessage => 0,
checkTypes => 1,
processPreview => $processPreview,
score => undef,
cmp_options => [],
@options,
);
next if !$options{processPreview} && $ans->{isPreview};
$wrongList = [$wrongList] unless ref($wrongList) eq 'ARRAY';
if (($ans->{score} < 1 || $options{checkCorrect})
&& ($ans->{ans_message} eq "" || $options{replaceMessage})
&& (!$options{checkTypes} || $correct->type eq $student->type))
{
foreach my $wrong (@{$wrongList}) {
if (ref($wrong) eq 'CODE') {
# Make the call to run the function inside an eval to trap errors
my $myResult = 0;
eval { $myResult = &$wrong($correct, $student, $ans); 1; } or do {
warn "An error occurred in this problem.";
last;
};
if ($myResult) {
$ans->{ans_message} = $ans->{error_message} = $message;
$ans->{score} = $options{score} if defined $options{score};
last;
}
} else {
unless (Value::isValue($wrong)) {
$wrong = main::Formula($wrong);
$wrong = $wrong->{tree}->Compute if $wrong->{tree}{canCompute};
}
if (AnswerHints::Compare($wrong, $student, $ans, @{ $options{cmp_options} })) {
$ans->{ans_message} = $ans->{error_message} = $message;
$ans->{score} = $options{score} if defined $options{score};
last;
}
}
}
}
}
return $ans;
},
@_
);
}
package AnswerHints;
#
# Calls the answer checker on two values with a copy of the answer hash
# and returns true if the two values match and false otherwise.
#
sub Compare {
my ($self, $other, $ans, @options) = @_;
return 0 unless $self->typeMatch($other); # make sure these can be compared
$ans = bless { %{$ans}, @options }, ref($ans); # make a copy
$ans->{typeError} = 0;
$ans->{ans_message} = $ans->{error_message} = "";
$ans->{score} = 0;
if ($self->address != $ans->{correct_value}->address) {
$ans->{correct_ans} = $self->string;
$ans->{correct_value} = $self;
$ans->{correct_formula} = Value->Package("Formula")->new($self);
}
if ($other->address != $ans->{student_value}->address) {
$ans->{student_ans} = $other->string;
$ans->{student_value} = $other;
$ans->{student_formula} = Value->Package("Formula")->new($other);
}
$self->cmp_preprocess($ans);
$self->cmp_equal($ans);
$self->cmp_postprocess($ans) if !$ans->{error_message} && !$ans->{typeError};
return $ans->{score} >= 1;
}
1;