From a59ac2fac1277e43f613a39a52577962675db821 Mon Sep 17 00:00:00 2001 From: chanderlud Date: Mon, 29 Sep 2025 20:19:31 -0700 Subject: [PATCH 01/16] casts numbers to float for correct percent calculation --- PrincessBrideTrivia/PrincessBrideTrivia/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs index 5e30e51..ee609e9 100644 --- a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs +++ b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs @@ -21,7 +21,7 @@ public static void Main(string[] args) public static string GetPercentCorrect(int numberCorrectAnswers, int numberOfQuestions) { - return (numberCorrectAnswers / numberOfQuestions * 100) + "%"; + return ((float)numberCorrectAnswers / (float)numberOfQuestions * 100) + "%"; } public static bool AskQuestion(Question question) From 6405f02278d73373cc609f03589d1f3dd0332730 Mon Sep 17 00:00:00 2001 From: Joseph Potapenko <129787506+JosephPotapenko@users.noreply.github.com> Date: Mon, 29 Sep 2025 20:20:13 -0700 Subject: [PATCH 02/16] Adding Questions into the array --- PrincessBrideTrivia/PrincessBrideTrivia/Program.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs index 5e30e51..b28a53c 100644 --- a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs +++ b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs @@ -86,7 +86,11 @@ public static Question[] LoadQuestions(string filePath) question.Answers[1] = answer2; question.Answers[2] = answer3; question.CorrectAnswerIndex = correctAnswerIndex; + + questions[i] = question; } + + return questions; } } From 0024d32ba9c45a4ab0b7785ed31770919f8c18e9 Mon Sep 17 00:00:00 2001 From: chanderlud Date: Mon, 29 Sep 2025 20:46:25 -0700 Subject: [PATCH 03/16] WIP AI generated trivia questions --- .../PrincessBrideTrivia.csproj | 4 + .../PrincessBrideTrivia/Program.cs | 132 +++++++++++++++++- 2 files changed, 133 insertions(+), 3 deletions(-) diff --git a/PrincessBrideTrivia/PrincessBrideTrivia/PrincessBrideTrivia.csproj b/PrincessBrideTrivia/PrincessBrideTrivia/PrincessBrideTrivia.csproj index 57c0da6..3aa56a7 100644 --- a/PrincessBrideTrivia/PrincessBrideTrivia/PrincessBrideTrivia.csproj +++ b/PrincessBrideTrivia/PrincessBrideTrivia/PrincessBrideTrivia.csproj @@ -7,6 +7,10 @@ enable + + + + PreserveNewest diff --git a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs index ee609e9..9fa6c0f 100644 --- a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs +++ b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs @@ -1,13 +1,21 @@ -namespace PrincessBrideTrivia; +using OpenAI.Chat; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace PrincessBrideTrivia; public class Program { - public static void Main(string[] args) + public static async Task Main(string[] args) { + var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); + string filePath = GetFilePath(); Question[] questions = LoadQuestions(filePath); int numberCorrect = 0; + bool runWhile = true; + for (int i = 0; i < questions.Length; i++) { bool result = AskQuestion(questions[i]); @@ -16,6 +24,26 @@ public static void Main(string[] args) numberCorrect++; } } + + Console.WriteLine("Enter 'exit' to exit"); + + while (runWhile) + { + Question q = await TriviaGenerator.GeneratePrincessBrideQuestionAsync(apiKey); + try + { + bool result = AskQuestion(q); + if (result) + { + numberCorrect++; + } + } + catch (Exception) + { + runWhile = false; + } + } + Console.WriteLine("You got " + GetPercentCorrect(numberCorrect, questions.Length) + " correct"); } @@ -29,7 +57,14 @@ public static bool AskQuestion(Question question) DisplayQuestion(question); string userGuess = GetGuessFromUser(); - return DisplayResult(userGuess, question); + + if (userGuess == "exit") + { + throw new Exception("user exit"); + } else + { + return DisplayResult(userGuess, question); + } } public static string GetGuessFromUser() @@ -90,3 +125,94 @@ public static Question[] LoadQuestions(string filePath) return questions; } } + +public static class TriviaGenerator +{ + // Model note: pick any capable text model you have access to. + private const string Model = "gpt-4.1"; + + /// + /// Generates one Princess Bride multiple-choice question using the OpenAI API. + /// Respects the Question shape you've specified, including string index. + /// + public static async Task GeneratePrincessBrideQuestionAsync(string apiKey, int choices = 4) + { + if (string.IsNullOrWhiteSpace(apiKey)) + throw new ArgumentException("API key is required.", nameof(apiKey)); + + if (choices is < 3 or > 6) + throw new ArgumentOutOfRangeException(nameof(choices), "choices must be between 3 and 6."); + + var client = new ChatClient(model: Model, apiKey: apiKey); + + var system = """ + You generate trivia strictly about the 1987 film "The Princess Bride". + Output ONLY a single JSON object with this exact C#-friendly shape: + { + "Text": string, // the question text + "Answers": string[], // exactly N distinct options, concise, no markup + "CorrectAnswerIndex": string // "1"-based index of the correct answer, as a string + } + Rules: + - The question must be unambiguous and answerable from the film (not the novel). + - Answers must be short (max ~80 chars each) and mutually exclusive. + - Do not include explanations, hints, or extra keys. + - Do not include code fences. Print raw JSON only. + """; + + var user = $""" + Create ONE multiple-choice question. + Number of options: {choices}. + Difficulty: medium. + """; + + var completion = await client.CompleteChatAsync( + new ChatMessage[] + { + new SystemChatMessage(system), + new UserChatMessage(user) + }, + new ChatCompletionOptions + { + // Mild creativity, but not so wild that it invents new plotlines + Temperature = (float)0.7 + }); + + var message = completion.Value.Content[0]; + var json = message.Text?.Trim(); + + Console.WriteLine(json); + + if (string.IsNullOrWhiteSpace(json)) + throw new InvalidOperationException("Model returned empty content."); + + // Strict JSON parse; throw if the model colored outside the lines + var question = JsonSerializer.Deserialize(json, new JsonSerializerOptions + { + ReadCommentHandling = JsonCommentHandling.Disallow, + AllowTrailingCommas = false, + PropertyNameCaseInsensitive = true, + NumberHandling = JsonNumberHandling.Strict + }); + + Validate(question, choices); + return question!; + } + + private static void Validate(Question q, int expectedChoices) + { + if (q is null) throw new InvalidOperationException("Failed to parse the model's JSON."); + if (string.IsNullOrWhiteSpace(q.Text)) throw new InvalidOperationException("Question text missing."); + if (q.Answers is null || q.Answers.Length != expectedChoices) + throw new InvalidOperationException($"Expected {expectedChoices} answers, got {q?.Answers?.Length ?? 0}."); + + if (q.Answers.Any(string.IsNullOrWhiteSpace)) + throw new InvalidOperationException("One or more answers are empty."); + + if (!int.TryParse(q.CorrectAnswerIndex, out var idx)) + throw new InvalidOperationException("CorrectAnswerIndex must be a numeric string."); + + if (idx < 0 || idx >= q.Answers.Length) + throw new InvalidOperationException("CorrectAnswerIndex out of range."); + } +} \ No newline at end of file From 3c4ff18b426da5ddee42aefce9265eb33e64b09d Mon Sep 17 00:00:00 2001 From: chanderlud Date: Mon, 29 Sep 2025 20:58:57 -0700 Subject: [PATCH 04/16] finished AI generated trivia questions & adds basic unit tests for question generation --- .../PrincessBrideTrivia.Tests/ProgramTests.cs | 15 +++++++++++++++ .../PrincessBrideTrivia/Program.cs | 9 +++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs b/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs index e3e4739..e0a9a1b 100644 --- a/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs +++ b/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs @@ -69,6 +69,21 @@ public void GetPercentCorrect_ReturnsExpectedPercentage(int numberOfCorrectGuess Assert.AreEqual(expectedString, percentage); } + [TestMethod] + public async Task GenerateQuestions_SuccessfullyGeneratesQuestions() + { + var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); + Question q = await TriviaGenerator.GeneratePrincessBrideQuestionAsync(apiKey); + Assert.IsNotNull(q); + } + + [TestMethod] + public async Task GenerateQuestions_GeneratesFourChoices() + { + var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); + Question q = await TriviaGenerator.GeneratePrincessBrideQuestionAsync(apiKey); + Assert.HasCount(4, q.Answers); + } private static void GenerateQuestionsFile(string filePath, int numberOfQuestions) { diff --git a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs index 374cf2d..d2db372 100644 --- a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs +++ b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs @@ -14,6 +14,7 @@ public static async Task Main(string[] args) Question[] questions = LoadQuestions(filePath); int numberCorrect = 0; + int numberOfQuestions = questions.Length; bool runWhile = true; for (int i = 0; i < questions.Length; i++) @@ -33,6 +34,7 @@ public static async Task Main(string[] args) try { bool result = AskQuestion(q); + numberOfQuestions++; if (result) { numberCorrect++; @@ -44,7 +46,7 @@ public static async Task Main(string[] args) } } - Console.WriteLine("You got " + GetPercentCorrect(numberCorrect, questions.Length) + " correct"); + Console.WriteLine("You got " + GetPercentCorrect(numberCorrect, numberOfQuestions) + " correct"); } public static string GetPercentCorrect(int numberCorrectAnswers, int numberOfQuestions) @@ -160,6 +162,7 @@ You generate trivia strictly about the 1987 film "The Princess Bride". Rules: - The question must be unambiguous and answerable from the film (not the novel). - Answers must be short (max ~80 chars each) and mutually exclusive. + - Make sure to switch up which index is correct. - Do not include explanations, hints, or extra keys. - Do not include code fences. Print raw JSON only. """; @@ -167,7 +170,7 @@ You generate trivia strictly about the 1987 film "The Princess Bride". var user = $""" Create ONE multiple-choice question. Number of options: {choices}. - Difficulty: medium. + Difficulty: hard. """; var completion = await client.CompleteChatAsync( @@ -185,8 +188,6 @@ Create ONE multiple-choice question. var message = completion.Value.Content[0]; var json = message.Text?.Trim(); - Console.WriteLine(json); - if (string.IsNullOrWhiteSpace(json)) throw new InvalidOperationException("Model returned empty content."); From b728c8104a59bc489bbab6838d34a00458ae5142 Mon Sep 17 00:00:00 2001 From: chanderlud Date: Mon, 29 Sep 2025 21:03:58 -0700 Subject: [PATCH 05/16] Updated comments --- PrincessBrideTrivia/PrincessBrideTrivia/Program.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs index d2db372..629be0c 100644 --- a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs +++ b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs @@ -134,12 +134,10 @@ public static Question[] LoadQuestions(string filePath) public static class TriviaGenerator { - // Model note: pick any capable text model you have access to. private const string Model = "gpt-4.1"; /// /// Generates one Princess Bride multiple-choice question using the OpenAI API. - /// Respects the Question shape you've specified, including string index. /// public static async Task GeneratePrincessBrideQuestionAsync(string apiKey, int choices = 4) { @@ -181,7 +179,7 @@ Create ONE multiple-choice question. }, new ChatCompletionOptions { - // Mild creativity, but not so wild that it invents new plotlines + // Mild creativity Temperature = (float)0.7 }); @@ -191,7 +189,7 @@ Create ONE multiple-choice question. if (string.IsNullOrWhiteSpace(json)) throw new InvalidOperationException("Model returned empty content."); - // Strict JSON parse; throw if the model colored outside the lines + // Strict JSON parse var question = JsonSerializer.Deserialize(json, new JsonSerializerOptions { ReadCommentHandling = JsonCommentHandling.Disallow, From 1ec8dcae6efd4ec16033edfc54e572bfb0f618eb Mon Sep 17 00:00:00 2001 From: chanderlud Date: Mon, 29 Sep 2025 21:06:33 -0700 Subject: [PATCH 06/16] disables question generation tests when an OpenAI API key is not available --- .../PrincessBrideTrivia.Tests/ProgramTests.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs b/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs index e0a9a1b..445fd0b 100644 --- a/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs +++ b/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs @@ -73,6 +73,12 @@ public void GetPercentCorrect_ReturnsExpectedPercentage(int numberOfCorrectGuess public async Task GenerateQuestions_SuccessfullyGeneratesQuestions() { var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); + + if (apiKey == null) + { + return; // test cannot run without the API key + } + Question q = await TriviaGenerator.GeneratePrincessBrideQuestionAsync(apiKey); Assert.IsNotNull(q); } @@ -81,6 +87,12 @@ public async Task GenerateQuestions_SuccessfullyGeneratesQuestions() public async Task GenerateQuestions_GeneratesFourChoices() { var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); + + if (apiKey == null) + { + return; // test cannot run without the API key + } + Question q = await TriviaGenerator.GeneratePrincessBrideQuestionAsync(apiKey); Assert.HasCount(4, q.Answers); } From deb917ee6ec3831c3a96bc64c14a585bd5c5512b Mon Sep 17 00:00:00 2001 From: chanderlud Date: Mon, 29 Sep 2025 21:14:53 -0700 Subject: [PATCH 07/16] improved error handling and flow --- .../PrincessBrideTrivia/Program.cs | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs index 629be0c..defb88e 100644 --- a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs +++ b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs @@ -19,7 +19,7 @@ public static async Task Main(string[] args) for (int i = 0; i < questions.Length; i++) { - bool result = AskQuestion(questions[i]); + (bool result, _) = AskQuestion(questions[i]); if (result) { numberCorrect++; @@ -30,19 +30,26 @@ public static async Task Main(string[] args) while (runWhile) { - Question q = await TriviaGenerator.GeneratePrincessBrideQuestionAsync(apiKey); try { - bool result = AskQuestion(q); - numberOfQuestions++; + Question q = await TriviaGenerator.GeneratePrincessBrideQuestionAsync(apiKey); + + (bool result, runWhile) = AskQuestion(q); + + if (runWhile) + { + numberOfQuestions++; + } + if (result) { numberCorrect++; } } - catch (Exception) + catch (InvalidOperationException ex) { - runWhile = false; + Console.WriteLine(ex.Message); + continue; } } @@ -54,7 +61,7 @@ public static string GetPercentCorrect(int numberCorrectAnswers, int numberOfQue return ((float)numberCorrectAnswers / (float)numberOfQuestions * 100) + "%"; } - public static bool AskQuestion(Question question) + public static (bool, bool) AskQuestion(Question question) { DisplayQuestion(question); @@ -62,10 +69,10 @@ public static bool AskQuestion(Question question) if (userGuess == "exit") { - throw new Exception("user exit"); + return (false, false); } else { - return DisplayResult(userGuess, question); + return (DisplayResult(userGuess, question), true); } } From ac8b0fe363a804cdf7689aa61aecd178adf970dd Mon Sep 17 00:00:00 2001 From: chanderlud Date: Tue, 30 Sep 2025 14:01:08 -0700 Subject: [PATCH 08/16] refactors TriviaGenerator class to its own file --- .../PrincessBrideTrivia/Program.cs | 94 +------------------ .../PrincessBrideTrivia/TriviaGenerator.cs | 94 +++++++++++++++++++ 2 files changed, 95 insertions(+), 93 deletions(-) create mode 100644 PrincessBrideTrivia/PrincessBrideTrivia/TriviaGenerator.cs diff --git a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs index defb88e..ff5ec6c 100644 --- a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs +++ b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs @@ -1,8 +1,4 @@ -using OpenAI.Chat; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace PrincessBrideTrivia; +namespace PrincessBrideTrivia; public class Program { @@ -137,92 +133,4 @@ public static Question[] LoadQuestions(string filePath) return questions; } -} - -public static class TriviaGenerator -{ - private const string Model = "gpt-4.1"; - - /// - /// Generates one Princess Bride multiple-choice question using the OpenAI API. - /// - public static async Task GeneratePrincessBrideQuestionAsync(string apiKey, int choices = 4) - { - if (string.IsNullOrWhiteSpace(apiKey)) - throw new ArgumentException("API key is required.", nameof(apiKey)); - - if (choices is < 3 or > 6) - throw new ArgumentOutOfRangeException(nameof(choices), "choices must be between 3 and 6."); - - var client = new ChatClient(model: Model, apiKey: apiKey); - - var system = """ - You generate trivia strictly about the 1987 film "The Princess Bride". - Output ONLY a single JSON object with this exact C#-friendly shape: - { - "Text": string, // the question text - "Answers": string[], // exactly N distinct options, concise, no markup - "CorrectAnswerIndex": string // "1"-based index of the correct answer, as a string - } - Rules: - - The question must be unambiguous and answerable from the film (not the novel). - - Answers must be short (max ~80 chars each) and mutually exclusive. - - Make sure to switch up which index is correct. - - Do not include explanations, hints, or extra keys. - - Do not include code fences. Print raw JSON only. - """; - - var user = $""" - Create ONE multiple-choice question. - Number of options: {choices}. - Difficulty: hard. - """; - - var completion = await client.CompleteChatAsync( - new ChatMessage[] - { - new SystemChatMessage(system), - new UserChatMessage(user) - }, - new ChatCompletionOptions - { - // Mild creativity - Temperature = (float)0.7 - }); - - var message = completion.Value.Content[0]; - var json = message.Text?.Trim(); - - if (string.IsNullOrWhiteSpace(json)) - throw new InvalidOperationException("Model returned empty content."); - - // Strict JSON parse - var question = JsonSerializer.Deserialize(json, new JsonSerializerOptions - { - ReadCommentHandling = JsonCommentHandling.Disallow, - AllowTrailingCommas = false, - PropertyNameCaseInsensitive = true, - NumberHandling = JsonNumberHandling.Strict - }); - - Validate(question, choices); - return question!; - } - - private static void Validate(Question q, int expectedChoices) - { - if (q is null) throw new InvalidOperationException("Failed to parse the model's JSON."); - if (string.IsNullOrWhiteSpace(q.Text)) throw new InvalidOperationException("Question text missing."); - if (q.Answers is null || q.Answers.Length != expectedChoices) - throw new InvalidOperationException($"Expected {expectedChoices} answers, got {q?.Answers?.Length ?? 0}."); - - if (q.Answers.Any(string.IsNullOrWhiteSpace)) - throw new InvalidOperationException("One or more answers are empty."); - - if (!int.TryParse(q.CorrectAnswerIndex, out var idx)) - throw new InvalidOperationException("CorrectAnswerIndex must be a numeric string."); - - if (idx < 0 || idx >= q.Answers.Length) - throw new InvalidOperationException("CorrectAnswerIndex out of range."); - } } \ No newline at end of file diff --git a/PrincessBrideTrivia/PrincessBrideTrivia/TriviaGenerator.cs b/PrincessBrideTrivia/PrincessBrideTrivia/TriviaGenerator.cs new file mode 100644 index 0000000..0470d14 --- /dev/null +++ b/PrincessBrideTrivia/PrincessBrideTrivia/TriviaGenerator.cs @@ -0,0 +1,94 @@ +using OpenAI.Chat; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace PrincessBrideTrivia +{ + public static class TriviaGenerator + { + private const string Model = "gpt-4.1"; + + /// + /// Generates one Princess Bride multiple-choice question using the OpenAI API. + /// + public static async Task GeneratePrincessBrideQuestionAsync(string apiKey, int choices = 4) + { + if (string.IsNullOrWhiteSpace(apiKey)) + throw new ArgumentException("API key is required.", nameof(apiKey)); + + if (choices is < 3 or > 6) + throw new ArgumentOutOfRangeException(nameof(choices), "choices must be between 3 and 6."); + + var client = new ChatClient(model: Model, apiKey: apiKey); + + var system = """ + You generate trivia strictly about the 1987 film "The Princess Bride". + Output ONLY a single JSON object with this exact C#-friendly shape: + { + "Text": string, // the question text + "Answers": string[], // exactly N distinct options, concise, no markup + "CorrectAnswerIndex": string // "1"-based index of the correct answer, as a string + } + Rules: + - The question must be unambiguous and answerable from the film (not the novel). + - Answers must be short (max ~80 chars each) and mutually exclusive. + - Make sure to switch up which index is correct. + - Do not include explanations, hints, or extra keys. + - Do not include code fences. Print raw JSON only. + """; + + var user = $""" + Create ONE multiple-choice question. + Number of options: {choices}. + Difficulty: hard. + """; + + var completion = await client.CompleteChatAsync( + new ChatMessage[] + { + new SystemChatMessage(system), + new UserChatMessage(user) + }, + new ChatCompletionOptions + { + // Mild creativity + Temperature = (float)0.7 + }); + + var message = completion.Value.Content[0]; + var json = message.Text?.Trim(); + + if (string.IsNullOrWhiteSpace(json)) + throw new InvalidOperationException("Model returned empty content."); + + // Strict JSON parse + var question = JsonSerializer.Deserialize(json, new JsonSerializerOptions + { + ReadCommentHandling = JsonCommentHandling.Disallow, + AllowTrailingCommas = false, + PropertyNameCaseInsensitive = true, + NumberHandling = JsonNumberHandling.Strict + }); + + Validate(question, choices); + return question!; + } + + private static void Validate(Question q, int expectedChoices) + { + if (q is null) throw new InvalidOperationException("Failed to parse the model's JSON."); + if (string.IsNullOrWhiteSpace(q.Text)) throw new InvalidOperationException("Question text missing."); + if (q.Answers is null || q.Answers.Length != expectedChoices) + throw new InvalidOperationException($"Expected {expectedChoices} answers, got {q?.Answers?.Length ?? 0}."); + + if (q.Answers.Any(string.IsNullOrWhiteSpace)) + throw new InvalidOperationException("One or more answers are empty."); + + if (!int.TryParse(q.CorrectAnswerIndex, out var idx)) + throw new InvalidOperationException("CorrectAnswerIndex must be a numeric string."); + + if (idx < 0 || idx >= q.Answers.Length) + throw new InvalidOperationException("CorrectAnswerIndex out of range."); + } + } +} From a8d2e1be9ed978719c871bea995db97f1e445d0f Mon Sep 17 00:00:00 2001 From: chanderlud Date: Tue, 30 Sep 2025 14:04:55 -0700 Subject: [PATCH 09/16] simplifies LoadQuestions by removing temporary variables --- .../PrincessBrideTrivia/Program.cs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs index ff5ec6c..3397c05 100644 --- a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs +++ b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs @@ -111,21 +111,14 @@ public static Question[] LoadQuestions(string filePath) for (int i = 0; i < questions.Length; i++) { int lineIndex = i * 5; - string questionText = lines[lineIndex]; - - string answer1 = lines[lineIndex + 1]; - string answer2 = lines[lineIndex + 2]; - string answer3 = lines[lineIndex + 3]; - - string correctAnswerIndex = lines[lineIndex + 4]; Question question = new(); - question.Text = questionText; + question.Text = lines[lineIndex]; question.Answers = new string[3]; - question.Answers[0] = answer1; - question.Answers[1] = answer2; - question.Answers[2] = answer3; - question.CorrectAnswerIndex = correctAnswerIndex; + question.Answers[0] = lines[lineIndex + 1]; + question.Answers[1] = lines[lineIndex + 2]; + question.Answers[2] = lines[lineIndex + 3]; + question.CorrectAnswerIndex = lines[lineIndex + 4]; questions[i] = question; } From 3c0106bc49af6f18c49772a68f0533792966487d Mon Sep 17 00:00:00 2001 From: chanderlud Date: Tue, 30 Sep 2025 14:17:47 -0700 Subject: [PATCH 10/16] refactors to string interpolation, rounds the percent to two decimal places for improved user experience --- PrincessBrideTrivia/PrincessBrideTrivia/Program.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs index 3397c05..aa826e1 100644 --- a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs +++ b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs @@ -49,12 +49,13 @@ public static async Task Main(string[] args) } } - Console.WriteLine("You got " + GetPercentCorrect(numberCorrect, numberOfQuestions) + " correct"); + Console.WriteLine($"You got {GetPercentCorrect(numberCorrect, numberOfQuestions)} correct"); } public static string GetPercentCorrect(int numberCorrectAnswers, int numberOfQuestions) { - return ((float)numberCorrectAnswers / (float)numberOfQuestions * 100) + "%"; + double roundedPercent = Math.Round((float)numberCorrectAnswers / (float)numberOfQuestions * 100, 2); + return $"{roundedPercent}%"; } public static (bool, bool) AskQuestion(Question question) @@ -91,10 +92,10 @@ public static bool DisplayResult(string userGuess, Question question) public static void DisplayQuestion(Question question) { - Console.WriteLine("Question: " + question.Text); + Console.WriteLine($"Question: {question.Text}"); for (int i = 0; i < question.Answers.Length; i++) { - Console.WriteLine((i + 1) + ": " + question.Answers[i]); + Console.WriteLine($"{i + 1}: {question.Answers[i]}"); } } From 1a8065d03a68367bd8738720c59de8a879331442 Mon Sep 17 00:00:00 2001 From: chanderlud Date: Tue, 30 Sep 2025 14:21:02 -0700 Subject: [PATCH 11/16] resolves several warnings --- .../PrincessBrideTrivia.Tests/ProgramTests.cs | 3 +-- PrincessBrideTrivia/PrincessBrideTrivia/Program.cs | 7 ++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs b/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs index 445fd0b..f53f536 100644 --- a/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs +++ b/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs @@ -30,8 +30,7 @@ public void LoadQuestions_RetrievesQuestionsFromFile() public void DisplayResult_ReturnsTrueIfCorrect(string userGuess, bool expectedResult) { // Arrange - Question question = new(); - question.CorrectAnswerIndex = "1"; + Question question = new() { CorrectAnswerIndex = "1" }; // Act bool displayResult = Program.DisplayResult(userGuess, question); diff --git a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs index aa826e1..ff8dc58 100644 --- a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs +++ b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs @@ -2,7 +2,7 @@ public class Program { - public static async Task Main(string[] args) + public static async Task Main() { var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); @@ -113,13 +113,10 @@ public static Question[] LoadQuestions(string filePath) { int lineIndex = i * 5; - Question question = new(); - question.Text = lines[lineIndex]; - question.Answers = new string[3]; + Question question = new() { Text = lines[lineIndex], Answers = new string[3], CorrectAnswerIndex = lines[lineIndex + 4] }; question.Answers[0] = lines[lineIndex + 1]; question.Answers[1] = lines[lineIndex + 2]; question.Answers[2] = lines[lineIndex + 3]; - question.CorrectAnswerIndex = lines[lineIndex + 4]; questions[i] = question; } From 6a134afac50bd06572961c3a6c862e16dbcc8554 Mon Sep 17 00:00:00 2001 From: Joseph Potapenko <129787506+JosephPotapenko@users.noreply.github.com> Date: Wed, 1 Oct 2025 21:34:28 -0700 Subject: [PATCH 12/16] Fixing the .HasCount to .AreEqual in order to follow proper test syntax. --- PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs | 4 ++-- PrincessBrideTrivia/PrincessBrideTrivia.sln | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs b/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs index 445fd0b..72c8cbd 100644 --- a/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs +++ b/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs @@ -16,7 +16,7 @@ public void LoadQuestions_RetrievesQuestionsFromFile() Question[] questions = Program.LoadQuestions(filePath); // Assert - Assert.HasCount(2, questions); + Assert.AreEqual(2, questions.Length); } finally { @@ -94,7 +94,7 @@ public async Task GenerateQuestions_GeneratesFourChoices() } Question q = await TriviaGenerator.GeneratePrincessBrideQuestionAsync(apiKey); - Assert.HasCount(4, q.Answers); + Assert.AreEqual(4, q.Answers.Length); } private static void GenerateQuestionsFile(string filePath, int numberOfQuestions) diff --git a/PrincessBrideTrivia/PrincessBrideTrivia.sln b/PrincessBrideTrivia/PrincessBrideTrivia.sln index ebcb8e2..b9bc2f5 100644 --- a/PrincessBrideTrivia/PrincessBrideTrivia.sln +++ b/PrincessBrideTrivia/PrincessBrideTrivia.sln @@ -1,7 +1,6 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 16.0.29215.179 +VisualStudioVersion = 17.14.36518.9 d17.14 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PrincessBrideTrivia", "PrincessBrideTrivia\PrincessBrideTrivia.csproj", "{43E8309C-D064-4743-B038-16F808E98D98}" EndProject From c40d23b8ec4b838d7fd0373bd04ec7ab6cd82e68 Mon Sep 17 00:00:00 2001 From: Joseph Potapenko <129787506+JosephPotapenko@users.noreply.github.com> Date: Wed, 1 Oct 2025 21:42:30 -0700 Subject: [PATCH 13/16] Reverting to .HasCount because apparently the MSTest analyzers require Assert.HasCount(...) instead of Assert.AreEqual(..., collection.Length). --- PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs b/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs index fdd474a..f53f536 100644 --- a/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs +++ b/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs @@ -16,7 +16,7 @@ public void LoadQuestions_RetrievesQuestionsFromFile() Question[] questions = Program.LoadQuestions(filePath); // Assert - Assert.AreEqual(2, questions.Length); + Assert.HasCount(2, questions); } finally { @@ -93,7 +93,7 @@ public async Task GenerateQuestions_GeneratesFourChoices() } Question q = await TriviaGenerator.GeneratePrincessBrideQuestionAsync(apiKey); - Assert.AreEqual(4, q.Answers.Length); + Assert.HasCount(4, q.Answers); } private static void GenerateQuestionsFile(string filePath, int numberOfQuestions) From 75e8527f6c9d4e2e98837e3f4d74687c6523fd98 Mon Sep 17 00:00:00 2001 From: Joseph Potapenko <129787506+JosephPotapenko@users.noreply.github.com> Date: Wed, 1 Oct 2025 21:53:07 -0700 Subject: [PATCH 14/16] Making the checking correct answer and the quitting functionalities in the same method in order to have more readable code. --- .../PrincessBrideTrivia/Program.cs | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs index ff8dc58..4465bb5 100644 --- a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs +++ b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs @@ -15,14 +15,19 @@ public static async Task Main() for (int i = 0; i < questions.Length; i++) { - (bool result, _) = AskQuestion(questions[i]); + (bool result, bool quitProgram) = AskQuestion(questions[i]); + if (quitProgram) + { + runWhile = false; + break; + } if (result) { numberCorrect++; } } - Console.WriteLine("Enter 'exit' to exit"); + Console.WriteLine("Enter 'quit' to exit"); while (runWhile) { @@ -30,16 +35,19 @@ public static async Task Main() { Question q = await TriviaGenerator.GeneratePrincessBrideQuestionAsync(apiKey); - (bool result, runWhile) = AskQuestion(q); + (bool result, bool quitProgram) = AskQuestion(q); - if (runWhile) + if (quitProgram) { - numberOfQuestions++; + runWhile = false; } - - if (result) + else { - numberCorrect++; + numberOfQuestions++; + if (result) + { + numberCorrect++; + } } } catch (InvalidOperationException ex) @@ -58,19 +66,19 @@ public static string GetPercentCorrect(int numberCorrectAnswers, int numberOfQue return $"{roundedPercent}%"; } - public static (bool, bool) AskQuestion(Question question) + // 🔹 Updated AskQuestion version + public static (bool answeredCorrectly, bool quitProgram) AskQuestion(Question question) { DisplayQuestion(question); - string userGuess = GetGuessFromUser(); - if (userGuess == "exit") - { - return (false, false); - } else + if (userGuess == "quit") { - return (DisplayResult(userGuess, question), true); + return (false, true); } + + bool isCorrect = DisplayResult(userGuess, question); + return (isCorrect, false); } public static string GetGuessFromUser() @@ -121,7 +129,6 @@ public static Question[] LoadQuestions(string filePath) questions[i] = question; } - return questions; } -} \ No newline at end of file +} From 8dffbfa2ff6eadd8ed0d940138056db479a714ec Mon Sep 17 00:00:00 2001 From: Joseph Potapenko <129787506+JosephPotapenko@users.noreply.github.com> Date: Wed, 1 Oct 2025 22:14:32 -0700 Subject: [PATCH 15/16] Previously, the "type quit to exit message" displayed at the very end of the program. I changed it to the very beginning so as to be more user friendly. I also fixed the grades to display properly- whether you quit early, or if you finish everything correctly. --- .../PrincessBrideTrivia/Program.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs index 4465bb5..19c9ef5 100644 --- a/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs +++ b/PrincessBrideTrivia/PrincessBrideTrivia/Program.cs @@ -13,6 +13,9 @@ public static async Task Main() int numberOfQuestions = questions.Length; bool runWhile = true; + // ✅ Show quit option at the very start + Console.WriteLine("Type 'quit' anytime to exit"); + for (int i = 0; i < questions.Length; i++) { (bool result, bool quitProgram) = AskQuestion(questions[i]); @@ -27,7 +30,12 @@ public static async Task Main() } } - Console.WriteLine("Enter 'quit' to exit"); + // ✅ If user answered all file questions and we are NOT running AI loop, print score now + if (!runWhile || apiKey == null) + { + Console.WriteLine($"You got {GetPercentCorrect(numberCorrect, numberOfQuestions)} correct"); + return; + } while (runWhile) { @@ -57,6 +65,7 @@ public static async Task Main() } } + // ✅ Always print score at the very end Console.WriteLine($"You got {GetPercentCorrect(numberCorrect, numberOfQuestions)} correct"); } @@ -66,7 +75,6 @@ public static string GetPercentCorrect(int numberCorrectAnswers, int numberOfQue return $"{roundedPercent}%"; } - // 🔹 Updated AskQuestion version public static (bool answeredCorrectly, bool quitProgram) AskQuestion(Question question) { DisplayQuestion(question); @@ -121,7 +129,12 @@ public static Question[] LoadQuestions(string filePath) { int lineIndex = i * 5; - Question question = new() { Text = lines[lineIndex], Answers = new string[3], CorrectAnswerIndex = lines[lineIndex + 4] }; + Question question = new() + { + Text = lines[lineIndex], + Answers = new string[3], + CorrectAnswerIndex = lines[lineIndex + 4] + }; question.Answers[0] = lines[lineIndex + 1]; question.Answers[1] = lines[lineIndex + 2]; question.Answers[2] = lines[lineIndex + 3]; From cdb3d52a6f991e85b24009fc14a3c034c6a7bd25 Mon Sep 17 00:00:00 2001 From: chanderlud Date: Thu, 2 Oct 2025 13:57:55 -0700 Subject: [PATCH 16/16] renames tests as suggested --- PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs b/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs index f53f536..13cc979 100644 --- a/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs +++ b/PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs @@ -69,7 +69,7 @@ public void GetPercentCorrect_ReturnsExpectedPercentage(int numberOfCorrectGuess } [TestMethod] - public async Task GenerateQuestions_SuccessfullyGeneratesQuestions() + public async Task GenerateQuestions_Success() { var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY"); @@ -83,7 +83,7 @@ public async Task GenerateQuestions_SuccessfullyGeneratesQuestions() } [TestMethod] - public async Task GenerateQuestions_GeneratesFourChoices() + public async Task GenerateQuestions_GeneratesFourChoices_Success() { var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY");