Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
a59ac2f
casts numbers to float for correct percent calculation
chanderlud Sep 30, 2025
6405f02
Adding Questions into the array
JosephPotapenko Sep 30, 2025
0903549
Merge branch 'assignment-1-colab' of https://github.com/chanderlud/EW…
JosephPotapenko Sep 30, 2025
0024d32
WIP AI generated trivia questions
chanderlud Sep 30, 2025
8d49d27
Merge branch 'assignment-1-colab' of https://github.com/chanderlud/EW…
chanderlud Sep 30, 2025
3c4ff18
finished AI generated trivia questions & adds basic unit tests for qu…
chanderlud Sep 30, 2025
b728c81
Updated comments
chanderlud Sep 30, 2025
1ec8dca
disables question generation tests when an OpenAI API key is not avai…
chanderlud Sep 30, 2025
deb917e
improved error handling and flow
chanderlud Sep 30, 2025
ac8b0fe
refactors TriviaGenerator class to its own file
chanderlud Sep 30, 2025
a8d2e1b
simplifies LoadQuestions by removing temporary variables
chanderlud Sep 30, 2025
3c0106b
refactors to string interpolation, rounds the percent to two decimal …
chanderlud Sep 30, 2025
1a8065d
resolves several warnings
chanderlud Sep 30, 2025
6a134af
Fixing the .HasCount to .AreEqual in order to follow proper test syntax.
JosephPotapenko Oct 2, 2025
3148200
Merge branch 'assignment-1-colab' of https://github.com/chanderlud/EW…
JosephPotapenko Oct 2, 2025
c40d23b
Reverting to .HasCount because apparently the MSTest analyzers requir…
JosephPotapenko Oct 2, 2025
75e8527
Making the checking correct answer and the quitting functionalities i…
JosephPotapenko Oct 2, 2025
8dffbfa
Previously, the "type quit to exit message" displayed at the very end…
JosephPotapenko Oct 2, 2025
cdb3d52
renames tests as suggested
chanderlud Oct 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions PrincessBrideTrivia/PrincessBrideTrivia.Tests/ProgramTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Preview

Copilot AI Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assert.HasCount is not a valid MSTest assertion method. Use Assert.AreEqual(4, q.Answers.Length) instead.

Suggested change
Assert.HasCount(4, q.Answers);
Assert.AreEqual(4, q.Answers.Length);

Copilot uses AI. Check for mistakes.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may have been the standard previously, but with the MSTests that were provided which are more current than the old standards, this is no longer the case, and .HasCount is actually correct.

}

private static void GenerateQuestionsFile(string filePath, int numberOfQuestions)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="OpenAI" Version="2.5.0" />
</ItemGroup>

<ItemGroup>
<None Update="Trivia.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
Expand Down
139 changes: 134 additions & 5 deletions PrincessBrideTrivia/PrincessBrideTrivia/Program.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
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;
int numberOfQuestions = questions.Length;
bool runWhile = true;

for (int i = 0; i < questions.Length; i++)
{
bool result = AskQuestion(questions[i]);
Expand All @@ -16,20 +25,48 @@ public static void Main(string[] args)
numberCorrect++;
}
}
Console.WriteLine("You got " + GetPercentCorrect(numberCorrect, questions.Length) + " correct");

Console.WriteLine("Enter 'exit' to exit");

while (runWhile)
{
Question q = await TriviaGenerator.GeneratePrincessBrideQuestionAsync(apiKey);
try
{
bool result = AskQuestion(q);
numberOfQuestions++;
if (result)
{
numberCorrect++;
}
}
catch (Exception)
{
runWhile = false;
}
}

Console.WriteLine("You got " + GetPercentCorrect(numberCorrect, numberOfQuestions) + " correct");
}

public static string GetPercentCorrect(int numberCorrectAnswers, int numberOfQuestions)
{
return (numberCorrectAnswers / numberOfQuestions * 100) + "%";
return ((float)numberCorrectAnswers / (float)numberOfQuestions * 100) + "%";
}

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()
Expand Down Expand Up @@ -86,7 +123,99 @@ public static Question[] LoadQuestions(string filePath)
question.Answers[1] = answer2;
question.Answers[2] = answer3;
question.CorrectAnswerIndex = correctAnswerIndex;

questions[i] = question;
}


return questions;
}
}

public static class TriviaGenerator
{
private const string Model = "gpt-4.1";

/// <summary>
/// Generates one Princess Bride multiple-choice question using the OpenAI API.
/// </summary>
public static async Task<Question> 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<Question>(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.");
}
}
Loading