Calculating quiz score, with weights and partial credit

There is a quiz-game with simple rules. You have to guess one of three options. Each option has a particular weight, for example:

• Option 1: 20 points
• Option 2: 30 points
• Option 3: 50 points

If player guesses the right option, he gets all amount of points. If not, he gets a part of those points:

 User choice || Right Answer || Score
1             1            100%
2             1            75%
3             1            50%
1             2            75%
2             2            100%
3             2            75%
1             3            25%
2             3            50%
3             3            100%


You can easilly write a method to calculate score:

calcRateScore: function(fact, user) {

var rateScore = 0;

switch (fact) {
case 1:
switch (user) {
case 1:
rateScore = 20;
break;
case 2:
rateScore = 20 * 0.75;
break;
case 3:
rateScore = 20 * 0.5;
break;
}
break;
case 2:
switch (user) {
case 1:
rateScore = 30 * 0.75;
break;
case 2:
rateScore = 30;
break;
case 3:
rateScore = 30 * 0.75;
break;
}
break;
case 3:
switch (user) {
case 1:
rateScore = 50 * 0.25;
break;
case 2:
rateScore = 50 * 0.5;
break;
case 3:
rateScore = 50;
break;
}
break;
}

return rateScore;
}


It works, but it looks horrible. How do I get rid of all this switch statements?

• Sorry, I don't get the percent / points logic.. Why gets the user in the second row of your table 75% (75% of what ?) Jun 21, 2016 at 14:10
• @webdeb 75% of the option "weight" Jun 21, 2016 at 14:18
• The result depends on the answer * fractionRulesWithinTheAnswer Jun 21, 2016 at 14:18
• The fact is, you have a manyToMany relationship, where the fractionRules are related to the other possible answers, but the fractions dont share the same logic across, instead they have their own logic applied to each.. isn't it overcomplicated? maybe it would make sense to rethink your model? Jun 21, 2016 at 14:23

You don't seem to have any concept of objects or other reusable code in what you have written. I would take a step back and think about what real-word objects you are trying to model. At a minimum, I would think you would need three different concepts:

• Answer Option: a single answer option that will be related to the question and hold logic on it fractional value as an answer.
• Question: which stores a related set of answers and provides logic on calculating score for the question.
• Quiz: which represents an ordered collection of questions.

Let me start by modeling the objects noted above:

// let's build a question class
function Question(text, baseScore) {
this.text = null;
this.baseScore = null;

this.setText(text);
this.setBaseScore(baseScore);
}

// add methods to question class
Question.prototype = {
setText: function(text) {
this.text = text;

// return the question object to allow for chaining
return this;
},
setBaseScore: function(score) {
this.baseScore = score;

return this;
},
return null;
}

return this;
}

return this;
},
getScore: function() {
if(this.selectedAnswerIndex === null || this.baseScore === null) {
return null;
}
if(scoreModifier === null) {
return null;
}

return this.baseScore * scoreModifier;
}
};

this.text = null;
this.questionScoreModifier = null;

this.setText(text);
this.setScoreModifier(modifier);
};

setText: function(text) {
this.text = text;

return this;
},
setScoreModifier(value) {
this.questionScoreModifier = value;

return this;
}
};

// build quiz class
function Quiz(title) {
this.title = null;
this.questions = [];
this.score = null;

this.setTitle(title);
}

// quiz class methods
Quiz.prototype = {
setTitle: function(text) {
this.title = text;

return this;
},
if(question instanceof Question) {
console.log('Give me a Question object!');
return null;
}
this.questions.push(question);

return this;
},

return this;
},
getTotalScore: function() {
var total = 0;
for(i=0; i<this.questions.length; i++) {
var questionScore = this.questions[i].getScore();
if (questionScore !== null) {
total +== questionScore;
}
}
this.score = total;

}
}


You now have the basic building blocks to create your quiz. You could include this code anywhere and have a reusable quiz.

Now you need to build the quiz itself. That may look like this:

// build your quiz
var myQuiz = new Quiz('My cool quiz');

// now let build some questions
var question1 = new Question(
'This is text of question 1. What is correct answer?',
50
);

// now attach answers to the question
question1

// add the question to the quiz

// you could continue code like above to fill out your quiz


Finally, we can start working with the quiz to set answers and get scores.

// now let's set a user answer on a question
// we can assume that we get both the question index
// and the answer index for the question from elsewhere in javascript
// for example from clicking on answer selection
var questionIdx = {some value};

// we can also get total quiz score
var totalQuizScore = myQuiz.getTotalScore();

// or we can get scores on individual questions
var question1Score = myQuiz.questions[0].getScore();


Note that the outcome here is very reusable code that could be pretty much dropped anywhere within a larger application to implement your quiz. In a real application you may also add something like a quiz rendering class to be able to render the quiz using javascript (and to separate display of quiz from core quiz objects).

It also makes it much easier to modify your code in the future. You need to change how questions are scored? - well just change the scoring logic within the answer and question classes as implemented in those class properties and methods. As long as you keep the contract (i.e method calls) with the quiz, the quiz class itself would likely not need to be modified.

The original task was just to get rid of switch/case statements. Given that rules of the game are never going to be changed, I decided to leave them hardcoded. The code code I provided in the question was just a method of a class that handles my quiz-game. So they are other methods that handle other checks and calculations already.

Keeping it simple, I decided to create an array with objects, which represent each of my option:

var answers = [
null,
{
weight: 20,
fractions: [0, 1, 0.75, 0.5]
},
{
weight: 30,
fractions: [0, 0.75, 1, 0.75]
},
{
weight: 50,
fractions: [0, 0.25, 0.5, 1]
}
];

calcRateScore: function(fact, user) {

• Sure, it looks more accurate.. But I would pass the answers as a param to the function.. and do only the calculation in the function. Jun 21, 2016 at 14:28