Different solution for Gophercise 1

This commit is contained in:
2025-05-31 13:14:20 -04:00
parent b22ab92d79
commit 1da6e395bf
56 changed files with 3374 additions and 0 deletions

16
1. Quiz Game Sol2/.gitignore vendored Normal file
View File

@@ -0,0 +1,16 @@
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
quiz

View File

@@ -0,0 +1,56 @@
# Exercise #1: Quiz Game
[![exercise status: released](https://img.shields.io/badge/exercise%20status-released-green.svg?style=for-the-badge)](https://gophercises.com/exercises/quiz)
## 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.
*Note: I didn't break this into multiple exercises like I do for some exercises because both of these combined should only take ~30m to cover in screencasts.*
### 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](https://golang.org/pkg/strings/) package.*
2. Add an option (a new flag) to shuffle the quiz order each time it is run.

98
1. Quiz Game Sol2/main.go Normal file
View File

@@ -0,0 +1,98 @@
package main
import (
"encoding/csv"
"flag"
"fmt"
"log"
"os"
"strconv"
"strings"
"time"
)
type Question struct {
question string
correctAnswer string
}
func readCSV(filenName string) [][]string {
log.Println("Opening CSV file from filepath", filenName)
f, err := os.Open(filenName)
if err != nil {
log.Fatal("%+v", err)
}
defer f.Close()
csvReader := csv.NewReader(f)
records, err := csvReader.ReadAll()
if err != nil {
log.Fatal("%+v", err)
}
return records
}
func timer(seconds int, c chan int) {
time.Sleep(time.Second * time.Duration(seconds))
c <- -1
}
func getQuestionsFromCSV(fileName string) []Question {
records := readCSV(fileName)
var questions []Question
for _, record := range records {
question := Question{record[0], record[1]}
questions = append(questions, question)
}
return questions
}
func strip(s string) string {
return strings.ReplaceAll(s, " ", "")
}
func askQuestion(question Question, c chan int) {
var userAnswer int
fmt.Printf("%s? :", question.question)
fmt.Scan(&userAnswer)
fmt.Print("\n")
userAnswerString := strconv.Itoa(userAnswer)
if userAnswerString == question.correctAnswer {
fmt.Println("Answer is Correct!")
c <- 1
} else {
fmt.Println("Answer is Incorrect :(")
c <- 0
}
}
func main() {
csvFileName := flag.String("fileName", "problems.csv", "file name string")
timeLimit := flag.Int("timeLimit", 30, "time in seconds")
flag.Parse()
questions := getQuestionsFromCSV(*csvFileName)
score := 0
c := make(chan int)
go timer(*timeLimit, c)
for _, question := range questions {
go askQuestion(question, c)
event := <-c
if event == -1 {
fmt.Printf("\n\nOops! Time is up!\n Final Score: %d\n", score)
os.Exit(0)
} else {
score += event
fmt.Printf("Score: %d\n", score)
}
}
}

View File

@@ -0,0 +1,12 @@
5+5,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
1 5+5 10
2 1+1 2
3 8+3 11
4 1+2 3
5 8+6 14
6 3+1 4
7 1+4 5
8 5+1 6
9 2+3 5
10 3+3 6
11 2+4 6
12 5+2 7

View File

@@ -0,0 +1,8 @@
# Student Examples
The following are example implementations submitted by students/gophers learning from this course.
The primary purposes of these examples are:
1. To provide example implementations to discuss and review when recording the screencasts for this course.
2. To provide a way for students to contribute to this course.

View File

@@ -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
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

View File

@@ -0,0 +1,131 @@
package quiz
import (
"bufio"
"encoding/csv"
"flag"
"fmt"
"io"
"log"
"os"
"strings"
"time"
)
//Number of Questions to ask
const totalQuestions = 5
//Question struct that stores question with answer
type Question struct {
question string
answer string
}
func main() {
filename, timeLimit := readArguments()
f, err := openFile(filename)
if err != nil {
return
}
questions, err := readCSV(f)
if err != nil {
// err := fmt.Errorf("Error in Reading Questions")
fmt.Println(err.Error())
return
}
if questions == nil {
return
}
score, err := askQuestion(questions, timeLimit)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("Your Score %d/%d\n", score, totalQuestions)
}
func readArguments() (string, int) {
filename := flag.String("filename", "problem.csv", "CSV File that conatins quiz questions")
timeLimit := flag.Int("limit", 30, "Time Limit for each question")
flag.Parse()
return *filename, *timeLimit
}
func readCSV(f io.Reader) ([]Question, error) {
// defer f.Close() // this needs to be after the err check
allQuestions, err := csv.NewReader(f).ReadAll()
if err != nil {
return nil, err
}
numOfQues := len(allQuestions)
if numOfQues == 0 {
return nil, fmt.Errorf("No Question in file")
}
var data []Question
for _, line := range allQuestions {
ques := Question{}
ques.question = line[0]
ques.answer = line[1]
data = append(data, ques)
}
return data, nil
}
func openFile(filename string) (io.Reader, error) {
return os.Open(filename)
}
func getInput(input chan string) {
for {
in := bufio.NewReader(os.Stdin)
result, err := in.ReadString('\n')
if err != nil {
log.Fatal(err)
}
input <- result
}
}
func askQuestion(questions []Question, timeLimit int) (int, error) {
totalScore := 0
timer := time.NewTimer(time.Duration(timeLimit) * time.Second)
done := make(chan string)
go getInput(done)
for i := range [totalQuestions]int{} {
ans, err := eachQuestion(questions[i].question, questions[i].answer, timer.C, done)
if err != nil && ans == -1 {
return totalScore, nil
}
totalScore += ans
}
return totalScore, nil
}
func eachQuestion(Quest string, answer string, timer <-chan time.Time, done <-chan string) (int, error) {
fmt.Printf("%s: ", Quest)
for {
select {
case <-timer:
return -1, fmt.Errorf("Time out")
case ans := <-done:
score := 0
if strings.Compare(strings.Trim(strings.ToLower(ans), "\n"), answer) == 0 {
score = 1
} else {
return 0, fmt.Errorf("Wrong Answer")
}
return score, nil
}
}
}

View File

@@ -0,0 +1,59 @@
package quiz
import (
"strings"
"testing"
"time"
"gotest.tools/assert"
)
func testEachQuestion(t *testing.T) {
timer := time.NewTimer(time.Duration(2) * time.Second).C
done := make(chan string)
var quest Question
quest.question = "1+1"
quest.answer = "2"
var ans int
var err error
allDone := make(chan bool)
go func() {
ans, err = eachQuestion(quest.question, quest.answer, timer, done)
allDone <- true
}()
done <- "2"
<-allDone
if err != nil {
t.Error(err)
}
assert.Equal(t, ans, 1)
}
func testReadCSV(t *testing.T) {
str := "1+1,2\n2+1,3\n9+9,18\n"
quest, err := readCSV(strings.NewReader(str))
if err != nil {
t.Error(err)
}
var que [3]Question
que[0].answer = "2"
que[1].answer = "3"
que[2].answer = "18"
que[0].question = "1+1"
que[1].question = "2+1"
que[2].question = "9+9"
assert.Equal(t, que[0], quest[0])
assert.Equal(t, que[1], quest[1])
assert.Equal(t, que[2], quest[2])
}
func TestEachQuestion(t *testing.T) {
t.Run("test eachQuestion", testEachQuestion)
}
func TestReadCSV(t *testing.T) {
t.Run("test ReadCSV", testReadCSV)
}

View File

@@ -0,0 +1,137 @@
package main
import (
"bufio"
"context"
"encoding/csv"
"fmt"
"io"
"log"
"os"
"strings"
"time"
"github.com/fatih/color"
)
const timeToAnswer = 5 * time.Second
type quiz struct {
challenge, response string
}
func (q *quiz) ask(timeout time.Duration, lines <-chan string, roundOver chan<- struct{}) bool {
color.Set(color.FgGreen)
fmt.Print("> ")
color.Unset()
fmt.Println(q.challenge)
select {
case line := <-lines:
return clean(line) == clean(q.response)
case <-time.After(timeout):
color.Set(color.FgRed)
fmt.Println("Out of time")
color.Unset()
roundOver <- struct{}{}
return false
}
}
func main() {
if len(os.Args) != 2 {
fmt.Println(`USAGE: go run main.go <CSV_FILE>`)
fmt.Println(len(os.Args))
return
}
qs, err := readCSV(os.Args[1])
if err != nil {
fmt.Printf("Failed to read file %s: %v\n", os.Args[1], err)
return
}
lines := make(chan string)
roundOver := make(chan struct{})
ctx, cancel := context.WithTimeout(context.Background(), timeToAnswer*time.Duration(len(qs)))
go listenForUserInput(ctx, bufio.NewReader(os.Stdin), lines, roundOver)
good := 0
for _, q := range qs {
if q.ask(timeToAnswer, lines, roundOver) {
good++
}
}
cancel()
fmt.Printf("Answered %d/%d questions.\n", good, len(qs))
}
func clean(a string) string {
return strings.TrimSpace(strings.ToLower(a))
}
func listenForUserInput(ctx context.Context, r io.RuneReader, lines chan<- string, roundOver <-chan struct{}) {
inputRunes := []rune{}
newline := false
for {
select {
case <-ctx.Done():
close(lines)
return
case <-roundOver:
inputRunes = inputRunes[:0]
newline = false
default:
if newline {
lines <- string(inputRunes)
inputRunes = inputRunes[:0]
newline = false
}
run, _, err := r.ReadRune()
if err != nil {
log.Fatalln("Couldn't read rune:", err)
}
if run == '\n' {
newline = true
} else {
inputRunes = append(inputRunes, run)
}
}
}
}
func readCSV(filename string) ([]quiz, error) {
f, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("couldn't open file: %v", err)
}
defer f.Close() // nolint
r := csv.NewReader(f)
out := []quiz{}
for {
record, err := r.Read()
if err == io.EOF {
break
}
if err != nil {
return nil, fmt.Errorf("error while reading CSV record: %v", err)
}
if len(record) != 2 {
return nil, fmt.Errorf("unexpected number of fields for record: %v", record)
}
out = append(out, quiz{record[0], record[1]})
}
return out, nil
}

View File

@@ -0,0 +1,97 @@
package main
import (
"bufio"
"encoding/csv"
"flag"
"fmt"
"io"
"log"
"os"
"time"
)
func fatalError(message string, err error) {
if err != nil {
log.Fatalln(message, ":", err)
}
}
type question struct {
question string
answer string
}
type quiz struct {
answered int
answeredCorrectly int
questions []question
}
// loadQuiz loads all questions into memory, assumed to be safe as
// the instructions state that the quiz will be < 100 questions
func loadQuiz(filePath string) *quiz {
csvFile, err := os.Open(filePath)
fatalError("Error opening quiz CSV file", err)
defer csvFile.Close()
reader := csv.NewReader(bufio.NewReader(csvFile))
var quiz quiz
for {
line, err := reader.Read()
if err == io.EOF {
break
}
fatalError("Error parsing CSV", err)
question := question{line[0], line[1]}
quiz.questions = append(quiz.questions, question)
}
return &quiz
}
// run prints questions, check answers, and records total
// answered and total correct
func (quiz *quiz) run() {
timer := time.NewTimer(time.Duration(*timeLimit) * time.Second)
quizLoop:
for _, question := range quiz.questions {
fmt.Println(question.question)
answerCh := make(chan string)
go func() {
scanner.Scan()
answer := scanner.Text()
answerCh <- answer
}()
select {
case <-timer.C:
break quizLoop
case answer := <-answerCh:
if answer == question.answer {
quiz.answeredCorrectly++
}
quiz.answered++
}
}
return
}
// report prints summary of quiz performance
func (quiz *quiz) report() {
fmt.Printf(
"You answered %v questions out of a total of %v and got %v correct",
quiz.answered,
len(quiz.questions),
quiz.answeredCorrectly,
)
}
var (
scanner = bufio.NewScanner(os.Stdin)
filePathPtr = flag.String("file", "./problems.csv", "Path to csv file containing quiz.")
timeLimit = flag.Int64("time-limit", 30, "Set the total time in seconds allowed for the quiz.")
)
func main() {
quiz := loadQuiz(*filePathPtr)
quiz.run()
quiz.report()
}

View File

@@ -0,0 +1,88 @@
package main
import (
"bufio"
"encoding/csv"
"flag"
"fmt"
"log"
"os"
"strings"
"time"
)
var (
// set flags
csvPath = flag.String("csv", "problems.csv", "a csv file in the format of 'question,answer'")
limit = flag.Int("limit", 30, "the time limit for the quiz in seconds'")
)
func main() {
// parse the flags
flag.Parse()
// open the csv file
file, err := os.Open(*csvPath)
if err != nil {
log.Println(err)
return
}
defer file.Close()
// open the csv file
csvReader := csv.NewReader(file)
// parse the csv file
csvData, err := csvReader.ReadAll()
if err != nil {
log.Println(err)
return
}
// put question/answer pairs into a map where questions are keys and answers are values
qaPair := make(map[string]string, len(csvData))
for _, data := range csvData {
qaPair[data[0]] = data[1]
}
// create a ticker for the time limit and a channel to signal the user finished the quiz
ticker := time.NewTicker(time.Second * time.Duration(*limit))
done := make(chan bool)
// create a scanner for user input
scanner := bufio.NewScanner(os.Stdin)
var userAnswer string
qNum, numCorrect := 0, 0
go func() {
// iteration order for maps in go is randomized so the questions won't be in the same order every time
for question, answer := range qaPair {
qNum++
// ask a question
fmt.Printf("Problem #%d: %s = ", qNum, question)
// get an answer
scanner.Scan()
userAnswer = scanner.Text()
// trim leading and trailing whitespace
userAnswer = strings.TrimSpace(userAnswer)
userAnswer = strings.ToLower(userAnswer)
answer = strings.ToLower(answer)
// check the answer
if answer == userAnswer {
numCorrect++
}
}
done <- true
}()
// select chooses the first channel with an available value
// if done is available first, the user finished
// if ticker is available first, the time limit has been reached
select {
case <-done:
case <-ticker.C:
fmt.Println("time's up!")
}
// print the results
fmt.Printf("You scored %d out of %d.\n", numCorrect, len(qaPair))
}

View File

@@ -0,0 +1,12 @@
5+5,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
1 5+5 10
2 1+1 2
3 8+3 11
4 1+2 3
5 8+6 14
6 3+1 4
7 1+4 5
8 5+1 6
9 2+3 5
10 3+3 6
11 2+4 6
12 5+2 7

View File

@@ -0,0 +1,155 @@
package main
import (
"bufio"
"encoding/csv"
"flag"
"fmt"
"io"
"log"
"math/rand"
"os"
"strings"
"time"
)
type problem struct {
question string
answer string
}
var (
problemsFile = flag.String("problems", "problems.csv", "A CSV file containing problems and their solutions")
quizTime = flag.Int("time", 30, "The time in seconds this quiz will run")
shuffle = flag.Bool("shuffle", false, "Wheteher or not to shuffle the problems")
osR = bufio.NewReader(os.Stdin)
)
func readProblems(csvFile *os.File) []problem {
csvR := csv.NewReader(csvFile)
problems := make([]problem, 0)
for {
record, err := csvR.Read()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
if record[0] != "" && record[1] != "" {
problems = append(problems, problem{record[0], strings.ToLower(record[1]})
}
}
return problems
}
func shuffleProblems(problems []problem) {
r := rand.New(rand.NewSource(time.Now().Unix()))
for i1 := 0; i1 < len(problems); i1++ {
i2 := r.Intn(len(problems))
problem1 := problems[i1]
problem2 := problems[i2]
problems[i1] = problem2
problems[i2] = problem1
}
}
func askQuestion(q string, tries int8) string {
fmt.Printf("\n%s: ", q)
input, err := osR.ReadString('\n')
if err != nil {
if tries < 5 {
log.Println("Your answer could not be processed, please try again")
return askQuestion(q, tries+1)
}
log.Fatal("Something is wrong with this program, going to exit...")
}
return strings.ToLower(strings.TrimSpace(strings.TrimRight(input, "\n")))
}
func askQuestions(problems []problem, timer, correctAnswersChan, done chan interface{}) {
index := 0
for {
select {
case <-timer:
fmt.Println("\nTime's up!")
return
default:
if index >= len(problems) {
close(done)
return
}
problem := problems[index]
answer := askQuestion(problem.question, 0)
if answer == problem.answer {
correctAnswersChan <- true
fmt.Println("Correct!")
} else {
fmt.Println("False...")
}
index = index + 1
}
}
}
func main() {
flag.Parse()
if !strings.HasSuffix(*problemsFile, "csv") {
log.Fatalf("Provided problems file '%s' is not a CSV file", *problemsFile)
}
csvFile, err := os.Open(*problemsFile)
if err != nil {
log.Fatalf("Could not open '%s'", *problemsFile)
}
problems := readProblems(csvFile)
totalProblems := len(problems)
if *shuffle {
shuffleProblems(problems)
}
timer := make(chan interface{})
correctAnswersChan := make(chan interface{})
done := make(chan interface{})
fmt.Println("Press ENTER to start the quiz...")
osR.ReadString('\n')
correctAnswers := 0
go func() {
for {
select {
case _ = <-correctAnswersChan:
correctAnswers = correctAnswers + 1
case <-done:
return
}
}
}()
go askQuestions(problems, timer, correctAnswersChan, done)
time.Sleep(time.Duration(*quizTime) * time.Second)
close(timer)
close(done)
if totalProblems == correctAnswers {
fmt.Println("\nCongratulations! You answered all questions correctly!")
} else {
fmt.Printf(
"\nYou answered %d questions correctly but failed to do so for %d questions, try again",
correctAnswers,
totalProblems-correctAnswers,
)
}
os.Exit(0)
}

View File

@@ -0,0 +1,12 @@
5+5,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
1 5+5 10
2 1+1 2
3 8+3 11
4 1+2 3
5 8+6 14
6 3+1 4
7 1+4 5
8 5+1 6
9 2+3 5
10 3+3 6
11 2+4 6
12 5+2 7

View File

@@ -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
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

View File

@@ -0,0 +1,87 @@
package main
import (
"bufio"
"encoding/csv"
"errors"
"flag"
"fmt"
"io"
"log"
"os"
"strings"
"time"
)
// DEFAULTFILE is the quiz file expected to be load by default
const DEFAULTFILE = "problems.csv"
func main() {
// Flag block
file := flag.String("f", DEFAULTFILE, "specify the path to file")
timeout := flag.Int("t", 0, "specify the number of seconds for timeout")
flag.Parse()
// Failed if the number of seconds is negative
if *timeout < 0 {
flag.PrintDefaults()
os.Exit(1)
}
// Open file
f, err := os.Open(*file)
if err != nil {
log.Fatal(err)
}
defer f.Close()
// Run the main logic
total, correct, err := run(f, *timeout)
if err != nil {
log.Println(err)
}
fmt.Printf("Number of questions: %v\nNumber of correct answers: %v\n", total, correct)
}
// run is the main function to execute quiz app
func run(qinput io.Reader, timeout int) (total, correct int, err error) {
// Reading csv file and parse it to the records var
r := csv.NewReader(qinput)
records, err := r.ReadAll()
if err != nil {
return total, correct, err
}
// Two channels. One for answers, another for timeout
answerChan := make(chan string)
timerChan := make(chan time.Time, 1)
// Iterate over the records
for _, record := range records {
question, expected := record[0], record[1]
fmt.Printf("Question: %v. Answer: ", question)
total++
// Listening for input in the separate goroutine
go getAnswer(answerChan)
// Run the timer in separate goroutine if the timeout is specified
if timeout > 0 {
go func() { timerChan <- <-time.After(time.Duration(timeout) * time.Second) }()
}
// Main select block
select {
case answer := <-answerChan:
if answer == expected {
correct++
}
case <-timerChan:
fmt.Println()
return total, correct, errors.New("Timeout reached!")
}
}
return total, correct, nil
}
// getAnswer func is for listening input from user
func getAnswer(answerChan chan string) {
reader := bufio.NewReader(os.Stdin)
answer, err := reader.ReadString('\n')
if err != nil {
log.Fatal(err)
}
answerChan <- strings.Replace(answer, "\n", "", -1)
}

View File

@@ -0,0 +1,85 @@
package main
import (
"encoding/csv"
"flag"
"fmt"
"os"
)
type quiz struct {
total int
correct int
incorrect int
items []*item
}
type item struct {
question string
answer string
got string
correct bool
}
func main() {
file := flag.String("file", "problems.csv", "file to parse the quiz")
flag.Parse()
qz, err := load(*file)
if err != nil {
fmt.Println(err.Error())
return
}
start(qz)
score(qz)
}
// load loads the values of the file and return a new quiz
func load(file string) (*quiz, error) {
f, err := os.OpenFile(file, os.O_RDONLY, 0666)
if err != nil {
return nil, err
}
defer f.Close()
r := csv.NewReader(f)
all, err := r.ReadAll()
if err != nil {
return nil, err
}
qz := new(quiz)
qz.total = len(all)
for _, items := range all {
i := item{question: items[0], answer: items[1]}
qz.items = append(qz.items, &i)
}
return qz, nil
}
// score prints the quiz score
func score(qz *quiz) {
fmt.Printf("total: %v correct: %v incorrect: %v\n", qz.total, qz.correct, qz.incorrect)
fmt.Println("Incorrect questions")
for _, q := range qz.items {
if !q.correct {
fmt.Printf("%v answer: %v got: %v\n", q.question, q.answer, q.got)
}
}
}
// start starts the quiz
func start(qz *quiz) {
fmt.Printf("interactive mode (%v questions in quiz)\n", qz.total)
for i, item := range qz.items {
var op = ""
fmt.Printf("%v) %v?: ", i+1, item.question)
fmt.Scanln(&op)
item.got = op
if op == item.answer {
qz.correct++
item.correct = true
continue
}
qz.incorrect++
}
}

View File

@@ -0,0 +1,4 @@
2x2,4
5x6,30
8x2,16
9x6,54
1 2x2 4
2 5x6 30
3 8x2 16
4 9x6 54

View File

@@ -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
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

View File

@@ -0,0 +1,13 @@
5+5,10
7+3,10
1+1,2
8+3,11
1+2,3
"Surname of 9th President of Turkey","Demirel"
3+1,4
1+4,5
5+1,6
"First name of first woman PM of UK","Margaret"
3+3,6
2+4,6
5+2,7
1 5+5 10
2 7+3 10
3 1+1 2
4 8+3 11
5 1+2 3
6 Surname of 9th President of Turkey Demirel
7 3+1 4
8 1+4 5
9 5+1 6
10 First name of first woman PM of UK Margaret
11 3+3 6
12 2+4 6
13 5+2 7

View File

@@ -0,0 +1,60 @@
package main
import (
"encoding/csv"
"flag"
"fmt"
"io/ioutil"
"log"
"path/filepath"
"strings"
"time"
)
func startTime(timer time.Timer, duration time.Duration) {
<-timer.C
fmt.Println(duration, "doldu")
}
func main() {
filename := flag.String("file", "problems.csv", "Questions file")
timelimit := flag.Int("time", 30, "Time limit of quiz in seconds")
flag.Parse()
filePath, pathErr := filepath.Abs(*filename)
if pathErr != nil {
log.Fatal("file not found. check if it exists again.")
}
remainTime := time.Duration(*timelimit) * time.Second
fmt.Println("Press return to start time (", remainTime, ")")
fmt.Scanln() //and time starts
timer := time.NewTimer(remainTime)
go startTime(*timer, remainTime)
csvF, csvErr := ioutil.ReadFile(filePath)
if csvErr != nil {
log.Fatal("error reading file: " + csvErr.Error())
}
probs, probErr := csv.NewReader(strings.NewReader(string(csvF))).ReadAll()
if probErr != nil {
log.Fatal("error on CSV format: " + probErr.Error())
}
var answer string
var trues, total int
for i, soru := range probs {
fmt.Printf("%d. soru: %s = ", i+1, soru[0])
fmt.Scan(&answer)
if answer == soru[1] {
trues++
}
total++
}
fmt.Printf("True answers: %d\n", trues)
fmt.Printf("Total questions: %d\n", total)
}

Binary file not shown.

View File

@@ -0,0 +1,120 @@
package main
import (
"encoding/csv"
"flag"
"fmt"
"io"
"log"
"os"
"time"
quiz "github.com/gophercises/quiz/students/hackeryarn/myquiz"
"github.com/gophercises/quiz/students/hackeryarn/problem"
)
const (
// FileFlag is used to set a file for the questions
FileFlag = "file"
// FileFlagValue is the value used when no FileFlag is provided
FileFlagValue = "problems.csv"
// FileFlagUsage is the help string for the FileFlag
FileFlagUsage = "Questions file"
// TimerFlag is used for setting a timer for the quiz
TimerFlag = "timer"
// TimerFlagValue is the value used when no TimerFlag is provided
TimerFlagValue = 30
// TimerFlagUsage is the help string for the TimerFlag
TimerFlagUsage = "Amount of seconds the quiz will allow"
)
// Flagger configures the flags used
type Flagger interface {
StringVar(p *string, name, value, usage string)
IntVar(p *int, name string, value int, usage string)
}
type quizFlagger struct{}
func (q *quizFlagger) StringVar(p *string, name, value, usage string) {
flag.StringVar(p, name, value, usage)
}
func (q *quizFlagger) IntVar(p *int, name string, value int, usage string) {
flag.IntVar(p, name, value, usage)
}
// Timer is used to start a timer
type Timer interface {
NewTimer(d time.Duration) *time.Timer
}
type quizTimer struct{}
func (q quizTimer) NewTimer(d time.Duration) *time.Timer {
return time.NewTimer(d)
}
// ReadCSV parses the CSV file into a Problem struct
func ReadCSV(reader io.Reader) quiz.Quiz {
csvReader := csv.NewReader(reader)
problems := []problem.Problem{}
for {
record, err := csvReader.Read()
if err == io.EOF {
break
} else if err != nil {
log.Fatalln("Error reading CSV:", err)
}
problems = append(problems, problem.New(record))
}
return quiz.New(problems)
}
// TimerSeconds is the amount of time allowed for the quiz
var TimerSeconds int
var file string
// ConfigFlags sets all the flags used by the application
func ConfigFlags(f Flagger) {
f.StringVar(&file, FileFlag, FileFlagValue, FileFlagUsage)
f.IntVar(&TimerSeconds, TimerFlag, TimerFlagValue, TimerFlagUsage)
}
// StartTimer begins a timer once the user provides input
func StartTimer(w io.Writer, r io.Reader, timer Timer) *time.Timer {
fmt.Fprint(w, "Ready to start?")
fmt.Fscanln(r)
return timer.NewTimer(time.Second * time.Duration(TimerSeconds))
}
func init() {
flagger := &quizFlagger{}
ConfigFlags(flagger)
flag.Parse()
}
func main() {
file, err := os.Open(file)
if err != nil {
log.Fatalln("Could not open file", err)
}
quiz := ReadCSV(file)
timer := StartTimer(os.Stdout, os.Stdin, quizTimer{})
go func() {
<-timer.C
fmt.Println("")
quiz.PrintResults(os.Stdout)
os.Exit(0)
}()
quiz.Run(os.Stdout, os.Stdin)
}

View File

@@ -0,0 +1,40 @@
package quiz
import (
"fmt"
"io"
"github.com/gophercises/quiz/students/hackeryarn/problem"
)
// Quiz represents the quiz to be given to the user
type Quiz struct {
problems []problem.Problem
rightAnswers int
}
// Run runs the quiz for all the problems keeping track of correct answers
func (q *Quiz) Run(w io.Writer, r io.Reader) {
for _, problem := range q.problems {
problem.AskQuestion(w)
correct := problem.CheckAnswer(r)
if correct {
q.rightAnswers++
}
}
q.PrintResults(w)
}
// PrintResults outputs the results of the quiz
func (q Quiz) PrintResults(w io.Writer) {
fmt.Fprintf(w, "You got %d questions right!\n", q.rightAnswers)
}
// New creates a new quiz from the supplied slice of problems
func New(problems []problem.Problem) Quiz {
return Quiz{
problems: problems,
rightAnswers: 0,
}
}

View File

@@ -0,0 +1,66 @@
package quiz
import (
"bytes"
"io"
"reflect"
"strings"
"testing"
"github.com/gophercises/quiz/students/hackeryarn/problem"
)
func TestNew(t *testing.T) {
problems := sampleProblems()
want := Quiz{problems: problems, rightAnswers: 0}
got := New(problems)
if !reflect.DeepEqual(want, got) {
t.Errorf("expeted to create quiz %v got %v", want, got)
}
}
func TestRun(t *testing.T) {
t.Run("it runs the quiz", func(t *testing.T) {
buffer := &bytes.Buffer{}
quiz := createQuiz()
runQuiz(buffer, &quiz)
expectedResults := 2
results := quiz.rightAnswers
if expectedResults != results {
t.Errorf("expected right answers of %v, got %v",
expectedResults, results)
}
expectedOutput := "7+3: 1+1: You got 2 questions right!\n"
if buffer.String() != expectedOutput {
t.Errorf("expected full output %v, got %v",
expectedOutput, buffer)
}
})
}
func sampleProblems() []problem.Problem {
record1 := []string{"7+3", "10"}
record2 := []string{"1+1", "2"}
return []problem.Problem{
problem.New(record1),
problem.New(record2),
}
}
func createQuiz() Quiz {
problems := sampleProblems()
return New(problems)
}
func runQuiz(buffer io.Writer, quiz *Quiz) {
answers := strings.NewReader("10\n2\n")
quiz.Run(buffer, answers)
}

View File

@@ -0,0 +1,49 @@
package problem
import (
"fmt"
"io"
"log"
"strings"
)
// Problem represents a single question answer pair
type Problem struct {
question string
answer string
}
// CheckAnswer checks the answer against the provided input
func (p Problem) CheckAnswer(r io.Reader) bool {
answer := readAnswer(r)
if answer != p.answer {
return false
}
return true
}
func readAnswer(r io.Reader) (answer string) {
_, err := fmt.Fscanln(r, &answer)
if err != nil {
log.Fatalln("Error reading in answer", err)
}
return strings.TrimSpace(answer)
}
// AskQuestion prints out the question
func (p Problem) AskQuestion(w io.Writer) {
_, err := fmt.Fprintf(w, "%s: ", p.question)
if err != nil {
log.Fatalln("Could not ask the question", err)
}
}
// New creates a Problem from a provided CSV record
func New(record []string) Problem {
return Problem{
question: record[0],
answer: record[1],
}
}

View File

@@ -0,0 +1,68 @@
package problem
import (
"bytes"
"testing"
)
func TestNew(t *testing.T) {
record := []string{"question", "answer"}
want := Problem{"question", "answer"}
got := New(record)
if got != want {
t.Errorf("expected to create problem %v got %v", want, got)
}
}
func TestCheckAnswer(t *testing.T) {
problem := createProblem()
t.Run("it checks the correct answer", func(t *testing.T) {
answer := getAnswer(problem, "10\n")
checkAnswer(t, answer, true)
})
t.Run("it checks incorrect answer", func(t *testing.T) {
answer := getAnswer(problem, "2\n")
checkAnswer(t, answer, false)
})
}
func TestAskQuestion(t *testing.T) {
problem := createProblem()
t.Run("it asks the question", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
problem.AskQuestion(buffer)
want := "7+3: "
got := buffer.String()
if want != got {
t.Errorf("Expected question %s, got %s", want, got)
}
})
}
func createProblem() Problem {
record := []string{"7+3", "10"}
return New(record)
}
func getAnswer(problem Problem, input string) bool {
r := bytes.NewBufferString(input)
return problem.CheckAnswer(r)
}
func checkAnswer(t *testing.T, got, want bool) {
if want != got {
t.Errorf("Expected to return %v got %v", want, got)
}
}

View File

@@ -0,0 +1,12 @@
5+5,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
1 5+5 10
2 1+1 2
3 8+3 11
4 1+2 3
5 8+6 14
6 3+1 4
7 1+4 5
8 5+1 6
9 2+3 5
10 3+3 6
11 2+4 6
12 5+2 7

View File

@@ -0,0 +1,136 @@
package main
import (
"bytes"
"reflect"
"testing"
"time"
quiz "github.com/gophercises/quiz/students/hackeryarn/myquiz"
"github.com/gophercises/quiz/students/hackeryarn/problem"
)
type flaggerMock struct {
stringVarCalls int
intVarCalls int
varNames []string
varUsages []string
varStringValues []string
varIntValues []int
}
func (f *flaggerMock) StringVar(p *string, name, value, usage string) {
f.stringVarCalls++
f.varNames = append(f.varNames, name)
f.varStringValues = append(f.varStringValues, value)
f.varUsages = append(f.varUsages, usage)
}
func (f *flaggerMock) IntVar(p *int, name string, value int, usage string) {
f.intVarCalls++
f.varNames = append(f.varNames, name)
f.varIntValues = append(f.varIntValues, value)
f.varUsages = append(f.varUsages, usage)
}
type timerMock struct {
duration int
}
func (t *timerMock) NewTimer(d time.Duration) *time.Timer {
t.duration = int(d.Seconds())
return time.NewTimer(1 * time.Millisecond)
}
func TestReadCSV(t *testing.T) {
input := "7+3,10\n1+1,2"
reader := bytes.NewBufferString(input)
record1 := []string{"7+3", "10"}
record2 := []string{"1+1", "2"}
problems := []problem.Problem{
problem.New(record1),
problem.New(record2),
}
want := quiz.New(problems)
got := ReadCSV(reader)
if !reflect.DeepEqual(want, got) {
t.Errorf("it should read in %v got %v", want, got)
}
}
func TestConfigFlags(t *testing.T) {
flagger := &flaggerMock{}
ConfigFlags(flagger)
assertStringCalls(t, flagger)
assertIntCalls(t, flagger)
assertFlags(t, flagger)
}
func TestStartTimer(t *testing.T) {
timer := &timerMock{}
w := &bytes.Buffer{}
r := bytes.NewBufferString("\n")
TimerSeconds := 30
StartTimer(w, r, timer)
if timer.duration != TimerSeconds {
t.Errorf("it should set timer for %d seconds, set for %d",
TimerSeconds, timer.duration)
}
if w.String() != "Ready to start?" {
t.Errorf("it should ask user if the user is ready, got %s", w.String())
}
}
func assertStringCalls(t *testing.T, flagger *flaggerMock) {
t.Helper()
if flagger.stringVarCalls != 1 {
t.Errorf("it should call StringVar %d times, called %d",
1, flagger.stringVarCalls)
}
}
func assertIntCalls(t *testing.T, flagger *flaggerMock) {
t.Helper()
if flagger.intVarCalls != 1 {
t.Errorf("it should call IntVar %d times, called %d",
1, flagger.intVarCalls)
}
}
func assertFlags(t *testing.T, flagger *flaggerMock) {
t.Helper()
expectedNames := []string{FileFlag, TimerFlag}
expectedUsages := []string{FileFlagUsage, TimerFlagUsage}
expectedStringValues := []string{FileFlagValue}
expectedIntValues := []int{TimerFlagValue}
if !reflect.DeepEqual(expectedNames, flagger.varNames) {
t.Errorf("it should setup flag names to be %v, got %v",
expectedNames, flagger.varNames)
}
if !reflect.DeepEqual(expectedUsages, flagger.varUsages) {
t.Errorf("it should setup flag usages to be %v, got %v",
expectedUsages, flagger.varUsages)
}
if !reflect.DeepEqual(expectedStringValues, flagger.varStringValues) {
t.Errorf("it should setup string values to be %v, got %v",
expectedStringValues, flagger.varStringValues)
}
if !reflect.DeepEqual(expectedIntValues, flagger.varIntValues) {
t.Errorf("it should setup int values to be %v, got %v",
expectedIntValues, flagger.varIntValues)
}
}

View File

@@ -0,0 +1,81 @@
package main
import (
"bufio"
"flag"
"fmt"
"os"
"strings"
"time"
)
type Problem struct {
question string
answer string
}
type Quiz struct {
problems []Problem
score int
}
type Settings struct {
filename *string
timeLimit *int
}
func main() {
quiz := Quiz{}
settings := Settings{}
settings.filename = flag.String("csv", "problems.csv", "a csv file in the format of 'question,answer'")
settings.timeLimit = flag.Int("limit", 30, "the time limit for the quiz in seconds")
// Get the flags (if any).
flag.Parse()
// Create a file handle for the file.
file, err := os.Open(*settings.filename)
// If there was an error opening the file, exit.
if err != nil {
panic(err)
}
defer file.Close()
// Create a buffered reader to read the file input.
fin := bufio.NewScanner(file)
// Read the problems from the comma-separated values file.
for fin.Scan() {
line := strings.Split(fin.Text(), ",")
problem := Problem{question: line[0], answer: line[1]}
quiz.problems = append(quiz.problems, problem)
}
// Create a timer to enforce a time limit.
timer := time.NewTimer(time.Second * time.Duration(*settings.timeLimit))
defer timer.Stop()
go func() {
<-timer.C
fmt.Printf("\nYou scored %d out of %d.", quiz.score, len(quiz.problems))
}()
// Quiz the user.
for i, problem := range quiz.problems {
fmt.Printf("Problem #%d: %s = ", (i + 1), problem.question)
var input string
fmt.Scan(&input)
if input == problem.answer {
quiz.score++
}
}
// Print the user's results.
fmt.Printf("You scored %d out of %d.", quiz.score, len(quiz.problems))
}

View File

@@ -0,0 +1,12 @@
5+5,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
1 5+5 10
2 1+1 2
3 8+3 11
4 1+2 3
5 8+6 14
6 3+1 4
7 1+4 5
8 5+1 6
9 2+3 5
10 3+3 6
11 2+4 6
12 5+2 7

View File

@@ -0,0 +1,73 @@
package main
import (
"bufio"
"encoding/csv"
"flag"
"fmt"
"io"
"log"
"os"
"strconv"
"time"
)
type Line struct {
Question string
Answer string
}
var (
csvName = flag.String("csv", "problems.csv", "path to csv file with quiz(question,answer)")
limit = flag.Int("limit", 30, "the time limit for the quiz in seconds")
)
func main() {
flag.Parse()
csvFile, err := os.Open(*csvName)
if err != nil {
fmt.Println(err)
return
}
defer csvFile.Close()
csvReader := csv.NewReader(bufio.NewReader(csvFile))
var lines []Line
for {
line, error := csvReader.Read()
if error == io.EOF {
break
} else if error != nil {
log.Fatal(error)
}
lines = append(lines, Line{
Question: line[0],
Answer: line[1],
})
}
reader := bufio.NewReader(os.Stdin)
count := 0
T := time.Duration(*limit)
timer := time.NewTimer(T * time.Second)
go func() {
<-timer.C
fmt.Println()
fmt.Println("You scored " + strconv.Itoa(count) + " out of " + strconv.Itoa(len(lines)))
os.Exit(0)
}()
for idx, line := range lines {
fmt.Print("Question №" + strconv.Itoa(idx+1) + ": " + line.Question + " = ")
ans, _ := reader.ReadString('\n')
if ans == line.Answer+"\n" {
count++
}
}
stop := timer.Stop()
if stop {
fmt.Println("You scored " + strconv.Itoa(count) + " out of " + strconv.Itoa(len(lines)))
}
}

View File

@@ -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))
}
}

View File

@@ -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
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

View File

@@ -0,0 +1,57 @@
package main
import "os"
import "fmt"
import "log"
import "flag"
import "time"
import "encoding/csv"
import "bufio"
import "strings"
func main() {
filenamePtr := flag.String("filename", "problems.csv", "file containing the set of problems")
limitPtr := flag.Int("limit", 30, "quiz time limit")
flag.Parse()
file, err := os.Open(*filenamePtr)
handleError(err)
defer file.Close()
csvReader := csv.NewReader(file)
rows, err := csvReader.ReadAll()
handleError(err)
numQs := len(rows)
numCorrect := 0
timer := time.NewTimer(time.Second * time.Duration(*limitPtr))
go func() {
<- timer.C
// when the timer ends, we kill the quiz
fmt.Println("\nTime is up")
os.Exit(0)
}()
consoleReader := bufio.NewReader(os.Stdin)
for idx, element := range rows {
q, a := element[0], element[1]
fmt.Print("Problem #", idx+1 ,": ", q, " = ")
input, _ := consoleReader.ReadString('\n')
// compare w/o whitespace
if strings.TrimSpace(input) == strings.TrimSpace(a) {
numCorrect++
}
}
fmt.Println("You got", numCorrect, "out of", numQs, "correct")
}
func handleError(err error){
if err != nil {
log.Fatal(err)
}
}

View File

@@ -0,0 +1,99 @@
package main
import (
"bufio"
"bytes"
"encoding/csv"
"flag"
"fmt"
"io/ioutil"
"log"
"math/rand"
"os"
"strings"
"time"
)
func main() {
csvFile := flag.String("csv", "problems.csv", "csv file with questions and answers")
duration := flag.Int("time", 30, "quiz time limit")
randomize := flag.Bool("random", false, "randomize questions")
// Parse
flag.Parse()
reader := bufio.NewReader(os.Stdin)
questions := loadRecordsFromCsv(*csvFile)
correct := 0
total := len(questions) - 1
if *randomize {
questions = shuffle(questions)
}
fmt.Println("Total Questions:", total)
fmt.Println("Duration [s]:", *duration)
done := make(chan bool, 1)
go func() {
for i := 0; i < total; i++ {
fmt.Printf("Question #%d %s = ", i+1, questions[i][0])
answer, _ := reader.ReadString('\n')
// convert CRLF to LF
answer = strings.Replace(answer, "\n", "", -1)
answer = strings.ToLower(answer)
answer = strings.TrimSpace(answer)
// compare answer
if strings.Compare(questions[i][1], answer) == 0 {
correct++
}
}
done <- true
}()
select {
case <-done:
fmt.Println("Good Job!")
case <-time.After(time.Duration(*duration) * time.Second):
fmt.Println("\nYou reached maximum time.")
}
fmt.Println("Your score:", correct, "/", total)
}
// shuffle questions
func shuffle(questions [][]string) [][]string {
s := rand.NewSource(time.Now().UnixNano())
r := rand.New(s)
for i := range questions {
np := r.Intn(len(questions) - 1)
questions[i], questions[np] = questions[np], questions[i]
}
return questions
}
func loadRecordsFromCsv(csvFile string) [][]string {
// load csv file into memory, returns bytes
content, err := ioutil.ReadFile(csvFile)
if err != nil {
log.Fatal(err)
}
r := csv.NewReader(bytes.NewReader(content)) // if we have string instead of btes we can use strings.NewReader(content)
records, err := r.ReadAll()
if err != nil {
log.Fatal(err)
}
return records[1:len(records)]
}

View File

@@ -0,0 +1,14 @@
question,answer
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
1 question answer
2 5+5 10
3 7+3 10
4 1+1 2
5 8+3 11
6 1+2 3
7 8+6 14
8 3+1 4
9 1+4 5
10 5+1 6
11 2+3 5
12 3+3 6
13 2+4 6
14 5+2 7

View File

@@ -0,0 +1,13 @@
5+5,10
7+3,10
8+3,11
8+6,14
1+1,2
1+2,3
3+1,4
1+4,5
2+3,5
5+1,6
3+3,6
2+4,6
5+2,7
1 5+5 10
2 7+3 10
3 8+3 11
4 8+6 14
5 1+1 2
6 1+2 3
7 3+1 4
8 1+4 5
9 2+3 5
10 5+1 6
11 3+3 6
12 2+4 6
13 5+2 7

View File

@@ -0,0 +1,141 @@
package main
import (
"bufio"
"encoding/csv"
"flag"
"fmt"
"io"
"log"
"math/rand"
"os"
"path/filepath"
"strings"
"sync"
"time"
)
var (
flagFilePath string
flagRandom bool
flagTime int
wg sync.WaitGroup
)
func init() {
flag.StringVar(&flagFilePath, "file", "questions.csv", "path/to/csv_file")
flag.BoolVar(&flagRandom, "random", true, "randomize order of questions")
flag.IntVar(&flagTime, "time", 10, "test duration")
flag.Parse()
}
func main() {
// this program will progress as follows
// read a csv filepath and a time limit from flags
// prompt for a key press
// on key press, start the quiz as follows
//
// while time has not elapsed:
// print a random question to the screen
// prompt the user for an answer
// store the answer in a container
// normalize answers so they compare correctly
// output total questions answered correctly and how many questions there
// were.
csvPath, err := filepath.Abs(flagFilePath)
if err != nil {
log.Fatalln("Unable to parse path" + csvPath)
}
file, err := os.Open(csvPath)
if err != nil {
log.Fatalln(err)
}
defer file.Close()
csvReader := csv.NewReader(file)
csvData, err := csvReader.ReadAll()
if err != nil {
log.Fatalln(err)
}
var totalQuestions = len(csvData)
questions := make(map[int]string, totalQuestions)
answers := make(map[int]string, totalQuestions)
responses := make(map[int]string, totalQuestions)
for i, data := range csvData {
questions[i] = data[0]
answers[i] = data[1]
}
respondTo := make(chan string)
// block until user presses enter
fmt.Println("Press [Enter] to start test.")
bufio.NewScanner(os.Stdout).Scan()
if flagRandom {
// seed the random number generator with the current time
rand.Seed(time.Now().UTC().UnixNano())
}
// randPool should contain random indexes into the questions map
randPool := rand.Perm(totalQuestions)
wg.Add(1)
timeUp := time.After(time.Second * time.Duration(flagTime))
go func() {
label:
for i := 0; i < totalQuestions; i++ {
index := randPool[i]
go askQuestion(os.Stdout, os.Stdin, questions[index], respondTo)
select {
case <-timeUp:
fmt.Fprintln(os.Stderr, "\nTime up!")
break label
case ans, ok := <-respondTo:
if ok {
responses[index] = ans
} else {
break label
}
}
}
wg.Done()
}()
wg.Wait()
correct := 0
for i := 0; i < totalQuestions; i++ {
if checkAnswer(answers[i], responses[i]) {
correct++
}
}
summary(correct, totalQuestions)
}
func askQuestion(w io.Writer, r io.Reader, question string, replyTo chan string) {
reader := bufio.NewReader(r)
fmt.Fprintln(w, "Question: "+question)
fmt.Fprint(w, "Answer: ")
answer, err := reader.ReadString('\n')
if err != nil {
close(replyTo)
if err == io.EOF {
return
}
log.Fatalln(err)
}
replyTo <- answer
}
func checkAnswer(ans string, expected string) bool {
if strings.EqualFold(strings.TrimSpace(ans), strings.TrimSpace(expected)) {
return true
}
return false
}
func summary(correct, totalQuestions int) {
fmt.Fprintf(os.Stdout, "You answered %d questions correctly (%d / %d)\n", correct,
correct, totalQuestions)
}

View File

@@ -0,0 +1,77 @@
package main
import (
"encoding/csv"
"flag"
"fmt"
"log"
"os"
"strings"
"time"
)
var (
problemsFile string
timeout int
correct int
b bool
inChan chan int
outChan chan int
)
func getAnswer(solution string) {
var inp string
fmt.Scanln(&inp)
if sanitize(inp) == sanitize(solution) {
outChan <- <-inChan + 1
} else {
outChan <- <-inChan
}
}
func updateCorrect() bool {
select {
case res := <-outChan:
correct = res
return true
case <-time.After(time.Duration(timeout) * time.Second):
close(outChan)
fmt.Println()
return false
}
}
func sanitize(s string) string {
return strings.ToLower(strings.Trim(s, "\n\r\t "))
}
func main() {
flag.StringVar(&problemsFile, "path", "problems.csv", "This is the flag to the CSV containing the problems for the quiz")
flag.IntVar(&timeout, "timeout", 30, "The amount of time you have for a single question in seconds")
flag.Parse()
file, err := os.Open(problemsFile)
if err != nil {
log.Fatal(err)
}
defer file.Close()
problems, err := csv.NewReader(file).ReadAll()
if err != nil {
log.Fatal(err)
}
inChan, outChan = make(chan int, 1), make(chan int, 1)
for c, q := range problems {
fmt.Printf("Question %v: %v -> ", c, q[0])
go getAnswer(q[1])
inChan <- correct
if !updateCorrect() {
break
}
}
fmt.Println("You got", correct, "out of", len(problems), "correct.")
}

View File

@@ -0,0 +1,12 @@
5+5,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
1 5+5 10
2 1+1 2
3 8+3 11
4 1+2 3
5 8+6 14
6 3+1 4
7 1+4 5
8 5+1 6
9 2+3 5
10 3+3 6
11 2+4 6
12 5+2 7

View File

@@ -0,0 +1,118 @@
package main
import (
"bufio"
"encoding/csv"
"errors"
"flag"
"fmt"
"io"
"log"
"math/rand"
"os"
"strings"
"time"
)
// problem is a problem to be solved by a user.
type problem struct {
challenge string
correctAnswer string
}
// scoreboard keeps track of a user's score.
type scoreboard struct {
total int64
correct int64
}
func main() {
var (
csvFileName = flag.String("csv", "problems.csv", "the location of the CSV file")
timeLimit = flag.Int("time-limit", 30, "the time limit for the user to answer all problems")
doShuffle = flag.Bool("shuffle", false, "whether to shuffle the order of problems or not")
)
flag.Parse()
problems, err := readProblemsFromCSVFile(*csvFileName)
if err != nil {
log.Fatal(fmt.Errorf("error reading problems from CSV file: %s", err))
}
if *doShuffle {
rand.Seed(time.Now().Unix())
rand.Shuffle(len(problems), func(i, j int) { problems[i], problems[j] = problems[j], problems[i] })
}
doneCh := make(chan bool)
scr := &scoreboard{
total: int64(len(problems)),
}
go func() {
for i, p := range problems {
fmt.Printf("What is %s?\n", p.challenge)
inputReader := bufio.NewReader(os.Stdin)
fmt.Print("Answer: ")
answer, err := inputReader.ReadString('\n')
if err != nil {
log.Fatal(fmt.Errorf("error reading user input: %s", err))
}
if purifyString(answer) == purifyString(p.correctAnswer) {
scr.correct++
fmt.Println("You are correct!")
} else {
fmt.Printf("Unfortunately not... The correct answer is %s\n", p.correctAnswer)
}
fmt.Printf("Your current score is %v/%v\n\n", scr.correct, scr.total)
if i == len(problems)-1 {
doneCh <- true
}
}
}()
go func() {
timer := time.NewTimer(time.Duration(*timeLimit) * time.Second)
<-timer.C
fmt.Println("")
fmt.Println("")
fmt.Println("Your time is up...")
doneCh <- true
}()
<-doneCh
fmt.Println("")
fmt.Printf("Your final score is %v/%v\n\n", scr.correct, scr.total)
}
// readProblemsFromCSVFile reads problems from a CSV file.
func readProblemsFromCSVFile(fileName string) ([]problem, error) {
csvFile, err := os.Open(fileName)
if err != nil {
return nil, fmt.Errorf("error opening file: %s", err)
}
reader := csv.NewReader(bufio.NewReader(csvFile))
var problems []problem
for {
line, err := reader.Read()
if err != nil {
if err == io.EOF {
break
}
return nil, fmt.Errorf("error reading CSV line: %s", err)
}
if len(line) != 2 {
return nil, errors.New("invalid line in CSV")
}
p := problem{
challenge: line[0],
correctAnswer: line[1],
}
problems = append(problems, p)
}
return problems, nil
}
// purifyString strips all unneeded variations from a string.
func purifyString(str string) string {
return strings.TrimSpace(strings.ToLower(str))
}

View File

@@ -0,0 +1,12 @@
5+5,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
1 5+5 10
2 1+1 2
3 8+3 11
4 1+2 3
5 8+6 14
6 3+1 4
7 1+4 5
8 5+1 6
9 2+3 5
10 3+3 6
11 2+4 6
12 5+2 7

View File

@@ -0,0 +1,90 @@
package main
import (
"encoding/csv"
"errors"
"flag"
"fmt"
"os"
"strings"
"time"
)
type recordtype struct {
question string
answer string
}
// ReadStringWithLimitTime - function read string from reader with time limit
func ReadStringWithLimitTime(limit int) (string, error) {
timer := time.NewTimer(time.Duration(limit) * time.Second).C
doneChan := make(chan bool)
answer, err := "", error(nil)
go func() {
fmt.Scanf("%s\n", &answer)
doneChan <- true
}()
for {
select {
case <-timer:
return "", errors.New("Timer expired")
case <-doneChan:
return answer, err
}
}
}
// ParseLines - parse lines from array of array of string to array of recordtype
func ParseLines(lines [][]string) []recordtype {
ret := make([]recordtype, len(lines))
for i, line := range lines {
ret[i] = recordtype{
question: line[0],
answer: strings.TrimSpace(line[1]),
}
}
return ret
}
func exit(msg string) {
fmt.Println(msg)
os.Exit(1)
}
func main() {
problemFileName := flag.String("csv", "./problems.csv", "a csv file in the format 'quastion,answer'")
limit := flag.Int("limit", 30, "the time limit for the quiz in seconds")
flag.Parse()
problemFile, err := os.Open(*problemFileName)
if err != nil {
exit(fmt.Sprintf("Failed to open the CSV file: %s\n", *problemFileName))
}
defer problemFile.Close() // close CSV file
readerProblem := csv.NewReader(problemFile)
lines, err := readerProblem.ReadAll()
if err != nil {
exit("Failed to parse the provided CSV file.")
}
problems := ParseLines(lines)
successAnswerCount := 0
for i, p := range problems {
fmt.Printf("Problem #%d: %s=", i+1, p.question)
answer, err := ReadStringWithLimitTime(*limit)
if err != nil {
println("Time expire!")
break
}
if strings.ToLower(strings.Trim(answer, "\n ")) == p.answer {
successAnswerCount++
}
}
println("You scored", successAnswerCount, "out of", len(problems))
}

View File

@@ -0,0 +1,12 @@
5+5, 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
1 5+5 10
2 1+1 2
3 8+3 11
4 1+2 3
5 8+6 14
6 3+1 4
7 1+4 5
8 5+1 6
9 2+3 5
10 3+3 6
11 2+4 6
12 5+2 7

View File

@@ -0,0 +1,96 @@
package main
import (
"bufio"
"encoding/csv"
"flag"
"fmt"
"io"
"log"
"os"
"strings"
"time"
)
// Quiz is structure for questions and answers
type Quiz struct {
question, answer string
}
// Stat is struct for quiz statistics
type Stat struct {
all, correct, incorrect int
}
func readCSV(file string) ([]Quiz, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
defer f.Close()
var quizes []Quiz
r := csv.NewReader(f)
for {
line, err := r.Read()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
quizes = append(quizes, Quiz{
strings.TrimSpace(line[0]),
strings.TrimSpace(line[1]),
})
}
return quizes, nil
}
func quiz(records []Quiz, timeout int) (*Stat, error) {
var stat Stat
reader := bufio.NewReader(os.Stdin)
timer := time.NewTimer(time.Second * time.Duration(timeout))
errs := make(chan error)
go func() {
for _, quiz := range records {
fmt.Print(quiz.question, ":")
ans, err := reader.ReadString('\n')
if err != nil {
errs <- err
}
stat.all++
if strings.TrimRight(ans, "\r\n") == quiz.answer {
stat.correct++
} else {
stat.incorrect++
}
}
}()
select {
case <-errs:
return nil, <-errs
case <-timer.C:
fmt.Println("\ntime's up!")
}
return &stat, nil
}
func main() {
f := flag.String("f", "problems.csv", "input file in csv format")
t := flag.Int("t", 30, "timeout for the quiz, in seconds")
flag.Parse()
recs, err := readCSV(*f)
if err != nil {
log.Fatal(err)
}
stat, err := quiz(recs, *t)
if err != nil {
log.Fatal(err)
}
fmt.Printf("\nQuestion answered: %v, Correct: %v, Incorrect: %v\n", stat.all, stat.correct, stat.incorrect)
}

View File

@@ -0,0 +1,159 @@
package main
import (
"bufio"
"flag"
"fmt"
"io/ioutil"
"log"
"math/rand"
"os"
"strconv"
"strings"
"time"
)
// Problem structure
type Problem struct {
q string
a int
}
// PROBLEMBUFCOUNT sets number of problems in buffer
const PROBLEMBUFCOUNT = 100
// DEFAULTTIMELIMIT sets the default time limit for the quiz
const DEFAULTTIMELIMIT = 30 //seconds
// Problem counter
var count int
// Score counter
var score int
var faults int
// readProblems takes reads problems from file, line by line
// problems are written to problems channel
func readProblems(problems chan Problem, filename string, shuffle bool) {
// problems buffer
buf := make([]Problem, PROBLEMBUFCOUNT)
// Open problems file
fd, err := os.Open(filename)
if err != nil {
log.Fatal(err)
}
defer fd.Close()
// Scan file
scanner := bufio.NewScanner(fd)
for scanner.Scan() {
// Read comma separated line
line := strings.Split(scanner.Text(), ",")
// first value is the question string
q := line[0]
// Convert string answer to integer
ans, err := strconv.Atoi(line[1])
if err != nil {
log.Fatal(err)
}
// store problem in buffer
buf[count] = Problem{q, ans}
count++
}
// shuffle problems
if shuffle {
rand.Seed(time.Now().Unix())
for i := range buf[:count] {
j := rand.Intn(i + 1)
buf[i], buf[j] = buf[j], buf[i]
}
}
// send problems over channel
for _, p := range buf[:count] {
problems <- p
}
}
// solveProblem consumes problems from chan
// will check timer before giving points
func solveProblem(problems chan Problem) {
// IO reader
scanner := bufio.NewScanner(os.Stdin)
// start consuming problems from channel
for p := range problems {
// Print problem question
fmt.Printf("%s = ", p.q)
// Scan IO
scanner.Scan()
input := strings.Trim(scanner.Text(), " ") // remove whitespace
// convert string answer to integer
givenAns, err := strconv.Atoi(input)
if err != nil {
fmt.Printf("'%s' is not a valid answer\n", input)
continue
}
// Check answer and give points
if givenAns == p.a {
score++
fmt.Println("Correct!")
} else {
faults++
fmt.Println("Wrong!")
}
}
return
}
// startTimer blocks for n seconds
func startTimer(seconds int) {
time.Sleep(time.Duration(seconds) * time.Second)
}
func main() {
// Parse Command line flags
filenamePtr := flag.String("f", "problems.csv", "name of problem csv file")
secondsPtr := flag.Int("t", DEFAULTTIMELIMIT, "number of seconds to solve problems")
shufflePtr := flag.Bool("s", false, "shuffle questions")
debugPtr := flag.Bool("debug", false, "show debug information")
flag.Parse()
// Show debug/logging information
if !*debugPtr {
log.SetOutput(ioutil.Discard)
}
log.Println("debug:", *debugPtr)
log.Println("filename:", *filenamePtr)
log.Println("shuffle:", *shufflePtr)
log.Println("timer:", *secondsPtr)
// Problems channel (buffered)
problems := make(chan Problem, PROBLEMBUFCOUNT)
// read problems goroutine
go readProblems(problems, *filenamePtr, *shufflePtr)
// Prompt to start the game
fmt.Printf("Press any key to start the quiz!")
bufio.NewScanner(os.Stdin).Scan()
// solve problems goroutine
go solveProblem(problems)
// start timer barrier
startTimer(*secondsPtr)
// Show final tally
fmt.Printf("\nNumber of questions: %d\n", count)
fmt.Printf("Correct answers: %d\n", score)
fmt.Printf("Incorrect answers: %d\n", faults)
}

View File

@@ -0,0 +1,12 @@
5+5,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
1 5+5 10
2 1+1 2
3 8+3 11
4 1+2 3
5 8+6 14
6 3+1 4
7 1+4 5
8 5+1 6
9 2+3 5
10 3+3 6
11 2+4 6
12 5+2 7

View File

@@ -0,0 +1,108 @@
package main
import (
"bufio"
"encoding/csv"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"strings"
"time"
)
var (
file = flag.String("csv", "problems.csv", "a csv file in the format 'question,answer'")
limit = flag.Int("limit", 30, "the time limit for the quiz in seconds")
)
func main() {
flag.Parse()
if *limit < 0 {
fmt.Println("Error: enter positive time limit")
os.Exit(1)
}
records, err := read(*file)
if err != nil {
fmt.Println("Error: ", err)
os.Exit(1)
}
fmt.Println("Press enter to start. Time limit is", *limit, "seconds")
in := bufio.NewReader(os.Stdin)
in.ReadString('\n')
input := make(chan string)
quizdone := make(chan bool)
timeout := time.NewTicker(time.Duration(*limit) * time.Second)
go getInput(input)
var score int
var maxscore int
go func() {
maxscore = len(records)
for i, v := range records {
fmt.Print("Problem #", i+1, ": ", v[0], " = ")
text := <-input
if text == v[1] {
score++
}
}
quizdone <- true
}()
select {
case <-quizdone:
case <-timeout.C:
fmt.Println("\nThe time is up! Game over!")
}
fmt.Println("You scored", score, "out of", maxscore)
}
func read(filename string) ([][]string, error) {
dat, err := ioutil.ReadFile(filename)
r := csv.NewReader(strings.NewReader(string(dat)))
if err == nil {
var records [][]string
for {
record, err := r.Read()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
records = append(records, record)
}
return records, nil
}
return nil, err
}
func trim(s string) string {
t := strings.Trim(s, "\n")
t = strings.Trim(t, " ")
return t
}
func getInput(input chan<- string) {
for {
in := bufio.NewReader(os.Stdin)
result, err := in.ReadString('\n')
if err != nil {
log.Fatal(err)
}
result = trim(result)
input <- result
}
}

View File

@@ -0,0 +1,14 @@
"What is the favourite food?",pizza
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
1 What is the favourite food? pizza
2 5+5 10
3 7+3 10
4 1+1 2
5 8+3 11
6 1+2 3
7 8+6 14
8 3+1 4
9 1+4 5
10 5+1 6
11 2+3 5
12 3+3 6
13 2+4 6
14 5+2 7

View File

@@ -0,0 +1,118 @@
package main
import (
"bufio"
"encoding/csv"
"flag"
"fmt"
"io"
"log"
"os"
"strings"
"time"
)
// Problem object
type Problem struct {
Question string
Answer string
}
// ValidateAnswer validates answer for the problem
func (p *Problem) ValidateAnswer(answer string) bool {
return p.Answer == answer
}
// Quiz object
type Quiz struct {
Problems []Problem
Score int
}
func main() {
var file string
flag.StringVar(&file, "file", "problems.csv", "--file=path/to/problems/file")
var quizTime int
flag.IntVar(&quizTime, "time", 30, "--time=15")
flag.Parse()
WaitForStart()
quiz := Quiz{
Problems: ParseProblemsFrom(file),
Score: 0,
}
go func() {
<-time.After(time.Duration(quizTime) * time.Second)
ShowTimeIsUpMessage()
ShowFinalMessage(quiz.Score, len(quiz.Problems))
os.Exit(0)
}()
RunQuiz(&quiz)
ShowFinalMessage(quiz.Score, len(quiz.Problems))
}
// WaitForStart makes the program wait for a user to press Enter button
func WaitForStart() {
fmt.Print("Press 'Enter' to start the quiz.")
bufio.NewReader(os.Stdin).ReadBytes('\n')
}
// ParseProblemsFrom parses problems from file by the provided path
func ParseProblemsFrom(pathToFile string) []Problem {
file, err := os.Open(pathToFile)
if err != nil {
log.Fatal("File does not exists")
}
reader := csv.NewReader(bufio.NewReader(file))
var problems []Problem
for {
line, error := reader.Read()
if error == io.EOF {
break
} else if error != nil {
log.Fatal(error)
}
problems = append(problems, Problem{
Question: line[0],
Answer: line[1],
})
}
return problems
}
// RunQuiz starts the quiz
func RunQuiz(q *Quiz) {
reader := bufio.NewReader(os.Stdin)
for _, problem := range q.Problems {
AskQuestion(&problem)
answer := ReadLine(reader)
if problem.ValidateAnswer(answer) {
q.Score++
}
}
}
// AskQuestion asks a problem's question
func AskQuestion(p *Problem) {
fmt.Print(p.Question + " ")
}
// ReadLine read a line using buifo.Reader
func ReadLine(reader *bufio.Reader) string {
str, _, err := reader.ReadLine()
if err == io.EOF {
return ""
}
return strings.TrimRight(string(str), "\r\n")
}
// ShowTimeIsUpMessage shows time is up message
func ShowTimeIsUpMessage() {
fmt.Println("\rTime is up!")
}
// ShowFinalMessage shows final message with the correctness statistics
func ShowFinalMessage(correctAnswersCount int, problemsCount int) {
fmt.Printf("\rThere is/are %d correct answers given for %d problems.\n", correctAnswersCount, problemsCount)
}

View File

@@ -0,0 +1,12 @@
5+5,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
1 5+5 10
2 1+1 2
3 8+3 11
4 1+2 3
5 8+6 14
6 3+1 4
7 1+4 5
8 5+1 6
9 2+3 5
10 3+3 6
11 2+4 6
12 5+2 7

View File

@@ -0,0 +1,71 @@
package main
import (
"bufio"
"encoding/csv"
"flag"
"fmt"
"math/rand"
"os"
"strings"
"time"
)
func main() {
csvFilename := flag.String("f", "problems.csv", "csv file with 2 columns, question & answer")
timeLimit := flag.Int("t", 30, "the time limit for the quiz in seconds")
hasShuffle := flag.Bool("s", false, "randomize question order")
flag.Parse()
correctCount := 0
reader := bufio.NewReader(os.Stdin)
fmt.Println("Press enter to start the quiz")
reader.ReadBytes('\n')
b, fileErr := os.Open(*csvFilename)
if fileErr != nil {
fmt.Print(fileErr, "\nFailed to read the CSV file provided")
os.Exit(1)
}
r := csv.NewReader(b)
records, csvErr := r.ReadAll()
if csvErr != nil {
fmt.Print(csvErr, "\nFailed to parse the CSV file provided")
os.Exit(1)
}
timeout := time.NewTimer(time.Duration(*timeLimit) * time.Second)
ansCh := make(chan string)
getUserInput := func() {
userAns, _ := reader.ReadString('\n')
ansCh <- strings.TrimSpace(strings.ToLower(userAns))
}
if *hasShuffle {
rand.Shuffle(len(records), func(i, j int) {
records[i], records[j] = records[j], records[i]
})
}
qLoop:
for i, record := range records {
fmt.Printf("Question %d:\t%s\t", i+1, record[0])
go getUserInput()
select {
case <-timeout.C:
fmt.Println("\nToo slow!")
break qLoop
case ans := <-ansCh:
if ans == record[1] {
correctCount++
}
}
}
fmt.Printf("You scored %d out of %d!\n", correctCount, len(records))
}

View File

@@ -0,0 +1,12 @@
5+5,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
1 5+5 10
2 1+1 2
3 8+3 11
4 1+2 3
5 8+6 14
6 3+1 4
7 1+4 5
8 5+1 6
9 2+3 5
10 3+3 6
11 2+4 6
12 5+2 7

View File

@@ -0,0 +1,94 @@
package main
import (
"encoding/csv"
"os"
"fmt"
"strings"
"log"
"flag"
"time"
"math/rand"
)
type Question struct {
question string
answer string
}
func getQuestions(filePath string) ([]Question) {
file, err := os.Open(filePath)
if(err != nil){
log.Fatal("Failed to open file.")
}
reader := csv.NewReader(file)
questionList, err := reader.ReadAll()
if(err != nil) {
log.Fatal("Failed to parse CSV file.")
}
questions := make([]Question, 0)
for _, question := range questionList {
questions = append(questions, Question{strings.TrimSpace(question[0]),
strings.TrimSpace(question[1])})
}
return questions
}
func Quiz(questions []Question, timer *time.Timer) (score int){
for i, question := range questions {
fmt.Printf("Problem #%d %s : ", i + 1, question.question)
answerChannel := make(chan string)
go func() {
var userAnswer string
fmt.Scanln(&userAnswer)
answerChannel <- userAnswer
}()
select {
case <-timer.C:
fmt.Println("\nTimeout")
return score
case userAnswer := <- answerChannel:
if(strings.TrimSpace(userAnswer) == question.answer) {
score += 1
}
}
}
return score
}
func randomize(questions []Question) []Question{
n := len(questions)
for i := n-1; i>0; i-- {
j := rand.Intn(i)
temp := questions[i]
questions[i] = questions[j]
questions[j] = temp
}
return questions
}
var csvPath string
var timeout int
var shuffle bool
func init() {
flag.StringVar(&csvPath, "csv", "problems.csv", "a CSV file in format of 'question,answer'")
flag.IntVar(&timeout, "limit", 30, "The time limit of the quiz in seconds")
flag.BoolVar(&shuffle, "shuffle", false, "Shuffle the questions (default 'false')")
}
func main() {
flag.Parse()
fmt.Print("Hit Enter to start the timer:")
questions := getQuestions(csvPath)
if(shuffle) {
questions = randomize(questions)
}
fmt.Scanln()
timer := time.NewTimer(time.Second * time.Duration(timeout))
score := Quiz(questions, timer)
fmt.Printf("Your scored %d out of %d\n", score, len(questions))
}

View File

@@ -0,0 +1,82 @@
package main
import (
"encoding/csv"
"flag"
"fmt"
"log"
"os"
"time"
)
type question struct {
problem string
result string
}
type quiz struct {
questions []question
score int
}
func (q *quiz) ask() {
q.score = 0
for _, question := range q.questions {
fmt.Println(question.problem)
var answer string
fmt.Scanln(&answer)
if answer == question.result {
q.score++
}
}
}
func readCSV(p string) []question {
qfile, err := os.Open(p)
if err != nil {
log.Fatalf("could not open file: %v\n", err)
}
defer qfile.Close()
csvrd := csv.NewReader(qfile)
records, err := csvrd.ReadAll()
if err != nil {
log.Fatalf("could not parse .csv: %v\n", err)
}
var questions []question
for _, record := range records {
q := question{problem: record[0], result: record[1]}
questions = append(questions, q)
}
return questions
}
var (
qCSV string
timeout int
)
func init() {
flag.StringVar(&qCSV, "quiz", "", "A .csv file with questions and answers.")
flag.IntVar(&timeout, "timeout", 30, "The time limit for answering questions.")
flag.Parse()
}
func main() {
if qCSV == "" {
log.Fatalln("a .csv file must be provided through the -quiz flag")
}
questions := readCSV(qCSV)
qz := quiz{questions: questions, score: 0}
timeoutCh := time.After(time.Duration(timeout) * time.Second)
resultCh := make(chan quiz)
go func() {
qz.ask()
resultCh <- qz
}()
select {
case <-resultCh:
case <-timeoutCh:
fmt.Println("Timeout!")
}
fmt.Println("Score: ", qz.score)
}