From 6d3e0326d326e78cb0e0c5cac5a7a04b993ec7fb Mon Sep 17 00:00:00 2001 From: "k.alex.mills@gmail.com" Date: Sun, 26 Aug 2018 20:19:40 -0500 Subject: [PATCH] Added solution using timer.AfterFunc and a duration flag. --- students/kalexmills/main.go | 112 +++++++++++++++++++++++++++++++ students/kalexmills/problems.csv | 13 ++++ 2 files changed, 125 insertions(+) create mode 100644 students/kalexmills/main.go create mode 100644 students/kalexmills/problems.csv diff --git a/students/kalexmills/main.go b/students/kalexmills/main.go new file mode 100644 index 0000000..a409f0c --- /dev/null +++ b/students/kalexmills/main.go @@ -0,0 +1,112 @@ +package main + +import ( +"encoding/csv" +"flag" +"fmt" +"log" +"os" +"time" +) + +var ( + filename string + timelimit time.Duration +) + +func init() { + flag.StringVar(&filename, "in", "problems.csv", "filename where problems are read from") + flag.DurationVar(&timelimit, "time", 30*time.Second, "time to allot for quiz") +} + + +// problem represents a prompt and the correct response +type problem struct { + prompt string + response string +} + +// printResults prints a prompt to the user explaining the results achieved. +func printResults(nWrong, nTotal int) { + nRight := nTotal - nWrong + fmt.Printf("You answered %d out of %d questions correctly.\n", nRight, nTotal) +} + +// readFile reads from the file, sending back a slice of problems. +func readFile() []problem { + file, err := os.Open(filename) + if err != nil { + log.Fatal("Error opening file: %s", err.Error()) + } + + r := csv.NewReader(file) + records, err := r.ReadAll() + + if err != nil { + log.Fatal(err) + } + + // Construct a slice of problems for later consumption + result := make([]problem, len(records)) + var i int + for idx, rec := range records { + if len(rec) < 2 { + fmt.Println("Ignoring faulty or missing record from line %d", idx) + continue + } + result[i] = problem{rec[0], rec[1]} + i++ + } + + return result +} + +func main() { + flag.Parse() + + problems := readFile() + + var nWrong, nDone int + // timeout is used to signal that the timer has elapsed + timeout := make(chan bool, 2) + timer := time.AfterFunc(timelimit, func() { + timeout <- true // signal that a timeout occurred + }) + + startTime := time.Now() + // Ask each question and get a response + for _, prob := range problems { + fmt.Printf("%s\n > ", prob.prompt) + + // A separate goroutine must be used to accept input from each question. At first I tried to just read from + // Stdin on the main thread. That was a mistake. If you want to be able to interrupt the prompt when the time + // expires, you need to have your main thread waiting on a response from either the timer or the answer thread. + // + // Lesson is to treat user input as an asynchronous message! + answerCh := make(chan string) + go func() { + var answer string + fmt.Scanf("%s\n", &answer) + answerCh <- answer + }() + + select { + case <-timeout: + fmt.Println("\nTimes up!") + nWrong += len(problems) - nDone // All remaining problems are marked wrong + printResults(nWrong, len(problems)) + os.Exit(0) + case response := <-answerCh: + nDone++ + if prob.response != response { + nWrong++ + } + } + } + + if timer.Stop() { + fmt.Printf("You completed the quiz with %s remaining\n", (timelimit - time.Since(startTime)).String()) + + printResults(nWrong, len(problems)) + } +} diff --git a/students/kalexmills/problems.csv b/students/kalexmills/problems.csv new file mode 100644 index 0000000..ab5e298 --- /dev/null +++ b/students/kalexmills/problems.csv @@ -0,0 +1,13 @@ +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 \ No newline at end of file