Progression

The progression of problem difficulty and when to unlock a new operation has been one of the hardest parts of the game design for me, but it’s also the most fundamental so I’m going to start my blog off with progression.

Difficulty Progression

The game starts off with only addition available to the player. Further, the difficulty of the addition problems is limited to a range of addends. At level 1 it is possible to get any problem from 0 + 0 to 10 + 10. This aligns directly with the Common Core standard for Grade 1: CCSS.Math.Content.1.OA.C.6.

The numbers are generated randomly by a random number generator:

function getRandomNumber(floor, ceiling) {
let range = (ceiling - floor) + 1;
return Math.floor((Math.random() * range) + floor);
}
Example of a problem generated at Level 1

As the player progresses through the game, the problems become more challenging. Problems are created at random according to the formula:

constant1 = getRandomNumber(0, (level * 10));
constant2 = getRandomNumber(0, (level * 10));
answer = constant1 + constant2;
Example of a problem generated at Level 10

Subtraction progresses along similar lines to addition with the exception that there are no negative answers possible so the numbers are generated a little differently:

constant1 = getRandomNumber(1, (level * 10));
constant2 = getRandomNumber(0, constant1);
answer = constant1 - constant2;

Multiplication and Division have a slightly different progression:

constant1 = getRandomNumber(1, (level + 5));
constant2 = getRandomNumber(0, ((level + 5) - ((((level % 2) + level) / 2) - 1)));
answer = constant1 * constant2;

The formula above for the multiplier is hard to parse visually but, through level 10, the range of problems looks like this:

Level Multiplicand Range Multiplier Range
11 – 60 – 6
21 – 70 – 7
31 – 80 – 7
41 – 90 – 8
51 – 100 – 8
61 – 110 – 9
71 – 120 – 9
81 – 130 – 10
91 – 140 – 10
101 – 150 – 11

Division has a similar progression but the terms of the problem are generated slightly differently:

constant2 = getRandomNumber(1, (level + 5));
answer = getRandomNumber(1, ((level + 5) - ((((level % 2) + level) / 2) - 1)));
constant1 = constant2 * answer;

Level Progression

Level advancement is determined by the average answer time of the player’s previous 20 questions. I didn’t want to judge the player’s proficiency based on overall average because poor performance early on would hamper their ability to progress in the long-term.

To progress a level, the player must have an average answer time of less than 5 seconds for the previous 20 problems. Most research says that an average answer time of less than 3 seconds is an indicator of automaticity. I went with 5 because the keyboard input will add extra time to their answer input that doesn’t reflect their answer recall speed.

I track the player’s overall answer time for each operation as well as the answer time for the last 20 questions in each operation. I track the average by storing the running average and the number of questions answered in a small, two-element array:

player.addition.totalAverage[runningAverage, questionsAnswerd];

After each correct answer, a new average is calculated based on the time taken to answer it using a function similar to this:

function getAverage(runningAverage, newTime, totalQuestions) {
runningAverage = ((runningAverage * totalQuestions) + newTime) / (totalQuestions + 1);
totalQuestions++;
return runningAverage;
}

Other Operations

Catacombs are unlocked for the player after the completion of specific levels. Subtraction (exclusive of negative numbers) is available after the player completes level 2 addition: (0–20) + (0–20). Multiplication is unlocked after level 4 addition: (0–40) + (0–40). Division is unlocked after the player completes level 4 subtraction (0–40) – (0–40) and level 2 multiplication (1–7) × (0–7).

I don’t have any science behind these choices, only a feeling that a familiarity with one operation is required before introduction to another.

Questions to be Answered

  • Is the progression of difficulty appropriate?
  • Is it the best method to facilitate learning and retention?
  • Is the 5-second-average a good measure of automaticity?
  • Are the other operations made available at an appropriate time?

If anyone with an opinion is reading this, I’d love to hear your thoughts on these questions or my code.


The latest demo of the game is hosted on GitHub and is available here. Feel free to check out the repository as well.