Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added students/hackeryarn/hackeryarn
Binary file not shown.
120 changes: 120 additions & 0 deletions students/hackeryarn/main.go
Original file line number Diff line number Diff line change
@@ -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)
}
40 changes: 40 additions & 0 deletions students/hackeryarn/myquiz/myquiz.go
Original file line number Diff line number Diff line change
@@ -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,
}
}
66 changes: 66 additions & 0 deletions students/hackeryarn/myquiz/myquiz_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
49 changes: 49 additions & 0 deletions students/hackeryarn/problem/problem.go
Original file line number Diff line number Diff line change
@@ -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],
}
}
68 changes: 68 additions & 0 deletions students/hackeryarn/problem/problem_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
12 changes: 12 additions & 0 deletions students/hackeryarn/problems.csv
Original file line number Diff line number Diff line change
@@ -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
Loading