diff --git a/Calculator.ivangar/Calculator.ivangar.slnx b/Calculator.ivangar/Calculator.ivangar.slnx new file mode 100644 index 00000000..34bb6a99 --- /dev/null +++ b/Calculator.ivangar/Calculator.ivangar.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/Calculator.ivangar/Calculator.ivangar/Application.cs b/Calculator.ivangar/Calculator.ivangar/Application.cs new file mode 100644 index 00000000..5a497bc3 --- /dev/null +++ b/Calculator.ivangar/Calculator.ivangar/Application.cs @@ -0,0 +1,159 @@ +using Calculator.ivangar.Enums; +using CalculatorLibrary; +using CalculatorLibrary.Enums; + +namespace Calculator.ivangar +{ + public class Application + { + private bool EndApp = false; + private readonly CalculatorEngine Calculator = new(); + + private readonly Random _random = new(); + + public void Run() + { + Menu.Intro(); + Menu.PrintMenu(); + + string? optionInput = Console.ReadLine(); + MainMenuOptions menuOption; + + while (!EndApp) + { + while (!Menu.TryGetMenuOption(optionInput, out menuOption)) + { + Menu.PrintMenu(invalid: true); + optionInput = Console.ReadLine(); + } + + switch (menuOption) + { + case MainMenuOptions.PerformOperation: + PerformCalculation(); + break; + case MainMenuOptions.ViewOperations: + Calculator.PrintAllOperations(); + break; + case MainMenuOptions.DeleteOperations: + Calculator.DeleteOperations(); + break; + case MainMenuOptions.Exit: + EndApp = true; + Console.WriteLine("\nThank you for using the Calculator."); + break; + } + + if (EndApp) + break; + + Menu.PrintMenu(); + optionInput = Console.ReadLine(); + } + + Calculator.Finish(); + } + + public void PerformCalculation() + { + // Ask the user to choose an operator. + Menu.PrintOperations(); + string? op = Console.ReadLine(); + + while (!Menu.ValidateOperationChoice(op)) + { + Menu.PrintOperations(invalid: true); + op = Console.ReadLine(); + } + + switch (op!.ToLower()) + { + case "r": + SquareRootOperation(op); + break; + case "p": + PowerOperation(op); + break; + case "t": + TrigonometryOperation(op); + break; + default: + BaseMathOperation(op); + break; + } + } + + public void BaseMathOperation(string operation) + { + double operand1 = Menu.GetOperand("Type a number, and then press Enter: "); + double operand2 = Menu.GetOperand("Type another number, and then press Enter: "); + + try + { + var result = Calculator.DoOperation(operand1, operation, operand2); + + if (double.IsNaN(result)) + Console.WriteLine("This operation will result in a mathematical error.\n"); + } + catch (Exception e) + { + Console.WriteLine("Oh no! An exception occurred trying to do the math.\n - Details: " + e.Message); + } + } + + public void SquareRootOperation(string operation) + { + var radicand = Menu.GetOperand("Type the radicand, and then press Enter: "); + + try + { + var result = Calculator.DoOperation(radicand, operation); + + if (double.IsNaN(result)) + Console.WriteLine("This operation will result in a mathematical error.\n"); + } + catch (Exception e) + { + Console.WriteLine("Oh no! An exception occurred trying to do the math.\n - Details: " + e.Message); + } + } + + public void PowerOperation(string operation) + { + double baseOperand = Menu.GetOperand("Type the base, and then press Enter: "); + double exponent = Menu.GetOperand("Type the exponent, and then press Enter: "); + + try + { + var result = Calculator.DoOperation(baseOperand, operation, exponent); + + if (double.IsNaN(result)) + Console.WriteLine("This operation will result in a mathematical error.\n"); + } + catch (Exception e) + { + Console.WriteLine("Oh no! An exception occurred trying to do the math.\n - Details: " + e.Message); + } + } + + public void TrigonometryOperation(string operation) + { + double degrees = Menu.GetOperand("Enter an angle in degrees°: "); + var functionValues = Enum.GetValues(); + var randomFunction = _random.Next(functionValues.Length); + var function = functionValues[randomFunction]; + + try + { + var result = Calculator.DoTrigonometryOperation(degrees, function, operation); + + if (double.IsNaN(result)) + Console.WriteLine("This operation will result in a mathematical error.\n"); + } + catch (Exception e) + { + Console.WriteLine("Oh no! An exception occurred trying to do the math.\n - Details: " + e.Message); + } + } + } +} diff --git a/Calculator.ivangar/Calculator.ivangar/Calculator.ivangar.csproj b/Calculator.ivangar/Calculator.ivangar/Calculator.ivangar.csproj new file mode 100644 index 00000000..8acb7da5 --- /dev/null +++ b/Calculator.ivangar/Calculator.ivangar/Calculator.ivangar.csproj @@ -0,0 +1,14 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + diff --git a/Calculator.ivangar/Calculator.ivangar/Enums/MainMenuOptions.cs b/Calculator.ivangar/Calculator.ivangar/Enums/MainMenuOptions.cs new file mode 100644 index 00000000..4ea27ffd --- /dev/null +++ b/Calculator.ivangar/Calculator.ivangar/Enums/MainMenuOptions.cs @@ -0,0 +1,10 @@ +namespace Calculator.ivangar.Enums +{ + public enum MainMenuOptions + { + PerformOperation = 1, + ViewOperations = 2, + DeleteOperations = 3, + Exit = 4, + } +} diff --git a/Calculator.ivangar/Calculator.ivangar/Menu.cs b/Calculator.ivangar/Calculator.ivangar/Menu.cs new file mode 100644 index 00000000..1c44e2d6 --- /dev/null +++ b/Calculator.ivangar/Calculator.ivangar/Menu.cs @@ -0,0 +1,86 @@ +using Calculator.ivangar.Enums; + +namespace Calculator.ivangar +{ + public static class Menu + { + private static readonly Dictionary _operations = new() + { + { 'a', "Add"}, + { 's', "Subtract"}, + { 'm', "Multiply"}, + { 'd', "Divide"}, + { 'r', "Square Root"}, + { 'p', "Power"}, + { 't', "Trigonometry function"}, + }; + + public static void Intro() + { + Console.WriteLine("Console Calculator in C#\r"); + Console.WriteLine("------------------------\n"); + } + + public static void PrintMenu(bool invalid = false) + { + if (invalid) + Console.WriteLine("\nInvalid input."); + + Console.WriteLine("\nPlease choose any of the following options (you have to type the number, i.e. 2):\n"); + + foreach (MainMenuOptions option in Enum.GetValues(typeof(MainMenuOptions))) + Console.WriteLine($"\t{(int)option}. {option}"); + } + + public static bool TryGetMenuOption(string? input, out MainMenuOptions menuItem) + { + menuItem = default; + + if (!int.TryParse(input, out int optionNumber)) + return false; + + if (!Enum.IsDefined(typeof(MainMenuOptions), optionNumber)) + return false; + + menuItem = (MainMenuOptions)optionNumber; + return true; + } + + public static void PrintOperations(bool invalid = false) + { + if (invalid) + Console.WriteLine("\nError: Unrecognized input."); + + Console.WriteLine("\nChoose an operation from the following list (you have to type the letter, i.e. 'a'):\n"); + + foreach (var (key, op) in _operations) + Console.WriteLine($"\t{key} - {op}"); + + Console.Write("Your option? "); + } + + public static double GetOperand(string message) + { + double cleanNum1; + + Console.Write(message); + string? numInput1 = Console.ReadLine(); + + while (!double.TryParse(numInput1, out cleanNum1)) + { + Console.Write("This is not valid input. Please enter a numeric value: "); + numInput1 = Console.ReadLine(); + } + + return cleanNum1; + } + + public static bool ValidateOperationChoice(string? option) + { + if (string.IsNullOrEmpty(option) || string.IsNullOrWhiteSpace(option) || option.Length != 1) + return false; + + return _operations.ContainsKey(char.ToLower(option[0])); + } + } +} diff --git a/Calculator.ivangar/Calculator.ivangar/Program.cs b/Calculator.ivangar/Calculator.ivangar/Program.cs new file mode 100644 index 00000000..5f9364cb --- /dev/null +++ b/Calculator.ivangar/Calculator.ivangar/Program.cs @@ -0,0 +1,4 @@ +using Calculator.ivangar; + +var application = new Application(); +application.Run(); \ No newline at end of file diff --git a/Calculator.ivangar/CalculatorLibrary/CalculatorLibrary.cs b/Calculator.ivangar/CalculatorLibrary/CalculatorLibrary.cs new file mode 100644 index 00000000..990b9693 --- /dev/null +++ b/Calculator.ivangar/CalculatorLibrary/CalculatorLibrary.cs @@ -0,0 +1,123 @@ +using CalculatorLibrary.Enums; +using CalculatorLibrary.Logging; + +namespace CalculatorLibrary +{ + public class CalculatorEngine + { + private readonly CalculatorLogWriter _log; + private readonly OperationRepository _operationRepository; + + public CalculatorEngine() + { + _log = new CalculatorLogWriter(); + _operationRepository = new OperationRepository(); + } + + public double DoOperation(double num1, string op, double num2 = 0) + { + double result = double.NaN; + char operation = default; + bool successfullOperation = true; + + switch (op) + { + case "a": + operation = '+'; + result = num1 + num2; + var sum = OperationFactory.CreateBasic(num1, num2, operation, result); + _operationRepository.Add(sum); + break; + case "s": + operation = '-'; + result = num1 - num2; + var subtraction = OperationFactory.CreateBasic(num1, num2, operation, result); + _operationRepository.Add(subtraction); + break; + case "m": + operation = '*'; + result = num1 * num2; + var multiplication = OperationFactory.CreateBasic(num1, num2, operation, result); + _operationRepository.Add(multiplication); + break; + case "d": + operation = '/'; + if (CalculatorMath.TryDivide(num1, num2, out result)) + { + var division = OperationFactory.CreateBasic(num1, num2, operation, result); + _operationRepository.Add(division); + } + else + successfullOperation = false; + break; + case "r": + operation = '√'; + if (CalculatorMath.TryCalculateSquareRoot(num1, out result)) + { + var squareRoot = OperationFactory.CreateSquareRoot(num1, operation, result); + _operationRepository.Add(squareRoot); + } + else + successfullOperation = false; + break; + case "p": + operation = '^'; + if (CalculatorMath.TryCalculateExponent(num1, num2, out result)) + { + var power = OperationFactory.CreateExponentiation(num1, num2, operation, result); + _operationRepository.Add(power); + } + else + successfullOperation = false; + break; + default: + Console.WriteLine("This operation does not exist"); + successfullOperation = false; + break; + } + + if (successfullOperation) + { + if (operation == '√') + _log.WriteOperation(num1, operation, result); + else + _log.WriteOperation(num1, num2, operation, result); + } + + return result; + } + + public double DoTrigonometryOperation(double degrees, TrigonometryFunctions function, string op) + { + double result = double.NaN; + char operation = '°'; + + if (CalculatorMath.TryCalculateTrigonometry(degrees, function, out result)) + { + _log.WriteTrigOperation(degrees, operation, function, result); + var trigOperation = OperationFactory.CreateTrigonometry(degrees, function, operation, result); + _operationRepository.Add(trigOperation); + } + + return result; + } + + public void Finish() + { + _log.Dispose(); + _operationRepository.PrintAll(); + } + + public void PrintAllOperations() + { + _operationRepository.PrintAll(); + } + + public void DeleteOperations() + { + Console.WriteLine("\nDeleting all current operations:\n"); + _operationRepository.Clear(); + _log.DeleteOperations(); + } + } +} diff --git a/Calculator.ivangar/CalculatorLibrary/CalculatorLibrary.csproj b/Calculator.ivangar/CalculatorLibrary/CalculatorLibrary.csproj new file mode 100644 index 00000000..b7601447 --- /dev/null +++ b/Calculator.ivangar/CalculatorLibrary/CalculatorLibrary.csproj @@ -0,0 +1,9 @@ + + + + net10.0 + enable + enable + + + diff --git a/Calculator.ivangar/CalculatorLibrary/CalculatorMath.cs b/Calculator.ivangar/CalculatorLibrary/CalculatorMath.cs new file mode 100644 index 00000000..c15f9b38 --- /dev/null +++ b/Calculator.ivangar/CalculatorLibrary/CalculatorMath.cs @@ -0,0 +1,67 @@ +using CalculatorLibrary.Enums; + +namespace CalculatorLibrary +{ + public static class CalculatorMath + { + public static bool TryDivide(double a, double b, out double result) + { + if (b == 0) + { + Console.WriteLine("Cannot divide by zero."); + result = default; + return false; + } + result = a / b; + return true; + } + + public static bool TryCalculateSquareRoot(double radicand, out double result) + { + if (radicand < 0) + { + Console.WriteLine("Cannot calculate square root of a negative number."); + result = default; + return false; + } + result = Math.Round(Math.Sqrt(radicand), 2, MidpointRounding.AwayFromZero); + return true; + } + + public static bool TryCalculateExponent(double baseNumber, double exponent, out double result) + { + result = Math.Pow(baseNumber, exponent); + + if (double.IsNaN(result) || double.IsInfinity(result)) + { + Console.WriteLine("The exponent is very big, try a smaller number"); + result = double.NaN; + return false; + } + + return true; + } + + public static bool TryCalculateTrigonometry(double degrees, TrigonometryFunctions function, out double result) + { + if (degrees > 360 || degrees < -360) + { + Console.WriteLine("The degree value is outside of this scope, try a smaller number"); + result = double.NaN; + return false; + } + + var radians = degrees * Math.PI / 180.0; + + result = function switch + { + TrigonometryFunctions.Sin => Math.Round(Math.Sin(radians), 4), + TrigonometryFunctions.Cos => Math.Round(Math.Cos(radians), 4), + TrigonometryFunctions.Tan => Math.Round(Math.Tan(radians), 4), + _ => double.NaN + }; + + return !double.IsNaN(result); + } + } +} diff --git a/Calculator.ivangar/CalculatorLibrary/Enums/TrigonometryFunctions.cs b/Calculator.ivangar/CalculatorLibrary/Enums/TrigonometryFunctions.cs new file mode 100644 index 00000000..e89be574 --- /dev/null +++ b/Calculator.ivangar/CalculatorLibrary/Enums/TrigonometryFunctions.cs @@ -0,0 +1,9 @@ +namespace CalculatorLibrary.Enums +{ + public enum TrigonometryFunctions + { + Sin, + Cos, + Tan + } +} diff --git a/Calculator.ivangar/CalculatorLibrary/Logging/CalculatorLogWriter.cs b/Calculator.ivangar/CalculatorLibrary/Logging/CalculatorLogWriter.cs new file mode 100644 index 00000000..ff618bbd --- /dev/null +++ b/Calculator.ivangar/CalculatorLibrary/Logging/CalculatorLogWriter.cs @@ -0,0 +1,128 @@ +using CalculatorLibrary.Enums; +using System.Text.Encodings.Web; +using System.Text.Json; + +namespace CalculatorLibrary.Logging +{ + public class CalculatorLogWriter : IDisposable + { + private Utf8JsonWriter _writer; + private FileStream _stream; + private readonly string _filePath; + + public CalculatorLogWriter(string filePath = "calculator.json") + { + _filePath = filePath; + InitializeWriter(); + } + + public void WriteOperation(double operand1, double operand2, char symbol, double result) + { + _writer.WriteStartObject(); + _writer.WriteNumber("Operand1", operand1); + _writer.WriteNumber("Operand2", operand2); + _writer.WriteString("Operation", symbol.ToString()); + WriteResult(result); + } + + public void WriteOperation(double operand1, char symbol, double result) + { + _writer.WriteStartObject(); + _writer.WriteNumber("Operand1", operand1); + _writer.WriteString("Operation", symbol.ToString()); + WriteResult(result); + } + + public void WriteTrigOperation(double operand1, char symbol, TrigonometryFunctions function, double result) + { + _writer.WriteStartObject(); + _writer.WriteNumber("Operand1", operand1); + _writer.WriteString("Operation", $"{function}{symbol}"); + WriteResult(result); + } + + public void WriteResult(double result) + { + _writer.WriteNumber("Result", result); + _writer.WriteEndObject(); + } + + public void DeleteOperations() + { + // Close writer AND stream + Dispose(); + _stream.Dispose(); + + string json = File.ReadAllText(_filePath); + + using var doc = JsonDocument.Parse(json); + var root = doc.RootElement; + + if (!root.TryGetProperty("Operations", out var ops) || ops.GetArrayLength() == 0) + { + Console.WriteLine("No operations to delete."); + InitializeWriter(); + return; + } + + Console.WriteLine("Deleting all operations from log file."); + + EmptyOperations(); + } + + public void Finish() + { + _writer.WriteEndArray(); + _writer.WriteEndObject(); + } + + public void Dispose() + { + Finish(); + _writer.Dispose(); + } + + // Rewrite file with empty Operations array + private void EmptyOperations() + { + var options = new JsonWriterOptions + { + Indented = true, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; + + using (var stream = new FileStream(_filePath, FileMode.Create, FileAccess.Write, FileShare.None)) + { + using var newWriter = new Utf8JsonWriter(stream, options); + + newWriter.WriteStartObject(); + newWriter.WritePropertyName("Operations"); + newWriter.WriteStartArray(); + newWriter.WriteEndArray(); + newWriter.WriteEndObject(); + + newWriter.Flush(); + } + + InitializeWriter(); + } + + // Initialize writer for logging + private void InitializeWriter() + { + _stream = new FileStream(_filePath, FileMode.Create, FileAccess.Write, FileShare.None); + + var options = new JsonWriterOptions + { + Indented = true, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; + + _writer = new Utf8JsonWriter(_stream, options); + + _writer.WriteStartObject(); + _writer.WritePropertyName("Operations"); + _writer.WriteStartArray(); + } + } +} diff --git a/Calculator.ivangar/CalculatorLibrary/Models/Exponentiation.cs b/Calculator.ivangar/CalculatorLibrary/Models/Exponentiation.cs new file mode 100644 index 00000000..feee8336 --- /dev/null +++ b/Calculator.ivangar/CalculatorLibrary/Models/Exponentiation.cs @@ -0,0 +1,11 @@ +namespace CalculatorLibrary.Models +{ + public record Exponentiation : MathOperation + { + public double Base { get; set; } + public double Exponent { get; set; } + + public override string ToString() => $"{Base} ^ {Exponent} = {Result:0.##}"; + + } +} diff --git a/Calculator.ivangar/CalculatorLibrary/Models/MathOperation.cs b/Calculator.ivangar/CalculatorLibrary/Models/MathOperation.cs new file mode 100644 index 00000000..d98e07dd --- /dev/null +++ b/Calculator.ivangar/CalculatorLibrary/Models/MathOperation.cs @@ -0,0 +1,11 @@ +namespace CalculatorLibrary.Models +{ + public record MathOperation + { + public double OperandA { get; init; } + public double OperandB { get; init; } + public char Operation { get; init; } + public double Result { get; init; } + public override string ToString() => $"{OperandA} {Operation} {OperandB} = {Result:0.##}"; + } +} diff --git a/Calculator.ivangar/CalculatorLibrary/Models/SquareRoot.cs b/Calculator.ivangar/CalculatorLibrary/Models/SquareRoot.cs new file mode 100644 index 00000000..f711b6e2 --- /dev/null +++ b/Calculator.ivangar/CalculatorLibrary/Models/SquareRoot.cs @@ -0,0 +1,9 @@ +namespace CalculatorLibrary.Models +{ + public record SquareRoot : MathOperation + { + public double Radicand { get; set; } + + public override string ToString() => $"{Operation} {Radicand} = {Result:0.##}"; + } +} diff --git a/Calculator.ivangar/CalculatorLibrary/Models/TrigonometryOperation.cs b/Calculator.ivangar/CalculatorLibrary/Models/TrigonometryOperation.cs new file mode 100644 index 00000000..516a9086 --- /dev/null +++ b/Calculator.ivangar/CalculatorLibrary/Models/TrigonometryOperation.cs @@ -0,0 +1,19 @@ +using CalculatorLibrary.Enums; + +namespace CalculatorLibrary.Models +{ + public record TrigonometryOperation : MathOperation + { + public double Degrees { get; set; } + + // Adding Radians property in case we need to convert later. + public double Radians + { + get { return Degrees * (Math.PI / 180); } + } + + public TrigonometryFunctions Function { get; set; } + + public override string ToString() => $"{Function}({Degrees}{Operation}) = {Result:0.##}"; + } +} diff --git a/Calculator.ivangar/CalculatorLibrary/OperationFactory.cs b/Calculator.ivangar/CalculatorLibrary/OperationFactory.cs new file mode 100644 index 00000000..4fceb80b --- /dev/null +++ b/Calculator.ivangar/CalculatorLibrary/OperationFactory.cs @@ -0,0 +1,20 @@ +using CalculatorLibrary.Enums; +using CalculatorLibrary.Models; + +namespace CalculatorLibrary +{ + public static class OperationFactory + { + public static MathOperation CreateBasic(double a, double b, char op, double result) => + new MathOperation { OperandA = a, OperandB = b, Operation = op, Result = result }; + + public static SquareRoot CreateSquareRoot(double radicand, char op, double result) => + new SquareRoot { Radicand = radicand, Operation = op, Result = result }; + + public static Exponentiation CreateExponentiation(double baseNumber, double exponent, char op, double result) => + new Exponentiation { Base = baseNumber, Exponent = exponent, Operation = op, Result = result }; + + public static TrigonometryOperation CreateTrigonometry(double degrees, TrigonometryFunctions function, char op, double result) => + new TrigonometryOperation { Degrees = degrees, Function = function, Operation = op, Result = result }; + } +} diff --git a/Calculator.ivangar/CalculatorLibrary/OperationRepository.cs b/Calculator.ivangar/CalculatorLibrary/OperationRepository.cs new file mode 100644 index 00000000..d417f2f5 --- /dev/null +++ b/Calculator.ivangar/CalculatorLibrary/OperationRepository.cs @@ -0,0 +1,39 @@ +using CalculatorLibrary.Models; + +namespace CalculatorLibrary +{ + public class OperationRepository + { + private readonly List _operations = new(); + + public IReadOnlyList Operations => _operations; + + public void Add(MathOperation op) + { + _operations.Add(op); + Console.WriteLine($"Operation: {op}\n"); + } + + public void Clear() => _operations.Clear(); + + public int Count => _operations.Count; + + public void PrintAll() + { + var countTimes = _operations.Count == 1 ? "time" : "times"; + Console.WriteLine($"\nThe calculator was used {_operations.Count} {countTimes}.\n"); + + if (_operations.Count == 0) + { + Console.WriteLine("\nThere are no calculations to display:\n"); + return; + } + + Console.WriteLine("Latest calculations:\n"); + foreach (var (index, operation) in _operations.Select((o, i) => (i, o))) + { + Console.WriteLine($"{index + 1}. {operation}"); + } + } + } +}