/** * This function actually traverses the tree and generates outcomes. * * @param stack_cas_session $questionvars the question varaibles. * @param stack_options $options * @param array $answers name => value the student response. * @param int $seed the random number seed. * @return stack_potentialresponse_tree_state the result. */ public function evaluate_response(stack_cas_session $questionvars, $options, $answers, $seed) { if (empty($this->nodes)) { throw new stack_exception('stack_potentialresponse_tree: evaluate_response ' . 'attempting to traverse an empty tree. Something is wrong here.'); } $localoptions = clone $options; $localoptions->set_option('simplify', $this->simplify); $cascontext = $this->create_cas_context_for_evaluation($questionvars, $localoptions, $answers, $seed); $results = new stack_potentialresponse_tree_state($this->value, true, 0, 0); // Traverse the tree. $nodekey = $this->firstnode; $visitednodes = array(); while ($nodekey != -1) { if (!array_key_exists($nodekey, $this->nodes)) { throw new stack_exception('stack_potentialresponse_tree: ' . 'evaluate_response: attempted to jump to a potential response ' . 'which does not exist in this question. This is a question ' . 'authoring/validation problem.'); } if (array_key_exists($nodekey, $visitednodes)) { $results->add_answernote('[PRT-CIRCULARITY]=' . $nodekey); break; } $visitednodes[$nodekey] = true; $nodekey = $this->nodes[$nodekey]->traverse($results, $nodekey, $cascontext, $localoptions); if ($results->_errors) { break; } } // Make sure these are PHP numbers. $results->_score = $results->_score + 0; $results->_penalty = $results->_penalty + 0; // Restrict score to be between 0 and 1. $results->_score = min(max($results->_score, 0), 1); // From a strictly logical point of view the 'score' and the 'penalty' are independent. // Hence, this clause belongs in the question behaviour. // From a practical point of view, it is confusing/off-putting when testing to see "score=1, penalty=0.1". // Why does this correct attempt attract a penalty? So, this is a unilateral decision: // If the score is 1 there is never a penalty. if ($results->_score > 0.99999995) { $results->_penalty = 0; } if ($results->errors) { $results->_score = null; $results->_penalty = null; } $results->set_cas_context($cascontext, $seed); return $results; }
/** * Actually execute the test for this node. */ public function do_test($nsans, $ntans, $ncasopts, $options, stack_potentialresponse_tree_state $results) { if (false === $ncasopts) { $ncasopts = $this->atoptions; } $at = new stack_ans_test_controller($this->answertest, $nsans, $ntans, $options, $ncasopts); $at->do_test(); $testpassed = $at->get_at_mark(); if ($testpassed) { $resultbranch = $this->branches[1]; $branchname = 'prtnodetruefeedback'; } else { $resultbranch = $this->branches[0]; $branchname = 'prtnodefalsefeedback'; } if ($at->get_at_answernote()) { $results->add_answernote($at->get_at_answernote()); } if ($resultbranch['answernote']) { $results->add_answernote($resultbranch['answernote']); } // If the answer test is running in quiet mode we suppress any // automatically generated feedback from the answertest itself. if (!$this->quiet && $at->get_at_feedback()) { $results->add_feedback($at->get_at_feedback()); } if ($resultbranch['feedback']) { $results->add_feedback($resultbranch['feedback'], $resultbranch['feedbackformat'], $branchname, $this->nodeid); } $results->_valid = $results->_valid && $at->get_at_valid(); $results->_score = $this->update_score($results->_score, $resultbranch); if ($resultbranch['penalty'] !== '') { $results->_penalty = $resultbranch['penalty']; } $results->_errors .= $at->get_at_errors(); return $resultbranch['nextnode']; }