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
andy
uniformly in[2, 99]
and letting the sum exceed100
- Making equations rather than answers appear equally likely.
- Generating
x
uniformly in[2, 97]
andy
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.