Skip to content

Commit d06f1d1

Browse files
kalexmillsjoncalhoun
authored andcommitted
Added solution using timer.AfterFunc and a duration flag. (#33)
1 parent b09f555 commit d06f1d1

File tree

2 files changed

+125
-0
lines changed

2 files changed

+125
-0
lines changed

students/kalexmills/main.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package main
2+
3+
import (
4+
"encoding/csv"
5+
"flag"
6+
"fmt"
7+
"log"
8+
"os"
9+
"time"
10+
)
11+
12+
var (
13+
filename string
14+
timelimit time.Duration
15+
)
16+
17+
func init() {
18+
flag.StringVar(&filename, "in", "problems.csv", "filename where problems are read from")
19+
flag.DurationVar(&timelimit, "time", 30*time.Second, "time to allot for quiz")
20+
}
21+
22+
23+
// problem represents a prompt and the correct response
24+
type problem struct {
25+
prompt string
26+
response string
27+
}
28+
29+
// printResults prints a prompt to the user explaining the results achieved.
30+
func printResults(nWrong, nTotal int) {
31+
nRight := nTotal - nWrong
32+
fmt.Printf("You answered %d out of %d questions correctly.\n", nRight, nTotal)
33+
}
34+
35+
// readFile reads from the file, sending back a slice of problems.
36+
func readFile() []problem {
37+
file, err := os.Open(filename)
38+
if err != nil {
39+
log.Fatal("Error opening file: %s", err.Error())
40+
}
41+
42+
r := csv.NewReader(file)
43+
records, err := r.ReadAll()
44+
45+
if err != nil {
46+
log.Fatal(err)
47+
}
48+
49+
// Construct a slice of problems for later consumption
50+
result := make([]problem, len(records))
51+
var i int
52+
for idx, rec := range records {
53+
if len(rec) < 2 {
54+
fmt.Println("Ignoring faulty or missing record from line %d", idx)
55+
continue
56+
}
57+
result[i] = problem{rec[0], rec[1]}
58+
i++
59+
}
60+
61+
return result
62+
}
63+
64+
func main() {
65+
flag.Parse()
66+
67+
problems := readFile()
68+
69+
var nWrong, nDone int
70+
// timeout is used to signal that the timer has elapsed
71+
timeout := make(chan bool, 2)
72+
timer := time.AfterFunc(timelimit, func() {
73+
timeout <- true // signal that a timeout occurred
74+
})
75+
76+
startTime := time.Now()
77+
// Ask each question and get a response
78+
for _, prob := range problems {
79+
fmt.Printf("%s\n > ", prob.prompt)
80+
81+
// A separate goroutine must be used to accept input from each question. At first I tried to just read from
82+
// Stdin on the main thread. That was a mistake. If you want to be able to interrupt the prompt when the time
83+
// expires, you need to have your main thread waiting on a response from either the timer or the answer thread.
84+
//
85+
// Lesson is to treat user input as an asynchronous message!
86+
answerCh := make(chan string)
87+
go func() {
88+
var answer string
89+
fmt.Scanf("%s\n", &answer)
90+
answerCh <- answer
91+
}()
92+
93+
select {
94+
case <-timeout:
95+
fmt.Println("\nTimes up!")
96+
nWrong += len(problems) - nDone // All remaining problems are marked wrong
97+
printResults(nWrong, len(problems))
98+
os.Exit(0)
99+
case response := <-answerCh:
100+
nDone++
101+
if prob.response != response {
102+
nWrong++
103+
}
104+
}
105+
}
106+
107+
if timer.Stop() {
108+
fmt.Printf("You completed the quiz with %s remaining\n", (timelimit - time.Since(startTime)).String())
109+
110+
printResults(nWrong, len(problems))
111+
}
112+
}

students/kalexmills/problems.csv

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
5+5,10
2+
7+3,10
3+
1+1,2
4+
8+3,11
5+
1+2,3
6+
8+6,14
7+
3+1,4
8+
1+4,5
9+
5+1,6
10+
2+3,5
11+
3+3,6
12+
2+4,6
13+
5+2,7

0 commit comments

Comments
 (0)