Skip to content

Nostromos/qli

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Exercise #1: Quiz Game

Summary

Build a CLI Quiz app that uses flags to customize the game experience. The quiz is trivia style - the user is asked a question and provides a freeform answer.

Thoughts

Total Time: 4 hours 14 minutes I thought this would be easier. I was so confident in my approach that I spent some time researching CLI formatting and ANSI colors codes. I eventually discarded that approach to better the underlying logic.

I did learn a lot about golang, packages, and concurrency. Idiomatic go still feels a bit alien, especially with repetitive error handling. Typing if err != nil {...} is annoying, but I trust its become idiomatic for a reason.

qli/quiz.go

Lines 15 to 18 in 6671cb7

// Represents a single question
type Question struct {
prompt string
answer string

I started with multiple structs and was getting really detailed with typing but ended trashing everything but one.

qli/quiz.go

Lines 115 to 135 in 6671cb7

quizLoop:
for i, q := range questions {
fmt.Printf("Question %d: %s =", i+1, q.prompt)
// So goroutines were the only way I could think of to handle the user input block (calling reader.Readstring() blocks input until a user types an answer and I'm supposed to kill the quiz even while waiting for an answer). I'm not sure if this is the best way to do it.
go func() {
input, _ := reader.ReadString('\n')
answerCh <- cleanInput(input)
}()
// Wait for answer or timeout
select {
case <-timer.C: // Per https://pkg.go.dev/time#Timer, when the timer expires, current time gets sent on on C, which is our signal to alert the user and break the loop (thus ending the quiz). The JS/TS dev in me hates breaking loops but it seems like go devs are ok with it, given their baser natures.
fmt.Println("\nTime's up!")
break quizLoop
case answer := <-answerCh:
if answer == q.answer {
correctAnswers++
}
}
}

My initial question/answer loop wouldn't break on timer expiration. In fact it originally had elapsed time saved to a variable that updated each loop. Concurrency is a relatively new concept to me and it took at least an hour for me to realize that reader.ReadString('\n') is blocking. It took me another 30-40 mins to figure out that I could use a goroutine to handle the user input portion and a select/case to deal with breaking out of the loop. I'm not sure if this is the best way to do it.

qli/quiz.go

Line 130 in 6671cb7

case answer := <-answerCh:

Golang Channels and value assignation are not intuitive to me (coming from the JS/TS world) so this syntax was confusing to learn but I can see how useful it would be.

Improvements

  • Remove copilot suggestions for future gophercises. Far too annoying and really hurts ability to learn patterns or reason myself.
  • Add handling for different question types: multiple choice, correct order, etc.
  • Ensure that all loop segments are handled by goroutines. Reading and printing questions are also blocking (I think), so this would make it insignificantly more responsive but probably unnoticeable to the user. It would be good practice.

Requirements

Part 1

  • Ingests CSV file
    • User-provided path via flags
    • Default path and questions included with package (defaults to problems.csv)
  • Parses CSV into questions and answers
  • Asks questions of user and accepts input as answer
  • Compares user provided input with answer and keeps track of # of correct/incorrect answers
  • Regardless of correct/incorrect, next question is asked immediately after user input
  • Quiz Format
    • < 100 questions
    • Single word/number answers
  • At end of quiz, program outputs total number of questions correct out of total

Part 2

  • Add timer with default limit of 30 secs, customizable with flag
  • User must press enter (or another key) before timer starts, then questions are printed out one at a time until an answer is given
  • Quiz must stop when time limit is exceeded - do not wait for the user to answer their final question. Ideally stops quiz entirely even if you're waiting on user answer.
  • Regardless of whether time limit was exceeded, program must still output "score" with unanswered questions and invalid answers considered incorrect.

Bonus

  • Add string trimming and cleanup to help ensure that correct answers with extra whitespace, capitalization, etc are not considered incorrect. Hint: Check out the strings package.
  • Add an option (a new flag) to shuffle the quiz order each time it is run.

Exercise details

This exercise is broken into two parts to help simplify the process of explaining it as well as to make it easier to solve. The second part is harder than the first, so if you get stuck feel free to move on to another problem then come back to part 2 later.

Part 1

Create a program that will read in a quiz provided via a CSV file (more details below) and will then give the quiz to a user keeping track of how many questions they get right and how many they get incorrect. Regardless of whether the answer is correct or wrong the next question should be asked immediately afterwards.

The CSV file should default to problems.csv (example shown below), but the user should be able to customize the filename via a flag.

The CSV file will be in a format like below, where the first column is a question and the second column in the same row is the answer to that question.

5+5,10
7+3,10
1+1,2
8+3,11
1+2,3
8+6,14
3+1,4
1+4,5
5+1,6
2+3,5
3+3,6
2+4,6
5+2,7

You can assume that quizzes will be relatively short (< 100 questions) and will have single word/number answers.

At the end of the quiz the program should output the total number of questions correct and how many questions there were in total. Questions given invalid answers are considered incorrect.

!NOTE: CSV files may have questions with commas in them. Eg: "what 2+2, sir?",4 is a valid row in a CSV. I suggest you look into the CSV package in Go and don’t try to write your own CSV parser.

Part 2

Adapt your program from part 1 to add a timer. The default time limit should be 30 seconds, but should also be customizable via a flag.

Your quiz should stop as soon as the time limit has exceeded. That is, you shouldn’t wait for the user to answer one final questions but should ideally stop the quiz entirely even if you are currently waiting on an answer from the end user.

Users should be asked to press enter (or some other key) before the timer starts, and then the questions should be printed out to the screen one at a time until the user provides an answer. Regardless of whether the answer is correct or wrong the next question should be asked.

At the end of the quiz the program should still output the total number of questions correct and how many questions there were in total. Questions given invalid answers or unanswered are considered incorrect.

Bonus

As a bonus exercises you can also…

  1. Add string trimming and cleanup to help ensure that correct answers with extra whitespace, capitalization, etc are not considered incorrect. Hint: Check out the strings package.
  2. Add an option (a new flag) to shuffle the quiz order each time it is run.

About

Quizzes in your CLI

Topics

Resources

Code of conduct

Stars

Watchers

Forks

Sponsor this project

Languages