. /** * Basic library. * * @package mod_game * @copyright 2007 Vasilis Daloukas * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); // Include those library functions that are also used by core Moodle or other modules. require_once($CFG->dirroot . '/mod/game/lib.php'); require_once($CFG->dirroot . '/mod/quiz/locallib.php'); // CONSTANTS. /* * Options determining how the grades from individual attempts are combined to give * the overall grade for a user */ define( "GAME_GRADEMETHOD_HIGHEST", "1"); define( "GAME_GRADEMETHOD_AVERAGE", "2"); define( "GAME_GRADEMETHOD_FIRST", "3"); define( "GAME_GRADEMETHOD_LAST", "4"); $gamegrademethod = array ( GAME_GRADEMETHOD_HIGHEST => get_string("gradehighest", "game"), GAME_GRADEMETHOD_AVERAGE => get_string("gradeaverage", "game"), GAME_GRADEMETHOD_FIRST => get_string("attemptfirst", "game"), GAME_GRADEMETHOD_LAST => get_string("attemptlast", "game")); define( "CONST_GAME_TRIES_REPETITION", "5"); /** * Returns the version of Moodle. * * @return string version */ function game_get_moodle_version() { global $DB; static $smoodleversion = null; if ($smoodleversion != null) { return $smoodleversion; } $rec = $DB->get_record_select( 'config', "name='release'"); if ($rec == false) { return $smoodleversion = ''; } else { $a = explode( '.', $rec->value); return $smoodleversion = sprintf( '%02u.%02u', $a[ 0], $a[ 1]); } } /** * Convert a string to upper. * * @param string $str * @param string $lang * @return string the uppercase of $str */ function game_upper( $str, $lang='') { if ($lang == 'user') { return $str; } $str = game_strtoupper( $str); switch ($lang) { case 'el': $from = 'ΆΈΉΊΌΎΏ'; $to = 'ΑΕΗΙΟΥΩ'; break; default: return $str; } $len = game_strlen( $from); for ($i = 0; $i < $len; $i++) { $str = str_replace( game_substr( $from, $i, 1), game_substr( $to, $i, 1), $str); } return $str; } /** * Returns the HTML of a select control. * * @param string $name * @param array $a * @param string $input * @param array $events * * @return string the HTML */ function game_showselectcontrol( $name, $a, $input, $events='') { $ret = "\r\n"; return $ret; } /** * Returns the HTML of a checkbox control. * * @param string $name * @param string $value * @return string the HTML */ function game_showcheckbox( $name, $value) { $a = array(); $a[ 0] = get_string( 'no'); $a[ 1] = get_string( 'yes'); return game_showselectcontrol( $name, $a, $value); } /** * Used by hangman. Returns a short answer. * * @param stdClass $game * @param boolean $allowspaces * @param boolean $userepetitions * * @return string the HTML */ function game_question_shortanswer( $game, $allowspaces=false, $userepetitions=true) { switch( $game->sourcemodule) { case 'glossary': return game_question_shortanswer_glossary( $game, $allowspaces, $userepetitions); case 'quiz': return game_question_shortanswer_quiz( $game, $allowspaces, $userepetitions); case 'question': return game_question_shortanswer_question( $game, $allowspaces, $userepetitions); } return false; } /** * Used by hangman. Returns a short answer from glossary. * * @param stdClass $game * @param boolean $allowspaces * @param boolean $userepetitions * */ function game_question_shortanswer_glossary( $game, $allowspaces, $userepetitions) { global $DB; if ($game->glossaryid == 0) { print_error( get_string( 'must_select_glossary', 'game')); } $select = "glossaryid={$game->glossaryid}"; $table = '{glossary_entries} ge'; if ($game->glossarycategoryid) { $table .= ',{glossary_entries_categories} gec'; $select .= ' AND gec.entryid = ge.id '. " AND gec.categoryid = {$game->glossarycategoryid}"; } if ($allowspaces == false) { $select .= " AND concept NOT LIKE '% %' "; } if ($game->glossaryonlyapproved) { // Only approved glossary entries will be used. $select .= ' AND (ge.approved=1 OR teacherentry=1)'; } if (($id = game_question_selectrandom( $game, $table, $select, 'ge.id', $userepetitions)) == false) { return false; } $sql = 'SELECT id, concept as answertext, definition as questiontext, id as glossaryentryid, '. ' 0 as questionid, glossaryid, attachment, 0 as answerid'. " FROM {glossary_entries} ge WHERE id = $id"; if (($rec = $DB->get_record_sql( $sql)) == false) { return false; } if ( $rec->attachment != '') { $rec->attachment = "glossary/{$game->glossaryid}/$rec->id/$rec->attachment"; } return $rec; } /** * Used by hangman. Returns a short answer from quiz. * * @param stdClass $game * @param boolean $allowspaces * @param boolean $userepetitions */ function game_question_shortanswer_quiz( $game, $allowspaces, $userepetitions) { global $DB; if ($game->quizid == 0) { print_error( get_string( 'must_select_quiz', 'game')); } if (game_get_moodle_version() < '02.07') { $select = "qtype='shortanswer' AND quiz='$game->quizid' ". " AND qqi.question=q.id"; $table = "{question} q,{quiz_question_instances} qqi"; } else { $select = "qtype='shortanswer' AND qs.quizid='$game->quizid' ". " AND qs.questionid=q.id"; $table = "{question} q,{quiz_slots} qs"; } $fields = "q.id"; if (($id = game_question_selectrandom( $game, $table, $select, $fields, $userepetitions)) == false) { return false; } $select = "q.id=$id AND qa.question=$id". " AND q.hidden=0 AND qtype='shortanswer'"; $table = "{question} q,{question_answers} qa"; $fields = "qa.id as answerid, q.id, q.questiontext as questiontext, ". "qa.answer as answertext, q.id as questionid, ". "0 as glossaryentryid, '' as attachment"; // Maybe there are more answers to one question. I use as correct the one with bigger fraction. $sql = "SELECT $fields FROM $table WHERE $select ORDER BY fraction DESC"; if (($recs = $DB->get_records_sql( $sql, null, 0, 1)) == false) { return false; } foreach ($recs as $rec) { return $rec; } } /** * Used by hangman. Returns a short answer from question. * * @param stdClass $game * @param boolean $allowspaces * @param boolean $userepetitions * * @return the short answer record */ function game_question_shortanswer_question( $game, $allowspaces, $userepetitions) { global $DB; if ($game->questioncategoryid == 0) { print_error( get_string( 'must_select_questioncategory', 'game')); } $select = 'category='.$game->questioncategoryid; if ($game->subcategories) { $cats = question_categorylist( $game->questioncategoryid); if (count( $cats) > 0) { $s = implode( ',', $cats); $select = 'category in ('.$s.')'; } } $select .= " AND qtype='shortanswer'"; $table = '{question} q'; $fields = 'q.id'; if (($id = game_question_selectrandom( $game, $table, $select, $fields, $userepetitions)) == false) { return false; } $select = "q.id=$id AND qa.question=$id". " AND q.hidden=0 AND qtype='shortanswer'"; $table = "{question} q,{question_answers} qa"; $fields = "qa.id as answerid, q.id, q.questiontext as questiontext, ". "qa.answer as answertext, q.id as questionid, ". "0 as glossaryentryid, '' as attachment"; // Maybe there are more answers to one question. I use as correct the one with bigger fraction. $sql = "SELECT $fields FROM $table WHERE $select ORDER BY fraction DESC"; if (($recs = $DB->get_records_sql( $sql, null, 0, 1)) == false) { return false; } foreach ($recs as $rec) { return $rec; } } /** * Select a random question. Used by millionaire, game_question_shortanswer_quiz, hidden picture. * * @param stdClass $game * @param string $table * @param string $select * @param string $idfields * @param boolean $userepetitions * * @return stdClass the random question */ function game_question_selectrandom( $game, $table, $select, $idfields='id', $userepetitions=true) { global $DB, $USER; $count = $DB->get_field_sql( "SELECT COUNT(*) FROM $table WHERE $select"); if ($count == 0) { return false; } $minnum = 0; $minid = 0; for ($i = 1; $i <= CONST_GAME_TRIES_REPETITION; $i++) { $sel = mt_rand(0, $count - 1); $sql = "SELECT $idfields, $idfields FROM ".$table." WHERE $select"; if (($recs = $DB->get_records_sql( $sql, null, $sel, 1)) == false) { return false; } $id = 0; foreach ($recs as $rec) { $id = $rec->id; } if ($minid == 0) { $minid = $id; } if ($userepetitions == false) { return $id; } if ($count == 1) { break; } $questionid = $glossaryentryid = 0; if ($game->sourcemodule == 'glossary') { $glossaryentryid = $id; } else { $questionid = $id; } $a = array( 'gameid' => $game->id, 'userid' => $USER->id, 'questionid' => $questionid, 'glossaryentryid' => $glossaryentryid); if (($rec = $DB->get_record( 'game_repetitions', $a, 'id,repetitions AS r')) != false) { if (($rec->r < $minnum) or ($minnum == 0)) { $minnum = $rec->r; $minid = $id; } } else { $minid = $questionid; break; } } if ($game->sourcemodule == 'glossary') { game_update_repetitions( $game->id, $USER->id, 0, $minid); } else { game_update_repetitions( $game->id, $USER->id, $minid, 0); } return $minid; } /** * Updates the repetition table. * * @param int $gameid * @param int $userid * @param int $questionid * @param int $glossaryentryid */ function game_update_repetitions( $gameid, $userid, $questionid, $glossaryentryid) { global $DB; $a = array( 'gameid' => $gameid, 'userid' => $userid, 'questionid' => $questionid, 'glossaryentryid' => $glossaryentryid); if (($rec = $DB->get_record( 'game_repetitions', $a, 'id,repetitions AS r')) != false) { $updrec = new stdClass(); $updrec->id = $rec->id; $updrec->repetitions = $rec->r + 1; if (!$DB->update_record( 'game_repetitions', $updrec)) { print_error("Update page: can't update game_repetitions id={$updrec->id}"); } } else { $newrec = new stdClass(); $newrec->gameid = $gameid; $newrec->userid = $userid; $newrec->questionid = $questionid; $newrec->glossaryentryid = $glossaryentryid; $newrec->repetitions = 1; if ($newrec->questionid == '') { $newrec->questionid = 0; } if ($newrec->glossaryentryid == '') { $newrec->glossaryentryid = 0; } if (!$DB->insert_record( 'game_repetitions', $newrec)) { print_error("Insert page: new page game_repetitions not inserted"); } } } /** * Select random questions for Sudoku. * * @param stdClass $game * @param int $count * * @return stdClass the random record(s) */ function game_questions_selectrandom( $game, $count=1) { global $DB; switch( $game->sourcemodule) { case 'quiz': if ($game->quizid == 0) { print_error( get_string( 'must_select_quiz', 'game')); } if (game_get_moodle_version() < '02.07') { $table = '{question} q, {quiz_question_instances} qqi'; $select = " qqi.quiz=$game->quizid". " AND qqi.question=q.id ". " AND q.qtype in ('shortanswer', 'truefalse', 'multichoice')". " AND q.hidden=0"; } else { $table = '{question} q, {quiz_slots} qs'; $select = " qs.quizid=$game->quizid". " AND qs.questionid=q.id ". " AND q.qtype in ('shortanswer', 'truefalse', 'multichoice')". " AND q.hidden=0"; } // Todo 'match'. $field = "q.id as id"; $table2 = 'question'; $fields2 = 'id as questionid,0 as glossaryentryid,qtype'; break; case 'glossary': if ($game->glossaryid == 0) { print_error( get_string( 'must_select_glossary', 'game')); } $table = '{glossary_entries} ge'; $select = "glossaryid='$game->glossaryid' "; if ($game->glossarycategoryid) { $table .= ',{glossary_entries_categories} gec'; $select .= " AND gec.entryid = ge.id ". " AND gec.categoryid = {$game->glossarycategoryid}"; } $field = 'ge.id'; $table2 = 'glossary_entries'; $fields2 = 'id as glossaryentryid, 0 as questionid'; break; case 'question': if ($game->questioncategoryid == 0) { print_error( get_string( 'must_select_questioncategory', 'game')); } $table = '{question} q'; // Inlcude subcategories. $select = 'category='.$game->questioncategoryid; if ($game->subcategories) { $cats = question_categorylist( $game->questioncategoryid); if (count( $cats)) { $select = 'category in ('.implode( ',', $cats).')'; } } $select .= " AND q.qtype in ('shortanswer', 'truefalse', 'multichoice') AND q.hidden=0"; // Todo 'match'. $field = "id"; $table2 = 'question'; $fields2 = 'id as questionid,0 as glossaryentryid'; break; default: print_error( 'No sourcemodule defined'); break; } $ids = game_questions_selectrandom_detail( $table, $select, $field, $count); if ($ids === false) { print_error( get_string( 'no_questions', 'game')); } if (count( $ids) > 1) { // Randomize the array. shuffle( $ids); } $ret = array(); foreach ($ids as $id) { if ($recquestion = $DB->get_record( $table2, array( 'id' => $id), $fields2)) { $new = new stdClass(); $new->questionid = (int )$recquestion->questionid; $new->glossaryentryid = (int )$recquestion->glossaryentryid; $ret[] = $new; } } return $ret; } /** * Select random questions. Used by game_questions_selectrandom. * * @param string $table * @param string $select * @param int $idfield * @param int $count * * @return stdClass the random record(s) */ function game_questions_selectrandom_detail( $table, $select, $idfield="id", $count=1) { global $DB; $sql = "SELECT $idfield FROM $table WHERE $select"; if (($recs = $DB->get_records_sql( $sql)) == false) { return false; } // The array contains the ids of all questions. $a = array(); foreach ($recs as $rec) { $a[ $rec->id] = $rec->id; } if ($count >= count( $a)) { return $a; } else { $id = array_rand( $a, $count); return ($count == 1 ? array( $id) : $id); } } /** * Tries to detect the language of word. * * @param string $word * * @return the language detected */ function game_detectlanguage( $word) { global $CFG; $langs = get_string_manager()->get_list_of_translations(); // English has more priority. if (array_key_exists( 'en', $langs)) { unset( $langs[ 'en']); $langs[ ''] = ''; } ksort( $langs); $langsinstalled = get_string_manager()->get_list_of_translations(); foreach ($langs as $lang => $name) { if ($lang == '') { $lang = 'en'; } if (!array_key_exists( $lang, $langsinstalled)) { continue; } $strings = get_string_manager()->load_component_strings( 'game', $lang); if (isset( $strings[ 'lettersall'])) { $letters = $strings[ 'lettersall']; $word2 = game_upper( $word, $lang); if (hangman_existall( $word2, $letters)) { return $lang; } } } return false; } /** * The words maybe are in two languages e.g. greek or english so I try to find the correct one. * * @param string $word * @param string $lang * @param string $userlanguage * * @return the letters detected */ function game_getallletters( $word, $lang='', $userlanguage='') { for (;;) { if ($lang == 'user') { $letters = $userlanguage; if (hangman_existall( $word, $letters)) { return $letters; } else { return ''; } } else { $strings = get_string_manager()->load_component_strings( 'game', ($lang == '' ? 'en' : $lang)); if (isset( $strings[ 'lettersall'])) { $letters = $strings[ 'lettersall']; $word2 = game_upper( $word, $lang); if (hangman_existall( $word2, $letters)) { return $letters; } } if ($lang == '') { break; } else { $lang = ''; } } } return ''; } /** * true if exist all the letters * * @param string $str * @param string $strfind * * @return the letters detected */ function hangman_existall( $str, $strfind) { $n = game_strlen( $str); for ($i = 0; $i < $n; $i++) { $pos = game_strpos( $strfind, game_substr( $str, $i, 1)); if ($pos === false) { return false; } } return true; } /** * return a short answer randomly selected. used by cross * * @param stdClass $game * * @return a question */ function game_questions_shortanswer( $game) { switch( $game->sourcemodule) { case 'glossary': $recs = game_questions_shortanswer_glossary( $game); break; case 'quiz'; $recs = game_questions_shortanswer_quiz( $game); break; case 'question'; $recs = game_questions_shortanswer_question( $game); break; } return $recs; } /** * return a short answer (from glossary) randomly selected. used by cross * * @param stdClass $game * * @return a question */ function game_questions_shortanswer_glossary( $game) { global $DB; $select = "glossaryid={$game->glossaryid}"; $table = '{glossary_entries} ge'; if ($game->glossarycategoryid) { $table .= ',{glossary_entries_categories} gec'; $select .= ' AND gec.entryid = ge.id '. ' AND gec.categoryid = '.$game->glossarycategoryid; } $sql = 'SELECT ge.id, concept as answertext, definition as questiontext, ge.id as glossaryentryid, '. ' 0 as questionid, attachment '. " FROM $table WHERE $select"; return $DB->get_records_sql( $sql); } /** * return a short answer (from quiz) randomly selected. used by cross * * @param stdClass $game * * @return a question */ function game_questions_shortanswer_quiz( $game) { global $DB; if ($game->quizid == 0) { print_error( get_string( 'must_select_quiz', 'game')); } if (game_get_moodle_version() < '02.07') { $select = "qtype='shortanswer' AND quiz='$game->quizid' ". " AND qqi.question=q.id". " AND qa.question=q.id". " AND q.hidden=0"; $table = "{question} q,{quiz_question_instances} qqi,{question_answers} qa"; $fields = "qa.id as qaid, q.id, q.questiontext as questiontext, ". "qa.answer as answertext, q.id as questionid,". " 0 as glossaryentryid,'' as attachment"; } else { $select = "qtype='shortanswer' AND qs.quizid='$game->quizid' ". " AND qs.questionid=q.id". " AND qa.question=q.id". " AND q.hidden=0"; $table = "{question} q,{quiz_slots} qs,{question_answers} qa"; $fields = "qa.id as qaid, q.id, q.questiontext as questiontext, ". "qa.answer as answertext, q.id as questionid,". " 0 as glossaryentryid,'' as attachment"; } return game_questions_shortanswer_question_fraction( $table, $fields, $select); } /** * return a short answer (from question) randomly selected. used by cross * * @param stdClass $game * * @return a question */ function game_questions_shortanswer_question( $game) { if ($game->questioncategoryid == 0) { print_error( get_string( 'must_select_questioncategory', 'game')); } // Include subcategories. $select = 'q.category='.$game->questioncategoryid; if ($game->subcategories) { $cats = question_categorylist( $game->questioncategoryid); if (count( $cats)) { $select = 'q.category in ('.implode(',', $cats).')'; } } $select .= " AND qtype='shortanswer' ". " AND qa.question=q.id". " AND q.hidden=0"; $table = "{question} q,{question_answers} qa"; $fields = "qa.id as qaid, q.id, q.questiontext as questiontext, ". "qa.answer as answertext, q.id as questionid"; return game_questions_shortanswer_question_fraction( $table, $fields, $select); } /** * question fraction * * @param string $table * @param string $fields * @param string $select * * @return the record */ function game_questions_shortanswer_question_fraction( $table, $fields, $select) { global $DB; $sql = "SELECT $fields FROM $table WHERE $select ORDER BY fraction DESC"; $recs = $DB->get_records_sql( $sql); if ($recs == false) { print_error( get_string( 'no_questions', 'game')); } $recs2 = array(); $map = array(); foreach ($recs as $rec) { if (array_key_exists( $rec->questionid, $map)) { continue; } $rec2 = new stdClass(); $rec2->id = $rec->id; $rec2->questiontext = $rec->questiontext; $rec2->answertext = $rec->answertext; $rec2->questionid = $rec->questionid; $rec2->glossaryentryid = 0; $rec2->attachment = ''; $recs2[] = $rec2; $map[ $rec->questionid] = $rec->questionid; } return $recs2; } /** * sets char * * @param string $s * @param int $pos * @param string $char */ function game_setchar( &$s, $pos, $char) { $ret = ""; if ($pos > 0) { $ret .= game_substr( $s, 0, $pos); } $s = $ret . $char . game_substr( $s, $pos + 1); } /** * insert a record * * @param stdClass $table * @param stdClass $rec */ function game_insert_record( $table, $rec) { global $DB; if ($DB->get_record($table, array('id' => $rec->id), 'id,id') == false) { $sql = 'INSERT INTO {'.$table.'}(id) VALUES('.$rec->id.')'; if (!$DB->execute( $sql)) { print_error( "Cannot insert an empty $table with id=$rec->id"); return false; } } if (isset( $rec->question)) { $temp = $rec->question; $rec->question = addslashes( $rec->question); } $ret = $DB->update_record( $table, $rec); if (isset( $rec->question)) { $rec->question = $temp; } return $ret; } /** * If score is negative doesn't update the record score is between 0 and 1. * * @param stdClass $game * @param stdClass $attempt * @param float $score * @param boolean $finished * * @return the record */ function game_updateattempts( $game, $attempt, $score, $finished) { global $DB, $USER; if ($attempt != false) { $updrec = new stdClass(); $updrec->id = $attempt->id; $updrec->timelastattempt = time(); $updrec->lastip = getremoteaddr(); if (isset( $_SERVER[ 'REMOTE_HOST'])) { $updrec->lastremotehost = $_SERVER[ 'REMOTE_HOST']; } else { $updrec->lastremotehost = gethostbyaddr( $updrec->lastip); } $updrec->lastip = substr( $updrec->lastip, 0, 30); $updrec->lastremotehost = substr( $updrec->lastremotehost, 0, 50); if ($score >= 0) { $updrec->score = $score; } if ($finished) { $updrec->timefinish = $updrec->timelastattempt; } $updrec->attempts = $attempt->attempts + 1; if (!$DB->update_record( 'game_attempts', $updrec)) { print_error( "game_updateattempts: Can't update game_attempts id=$updrec->id"); } // Update grade item and send all grades to gradebook. $grades = new stdClass(); $grades->userid = $USER->id; $grades->rawgrade = game_score_to_grade($score, $game); $grades->datesubmitted = time(); game_grade_item_update( $game, $grades); game_update_grades( $game, $grades->userid); } // Update table game_grades. if ($finished) { game_save_best_score( $game); } } /** * Computes max grade * * @param stdClass $game * @param stdClass $attempt * @param float $grade * @param boolean $finished * * @return the record */ function game_updateattempts_maxgrade( $game, $attempt, $grade, $finished) { global $DB; $recgrade = $DB->get_field( 'game_attempts', 'score', array( 'id' => $attempt->id)); if ($recgrade > $grade) { $grade = -1; // Don't touch the grade. } game_updateattempts( $game, $attempt, $grade, $finished); } /** * Update queries * * @param stdClass $game * @param stdClass $attempt * @param string $query * @param float $score * @param string $studentanswer * @param boolean $updatetries * * @return the record */ function game_update_queries( $game, $attempt, $query, $score, $studentanswer, $updatetries=false) { global $DB, $USER; if ($query->id != 0) { $select = "id=$query->id"; } else { $select = "attemptid = $attempt->id AND sourcemodule = '{$query->sourcemodule}'"; switch ($query->sourcemodule) { case 'quiz': $select .= " AND questionid='$query->questionid' "; break; case 'glossary': $select .= " AND glossaryentryid='$query->glossaryentryid'"; break; } } if (($recq = $DB->get_record_select( 'game_queries', $select)) === false) { $recq = new stdClass(); $recq->gamekind = $game->gamekind; $recq->gameid = $attempt->gameid; $recq->userid = $attempt->userid; $recq->attemptid = $attempt->id; $recq->sourcemodule = $query->sourcemodule; $recq->questionid = $query->questionid; $recq->glossaryentryid = $query->glossaryentryid; if ($updatetries) { $recq->tries = 1; } if (!($recq->id = $DB->insert_record( 'game_queries', $recq))) { print_error( 'Insert page: new page game_queries not inserted'); } } $updrec = new stdClass(); $updrec->id = $recq->id; $updrec->timelastattempt = time(); if ($score >= 0) { $updrec->score = $score; } if ($studentanswer != '') { $updrec->studentanswer = $studentanswer; } if ($updatetries) { $updrec->tries = $recq->tries + 1; } if (!($DB->update_record( 'game_queries', $updrec))) { print_error( "game_update_queries: not updated id=$updrec->id"); } } /** * get attempt * * @param stdClass $game * @param stdclass $detail * @param boolean $autoadd */ function game_getattempt( $game, &$detail, $autoadd=false) { global $DB, $USER; $select = "gameid=$game->id AND userid=$USER->id and timefinish=0 "; if ($USER->id == 1) { $key = 'mod/game:instanceid'.$game->id; if (array_key_exists( $key, $_SESSION)) { $select .= ' AND id="'.$_SESSION[ $key].'"'; } else { $select .= ' AND id=-1'; } } if (($recs = $DB->get_records_select( 'game_attempts', $select))) { foreach ($recs as $attempt) { if ($USER->id == 1) { $_SESSION[ $key] = $attempt->id; } $detail = $DB->get_record( 'game_'.$game->gamekind, array( 'id' => $attempt->id)); return $attempt; } } if ($autoadd) { game_addattempt( $game); return game_getattempt( $game, $detail, false); } return false; } /** * get user attempts * * @param integer $gameid the game id. * @param integer $userid the userid. * @param string $status 'all', 'finished' or 'unfinished' to control * * @return an array of all the user's attempts at this game. Returns an empty array if there are none. */ function game_get_user_attempts( $gameid, $userid, $status = 'finished') { global $DB; $statuscondition = array( 'all' => '', 'finished' => ' AND timefinish > 0', 'unfinished' => ' AND timefinish = 0' ); if ($attempts = $DB->get_records_select( 'game_attempts', "gameid = ? AND userid = ? AND preview = 0" . $statuscondition[$status], array( $gameid, $userid), 'attempt ASC')) { return $attempts; } else { return array(); } } /** * Returns an unfinished attempt (if there is one) for the given user on the given game. * This function does not return preview attempts. * * @param integer $gameid the id of the game. * @param integer $userid the id of the user. * * @return mixed the unfinished attempt if there is one, false if not. */ function game_get_user_attempt_unfinished( $gameid, $userid) { $attempts = game_get_user_attempts( $gameid, $userid, 'unfinished'); if ($attempts) { return array_shift($attempts); } else { return false; } } /** * Get the best current score for a particular user in a game. * * @param object $game the game object. * @param integer $userid the id of the user. * * @return float the user's current grade for this game. */ function game_get_best_score($game, $userid) { global $DB; $score = $DB->get_field( 'game_grades', 'score', array( 'gameid' => $game->id, 'userid' => $userid)); // Need to detect errors/no result, without catching 0 scores. if (is_numeric($score)) { return $score; } else { return null; } } /** * Get the best current grade for a particular user in a game. * * @param object $game the game object. * @param integer $userid the id of the user. * * @return float the user's current grade for this game. */ function game_get_best_grade($game, $userid) { $score = game_get_best_score( $game, $userid); if (is_numeric( $score)) { return round( $score * $game->grade, $game->decimalpoints); } else { return null; } } /** * Converts score to grade * * @param float $score * @param stdClass $game * * @return float the user's current grade for this game. */ function game_score_to_grade($score, $game) { if ($score) { return round($score * $game->grade, $game->decimalpoints); } else { return 0; } } /** * Determine review options * * @param object $game the game instance. * @param object $attempt the attempt in question. * @param object $context the roles and permissions context, for game module instance * * @return object an object with boolean fields responses, scores, feedback, * correct_responses, solutions and general feedback */ function game_get_reviewoptions($game, $attempt, $context=null) { $options = new stdClass; $options->readonly = true; // Provide the links to the question review and comment script. $options->questionreviewlink = '/mod/game/reviewquestion.php'; if ($context and !$attempt->preview) { // The teacher should be shown everything except during preview when the teachers wants to see just what the students see. $options->responses = true; $options->scores = true; $options->feedback = true; $options->correct_responses = true; $options->solutions = false; $options->generalfeedback = true; $options->overallfeedback = true; // Show a link to the comment box only for closed attempts. if ($attempt->timefinish) { $options->questioncommentlink = '/mod/game/comment.php'; } } else { if (((time() - $attempt->timefinish) < 120) || $attempt->timefinish == 0) { $gamestatemask = GAME_REVIEW_IMMEDIATELY; } else if (!$game->timeclose or time() < $game->timeclose) { $gamestatemask = GAME_REVIEW_OPEN; } else { $gamestatemask = GAME_REVIEW_CLOSED; } $options->responses = ($game->review & $gamestatemask & GAME_REVIEW_RESPONSES) ? 1 : 0; $options->scores = ($game->review & $gamestatemask & GAME_REVIEW_SCORES) ? 1 : 0; $options->feedback = ($game->review & $gamestatemask & GAME_REVIEW_FEEDBACK) ? 1 : 0; $options->correct_responses = ($game->review & $gamestatemask & GAME_REVIEW_ANSWERS) ? 1 : 0; $options->solutions = ($game->review & $gamestatemask & GAME_REVIEW_SOLUTIONS) ? 1 : 0; $options->generalfeedback = ($game->review & $gamestatemask & GAME_REVIEW_GENERALFEEDBACK) ? 1 : 0; $options->overallfeedback = $attempt->timefinish && ($game->review & $gamestatemask & GAME_REVIEW_FEEDBACK); } return $options; } /** * Compute attempt layout * * @param object $game the game object. * @param stdClass $attempt * @return float the user's current grade for this game. */ function game_compute_attempt_layout( $game, &$attempt) { global $DB; $ret = ''; $recs = $DB->get_records_select( 'game_queries', "attemptid=$attempt->id", null, '', 'id,questionid,sourcemodule,glossaryentryid'); if ($recs) { foreach ($recs as $rec) { if ($rec->sourcemodule == 'glossary') { $ret .= $rec->glossaryentryid.'G,'; } else { $ret .= $rec->questionid.','; } } } $attempt->layout = $ret.'0'; } /** * Combines the review options from a number of different game attempts. * * @param object $game the game instance. * @param array $attempts an array of attempt objects. * @param object $context the roles and permissions context, the game instance * * @return array of two options objects, one showing which options are true for * at least one of the attempts, the other showing which options are true * for all attempts. */ function game_get_combined_reviewoptions($game, $attempts, $context=null) { $fields = array('readonly', 'scores', 'feedback', 'correct_responses', 'solutions', 'generalfeedback', 'overallfeedback'); $someoptions = new stdClass; $alloptions = new stdClass; foreach ($fields as $field) { $someoptions->$field = false; $alloptions->$field = true; } foreach ($attempts as $attempt) { $attemptoptions = game_get_reviewoptions( $game, $attempt, $context); foreach ($fields as $field) { $someoptions->$field = $someoptions->$field || $attemptoptions->$field; $alloptions->$field = $alloptions->$field && $attemptoptions->$field; } } return array( $someoptions, $alloptions); } /** * Save the overall grade for a user at a game in the game_grades table * * @param object $game The game for which the best grade is to be calculated and then saved. * * @return boolean Indicates success or failure. */ function game_save_best_score($game) { global $DB, $USER; // Get all the attempts made by the user. if (!$attempts = game_get_user_attempts( $game->id, $USER->id, 'all')) { print_error( 'Could not find any user attempts gameid='.$game->id.' userid='.$USER->id); } // Calculate the best grade. $bestscore = game_calculate_best_score( $game, $attempts); // Save the best grade in the database. if ($grade = $DB->get_record('game_grades', array( 'gameid' => $game->id, 'userid' => $USER->id))) { $grade->score = $bestscore; $grade->timemodified = time(); if (!$DB->update_record('game_grades', $grade)) { print_error('Could not update best grade'); } } else { $grade = new stdClass(); $grade->gameid = $game->id; $grade->userid = $USER->id; $grade->score = $bestscore; $grade->timemodified = time(); if (!$DB->insert_record( 'game_grades', $grade)) { print_error( 'Could not insert new best grade'); } } // Updates gradebook. $grades = new stdClass(); $grades->userid = $USER->id; $grades->rawgrade = game_score_to_grade($bestscore, $game); $grades->datesubmitted = time(); game_grade_item_update( $game, $grades); return true; } /** * Calculate the overall score for a game given a number of attempts by a particular user. * * @return double The overall score * @param object $game The game for which the best score is to be calculated * @param array $attempts An array of all the attempts of the user at the game */ function game_calculate_best_score($game, $attempts) { switch ($game->grademethod) { case GAME_GRADEMETHOD_FIRST: foreach ($attempts as $attempt) { return $attempt->score; } break; case GAME_GRADEMETHOD_LAST: foreach ($attempts as $attempt) { $final = $attempt->score; } return $final; case GAME_GRADEMETHOD_AVERAGE: $sum = 0; $count = 0; foreach ($attempts as $attempt) { $sum += $attempt->score; $count++; } return (float)$sum / $count; default: case GAME_GRADEMETHOD_HIGHEST: $max = 0; foreach ($attempts as $attempt) { if ($attempt->score > $max) { $max = $attempt->score; } } return $max; } } /** * Return the attempt with the best score for a game * * Which attempt is the best depends on $game->grademethod. If the grade * method is GRADEAVERAGE then this function simply returns the last attempt. * @return object The attempt with the best grade * @param object $game The game for which the best grade is to be calculated * @param array $attempts An array of all the attempts of the user at the game */ function game_calculate_best_attempt($game, $attempts) { switch ($game->grademethod) { case GAME_ATTEMPTFIRST: foreach ($attempts as $attempt) { return $attempt; } break; case GAME_GRADEAVERAGE: // Need to do something with it. case GAME_ATTEMPTLAST: foreach ($attempts as $attempt) { $final = $attempt; } return $final; default: case GAME_GRADEHIGHEST: $max = -1; foreach ($attempts as $attempt) { if ($attempt->sumgrades > $max) { $max = $attempt->sumgrades; $maxattempt = $attempt; } } return $maxattempt; } } /** * get questions for sudoku * * @param string $questionlist */ function game_sudoku_getquestions( $questionlist) { global $CFG, $DB; // Load the questions. $sql = "SELECT q.*,qmo.single ". " FROM {$CFG->prefix}question ". " LEFT JOIN {$CFG->prefix}qtype_multichoice_options qmo ON q.id=qmo.questionid AND q.qtype='multichoice' ". " WHERE q.id IN ($questionlist)"; if (!$questions = $DB->get_records_select( 'question', "id IN ($questionlist)")) { print_error( get_string( 'no_questions', 'game')); } // Load the question type specific information. if (!get_question_options($questions)) { print_error('Could not load question options'); } return $questions; } /** * Filter glossary * * @param string $text * @param int $entryid * @param int $contextid * @param int $courseid */ function game_filterglossary( $text, $entryid, $contextid, $courseid) { global $CFG, $DB; for (;;) { $find = '@@PLUGINFILE@@'; $pos = strpos( $text, $find); if ($pos === false) { break; } $pos2 = strpos( $text, '/', $pos); if ($pos2 === false) { break; } $pos3 = strpos( $text, '"', $pos); if ($pos3 === false) { break; } $file = substr( $text, $pos2 + 1, $pos3 - $pos2 - 1); $new = $CFG->wwwroot."/pluginfile.php/$contextid/mod_glossary/entry/$entryid/$file"; $text = substr( $text, 0, $pos).$new.substr( $text, $pos3); } $questiontext = str_replace( '$$'.'\\'.'\\'.'frac', '$$\\'.'frac', $text); return game_filtertext( $text, $courseid); } /** * Filter book * * @param string $text * @param int $chapterid * @param int $contextid * @param int $courseid */ function game_filterbook( $text, $chapterid, $contextid, $courseid) { global $CFG, $DB; for (;;) { $find = '@@PLUGINFILE@@'; $pos = strpos( $text, $find); if ($pos === false) { break; } $pos2 = strpos( $text, '/', $pos); if ($pos2 === false) { break; } $pos3 = strpos( $text, '"', $pos); if ($pos3 === false) { break; } $file = substr( $text, $pos2 + 1, $pos3 - $pos2 - 1); $new = $CFG->wwwroot."/pluginfile.php/$contextid/mod_book/chapter/$chapterid/$file"; $text = substr( $text, 0, $pos).$new.substr( $text, $pos3); } $questiontext = str_replace( '$$'.'\\'.'\\'.'frac', '$$\\'.'frac', $text); return game_filtertext( $text, $courseid); } /** * Filter questio * * @param string $questiontext * @param int $questionid * @param int $contextid * @param int $courseid */ function game_filterquestion( $questiontext, $questionid, $contextid, $courseid) { global $CFG, $DB; for (;;) { $find = '@@PLUGINFILE@@'; $pos = strpos( $questiontext, $find); if ($pos === false) { break; } $pos2 = strpos( $questiontext, '/', $pos); if ($pos2 === false) { break; } $pos3 = strpos( $questiontext, '"', $pos); if ($pos3 === false) { break; } $file = substr( $questiontext, $pos2 + 1, $pos3 - $pos2 - 1); $new = $CFG->wwwroot."/pluginfile.php/$contextid/mod_game/questiontext/$questionid/$file"; $questiontext = substr( $questiontext, 0, $pos).$new.substr( $questiontext, $pos3); } $questiontext = str_replace( '$$'.'\\'.'\\'.'frac', '$$\\'.'frac', $questiontext); return game_filtertext( $questiontext, $courseid); } /** * Filter question answer * * @param string $questiontext * @param int $questionid * @param int $contextid * @param int $courseid */ function game_filterquestion_answer( $questiontext, $questionid, $contextid, $courseid) { global $CFG, $DB; for (;;) { $find = '@@PLUGINFILE@@'; $pos = strpos( $questiontext, $find); if ($pos === false) { break; } $pos2 = strpos( $questiontext, '/', $pos); if ($pos2 === false) { break; } $pos3 = strpos( $questiontext, '"', $pos); if ($pos3 === false) { break; } $file = substr( $questiontext, $pos2 + 1, $pos3 - $pos2 - 1); $new = $CFG->wwwroot."/pluginfile.php/$contextid/mod_game/answer/$questionid/$file"; $questiontext = substr( $questiontext, 0, $pos).$new.substr( $questiontext, $pos3); } return game_filtertext( $questiontext, $courseid); } /** * Filter text * * @param string $text * @param int $courseid */ function game_filtertext( $text, $courseid) { $formatoptions = new stdClass(); $formatoptions->noclean = true; $formatoptions->filter = 1; $text = trim( format_text( $text, FORMAT_MOODLE, $formatoptions)); $start = '
'; if (substr( $text, 0, strlen( $start)) == $start) { if (substr( $text, -6) == '
') { $text = substr( $text, strlen( $start), -6); } } if (substr( $text, 0, 3) == '

') { if (substr( $text, -4) == '

') { $text = substr( $text, 3, -4); } } return $text; } /** * To javascript string * * @param string $text */ function game_tojavascriptstring( $text) { $from = array('"', "\r", "\n"); $to = array('\"', ' ', ' '); $from[] = '