/** * Traverse this node, updating the results array that is used by * {@link stack_potentialresponse_tree::evaluate_response()}. * * @param stack_potentialresponse_tree_state $results to be updated. * @param int $key the index of this node. * @param stack_cas_session $cascontext the CAS context that holds all the relevant variables. * @param stack_options $options * @return array with two elements, the updated $results and the index of the next node. */ public function traverse($results, $key, $cascontext, $options) { $errorfree = true; if ($cascontext->get_errors_key('PRSANS' . $key)) { $results->_errors .= $cascontext->get_errors_key('PRSANS' . $key); $results->add_feedback(' ' . stack_string('prtruntimeerror', array('node' => 'PRSANS' . ($key + 1), 'error' => $cascontext->get_errors_key('PRSANS' . $key)))); $errorfree = false; } if ($cascontext->get_errors_key('PRTANS' . $key)) { $results->_errors .= $cascontext->get_errors_key('PRTANS' . $key); $results->add_feedback(' ' . stack_string('prtruntimeerror', array('node' => 'PRTANS' . ($key + 1), 'error' => $cascontext->get_errors_key('PRTANS' . $key)))); $errorfree = false; } if ($cascontext->get_errors_key('PRATOPT' . $key)) { $results->_errors .= $cascontext->get_errors_key('PRATOPT' . $key); $results->add_feedback(' ' . stack_string('prtruntimeerror', array('node' => 'PRATOPT' . ($key + 1), 'error' => $cascontext->get_errors_key('PRATOPT' . $key)))); $errorfree = false; } if (!$errorfree) { return -1; } $sans = $cascontext->get_value_key('PRSANS' . $key); $tans = $cascontext->get_value_key('PRTANS' . $key); $atopts = $cascontext->get_value_key('PRATOPT' . $key); // If we can't find atopts then they were not processed by the CAS. // They might still be some in the potential response which do not // need to be processed. if (false === $atopts) { $atopts = null; } $nextnode = $this->do_test($sans, $tans, $atopts, $options, $results); return $nextnode; }
/** * 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 generate the display of the PRT feedback. * @param string $name the PRT name. * @param question_attempt $qa the question attempt to display. * @param question_definition $question the question being displayed. * @param stack_potentialresponse_tree_state $result the results to display. * @param question_display_options $options controls what should and should not be displayed. * @return string nicely formatted feedback, for display. */ protected function prt_feedback_display($name, question_attempt $qa, question_definition $question, stack_potentialresponse_tree_state $result, question_display_options $options, $includestandardfeedback) { $err = ''; if ($result->errors) { $err = $result->errors; } $feedback = ''; $feedbackbits = $result->get_feedback(); if ($feedbackbits) { $feedback = array(); $format = null; foreach ($feedbackbits as $bit) { $feedback[] = $qa->rewrite_pluginfile_urls($bit->feedback, 'qtype_stack', $bit->filearea, $bit->itemid); if (!is_null($bit->format)) { if (is_null($format)) { $format = $bit->format; } if ($bit->format != $format) { throw new coding_exception('Inconsistent feedback formats found in PRT ' . $name); } } } if (is_null($format)) { $format = FORMAT_HTML; } $feedback = $result->substitue_variables_in_feedback(implode(' ', $feedback)); $feedback = format_text(stack_maths::process_display_castext($feedback, $this), $format, array('noclean' => true, 'para' => false)); } $gradingdetails = ''; if (!$result->errors && $qa->get_behaviour_name() == 'adaptivemultipart') { // This is rather a hack, but it will probably work. $renderer = $this->page->get_renderer('qbehaviour_adaptivemultipart'); $gradingdetails = $renderer->render_adaptive_marks($qa->get_behaviour()->get_part_mark_details($name), $options); } if ($includestandardfeedback) { $standardfeedback = $this->standard_prt_feedback($qa, $question, $result); } else { $standardfeedback = ''; } return html_writer::nonempty_tag('div', $standardfeedback . $err . $feedback . $gradingdetails, array('class' => 'stackprtfeedback stackprtfeedback-' . $name)); }