Russian Word Quiz in Go
Tags: go
Published: 2020-08-09 05:01:44

While waiting for a game to download, I figured I'd write a quick program.

I've always had an interest in the Russian language, but floundered every time I tried to learn it.

This program will not change this (probably.) However, Being bored, as I frequently am, I decided to write up a quick little CLI quiz for learning new Russian words.

This is part one, where we go over the basic structure. In part two we'll cover setting up Redis to track some data. But for now, we'll make it simple and stored in memory.

This is not ideal by any measure, after all we'll be storing a lot of data locally, but it'll provide a jumping off point. Also, I'm still new to Go, so criticism is appreciated!

# In The Beginning
To start off, we need to find a good word list, preferably one with a Russian word, an English translation, and an example.

After some digging, I found a handy CSV over on [reddit](https://www.reddit.com/r/russian/comments/289wba/10000_most_common_russian_words_in_spreadsheet/) that contains 10,000 words with the fields we want.

Download the list as a CSV, put it in your project directory and let's go.

# Defining a line
Now that we have some data, we need to define how what an entry into our dictionary is. For now, we'll just match up the fields to a struct. Later we'll add more data points that will be useful when making this more robust.

Our entry only needs to contain a couple fields. A word, a definition, and an example. So let's build a quick struct to store everything.

```
package main

type Entry struct {
	Word    string
	Meaning string
	Example string
}

var words []Entry

func main() {}
```

Cool. Now we have a structure that represents a line in our file. Now we just need to parse it. Luckily for us, Go makes this very simple. Let's create a function that opens and parses the file into a slice of entries. 

# Parsing our list
Go has a nice little package for CSV work. All we need to do is open our file, use our file as the reader for the CSV, and parse it.

```
func openFile(filename string) []Entry {
    var parsed []Entry // we'll store all the marshaled lines here
    f, err := os.Open(filename) // we only need to read the file, so just open normally
    if err != nil {
        log.Fatal(err) // something went wrong
    }
    defer f.Close() // remember kids, always close your files

    lines, err := csv.NewReader(f).ReadAll()
    if err != nil {
        log.Fatal(err) // something went wrong reading the file
    }

    for _, line := range lines {
        parsed = append(parsed, Entry{
            Word:    line[0],
			Meaning: line[1],
			Example: line[2],
        })
    }

    return parsed
}
```

Simple. We'll handle the errors in a more graceful manner in time. But for the time being, we have accomplished out goal of parsing the CSV into a slice of entries for our enjoyment. Let's go something with it.

# Practicing
Now we'll create a small function that picks a random word from our list, displays it, and waits for the user to input their guess for the meaning.

```
func practice() {
	var input string // store our user's input
	rand.Seed(time.Now().UnixNano()) // seed the rng
	word := words[rand.Intn(len(words))] // get a word from our list

	fmt.Printf("What does this word mean?\n%s\n>> ", word.Word)

    // since we have words that might require a space, such as vaccuum cleaner
    // which is one word in Russian, we need to be able to handle whitespace.
    // fmt's Scanln doesn't handle whitespace, so we'll create a buffered scanner
	scanner := bufio.NewScanner(os.Stdin) // tell it to watch stdin
	if scanner.Scan() {
		input = scanner.Text() // store our input
	}

    // check for errors
	if err := scanner.Err(); err != nil {
		fmt.Println("Unable to read input: ", err)
        return
	}

    // compare our strings, this isn't great, but we'll start with this
    // if they don't match, tell the user it's incorrect
	if strings.ToLower(input) != strings.ToLower(word.Meaning) {
		fmt.Println("Incorrect")
		fmt.Printf("The meaning is actually: %s\n", word.Meaning)

        // if the entry has an example, display it
		if word.Example != "" {
			fmt.Printf("Ex: %s\n", word.Example)
		} else {
			fmt.Println("No example found")
		}
		return
	}

	fmt.Println("Correct!")
}
```

Super simple. This doesn't do much, but again, this is part one 🤷‍♀️

# Tie it together
Now let's throw everything in our main.

```
func main() {
    words = openFile("10000.csv")
    practice()
}
```

Give it a run and try it out. It's pretty boring. But now we know that we can handle a list and make very strict comparisons.

Part 2 will be done soon and we'll start making this actually usable. Stay tuned if you want.