Generating generic questions for calendar activity.

Overview

One of the tasks for GSoC 2019, was adding multiple datasets to calendar activities, but along with the task I also planned on adding another inprovement to the activity. The activity initially used hardcoded questions and answesr regarding calenders. The dataset for which looked like

data: [
    {
       "navigationBarVisible" : true,
        "minimumDate": "2018-01-01",
        "maximumDate": "2018-12-31",
        "visibleMonth": 1,
        "visibleYear": 2018,
        "mode": "findMonthOnly",
        "questionsExplicitlyGiven": true,
        "questionAnswers": [
             {
                "question": qsTr("Find the month starting a Thursday and having 28 days"),
                "answer": {"month": [1]}
            },
            {
                "question": qsTr("Find a month starting a Monday and having 31 days"),
                "answer": {"month": [0, 9]}
            },
            {
                "question": qsTr("Find the month between June and August"),
                "answer": {"month": [6]}
            },
            {
                "question": qsTr("Find a month starting a Saturday"),
                "answer": {"month": [8, 11]}
            },
            {
                "question": qsTr("Find a month having 30 days"),
                "answer": {"month": [3, 5, 8, 10]}
            }
        ]
    }, ... and more levels so on
    ]

So I planned on creating an algorithm that could generate some template questions and answers. The activity has four type of questions depending in the mode. The available modes are

      // findMonthOnly --> For questions based on finding month only.
      // findYearMonthDay --> For questions based on finding year, month and day.
      // findDayOfWeek --> For questions based on finding day of week only.
      // findDay --> For questions based on finding day of a given month and year.

So, I the algorithm should generate different questions for each type.

Function to generate date in Range

The first task is to create a function that would generate a random date within the given range i.e minDate and maxDate.

function generateRandomYearMonthDay(minimumDate, maximumDate) {
    var minYear = Number(minimumDate.slice(0, 4))
    var maxYear = Number(maximumDate.slice(0, 4))
    var minMonth = Number(minimumDate.slice(5, 7))
    var maxMonth = Number(maximumDate.slice(5, 7))
    var minDate = Number(minimumDate.slice(8, 10))
    var currentYear = minYear + Math.floor(Math.random() * Math.floor((maxYear - minYear + 1)))
    var currentMonth = minMonth + Math.floor(Math.random() * Math.floor((maxMonth - minMonth + 1)))
    var currentDate
    daysInMonths[1] = (isLeapYear(currentYear)) ? 29 : 28;
    currentDate = minDate + Math.floor(Math.random() * Math.floor((daysInMonths[currentMonth - 1] - minDate + 1)))
    return { year: currentYear, month: currentMonth - 1, day: currentDate }
}

Function to add offset

There are some questions which require the user to find a date, a given number of days ahead of the current date. So I created a function to calculate that date.

function addOffsetToCurrentDate(currentDate) {
    var maxOffset = currentLevelConfig.questionAnswers.maxOffset
    var offset = Math.floor(maxOffset / 2) + Math.floor(Math.random() * Math.floor(maxOffset))
    daysInMonths[1] = (isLeapYear(currentDate.year)) ? 29 : 28;
    offset += currentDate.day
    var answerDate = 1;
    var answerMonth = currentDate.month
    var answerYear = currentDate.year
    while(offset > 0) {
        if(offset - daysInMonths[answerMonth] > 0) {
            offset -= daysInMonths[answerMonth]
            answerMonth++;
        } else {
            answerDate = offset;
            offset = 0
        }
        if(answerMonth > 12) {
            answerYear++;
            daysInMonths[1] = (isLeapYear(answerYear)) ? 29 : 28;
            answerMonth = 0;
        }
    }
    return { year: answerYear, month: answerMonth, day: answerDate, offset: offset } 
}

Function to generate template questions

Now there has to be a function that would return the correct question text template as per the mode of the question. So I created a function for that.

function getTemplateQuestionText(mode, date) {
    var questionText
    if(mode == "findDayOfWeek") {
        questionText = qsTr("What day of the week is on %1?").arg(getDateInLongFormat(date))
    } else if(mode == "findDay") {
        questionText = qsTr("Select day %1?").arg(date.day)
    } else if(mode == "findMonthOnly") {
        questionText = qsTr("Find month number %1").arg(date.month + 1)
    } else {
        if(date.offset) {
            //: The second argument represents the given date in complete format(with complete month name) and the first argument represents the difference in days between given date and answer date.
            questionText = qsTr("Find the date %1 days after %2").arg(date.offset).arg(getDateInLongFormat(date))
        } else
            //: The argument represents the answer date in complete format(with complete month name)
            questionText = qsTr("Find the date %1").arg(getDateInLongFormat(date))
    }
    return questionText
}

Function to set question and answer values

Finally I created a function that would set the correct values of the question and answer variables as per the selected mode.

 if(!currentLevelConfig.questionsExplicitlyGiven) {
    var randomDate = generateRandomYearMonthDay(currentLevelConfig.minimumDate, currentLevelConfig.maximumDate)
    items.score.currentSubLevel = currentSubLevel
    if(currentLevelConfig.mode == "findDayOfWeek") {
        var selectedDate = new Date(randomDate.year, randomDate.month - 1, randomDate.day)
        correctAnswer.dayOfWeek = Number(selectedDate.getDay())
    } else if(currentLevelConfig.mode == "findYearMonthDay" && currentLevelConfig.questionAnswers.maxOffset) {
        correctAnswer = addOffsetToCurrentDate(randomDate)
        randomDate.offset = correctAnswer.offset
    } else {
        correctAnswer = randomDate
    }
    items.questionItem.text = getTemplateQuestionText(currentLevelConfig.mode, randomDate)
}

The part of code is executed only if the questions are not explicitly given.

Dataset contents for template questions

If the dataset creater wants to use template function then he can define then like

{
   "navigationBarVisible" : true,
    "minimumDate": "2018-01-01",
    "maximumDate": "2019-12-31",
    "visibleMonth": 10,
    "visibleYear": 2018,
    "mode": "findYearMonthDay",
    "questionsExplicitlyGiven": false,
    "questionAnswers": {
        "length": 5,
        "maxOffset": 90
    }
},

where the variable length defines the length of questionSet or the number of questions.

Comments

Popular posts from this blog

Multiple Datasets: Tutorial

Overriding Wordlist component function to use Multiple Datasets.