From 00c7268cdc74002a6658f659ca74d8b33dd15860 Mon Sep 17 00:00:00 2001
From: Vlad Zabolotnyi <ftftnvm2@gmail.com>
Date: Thu, 19 Sep 2024 14:09:01 +0300
Subject: [PATCH] feat: add quiz implementation

---
 students/zabftft/main.go | 154 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 154 insertions(+)
 create mode 100644 students/zabftft/main.go

diff --git a/students/zabftft/main.go b/students/zabftft/main.go
new file mode 100644
index 0000000..1604c6f
--- /dev/null
+++ b/students/zabftft/main.go
@@ -0,0 +1,154 @@
+package main
+
+import (
+	"bufio"
+	"encoding/csv"
+	"errors"
+	"flag"
+	"fmt"
+	"log"
+	"math/rand"
+	"os"
+	"strings"
+	"time"
+)
+
+var (
+	ErrIncorrectFileFormat = errors.New("invalid file format")
+	ErrTimeLimitIsNegative = errors.New("negative time limit")
+)
+
+const (
+	FileFlag         = "file"
+	FileName         = "problems.csv"
+	TimeLimitFlag    = "limit"
+	TimeLimitSeconds = 3
+	ShuffleFlag      = "shuffle"
+)
+
+type gameResult struct {
+	total   int
+	correct int
+	err     error
+}
+
+func main() {
+	fileName := flag.String(FileFlag, FileName, "filename where questions and asnwers are placed for the quiz")
+	timeLimit := flag.Int(TimeLimitFlag, TimeLimitSeconds, "time limitation for the quiz in seconds")
+	shuffle := flag.Bool(ShuffleFlag, false, "shuffle quiz questions")
+	flag.Parse()
+
+	if err := validateFlags(*fileName, *timeLimit, *shuffle); err != nil {
+		log.Fatal(err.Error())
+	}
+
+	fileContent, err := getFileContent(*fileName)
+
+	if err != nil {
+		log.Fatal(err.Error())
+	}
+
+	if *shuffle == true {
+		fileContent = shuffleQuestions(fileContent)
+	}
+
+	quizDuration := time.Duration(*timeLimit) * time.Second
+	runQuiz(fileContent, quizDuration)
+}
+
+func validateFlags(fileName string, timeLimit int, shuffle bool) error {
+	if !strings.Contains(fileName, ".csv") {
+		return ErrIncorrectFileFormat
+	}
+
+	if timeLimit <= 0 {
+		return ErrTimeLimitIsNegative
+	}
+
+	return nil
+}
+
+func getFileContent(fileName string) ([][]string, error) {
+	file, err := os.Open(fileName)
+	if err != nil {
+		return nil, err
+	}
+	defer file.Close()
+
+	r := csv.NewReader(file)
+	return r.ReadAll()
+}
+
+func shuffleQuestions(fileContent [][]string) [][]string {
+	rand.Shuffle(len(fileContent), func(i, j int) {
+		fileContent[i], fileContent[j] = fileContent[j], fileContent[i]
+	})
+	return fileContent
+}
+
+func runQuiz(data [][]string, timeLimit time.Duration) error {
+	r := bufio.NewReader(os.Stdin)
+	gc := make(chan gameResult)
+
+	entryPrompt(r)
+
+	timer := time.NewTimer(timeLimit)
+	go askQuestions(r, data, gc)
+
+	var result gameResult
+	for {
+		select {
+		case <-timer.C:
+			close(gc)
+			fmt.Printf("\nTotal Questions: %d. Correct Answers: %d", result.total, result.correct)
+			return nil
+		case result = <-gc:
+			if result.err != nil {
+				return result.err
+			}
+		}
+	}
+}
+
+func entryPrompt(r *bufio.Reader) error {
+	fmt.Print("Welcome to Quiz!\n\n")
+	fmt.Print("Press Enter when you are ready\n")
+	_, err := r.ReadString('\n')
+
+	if err != nil {
+		return fmt.Errorf("error reading input: %w", err)
+	}
+
+	return nil
+}
+
+func askQuestions(r *bufio.Reader, data [][]string, gc chan<- gameResult) {
+	var totalQuestions int
+	var correctAnswers int
+
+	for i, question := range data {
+		quizQuestion, correctAnswer := question[0], question[1]
+		fmt.Printf("Question #%d: %s\n", i+1, quizQuestion)
+		fmt.Print("Your answer: ")
+
+		answer, err := r.ReadString('\n')
+
+		if err != nil {
+			gc <- gameResult{err: err}
+			break
+		}
+
+		answer = cleanAnswer(answer)
+
+		if answer == correctAnswer {
+			correctAnswers++
+		}
+		totalQuestions++
+
+		gc <- gameResult{total: totalQuestions, correct: correctAnswers}
+	}
+}
+
+func cleanAnswer(answer string) string {
+	return strings.TrimSpace(strings.ToLower(answer))
+}