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");
$GAME_GRADE_METHOD = 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", "3");
/**#@-*/
function game_upper( $str, $lang='')
{
$str = textlib::strtoupper( $str);
$strings = get_string_manager()->load_component_strings( 'game', ($lang == '' ? 'en' : $lang));
if( !isset( $strings[ 'convertfrom']))
return $str;
if( !isset( $strings[ 'convertto']))
return $str;
$from = $strings[ 'convertfrom'];
$to = $strings[ 'convertto'];
$len = textlib::strlen( $from);
for($i=0; $i < $len; $i++){
$str = str_replace( textlib::substr( $from, $i, 1), textlib::substr( $to, $i, 1), $str);
}
return $str;
}
function game_showselectcontrol( $name, $a, $input, $events=''){
$ret = "\r\n";
return $ret;
}
function game_showcheckbox( $name, $value)
{
$a = array();
$a[ 0] = get_string( 'no');
$a[ 1] = get_string( 'yes');
return game_showselectcontrol( $name, $a, $value);
$ret = 'sourcemodule)
{
case 'glossary':
return game_question_shortanswer_glossary( $game, $allowspaces, $use_repetitions);
case 'quiz':
return game_question_shortanswer_quiz( $game, $allowspaces, $use_repetitions);
case 'question':
return game_question_shortanswer_question( $game, $allowspaces, $use_repetitions);
}
return false;
}
//used by hangman
function game_question_shortanswer_glossary( $game, $allowspaces, $use_repetitions)
{
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( ($id = game_question_selectrandom( $game, $table, $select, 'ge.id', $use_repetitions)) == 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
function game_question_shortanswer_quiz( $game, $allowspaces, $use_repetitions)
{
global $DB;
if( $game->quizid == 0){
print_error( get_string( 'must_select_quiz', 'game'));
}
$select = "qtype='shortanswer' AND quiz='$game->quizid' ".
" AND qqi.question=q.id";
$table = "{question} q,{quiz_question_instances} qqi";
$fields = "q.id";
if( ($id = game_question_selectrandom( $game, $table, $select, $fields, $use_repetitions)) == 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
function game_question_shortanswer_question( $game, $allowspaces, $use_repetitions)
{
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, $use_repetitions)) == 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 millionaire, game_question_shortanswer_quiz, hidden picture
function game_question_selectrandom( $game, $table, $select, $id_fields='id', $use_repetitions=true)
{
global $DB, $USER;
$count = $DB->get_field_sql( "SELECT COUNT(*) FROM $table WHERE $select");
if( $count == 0)
return false;
$min_num = 0;
$min_id = 0;
for($i=1; $i <= CONST_GAME_TRIES_REPETITION; $i++){
$sel = mt_rand(0, $count-1);
$sql = "SELECT $id_fields,$id_fields 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( $min_id == 0){
$min_id = $id;
}
if( $use_repetitions == 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 r')) != false){
if( ($rec->r < $min_num) or ($min_num == 0)){
$min_num = $rec->r;
$min_id = $id;
}
}else
{
$min_id = $questionid;
break;
}
}
if( $game->sourcemodule == 'glossary')
game_update_repetitions( $game->id, $USER->id, 0, $min_id);
else
game_update_repetitions( $game->id, $USER->id, $min_id, 0);
return $min_id;
}
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 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_r( $newrec);
print_error("Insert page: new page game_repetitions not inserted");
}
}
}
//used by sudoku
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'));
}
$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";
//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;
}
//used by game_questions_selectrandom
function game_questions_selectrandom_detail( $table, $select, $id_field="id", $count=1)
{
global $DB;
$sql = "SELECT $id_field 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);print_r( $a); echo "count=$count
";
return ( $count == 1 ? array( $id) : $id);
}
}
//Tries to detect the language of word
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);
$langs_installed = get_string_manager()->get_list_of_translations();
foreach( $langs as $lang => $name)
{
if( $lang == '')
$lang = 'en';
if( !array_key_exists( $lang, $langs_installed))
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.
function game_getallletters( $word, $lang='')
{
for(;;)
{
$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 '';
}
function hangman_existall( $str, $strfind)
{
$n = textlib::strlen( $str);
for( $i=0; $i < $n; $i++)
{
$pos = textlib::strpos( $strfind, textlib::substr( $str, $i, 1));
if( $pos === false)
return false;
}
return true;
}
//used by cross
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;
}
//used by cross
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);
}
//used by cross
function game_questions_shortanswer_quiz( $game)
{
global $DB;
if( $game->quizid == 0){
print_error( get_string( 'must_select_quiz', 'game'));
}
$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";
return game_questions_shortanswer_question_fraction( $table, $fields, $select);
}
//used by cross
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);
}
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;
}
function game_setchar( &$s, $pos, $char)
{
$ret = "";
if( $pos > 0){
$ret .= textlib::substr( $s, 0, $pos);
}
$s = $ret . $char . textlib::substr( $s, $pos+1);
}
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
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->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
game_grade_item_update( $game);
game_update_grades( $game);
}
//Update table game_grades
if( $finished){
game_save_best_score( $game);
}
}
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);
}
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");
}
}
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;
}
/**
* @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;
$status_condition = array(
'all' => '',
'finished' => ' AND timefinish > 0',
'unfinished' => ' AND timefinish = 0'
);
if ($attempts = $DB->get_records_select( 'game_attempts',
"gameid = ? AND userid = ? AND preview = 0" . $status_condition[$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;
}
}
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;
}
}
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 $context the roles and permissions context,
* normally the context for the 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 /* && has_capability('mod/game:viewreports', $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) {
$game_state_mask = GAME_REVIEW_IMMEDIATELY;
} else if (!$game->timeclose or time() < $game->timeclose) {
$game_state_mask = GAME_REVIEW_OPEN;
} else {
$game_state_mask = GAME_REVIEW_CLOSED;
}
$options->responses = ($game->review & $game_state_mask & GAME_REVIEW_RESPONSES) ? 1 : 0;
$options->scores = ($game->review & $game_state_mask & GAME_REVIEW_SCORES) ? 1 : 0;
$options->feedback = ($game->review & $game_state_mask & GAME_REVIEW_FEEDBACK) ? 1 : 0;
$options->correct_responses = ($game->review & $game_state_mask & GAME_REVIEW_ANSWERS) ? 1 : 0;
$options->solutions = ($game->review & $game_state_mask & GAME_REVIEW_SOLUTIONS) ? 1 : 0;
$options->generalfeedback = ($game->review & $game_state_mask & GAME_REVIEW_GENERALFEEDBACK) ? 1 : 0;
$options->overallfeedback = $attempt->timefinish && ($game->review & $game_state_mask & GAME_REVIEW_FEEDBACK);
}
return $options;
}
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.
* Returns an array of two ojects, so he suggested way of calling this
* funciton is:
* list($someoptions, $alloptions) = game_get_combined_reviewoptions(...)
*
* @param object $game the game instance.
* @param array $attempts an array of attempt objects.
* @param $context the roles and permissions context,
* normally the context for the game module 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 $quiz The game for which the best grade is to be calculated and then saved.
* @param integer $userid The userid to calculate the grade for. Defaults to the current user.
* @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');
}
}
// update 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;
}
}
/**
* Loads the most recent state of each question session from the database
*
* For each question the most recent session state for the current attempt
* is loaded from the game_questions table and the question type specific data
*
* @return array An array of state objects representing the most recent
* states of the question sessions.
* @param array $questions The questions for which sessions are to be restored or
* created.
* @param object $cmoptions
* @param object $attempt The attempt for which the question sessions are
* to be restored or created.
* @param mixed either the id of a previous attempt, if this attmpt is
* building on a previous one, or false for a clean attempt.
*/
function game_get_question_states(&$questions, $cmoptions, $attempt, $lastattemptid = false) {
global $DB, $QTYPES;
// get the question ids
$ids = array_keys( $questions);
$questionlist = implode(',', $ids);
$statefields = 'questionid as question, manualcomment, score as grade';
$sql = "SELECT $statefields".
" FROM {game_questions} ".
" WHERE attemptid = '$attempt->id'".
" AND questionid IN ($questionlist)";
$states = $DB->get_records_sql($sql);
// loop through all questions and set the last_graded states
foreach ($ids as $i) {
// Create the empty question type specific information
if (!$QTYPES[$questions[$i]->qtype]->create_session_and_responses(
$questions[$i], $states[$i], $cmoptions, $attempt)) {
return false;
}
$states[$i]->last_graded = clone($states[$i]);
}
return $states;
}
function game_sudoku_getquestions( $questionlist)
{
global $DB;
// Load the questions
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;
}
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);
}
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);
}
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);
}
function game_filtertext( $text, $courseid){
$formatoptions = new stdClass();
$formatoptions->noclean = true;
$formatoptions->filter = 1;
$text = trim( format_text( $text, FORMAT_MOODLE, $formatoptions, $courseid));
$start = '
'){ if( substr( $text, -4) == '
'){ $text = substr( $text, 3, -4); } } return $text; } function game_tojavascriptstring( $text) { $from = array('"',"\r", "\n"); $to = array('\"', ' ', ' '); $from[] = '