The Moodle question engine does keep a copy of all attempts being used, providing the code that users questions uses them 'properly' - as described on https://docs.moodle.org/dev/Using_the_question_engine_from_module. I know the query I linked you to had a join on the quiz_attempts table, but that is just to illustrate how the link works. It is not essential. The question_usages table is generic.
So, it is a choice made by the creators of quizventure, that they are not recording the 'attempts' at questions that students make in the game. They have chosen to just load the question definitions from the question bank, and use the data themselves. Fairly simple proof that quizventure is not storing the data: https://github.com/xow/moodle-mod_quizgame/search?q=usage&unscoped_q=usage
However, several other plugins that use questions (including mod_studentquiz, filter_embedquestions, ...) do store their data in the standard tables.
Quizventure could be fixed to store the attempts. Basically, you would need to create a 'question usage' behind the scenes each time a user starts a game, then as they shoot aliens, add the questions and record the 'response' the student gave. You need to talk to the maintainers of quizventure.
load_question returns an instance of a question_definition class. It does get_question_data, then calls make_question. So, of course calling make_question again will give an error.
Putting a lot of questions in a usage in PHP code is not going to do much good. You need to add the question to the usage when the student 'attempts' it. And the attempting happens in JavaScript in the student's browser, so you are going to have to think how you will send the data from there back to Moodle.
I have to say, this is not a good choice of project for learning Moodle development. It involves several different complicated concepts, and requires them to all work at once. If you were trying to learn just once concept at a time, I am sure you could do it. Trying to learn them all in the same development just seems like a recipe for disaster, or at least frustration.
On the other hand, if this is the feature you want, .... How could we break it down so you only have to learn one thing at a time?
I guess you could start like this:
First try to change the JavaScript in quizventure, so that every time the student shoots an alien, it makes an AJAX/Web service call back to Moodle which passes the questionid, and which answer they shot (or somthing like that). Don't worry about making that web service do anything useful. It can just write something to the server logs, so you know it is working (for example debugging("$questionid, $answerid");. If you can get that much working, then think about learning how to make the AJAX call record the user's attempt at a question in a useful way.
If you want a simple example of an AJAX call in Moodle, I suggest you have a look at block_starredcourses. That looks like about the simplest possible example. Hope that helps.
This is great mentorship Tim. Thank you. I will follow your recommendation. I'm a product manager focused on the user experience I'm trying to provide but since I haven't gotten any tech friends interested yet, I've had to go at it myself. Like you said it's not been easy but I with the help of pointers I have managed to get a lot of the user experience features I'm working on done. I'll keep this topic posted on how I fare with this. Much appreciated. Thanks.
Or, would I have to pass the entire quba object, render the question from it and then the answer attempts and pass it back out?
Sorry for the slow reply. (It is a holiday weekend, after all.)
I think your diagram shows more complexity than is needed. I think you hardly need to change quizventure at all - just the change you made to get the JavaScript to make the Ajax call in addition to whatever it was already doing. All you need to do is make the PHP code that receives that Ajax call do something useful.
I think the something useful needs to look like:
- Find the $quba for the current user on this quizventure. (If one does not exist, create one. Probably easiest to set the behaviour to 'immediatefeedback'.)
- Add the question that was just answered, and start it.
- Process the answer that student gave, to record it.
You can sort-of see the code needed by looking at a typical unit test for the multichoice question type (https://github.com/moodle/moodle/blob/master/question/type/multichoice/tests/walkthrough_test.php#L53).The only problem is that these tests use a number of helper methods to reduce duplicated. This makes these tests less verbose, but also means that the tests are less clear as an example of how to interact with questions. You will need to un-pick what the helpers are doing.
There is also a decision about how long to use each $quba. You could just have one, which records all of a students interaction with a quizventure. Or you could try having one per session of game-play. Either could work.
My suggestion above was to just create one if, when you need on, you find one does not already exist. I guess the alternative is to do what you suggest in your diagram, and create it when the student launches the game. Either method could work. I am not sure which would be easier.
Of course, the other thing to do is to use the fact that you are now saving the responses to make this information available to students and/or teachers in a useful way.
I hope that helps.
Thank you for the walkthrough. I have 1 and 2 in your list done. I chose to create a new quba every time a new quiz is started. I see the instance in the DB. I start questions like you mention once they are rendered. I'm able to start the question after every response and it creates entries as expected in the attempt table. The only thing I can't do right now is get the response summary to reflect the answer that was selected. Basically step 3 in your comment. It seems from the test example you mentioned that I'd have to create an array for the response and then use $quba->process_action(slot,$data) to process the response. I just need to figure out what the array needs to look like. I'll continue to work on the final piece of this puzzle and update here! Thanks a bunch! It is great learning something complex. Feels like an accomplishment!
function quizgame_add_attempt($quizgameid,$usageid,$questionid,$qsummary,$asummary,$aid,$fraction,$slot,$ansidx) {
global $DB;
$cm = get_coursemodule_from_instance('quizgame', $quizgameid, 0, false, MUST_EXIST);
$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
$context = context_module::instance($cm->id);
$timenow = time();
$quba = question_engine::load_questions_usage_by_activity($usageid);
$qtmp=question_bank::load_question($questionid);
$res=$quba->add_question($qtmp);
$preferredbehaviour='immediatefeedback';
$quba->set_preferred_behaviour($preferredbehaviour);
$variantstrategy = new question_variant_random_strategy();
$quba->start_question($res);
// $quba->process_action($slot, ['answer' => 0]);
$data=['answer' => $ansidx];
$prefix = $quba->get_field_prefix($slot);
$qa=$quba->get_question_attempt($slot);
$fulldata = array(
'slots' => $slot,
$prefix . ':sequencecheck' => $qa->get_sequence_check_count(),
);
foreach ($data as $name => $value) {
$fulldata[$prefix . $name] = $value;
}
$quba->process_all_actions(time(), $fulldata);
question_engine::save_questions_usage_by_activity($quba);
return $res;
}
I see the index of the answer responses in the attempt_step_data table but dont see them reflected in the attempt or attempt_step tables. I'm sure I'm missing the finish_question method here but I'm not sure when to call it. If I call it after every response, it seems to create a new question instance everytime. I think there is a method that just creates a new updates an attempt with responses but does not create a new attempt. Not sure what method that is. Also a bunch of the args I pass are useless and I'll remove them once I get this working.
That is very close, and the one thing you are missing is not terribly obvious. Probably a good way to see what the issue is is to go into the question bank, and preview a multiple choice question. Make sure the preview is set to use immediatefeedback behaviour. (Here is a direct link to the moodle demo site.) Open your browser developer tools, so you can see the post data as you do things.
At the moment, the $fulldata array you are creating matches what happens when you click the 'Save' button under the question.
What you want to happen is what happens when you click the Check button. That button belongs to the question behaviour, and you need to include its value. That is add
$fulldata[$prefix . '-submit'] = 1;
to the array.
Also, a small point, but you should probably only call
$quba->set_preferred_behaviour($preferredbehaviour);
once, when you create the usage. It should not be necessary to call it again.
(questions[level].type == 'multichoice') {
questions[level].answers.forEach(function(answer,i) {
console.log(i);
console.log(answer.text);
var enemy = new MultiEnemy(Math.random() * bounds.width, -Math.random() * bounds.height / 2,
answer.text, answer.fraction, questions[level].single,answer.aid,i);
I pass this index to the ajax call above which is what gets used as $data=['answer' => $ansidx];
I check the DB after I have shot an answer and the responsesummary entry is either different from what I shot or I get NULL. I'm thinking, if the options get shuffled before being rendered in the game, then the option index would not match the index that is created with the 'add_question' method in the ajax call. I do have the answer id that has been shot. Is there a way to pass that info instead of the array index of the answer in this command $data=['answer' => $ansidx]; ?