User:Rolig Loon/Quiz From Notecard

From Second Life Wiki
Jump to: navigation, search

A multiple-choice testing script that reads questions and answer choices from a notecard and presents them in dialog boxes.

Features

* Allows only one student at a time to take the quiz.
* Gives immediate feedback to the student when a dialog button is pushed.
* Does not allow student to retake the quiz.
* Stores quiz scores and gives instructor a report on chat command.
* Quiz can be timed.

Script

//A Dialog-driven quiz, using text from a notecard -- Rolig Loon -- October 2009
//
// Free for public use -- please don't do something crass like selling my script.
// Modify if you must, but please keep these header lines intact.  Be nice.
//
// Notecard format:
// A line beginning with a "Q" is part of a question
// A line beginning with an "A" is a string of comma-delimited zeros (wrong) and a one (right) to identify the answer
// A line beginning with a "#" is a comment
// Blank lines are ignored
//
// Dialog boxes are limited to 512 characters, so make each question (including choices) short enough to fit.
// There is no limit to the number of questions in a quiz, and you may have up to 12 answer choices per question
//
// =============== sample notecard ===========
//# Lines starting with Q appear verbatim in a dialog box
//# You may have as many Q lines as you want per question, but only one A line
//# Interpreted data begins immediately after the lead character in a line
//QWhat is the capital of Minnesota?
//Q1. St. Paul
//Q2. Minneapolis
//Q3. Iowa City
//Q4. Boston
//A1,0,0,0
//QHow many fingers are on my right hand?
//Q1. One
//Q2. Two
//Q3. Three
//Q4. Four
//Q5. Five
//A0,0,0,0,1
// ================= end of sample =================
//
// Instructor types "results" in channel 24 to get a report of all student scores
 
string gCard; //Notecard name
integer gLine; //Current line being read
key gQID; //Dataserver key
integer gtouch; //Activates/deactivates touch_start event 
list gAvList = []; //Cumulative list of people who have taken this quiz
string gAv; //Name of the current quiz-taker
key gAvKey; //Key of the current quiz-taker
integer CHAN; //Channel for dialog communication
integer Handle; //Listen handle for dialog
integer gScore; //This quiz-taker's score
string gQuestion; //Text for the current question
list gAnswers; //Answer key for the current question
list gAllScores = []; //Cumulative list of scores for quiz-takers
integer Timespan = 10;  // This is the maximum time alloted for the quiz, in minutes. Change here if needed. 
 
integer IsNameOnList(list namelist, string name)  //Verifies whether av has already taken the quiz
{
    integer i;
    integer len = llGetListLength(namelist);
    for (i=0; i<=len-1;++i)
        if(llList2String(namelist,i) == name)
        {
            return TRUE;
        }
    return FALSE;
}
 
init()  //Resets parameters for the next quiz-taker
{
    llSetTimerEvent(0);
    gtouch = 0;
    gAv = "";
    gAvKey = NULL_KEY;
    gLine = 0;
    gAllScores += gScore;
    gScore = 0;
    llListenControl(Handle,FALSE);
}
 
list order_buttons(list buttons)
{
return llList2List(buttons, -3, -1) + llList2List(buttons, -6, -4)
+ llList2List(buttons, -9, -7) + llList2List(buttons, -12, -10);
}
 
default
{
    state_entry()
    {
        gCard = llGetInventoryName(INVENTORY_NOTECARD,0);
        gAvList = [];
        CHAN = (integer)(llFrand(100000000))* (-1);
        Handle = llListen(CHAN,"","","");
        llListen(24,"",llGetOwner(),"");
        init();
    }
 
    touch_start(integer total_number)
    {
        if (gtouch == 0)  // Starting quiz with a new person
        {
            if(IsNameOnList(gAvList,llDetectedName(0)))
            {
                llInstantMessage(llDetectedKey(0),"Sorry, "+ llDetectedName(0) + ". You have already taken the quiz. You cannot take it twice.");
                return;
            }
            else
            {
                gAvList += llDetectedName(0); // Add av to the list of people who have attempted this quiz
            }
            gAv = llDetectedName(0);
            gAvKey = llDetectedKey(0);
            llInstantMessage(gAvKey,"Hello, "+ gAv+ ". You will have "+(string)Timespan+" minutes to finish this quiz. Respond to questions as they appear in blue dialog boxes on your screen.");
            llInstantMessage(gAvKey,"Touch this panel again to stop the quiz.");
            llSetTimerEvent(Timespan*60);
        }
        if (gAv != llDetectedName(0)) //Only accept touches from this av until the quiz is finished
        {
            llInstantMessage(llDetectedKey(0),"Someone else is taking the quiz now.  Please wait.");
            return;
        }
        if (gtouch >=1) // This is the emergency stop. Av wants to stop taking the quiz before the last question
        {
            llListenControl(Handle,TRUE);
            llDialog(gAvKey,"If you stop now, you may not restart later. \nDo you want to QUIT now?", ["YES", "NO"],CHAN);
            return;
        }
        // An av should only reach this point if it is the first touch
        gQID = llGetNotecardLine(gCard,gLine); //Read the first line of the notecard
        ++gtouch;
    }
 
    changed(integer change)
    {
        if(change & CHANGED_INVENTORY)
        {
            llResetScript();
        }
    }
 
timer()
{
    llInstantMessage(gAvKey,"Your time is up. Thank you for taking the quiz. Your score is "+ (string)gScore);
    init(); //Restart the quiz for the next person
}
 
dataserver(key query_id, string data)
{
    if(query_id == gQID) //If the data request came from this script
    {
        if(data != EOF) //If there is still data to be read from the notecard
        {
            if(llGetSubString(data,0,0) == "#"||llGetSubString(data,0,0) == "") //Ignore comment lines and blank lines
            {
                ++gLine;
                gQID = llGetNotecardLine(gCard,gLine);
            }
            else if (llGetSubString(data,0,0) == "Q") //Read the question and all answer choices
            {
                gQuestion += llGetSubString(data,1,-1) + "\n"; //Format each "Q" line as a new line in the dialog box
                ++gLine;
                gQID = llGetNotecardLine(gCard,gLine);
            }
            else if (llGetSubString(data,0,0) == "A") //Read the answer key
            {
                gAnswers = llParseString2List(llGetSubString(data,1,-1),[","],[]);
                integer len = llGetListLength(gAnswers);
                integer i;
                list buttons = [];
                for (i=1;i<=len;++i) //Create a numbered button for each choice
                {
                    buttons += [(string)i];
                }
                llListenControl(Handle,TRUE);
                llDialog(gAvKey,gQuestion,order_buttons(buttons),CHAN); //Display the question in a dialog box
            }
        }
        else // If there are no more lines on the notecard
        {
            llInstantMessage(gAvKey,"You have finished the quiz. Congratulations. Your score is "+(string)gScore);
            init(); //Restart the quiz for the next person
        }
    }
}
 
listen (integer channel, string name, key id, string message)
{
    if (channel == 24) //Teacher said something on channel 24
    {
        if (llToLower(message) == "results") //and the message was "results"
        {
            integer len = llGetListLength(gAvList);
            integer i;
            for (i=0;i<=len-1;++i)
            {
                llOwnerSay(llList2String(gAvList,i) +  "....  Score = " + llList2String(gAllScores,i+1));
            }
        }
    }
    else if (message == "YES") // Av has touched the panel and wants to quit
    {
        llInstantMessage(gAvKey,"You have left the quiz with a score of "+(string)gScore +". Goodbye!");
        init();
    }
    else if(message == "NO") // Av has touched the panel and does NOT want to quit
    {
        return;
    }
    else // A question has been displayed in a dialog box
    {
        integer pos = llListFindList(gAnswers,["1"]); //Search the answer key. The correct answer is pos+1
        if(message == (string)(pos+1))
        {
            ++gScore;
            llInstantMessage(gAvKey,"Correct! Your score is now "+ (string)gScore+ ". Next ...");
        }
        else if (pos != -1)
        {
            llInstantMessage(gAvKey,"Wrong. The correct answer was "+ (string)(pos+1) + ". Next ....");
        }
        else if (pos == -1) //The teacher screwed up and didn't code a correct answer
        {
            llInstantMessage(gAvKey,"Ooops! There is no right answer to this question. Let's move on....");
        }
        gQuestion = ""; //Erase the current question
        gAnswers = []; //And its answer key
        ++gLine;
        gQID = llGetNotecardLine(gCard,gLine); // Get the next question
    }
}
}