Nathan
The SolutionFor object allows you to define an equality (in whatever variables are defined in your problem), and get an answer checker that determines if a point satisfies the equation. We won't be using its answer checker directly, but it also overloads the == operator to test if a given point satisfies the equation, and we will use that to let use check several points.
The MultiAnswer object allows you to make a checker that has access to the answers supplied in more than one answer blank, and to use all of them to determine whether they are correct or not. We will use this to allow us to compare the answers to make sure they are distinct, in addition to being correct.
You begin by loading the parserMultiAnswer.pl and parserSolutionFor.pl libraries (together with MathObjects.pl). These are in the pg/macros directory, and you can view the comments in them for more details on how they work.
DOCUMENT(); loadMacros( "PGstandard.pl", "MathObjects.pl", "parserSolutionFor.pl", "parserMultiAnswer.pl", "PGcourse.pl" ); TEXT(beginproblem);Then you set the context to the "Point" context so you have more than one variable defined and can produce points in your answers. Then you create the points that satisfy your equation and form a SolutionFor object with one of them (it is not used, but is required by the object).
Context("Point"); $p1 = Compute("(1,0)"); $p2 = Compute("(2,sqrt(3))"); $p3 = Compute("(-1,0)"); $f = SolutionFor("x^2 - y^2 = 1",$p1);Next you define the MultiAnswer object with three example points for the student (these will be shown as the correct answers if the student requests them after the due date). As part of this, you supply a checker routine that determines if the student's answers are correct. All of the type-checking and other bookkeeping functions are performed by the MultiAnswer object (and the underlying MathObjects), so you only need to provide the specific conditions under which the answers will be correct. This is the heart of the example.
$ma = MultiAnswer($p1,$p2,$p3)->with( checker => sub { my ($correct,$student,$self,$ans) = @_; my @scores = (); my $isPreview = $main::inputs_ref->{previewAnswers}; foreach my $i (1..scalar(@$student)) { my $score = ($f == $student->[$i-1]); # SolutionFor makes this return 0 or 1 foreach my $j (1..$i-1) { if ($student->[$i-1] == $student->[$j-1]) { $self->setMessage($i,"This is the same as your ".$self->NameForNumber($j)." point") unless $isPreview; $score = 0; last; } } push(@scores,$score); } return @scores; }, );The checker receives four parameters: a pointer to the array of correct answers (we ignore this), a pointer to the array of student answers, a pointer to the MultiAnswer object itself, and a pointer to the answer hash (which we also ignore). The checker is supposed to return either a single number (all parts will receive the same score) or an array of scores, one for each answer. We will do the latter so that partial credit can be awarded. We also determine if the Preview button was pushed so that we don't produce error messages that would give away part of the answer in that case.
The main portion of this checker is a loop that runs from 1 to the number of entries in the student array (it will be three in this problem, but that could change). For each student answer, we compute the score based on whether the student's point satisfies the equation ($f == $student->[$i] does this, because the SolutionFor object, $f, overloads the equality check to return 1 or 0 depending on whether the point satisfies the equation or not).
Next, we look through the previous answers and check if any are equal to this new one; if so, we report an error and mark this point incorrect. Then we add the score to the array and end the loop. Finally, we return the array of scores.
The remainder of the problem is just producing the text and installing the MultiAnswer's answer checker. The only other thing to note is that the answer rules must be produced by the MultiAnswer object, not by direct calls to ans_rule. This is the key to allowing the MultiAnswer access to all the answer blanks. Also note that the SolutionFor's Formula object is stored in a field named f so we can use that to display the equation the students are to solve.
Context()->texStrings; BEGIN_TEXT Consider the implicit equation \[$f->{f}.\] Three distinct solutions to this equality are: $PAR \((x_1,y_1)\) = \{$ma->ans_rule(20)\}$BR \((x_2,y_2)\) = \{$ma->ans_rule(20)\}$BR \((x_3,y_3)\) = \{$ma->ans_rule(20)\} END_TEXT Context()->normalStrings; ANS($ma->cmp); $showPartialCorrectAnswers = 1; ENDDOCUMENT();That's it. Hope that works for you.
Davide
PS, it would be possible to use the List object with a custom list checker to do this, but since you know there must be three answers, it's not really necessary to do a list, and the answer messages and partial credit will work better with three separate blanks, as the students will see more readily which answers are right and which aren't That is harder (though still possible) with the list checker, but that is for another message.
PPS, the attachment has unix-style line breaks, so if you try to view it on a PC, it might not format correctly. Best to move it directly to your WeBWorK server and view it there.
Nathan