Building a C# Console App for Practising Basic Arithmetic

Building a C# Console App for Practising Basic Arithmetic

This post showcases the development of Arithmetic Trainer from zero to a polished initial version.

Arithmetic Trainer is a simple console-based app for practising basic arithmetic. Here is an example of a typical practice session:

Starting practice on Multiplication in range [2, 99]. Press q to stop.
23 * 3 = ?
69
Correct
2 * 2 = ?
4
Correct
4 * 16 = ?
q

Inspired by Math Game project from C# Academy, this app adopts a more focused name: "Arithmetic" over "Math" and "Trainer" over "Game" to emphasise its role as a training tool rather than a game.

The implementation proceeds in three stages:

  • Start Simple - in which basic functionality is put together.
  • Make It Last - in which procedural code is refactored into object-oriented and vague concepts are made explicit.
  • Polish the Look - in which the user interface is enhanced with the help of the Spectre.Console library.

The complete project is available on GitHub here.

Start Simple

Addition

The first commit implements a simple practice loop for addition:

Random random = new();

while (true)
{
    int result = random.Next(4, 100);
    int x = random.Next(2, result - 1);
    int y = result - x;
    Console.WriteLine($"{x} + {y} = ?");
    string input = Console.ReadLine() ?? "";
    if (input != result.ToString())
    {
        Console.WriteLine($"Wrong, the answer is {result}");
    }
    else
    {
        Console.WriteLine("Correct");
    }
}

The biggest challenge in implementing Arithmetic Trainer is question generation. The algorithm above makes each answer appear equally likely. As a consequence, the question 2 + 2 = ? is presented on average as often as all question of the form x + y = 99 together.

Possible alternatives include:

  • Generating x and y uniformly in [2, 99] and letting the sum exceed 100
  • Making equations rather than answers appear equally likely.
  • Generating x uniformly in [2, 97] and y uniformly in [2, 99-x]

However, since this section is called "Start Simple", it is not the time to nitpick.

Subtraction

The second commit adds subtraction, along with a menu to choose an operation to practice and an option to quit practice.

Each practice loop now resides in its own method: DoAddition and DoSubtraction, while the main loop is used to select practice.

Here is the subtraction questions generation algorithm:

int result = random.Next(2, 98);
int x = random.Next(result + 2, 100);
int y = x - result;

Here too, the algorithm ensures equal representation for each possible answer. In this case, the equation 99 - 2 = 97 becomes equally likely to all of the form x - y = 4 together.

Division

The third commit adds division, which is implemented identically to addition and subtraction.

int result = random.Next(2, 50);
int y = random.Next(2, 99 / result + 1);
int x = result * y;

The expression 99 / result + 1 ensures that x is at most 99.

As a result of making each answer equally likely, majority of questions are of the form x / 2 = ?.

Multiplication

The fourth commit adds multiplication.

Generating multiplication questions presents a unique challenge: because the product of two integers greater than one cannot be a prime, the algorithm must retry whenever it selects a prime as the answer:

while (true)
{
	int product = Random.Next(4, 100);
	List<int> divisors = [];
	for (int d = 2; d <= product / 2; d++)
	{
		if (product % d == 0)
		{
			divisors.Add(d);
		}
	}
	if (divisors.Count == 0)
	{
		continue;
	}
	int x = divisors[Random.Next(0, divisors.Count)];
	int y = product / x;
	return (x, y, product);
}

History

The fifth commit adds practice history, which logs all user attempts.

Here is how it looks:

39 + 46 = ? Input: 85 Outcome: Correct
22 + 22 = ? Input: 44 Outcome: Correct
98 - 2 = ? Input: 96 Outcome: Correct
51 - 7 = ? Input: 44 Outcome: Correct
21 * 3 = ? Input: 63 Outcome: Correct
6 * 14 = ? Input: 74 Outcome: Wrong, the answer is 84
78 / 3 = ? Input: 18 Outcome: Wrong, the answer is 26
64 / 2 = ? Input: 37 Outcome: Wrong, the answer is 32

Results

The result is an app that meets all the Math Game requirements: it supports all four basic arithmetic operations, works with one- and two-digit positive integers, includes a menu, and stores a history of attempts.

The source code takes only 150 lines and resides in a single file.

Make It Last

Now that the basic functionality has been put together, it's time to organise it and make it extensible.

Here is the proposed terminology:

  • A question and answer pair make a problem.
  • A problem plus the user's response make an attempt.
  • An attempt has an outcome, which indicates whether the user’s response was correct or incorrect.
  • History is a list of attempts.
  • Practice is a conversation between the program and the user in the form of question-response-outcome loop.

The terminology is chosen more or less arbitrarily. It is consistent only within the context of this project.

The sixth commit makes this terminology explicit by modelling it with records and their properties.

The seventh commit introduces problem generators, allowing the four specific practice loops to be combined into a single, generic solution.

Problem generators also take advantage of IEnumerable<Problem> to implement a neat loop like this:

foreach (Problem problem in problemGenerator.Generate())

The reward for all this work is the MixedProblemGenerator class, implemented in the eighth commit. This class combines multiple problem generators, randomly selecting one at each step to generate the next question:

public sealed class MixedProblemGenerator(params ProblemGenerator[] problemGenerators) : ProblemGenerator
{
    public override string Description => "Mixed problems";
    public override Problem Next()
    {
        return problemGenerators[Random.Next(problemGenerators.Length)].Next();
    }
}

The result is the practice mode that combines all four operations:

Starting practice on Mixed problems. Press q to stop.
54 - 12 = ?
42
Correct
12 / 2 = ?
6
Correct
88 - 2 = ?
86
Correct
91 / 13 = ?
7
Correct
2 * 10 = ?
30
Incorrect. The answer is 20
78 - 32 = ?
q

Polish the Look

Designing the user interface presents the second biggest challenge in developing the Arithmetic Trainer project, surpassed only by the complexities of problem generation.

The introduction of a dedicated UI library, Spectre.Console, offers two improvements.

The first is Selection prompts, implemented in the ninth commit, which enable the user to pick rather than type commands, eliminating non-sensical input.

Before (type the appropriate letter and press enter):

Welcome to Arithmetic Trainer
Pick operation: +, -, *, / to start a new practice
Press a to start mixed practice
Press h to view practice history
Press q to quit

After (navigate between options with up and down keys and press enter to confirm):

Welcome to Arithmetic Trainer
Select action:          
                        
> Start practice        
  View practice history 
  Quit           
Pick practice mode:  
                     
  Addition (+)       
  Subtraction (-)    
  Multiplication (*) 
  Division (/)       
> All Together       
  Cancel      

The second is Tables, introduced in the tenth commit, which make the history table better organised.

Before:

50 + 14 = ? Response: 64 Outcome: Correct
4 * 22 = ? Response: 88 Outcome: Correct
92 - 25 = ? Response: 67 Outcome: Correct
91 - 38 = ? Response: 54 Outcome: Incorrect. The answer is 53
98 - 10 = ? Response: 88 Outcome: Correct
11 + 5 = ? Response: 16 Outcome: Correct
74 / 2 = ? Response: 37 Outcome: Correct
80 - 58 = ? Response: 30 Outcome: Incorrect. The answer is 22
37 + 2 = ? Response: 39 Outcome: Correct

After:

┌─────────────┬──────────┬─────────────────────────────┐
│ Question    │ Response │ Outcome                     │
├─────────────┼──────────┼─────────────────────────────┤
│ 96 - 25 = ? │ 71       │ Correct                     │
│ 61 + 9 = ?  │ 79       │ Incorrect. The answer is 70 │
│ 35 + 61 = ? │ 965      │ Incorrect. The answer is 96 │
│ 45 / 3 = ?  │ 14       │ Incorrect. The answer is 15 │
│ 99 - 50 = ? │ 49       │ Correct                     │
│ 89 - 26 = ? │ 63       │ Correct                     │
│ 91 - 54 = ? │ 27       │ Incorrect. The answer is 37 │
│ 97 - 4 = ?  │ 93       │ Correct                     │
│ 54 / 2 = ?  │ 27       │ Correct                     │
└─────────────┴──────────┴─────────────────────────────┘

Conclusion

The result is a fully working arithmetic practice app. Further improvements could include the addition of features like difficulty levels and a timer, as suggested in the challenge ideas of the Math Game project.

Read more