6
\$\begingroup\$

I made a C# console math project where the user answers addition, subtraction, multiplication, division, power or square-root questions based on the difficulty they choose!

However, I am struggling to refactor one part of my code.

Here is my code:

using System;
using System.Collections.Generic;

namespace mathstester
{
    class Program
    {
        public enum UserDifficulty
        {
            Easy,
            Normal,
            Hard
        }

        public enum MathOperation
        {
            Addition = 1,
            Subtraction = 2,
            Multiplication = 3,
            Division = 4,
            Power = 5,
            SquareRoot = 6
        }
        public static (int operationMin, int operationMax) GetPossibleOperationsByDifficulty(UserDifficulty userDifficulty)
        {

            switch (userDifficulty)
            {
                case UserDifficulty.Easy:
                    return (1, 4);
                case UserDifficulty.Normal:
                    return (1, 5);
                case UserDifficulty.Hard:
                    return (3, 7);
                default:
                    throw new Exception();
            }
        }
        public static (string message, double correctAnswer) GetMathsEquation(MathOperation mathOperation, UserDifficulty userDifficulty)
        {

            int number1;
            int number2;
            Random randomNumber = new Random();

            switch (mathOperation)
            {
                case MathOperation.Addition:
                    number1 = randomNumber.Next(1000);
                    number2 = randomNumber.Next(1000);
                    return ($"{number1} + {number2}", number1 + number2);
                case MathOperation.Subtraction:
                    number1 = randomNumber.Next(1000);
                    number2 = randomNumber.Next(1000);
                    return ($"{number1} - {number2}", number1 - number2);
                case MathOperation.Multiplication:
                    number1 = userDifficulty == UserDifficulty.Easy ? randomNumber.Next(13) : randomNumber.Next(1000);
                    number2 = userDifficulty == UserDifficulty.Easy ? randomNumber.Next(13) : randomNumber.Next(1000);
                    return ($"{number1} * {number2}", number1 * number2);
                case MathOperation.Division:
                    number1 = randomNumber.Next(10000);
                    number2 = randomNumber.Next(1000);
                    return ($"{number1} / {number2}", number1 / (double)number2);
                case MathOperation.Power:
                    number1 = randomNumber.Next(13);
                    number2 = randomNumber.Next(5);
                    return ($"{number1} ^ {number2}", Math.Pow(number1, number2));
                case MathOperation.SquareRoot:
                    number1 = randomNumber.Next(1000);
                    return ($"√{number1}", Math.Sqrt(number1));
                default:
                    throw new Exception();
            }
        }

        public class OperationQuestionScore
        {
            public int AdditionQuestion { get; set; }
            public int AdditionScore { get; set; }
            public int SubtractionQuestion { get; set; }
            public int SubtractionScore { get; set; }
            public int MultiplicationQuestion { get; set; }
            public int MultiplicationScore { get; set; }
            public int DivisionQuestion { get; set; }
            public int DivisionScore { get; set; }
            public int PowerQuestion { get; set; }
            public int PowerScore { get; set; }
            public int SquareRootQuestion { get; set; }
            public int SquareRootScore { get; set; }
        }

        public static OperationQuestionScore Score()
        {
            return new OperationQuestionScore();
        }

        public static (int, OperationQuestionScore, OperationQuestionScore) RunTest(int numberOfQuestionsLeft, UserDifficulty userDifficulty)
        {
            int totalScore = 0;
            Random random = new Random();
            var (operationMin, operationMax) = GetPossibleOperationsByDifficulty(userDifficulty);
            var score = Score();
            var question = Score();
            while (numberOfQuestionsLeft > 0)
            {
                int mathRandomOperation = random.Next(operationMin, operationMax);
                MathOperation mathOperation = (MathOperation)mathRandomOperation;
                var (message, correctAnswer) = GetMathsEquation(mathOperation, userDifficulty);
                if (mathRandomOperation == 4 || mathRandomOperation == 6)
                {
                    Console.Write($"To the nearest integer, What is {message} =");
                }
                else
                {
                    Console.Write($"What is {message} =");
                }
                double userAnswer = Convert.ToDouble(Console.ReadLine());
                if (Math.Round(correctAnswer) == userAnswer)
                {
                    Console.WriteLine("Well Done!");
                    switch (mathOperation)
                    {
                        case MathOperation.Addition:
                            question.AdditionQuestion++;
                            score.AdditionScore++;
                            break;
                        case MathOperation.Subtraction:
                            question.SubtractionQuestion++;
                            score.SubtractionScore++;
                            break;
                        case MathOperation.Multiplication:
                            question.MultiplicationQuestion++;
                            score.MultiplicationScore++;
                            break;
                        case MathOperation.Division:
                            question.DivisionQuestion++;
                            score.DivisionScore++;
                            break;
                        case MathOperation.Power:
                            question.PowerQuestion++;
                            score.PowerScore++;
                            break;
                        case MathOperation.SquareRoot:
                            question.SquareRootQuestion++;
                            score.SquareRootScore++;
                            break;
                    }
                    totalScore++;
                }
                else
                {
                    Console.WriteLine("Your answer is incorrect!");
                    switch (mathOperation)
                    {
                        case MathOperation.Addition:
                            question.AdditionQuestion++;
                            break;
                        case MathOperation.Subtraction:
                            question.SubtractionQuestion++;
                            break;
                        case MathOperation.Multiplication:
                            question.MultiplicationQuestion++;
                            break;
                        case MathOperation.Division:
                            question.DivisionQuestion++;
                            break;
                        case MathOperation.Power:
                            question.PowerQuestion++;
                            break;
                        case MathOperation.SquareRoot:
                            question.SquareRootQuestion++;
                            break;
                    }
                }
                numberOfQuestionsLeft--;
            }
            return (totalScore, score, question);
        }
        public static void Main(string[] args)
        {
            Dictionary<string, UserDifficulty> difficultyDictionary = new Dictionary<string, UserDifficulty>();
            difficultyDictionary.Add("E", UserDifficulty.Easy);
            difficultyDictionary.Add("N", UserDifficulty.Normal);
            difficultyDictionary.Add("H", UserDifficulty.Hard);

            string userInputDifficulty = "";
            do
            {
                Console.WriteLine("What difficulty level would you like to do! Please type E for Easy, N for Normal and H for hard");
                userInputDifficulty = Console.ReadLine().ToUpper();
            } while (userInputDifficulty != "E" && userInputDifficulty != "N" && userInputDifficulty != "H");

            UserDifficulty userDifficulty = difficultyDictionary[userInputDifficulty];

            int numberOfQuestions = 0;
            do
            {
                Console.WriteLine("How many questions would you like to answer? Please type a number divisible by 10!");
                int.TryParse(Console.ReadLine(), out numberOfQuestions);
            } while (numberOfQuestions % 10 != 0);

            var (totalScore, score, question) = RunTest(numberOfQuestions, userDifficulty);
            Console.WriteLine($"Total score: {totalScore} of {numberOfQuestions}");

            if (userDifficulty == UserDifficulty.Easy)
            {
                Console.WriteLine($"Addition score: {score.AdditionScore} of {question.AdditionQuestion}");
                Console.WriteLine($"Subtraction score: {score.SubtractionScore} of {question.SubtractionQuestion}");
                Console.WriteLine($"Multiplication score: {score.MultiplicationScore} of {question.MultiplicationQuestion}");
            }
            else if (userDifficulty == UserDifficulty.Normal)
            {
                Console.WriteLine($"Addition score: {score.AdditionScore} of {question.AdditionQuestion}");
                Console.WriteLine($"Subtraction score: {score.SubtractionScore} of {question.SubtractionQuestion}");
                Console.WriteLine($"Multiplication score: {score.MultiplicationScore} of {question.MultiplicationQuestion}");
                Console.WriteLine($"Division score: {score.DivisionScore} of {question.DivisionQuestion}");
            }
            else if (userDifficulty == UserDifficulty.Hard)
            {
                Console.WriteLine($"Multipication score: {score.MultiplicationScore} of {question.MultiplicationQuestion}");
                Console.WriteLine($"Division score: {score.DivisionScore} of {question.DivisionQuestion}");
                Console.WriteLine($"Power score: {score.PowerScore} of {question.PowerQuestion}");
                Console.WriteLine($"Squareroot score: {score.SquareRootScore} of {question.SquareRootQuestion}");
            }
        }
    }
}

In my "RunTest" function I've got two "switch" statements inside an "if-else" statement. Without changing my code too much, I want to shorten the code by using only one "switch" statement inside my "if-else" statement.

I need help refactoring my code so I don't have so many loops inside a loop.

\$\endgroup\$

1 Answer 1

7
+50
\$\begingroup\$

One way that you can simplify much of this code is by building out a set of classes to represent your different operations. You can then encapsulate the rules for that operation within those classes.

To get started, you can define an abstract Operation class to define the shared properties and methods of the operations:

public abstract class Operation
{
    protected readonly Random Random = new Random();

    private readonly string _operationName;

    public Operation(string operationName, params UserDifficulty[] userDifficulties)
    {
        _operationName = operationName;
        UserDifficulties = userDifficulties;
    }

    public int Question { get; set; }
    public int Score { get; set; }
    public UserDifficulty[] UserDifficulties { get; }
    public string GetScoreDisplay() => $"{_operationName} score: {Score} of {Question}";
    public abstract (string message, double correctAnswer) GetMathsEquation(UserDifficulty userDifficulty);
}

You can then create a class for each of your different operations, and implement the base class accordingly. For example, Addition would look like:

public class Addition : Operation
{
    public Addition() : base("Addition", UserDifficulty.Easy, UserDifficulty.Normal)
    {
    }

    public override (string message, double correctAnswer) GetMathsEquation(UserDifficulty userDifficulty)
    {
        var number1 = Random.Next(1000);
        var number2 = Random.Next(1000);
        return ($"{number1} + {number2}", number1 + number2);
    }
}

With these all in place, you can actually remove every switch statement in your code. This is because the specific operation logic is implemented in each class. All you need to do is construct a collection of the Operation objects and then call the appropriate methods.

The RunTest would now look as follows:

public static (int, List<Operation>) RunTest(int numberOfQuestionsLeft, UserDifficulty userDifficulty)
{
    int totalScore = 0;
    Random random = new Random();
    var operations = new List<Operation>
    {
        new Addition(),
        new Subtraction(),
        new Multiplication(),
        new Division(),
        new Power(),
        new SquareRoot(),
    }.Where(o => o.UserDifficulties.Contains(userDifficulty)).ToList();

    while (numberOfQuestionsLeft > 0)
    {
        int randomOperation = random.Next(operations.Count);
        Operation operation = operations[randomOperation];
        var (message, correctAnswer) = operation.GetMathsEquation(userDifficulty);
        if (operation is Division || operation is SquareRoot)
        {
            Console.Write($"To the nearest integer, What is {message} =");
        }
        else
        {
            Console.Write($"What is {message} =");
        }
        double userAnswer = Convert.ToDouble(Console.ReadLine());
        if (Math.Round(correctAnswer) == userAnswer)
        {
            Console.WriteLine("Well Done!");
            operation.Score++;
            totalScore++;
        }
        else
        {
            Console.WriteLine("Your answer is incorrect!");
        }
        operation.Question++;
        numberOfQuestionsLeft--;
    }
    return (totalScore, operations);
}

And the usage of that method would look like:

var (totalScore, operations) = RunTest(numberOfQuestions, userDifficulty);
Console.WriteLine($"Total score: {totalScore} of {numberOfQuestions}");

foreach (var operation in operations)
{
    Console.WriteLine(operation.GetScoreDisplay());
}

Here's another option that achieves your goal of reducing the number of switch statements in the RunTest method from two to one with fewer overall code changes:

public static (int, OperationQuestionScore, OperationQuestionScore) RunTest(int numberOfQuestionsLeft, UserDifficulty userDifficulty)
{
    int totalScore = 0;
    Random random = new Random();
    var (operationMin, operationMax) = GetPossibleOperationsByDifficulty(userDifficulty);
    var score = Score();
    var question = Score();

    while (numberOfQuestionsLeft > 0)
    {
        int mathRandomOperation = random.Next(operationMin, operationMax);
        MathOperation mathOperation = (MathOperation)mathRandomOperation;
        var (message, correctAnswer) = GetMathsEquation(mathOperation, userDifficulty);
        if (mathRandomOperation == 4 || mathRandomOperation == 6)
        {
            Console.Write($"To the nearest integer, What is {message} =");
        }
        else
        {
            Console.Write($"What is {message} =");
        }

        Action<OperationQuestionScore> incrementQuestion;
        Action<OperationQuestionScore> incrementScore;
        switch (mathOperation)
        {
            case MathOperation.Addition:
                incrementQuestion = o => o.AdditionQuestion++;
                incrementScore = o => o.AdditionScore++;
                break;
            case MathOperation.Subtraction:
                incrementQuestion = o => o.SubtractionQuestion++;
                incrementScore = o => o.SubtractionScore++;
                break;
            case MathOperation.Multiplication:
                incrementQuestion = o => o.MultiplicationQuestion++;
                incrementScore = o => o.MultiplicationScore++;
                break;
            case MathOperation.Division:
                incrementQuestion = o => o.DivisionQuestion++;
                incrementScore = o => o.DivisionScore++;
                break;
            case MathOperation.Power:
                incrementQuestion = o => o.PowerQuestion++;
                incrementScore = o => o.PowerScore++;
                break;
            case MathOperation.SquareRoot:
                incrementQuestion = o => o.SquareRootQuestion++;
                incrementScore = o => o.SquareRootScore++;
                break;
            default:
                incrementQuestion = _ => { };
                incrementScore = _ => { };
                break;
        }

        double userAnswer = Convert.ToDouble(Console.ReadLine());
        if (Math.Round(correctAnswer) == userAnswer)
        {
            Console.WriteLine("Well Done!");
            incrementQuestion(question);
            incrementScore(score);
            totalScore++;
        }
        else
        {
            Console.WriteLine("Your answer is incorrect!");
            incrementQuestion(question);
        }
        numberOfQuestionsLeft--;
    }
    return (totalScore, score, question);
}

As you can see, a single switch statement now exists before the if/else block. And two Action delegates are being created within the switch statement to represent the proper "increment" operation needed in the if/else block.

\$\endgroup\$
2
  • \$\begingroup\$ Thanks @devNull But Is there a way so I don't need to change my code so much? \$\endgroup\$
    – qqqqqkks
    Apr 1, 2020 at 23:01
  • 1
    \$\begingroup\$ @crazydanyal1414 I've edited my answer to include an additional option that only requires changing the code within the RunTest method \$\endgroup\$
    – devNull
    Apr 2, 2020 at 1:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.