Первое, что нужно сделать - определить доменные сущности, с которыми будет работать программа. В вашем случае это:
- тест, который состоит из вопросов;
- вопрос, который состоит из текста и ответов (упорядоченные по индексам);
- ответ, котрый состоит из текста и признака правильности.
Кодpublic class Test
{
public IList<Question> Questions { get; set; }
}
public class Question
{
public string Text { get; set; }
public IList<Answer> Answers { get; set; }
}
public class Answer
{
public string Text { get; set; }
public bool IsRight { get; set; }
}
Необходимо декомпозировать задачу на атомарные подзадачи. Каждую подзадачу можно унести в отдельную функцию, чтобы ограничить сферы ответственности кода (
в духе SOLID в общем и SRP в частности), чтобы обезопасить себя от "эффекта бабочки", когда небольшое изменение отдого кусочка кода в одной части приложения рушит остальную работу в другой части. Да и читать код будет проще, не путаясь в деталях реализации из огромной портянки неразрывного кода с неочевидными связями переменных. Получается примерно так:
- логика "главного меню";
- логика теста:
- создание контента теста;
- прохождение теста:
- распечатка вопроса;
- получение ввода пользователя;
- проверка ответа;
- подсчет и вывод статистики.
Затем необходимо сформировать структуру теста. Для этого можно создать функцию-фабрику, которая возвращает новый объект теста (данные хардкодятся как здесь, читаются из базы, ходят за ними в интернет... whatever, нас интересует само тестирование):
Кодpublic static Test CreateTest()
{
return new Test
{
Questions = new List<Question>
{
new Question
{
Text = "Какая команда выводит текст в консоль?",
Answers = new List<Answer>
{
new Answer { Text = "if/else" },
new Answer { Text = "System.out.println();" },
new Answer { Text = "Console.WriteLine();", IsRight = true },
new Answer { Text = "int x = 10;" }
}
},
new Question
{
Text = "Какой тип из списка целочисленный?",
Answers = new List<Answer>
{
new Answer { Text = "char" },
new Answer { Text = "int", IsRight = true },
new Answer { Text = "float" },
new Answer { Text = "double" }
}
}
//,...
}
};
}
Пусть программа, как у вас, приветствует пользователя, а затем, пока он не введет какой-то из двух корректных выводов (регистронезависимо (
...IngoreCase
)), будет показывать приглашение с подсказкой. Если
Go
- вызываем создание теста и заходим в игру. Если игра завершилась или пользователь изначально ввел
Exit
- печатаем знаменитое
Press any key...
и выходим из программы.
Кодpublic static void Main()
{
Console.WriteLine("Всем привет! И это моя первая мини-программа для проверки ваших знаний языка программирования C#");
string input;
do
{
Console.WriteLine("Если готовы пройти тест напишите Go, а если хотите выйти напишите Exit.");
input = Console.ReadLine();
if (input.Equals("Go", StringComparison.InvariantCultureIgnoreCase))
{
var test = CreateTest();
PlayGame(test); // <----- Логика тестирования находится здесь
}
} while ( !input.Equals("Go", StringComparison.InvariantCultureIgnoreCase)
&& !input.Equals("Exit", StringComparison.InvariantCultureIgnoreCase));
Console.WriteLine("Для выхода нажмите любую клавишу...");
Console.ReadKey();
}
Для игры нам потребуется массив с номерами "проваленных" вопросов (которые пригодятся для статистики в конце -
CalculateResult
). Мы обходим все вопросы из теста, сначала выводя их текст с вариантами на экран (
PrintQuestion
), затем спрашивая юзера его выбор (
GetUserChoice
). Затем ответ проверяется (
IsCorrectAnswer
), если он неверен - добавляем номер ответа (
i
- это индекс массива с нуля, поэтому +1) в массив ошибок. После всех вопросов - подсчитываем и выводим статистику.
Кодprivate static void PlayGame(Test test)
{
var mistakes = new List<int>();
for (int i = 0; i < test.Questions.Count; ++i)
{
var question = test.Questions[i];
PrintQuestion(question, i);
int choice = GetUserChoice(question.Answers.Count);
if (!IsCorrectAnswer(choice, question.Answers))
{
mistakes.Add(i + 1);
}
}
CalculateResults(test.Questions.Count, mistakes);
}
Вопрос распечатывается элементарно: сначала его текст, затем варианты ответов, перед которыми ставиться табуляция (
\t
), чтобы сформировался отступ списка:
Кодprivate static void PrintQuestion(Question question, int questionIndex)
{
Console.WriteLine($"{questionIndex + 1}. {question.Text}");
for (int i = 0; i < question.Answers.Count; ++i)
{
Console.WriteLine($"\t{i + 1}. {question.Answers[i].Text}");
}
}
Получение варинта юзера делается циклом аналогичным вводу Go/Exit в начале. Его завершением управляет переменная
isCorrectInput
, которая ставится равной
true
только в самом конце, когда значение успешно спарсится и пройдет проверку на валидность номера ответа (от 1 до максимального номера ответа):
Кодprivate static int GetUserChoice(int answersCount)
{
bool isCorrectInput = false;
int choice = -1;
do
{
try
{
Console.WriteLine("Введите номер ответа:");
string input = Console.ReadLine();
choice = Convert.ToInt32(input);
if (choice >= 1 && choice <= answersCount) isCorrectInput = true;
}
catch (Exception ex)
{
Console.WriteLine("Неправильный формат ввода");
}
} while (!isCorrectInput);
return choice;
}
Для проверки правильности сначала выделяем правильные ответы вопроса (
rightAnswers
), а потом сравниваем их порядковые индексы с выбранным пользователем индексом. Если хоть один ответ совпал (да, их может быть несколько) - вернется
true
:
Кодprivate static bool IsCorrectAnswer(int choice, IList<Answer> answers)
{
var rightAnswers = answers.Where(x => x.IsRight);
int chosenAnswerIndex = choice - 1;
return rightAnswers.Any(x => answers.IndexOf(x) == chosenAnswerIndex);
}
Ну и вишенка - распечатка результатов. Если ошибок нет - все гуд, 100% решений. Если ошибки есть, то мы решаем простую пропорцию, вычисляя сколько процентов ошибочных ответов было дано, а затем вычитаем их из 100%, получая процент успеха.
Кодprivate static void CalculateResults(int questionsCount, IList<int> mistakes)
{
if (mistakes.Count == 0)
{
Console.WriteLine("Вы на все вопросы ответили правильно! Тест пройден на 100%! ");
}
else
{
string invalidQuestionsString = string.Join(", ", mistakes.Select(x => $"#{x}"));
int progressPercentage = 100 - (int)Math.Ceiling(100 * (double)mistakes.Count / questionsCount);
Console.WriteLine($"Среди ваших ответов есть неправильные ({mistakes.Count}: {invalidQuestionsString})). Тест пройден на {progressPercentage}% ");
}
}
ЗЫ Вы можете поиграть с кодом, попробовать добавить больше вопросов, принудительное прерывание после первого неправильного ответа, обработку множественных корректных ответов (добавлять или баллы за частичное угадывание), разнообразить типы вопросов и прочее.
Полный листинг на ideone