I’m new to go and working on a toy quiz cli program. I’m trying to implement a timer such that when the timer finishes, the quiz is over.
This is my initial implementation.
func StartTimer(quizFinished chan bool, timer *time.Timer) {
// Start timer
<-timer.C
fmt.Println("\nQuiz has ended")
quizFinished <- true
return
}
func askQuestions(timer *time.Timer, questions []string, answers []string) []string {
var input string
var userAnswers []string
quizFinished := make(chan bool)
fmt.Println("Starting Quiz...")
go StartTimer(quizFinished, timer)
for index, element := range questions {
select {
case <-quizFinished:
fmt.Println("Quiz has ended")
return userAnswers
default:
fmt.Printf("Question %d: %s? ", index+1, element)
fmt.Scanln(&input)
userAnswers = append(userAnswers, input)
}
}
return userAnswers
}
Edited implementation
package main
import (
"bufio"
"flag"
"fmt"
"log"
"os"
s "strings"
"time"
)
func readFileData(fileName string) (questions []string, answers []string) {
file, err := os.Open(fileName)
if err != nil {
log.Fatalf("failed reading data from file: %v", err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
questionAndAnswer := s.Split(scanner.Text(), ",")
question, answer := questionAndAnswer[0], questionAndAnswer[1]
questions = append(questions, question)
answers = append(answers, answer)
}
return questions, answers
}
func askQuestions(timer *time.Timer, questions []string) []string {
var input string
var userAnswers []string = make([]string, 0, len(questions))
quizFinished := make(chan bool, 1)
fmt.Println("Starting Quiz...")
go func() {
<-timer.C
fmt.Println("\nquiz over...")
quizFinished <- true
}()
for index, element := range questions {
fmt.Printf("Question %d: %s? ", index+1, element)
answerCh := make(chan string, 1)
go func() {
fmt.Scanln(&input)
answerCh <- input
}()
select {
case <-quizFinished:
return userAnswers
case answer := <-answerCh:
userAnswers = append(userAnswers, answer)
}
}
return userAnswers
}
func main() {
fileNamePtr := flag.String("filename", "problems.csv", "Specify a file name to read in")
quizTimePtr := flag.Int("time", 5, "Specify how long the quiz should take")
flag.Parse()
timer := time.NewTimer(time.Duration(*quizTimePtr) * time.Second)
questions, answers := readFileData(*fileNamePtr)
// var userAnswers = askQuestions(timer, questions, answers)
var userAnswers = askQuestions(timer, questions)
correctAnswers := 0
incorrectAnswers := 0
for i := 0; i < len(answers); i++ {
if userAnswers[i] == answers[i] {
correctAnswers++
} else {
incorrectAnswers++
}
}
grade := float32(correctAnswers) / float32(len((questions)))
fmt.Printf("Total Questions: %d \n", len(questions))
fmt.Printf("Total Correct Answers: %d \n", correctAnswers)
fmt.Printf("Total Incorrect Answers: %d \n", incorrectAnswers)
fmt.Printf("Grade: %.0f%% \n", grade*100)
}
I keep getting the following output and error:
Starting Quiz...
Question 1: 5+5? 10
Question 2: 1+1?
Quiz has ended
2
Returning from quiz...
panic: runtime error: index out of range [2] with length 2
goroutine 1 [running]:
I don’t understand channels and goroutines well enough yet to understand what is happening. The expected behavior is when the timer stops, it prints “Quiz has ended” sends a value into the quizFinished channel, which is read in the select statement. Another issue I ran into is that Scanln will wait for user input after the quiz has ended. Any help is appreciated!
When you call the Scanln
function in the main goroutine, it pauses and wait for user input. Even if your timer finish in another goroutine and try to notify the main goroutine, The main goroutine will not respond because it is blocked by ‘Scanner’.
This is why, even when the timer ends, Scanln
is still waiting for user input.
package main
import (
"fmt"
"time"
)
func askQuestions(timer *time.Timer, questions []string) []string {
var input string
var userAnswers []string
quizFinished := make(chan bool, 1)
fmt.Println("start quiz...")
go func() {
<-timer.C
fmt.Println("\nquiz over...")
quizFinished <- true
}()
for index, element := range questions {
fmt.Printf("question %d: %s? ", index+1, element)
answerCh := make(chan string, 1)
go func() {
fmt.Scanln(&input)
answerCh <- input
}()
select {
case <-quizFinished:
fmt.Println("exit quiz...")
return userAnswers
case answer := <-answerCh:
userAnswers = append(userAnswers, answer)
}
}
return userAnswers
}
func main() {
timer := time.NewTimer(10 * time.Second)
questions := []string{"5+5", "1+1", "8+3"}
responses := askQuestions(timer, questions)
fmt.Println("answer:", responses)
}
for i := 0; i < len(answers); i++ {
you are looping through all of the answers and comparing with the users answer – but if the user ran out of time thenlen(userAnswers)
<len(answers)
hence the error.