Gopher Gym: Quiz Game - Part 1
Tags: go, learning, gophercise
Published: 2019-06-02 01:02:12

# 29 May 2019 at 05:20
I started my morning with some cowboy coffee; which is coffee brewed in a saucepan, since I seem to have misplaced my aero press, as I listened to [Go for beginners](https://open.spotify.com/episode/6TL5HwIMLVuSVQCUt2sLMc?si=xs4s3t2TS12QCrlWhKdbrQ), and discovered [Gophercises](https://gophercises.com), a set of coding exercises in Go aimed at people who’ve done a few tutorials but have kinda lost steam. I felt like that described me, so I’ve decided to tackle these exercises while journalling my progress and learning in this blog.

/*I have a peculiar writing style because I write in tandem with coding, so there’s a lot of tense switching, so apologies in advance. All comments and criticisms are encouraged, no matter how harsh or abrasive, as long as they are constructive!*/

I’m going to tackle the first challenge, [GitHub - gophercises/quiz: Ex 1 - Run timed quizzes via the command line](https://github.com/gophercises/quiz). 

So the first thing I need to do is make a new go project. I create a new repository on github, clone it into my `$GOPATH/src/github.com/adilw3nomad`, and made a simple `main.go` file that prints `Hello!` when it’s run.

I run `go build && ./GopherQuiz`, which compiles my program into a binary called `GopherQuiz` and runs said file. `Hello!` was printed to the command line. Success! I now have something to work with. 

Lets go over the exercise details; I’m going to make the points which I feel are important to the domain in bold. 
> 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***.

I like to break down stuff like this into bullet points;

- There’s a quiz, which has questions. 
- Questions can be either right or wrong, ergo they have an answer. 
- There’s a score, or at least a concept of a score
- The quiz is created from a CSV file with questions and answers

That’s the domain of the application; now let’s have a think about the behaviour. The application has a single dependency; it requires a CSV file to create the quiz. Without a CSV file, there is no quiz, and the app is useless. So the first thing we need to do is get our app to load and read a CSV file. I won’t worry about the filename flag just yet, or even the content of the CSV. All I want to focus on is loading and reading a CSV file into the application. 

1. Create array/slice to hold quiz items. `(Question, Answer)`
2. Read each line of the CSV and create a quiz item from it. 
3. Iterate through the slice, printing the question to stdout 
4. Accept an answer from the user
4. If answer matches the user’s input, increase score counter by one. 

I’m going to do this in the worst way possible; with no tests, all in the `main` function. Then I’ll write some tests for it, and then I’ll refactor, knowing that my coce still works thanks to the tests. 

# 31 May 2019 at 06:36
``` go
func main() {
  csvFile, err := os.Open(“problems.csv”)
  if err != nil {
    log.Fatal(err)
  }
  defer csvFile.Close()

  reader := csv.NewReader(csvFile)
  records, err := reader.ReadAll()

  if err != nil {
    log.Fatal(err)
  }

  for i := 0; i < len(records); i++ {
    fmt.Println("Question: ", records[i][0])
    fmt.Println("Answer: ", records[i][1])
  }
}
```

So this is what I have so far; I open the file using the `os` package, and do not close it until the program stops running. I then create a 2 dimensional array of each of the CSV elements using `csv.Reader.ReadAll`. Finally, to see what I’m working with, I loop through the records and print each one. protip; `PrintLn` is useful for just printing out whatever as long as it’s printable. 

So I now have my CSV input, what do I want to do with it? I want to 
- Ask each question 
- Accept an user’s answer 
- Compare it to the correct answer
- Increase a counter if they are the same. 

What does it mean to ask a question in a command line app? It means writing to stdout. What does it mean to accept an answer? Reading from stdin. I’ve already got writing to stdout covered  by using `PrintLn`, and in order to read from stdin, I’m going to use the  [bufio](https://golang.org/pkg/bufio/) package. 
 

``` go
// omitted unchanged code for brevity
for I := 0; I < len(records); I++ {
  // Print the question out
  fmt.Println(“Question: “, records[I][0])
  // Read from stdin for answer
  reader := bufio.NewReader(os.Stdin)
  fmt.Print(“Enter your answer now: “)
  // Expect answer to be given once they hit return
  text, _ := reader.ReadString(‘\n’)

  if text == records[I][1] {
    fmt.Println(“Correct!”)
  } else {
    fmt.Println(“WRONG! Answer is: “, records[I][1])
  }
}
```

What we’ve done here is loop through the lines of the CSV, print the question out, then instantiate a new buffered I/O reader which uses the `os.Stdin` interface. Next we print an instruction to the user, and use the `reader.ReadString` method to read their input. The parameter passed to `ReadString` is the delimiter that lets the reader know when to stop reading. Then a simple conditional statement checks to see if the answer is correct.

Pretty sweet, except it doesn’t work! I suspect it’s because I am not making the assertion correctly. Let’s do some debugging!. But first, I’m going to take a break and play a game of Autochess :D 

# 31 May 2019 at 08:22
A game of autochess (and some laundry time) later, I’m back and ready to roll. So, first I hypothesise as to why it doesn’t recognise correct answers; I posit that the type of variables I am comparing (`text` and `records[I][1]`) are not the same. Let’s find out the types of each of these by printing them. 

We know (thanks to vscode’s Go package), that `reader.ReadString` returns a string. What does `csv.Reader.ReadAll` return? A 2-dimensional slice of with strings. Hm. Using this little bit of nasty code, I can confirm they are both the same type. So what the hell is going on!

``` go
fmt.Println(fmt.Sprintf(“%T”, text)) // string
fmt.Println(fmt.Sprintf(“%T”, records[I][1])) // string
```

Time for a new hypothesis; but first, some refactoring. I’m finding it a bit difficult to make sense of what I’m writing, mainly because of all these horrible names, like `records[I][1]`. 
I’m going to turn these record’s into what they are; items in a quiz. How, you say? By making a struct!

Go’s structs are typed collections of fields. They’re useful for grouping data together to form records, and are pretty essential if you want to do OOP in Go. Go isn’t a pure object oriented language, but through methods and structs, you can write object-oriented code. 

Here’s how to define a struct in Go, the aptly named `quizItem`
``` go
type quizItem struct {
  question string
  answer   string
}
```

We start with the keyword `type`, since Go is statically typed, and we are defining a new type of variable. Followed by the name of the struct, `quizItem`, and finally the keyword `struct`. In the body of the curly braces, we define the structs attributes, followed by their type. 

Here’s how the quiz loop looks now that we’ve added a struct
``` go
/// omitted unchanged code for brevity
for I := 0; I < len(records); I++ {
  // Create quizItem object
  quizItem := quizItem{records[I][0], records[I][1]}
  // Print out the question
  fmt.Println(“Question: “, quizItem.question)
  // Create reader and allow user to input their answer
  reader := bufio.NewReader(os.Stdin)
  fmt.Print(“Enter your answer now: “)
  // Expect answer to be given once they hit return
  text, _ := reader.ReadString(‘\n’)

  if text == quizItem.answer {
    fmt.Println(“Correct!”)
  } else {
    fmt.Println(“WRONG! Answer is: “, quizItem.answer)
  }
}
```
As you can see on line 3, instantiating the struct is pretty straight forward, and now the code is a hell of lot more readable. Now that it’s easier to read, time to make a new hypothesis as to why the answer comparison does not work. I hypothesise that perhaps I’m making an assumption on what the value of `text` is going to be. 

Let’s print it out and check! I’ll print the user’s answer before running the comparison check, so now the code looks like this

``` go
/// omitted unchanged code for brevity
for I := 0; I < len(records); I++ {
  // Create quizItem object
  quizItem := quizItem{records[I][0], records[I][1]}
  // Print out the question
  fmt.Println(“Question:”, quizItem.question)
  // Create reader and allow user to input their answer
  reader := bufio.NewReader(os.Stdin)
  fmt.Print(“Enter your answer now: “)
  // Expect answer to be given once they hit return
  text, _ := reader.ReadString(‘\n’)
  fmt.Println(“Your answer is:”, text)

  if text == quizItem.answer {
    fmt.Println(“Correct!”)
  } else {
    fmt.Println(“WRONG! Answer is:”, quizItem.answer)
  }
}
```

```shell
$ go build && ./GopherQuiz
Question: 5+5
Enter your answer now:10

Your answer is: 10
WRONG! Answer is: 10
Question: 7+3
Enter your answer now:
```

Something here is odd; there a new line after printing the user’s answer. Let’s investigate it by looking at the base16 value of the string. We can do this by using `fmt.Printf(“%x”, text)`, where `%x` is a format modifier signifying that I want the string to formatted in base16. Let’s write some code to test this out; 

``` go
fmt.Println("Your answer is:", text)
fmt.Printf("Your answer in base16 is %x \n", text)
fmt.Printf("The expected answer in base16 is %x \n", quizItem.answer)
```

```shell
$ go build && ./GopherQuiz
Question: 5+5
Enter your answer now:10
Your answer is: 10

Your answer in base16 is 31300a
The expected answer in base16 is 3130
```


Aha! My suspicions are confirmed; there /*is*/ a difference between the two. The user input ends in `0a`, which is hex code for linefeed. 

What’s happened here is that when reading from stdin, we took everything up to ***and including*** `\n`, which is the reason why there was a new line after printing the user’s input. 

Now that we know the issue, how can we solve it? The simplest way would be to remove the linefeed character from the input before comparing. Let’s use the `strings` package, which is a library of tools for manipulating strings. A quick test: 

``` go
text = strings.TrimSuffix(text, “\n”)
fmt.Printf(“Your answer trimmed in base16 is %x \n”, text)
fmt.Printf(“The expected answer in base16 is %x \n”, quizItem.answer)
```

```shell
$ go build && ./GopherQuiz
Question: 5+5
Enter your answer now:10
Your answer is: 10

Your answer trimmed in base16 is 3130
The expected answer in base16 is 3130
```

Success! So what we’ve done here is use the `TrimSuffix` method, passing it the text we want to apply the trim to, and the suffix we wish to trim. With this, the quiz should work! Here’s a quick reminder of how the entire program looks; 

``` go
package main

import (
"bufio"
"encoding/csv"
"fmt"
"log"
"os"
"strings"
)

type quizItem struct {
  question string
  answer   string
}

func main() {
  csvFile, err := os.Open("problems.csv")
  if err != nil {
    log.Fatal(err)
  }
  defer csvFile.Close()

  reader := csv.NewReader(csvFile)
  records, err := reader.ReadAll()

  if err != nil {
    log.Fatal(err)
  }

  for i := 0; i < len(records); i++ {
    // Create quizItem object
    quizItem := quizItem{records[i][0], records[i][1]}
    // Print out the question
    fmt.Println("Question:", quizItem.question)
    // Create reader and allow user to input their answer
    reader := bufio.NewReader(os.Stdin)
    fmt.Print("Enter your answer now: ")
    // Expect answer to be given once they hit return
    text, _ := reader.ReadString('\n')
    fmt.Println("Your answer is:", text)
    // Trim the newline suffix from the input
    text = strings.TrimSuffix(text, "\n")
    if text == quizItem.answer {
      fmt.Println("Correct! Well done")
    } else {
      fmt.Println("WRONG! Answer is:", quizItem.answer)
    }
  }

}
```

[And this is how it looks like in action!](https://cloudup.com/c743HhvYEKq)

So the next things we need to do are; 
- Keep track of the score, and output how many are correct/wrong
- Add a flag that allows the CSV file to be customised. 

But before we go any further, we must have tests! Having tests helps you understand your code, saves you time from having to manually test things, and also gives you a little rush every time it all goes green. 

[Follow me on twitter](https://twitter.com/adil_w3nomad) if you wanna ask me questions or have any suggestions!