# Gopher exercise in Elixir: timeout implementation - stuck!

So, I’ve started to work on these exercises but using Elixir: https://courses.calhoun.io/courses/cor_gophercises
specifically, the first one:
`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.`
and
`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.`

So my Part 1 implementation is this:

``````defmodule CsvQuiz do
@moduledoc """
Documentation for CsvQuiz.
"""
def csv_quiz(path) do
[i, s] =
parser(path)
|> Enum.reduce([0, 0], fn array, [iteration, acc] ->
solution(array, iteration, acc) end)

IO.puts("\nYou scored #{s} of #{i}.")

System.halt()
end

defp parser(path) do
|> String.split("\n")
|> Enum.reject(fn x -> x == "" end)
|> Enum.map(&String.split(&1, ","))
end

defp solution([question, answer], iteration, acc) do
num = iteration + 1
input = IO.gets("Problem ##{num}: #{question} = ") |> String.trim()

result =
if input == answer do
acc + 1
else
acc
end

[num, result]
end
end
``````

and that is how to run it and what I have as an output:

``````❯ iex -S mix

Erlang/OTP 22 [erts-10.4.1] [source] [64-bit]
[smp:12:12] [ds:12:12:10] [async-threads:1] [hipe]

Compiling 1 file (.ex)
Interactive Elixir (1.9.0) - press Ctrl+C to exit (type h() ENTER for help)

iex(1)> CsvQuiz.csv_quiz("/gophercises_in_elixir/lib/1_csv_quiz_problems.csv")
Problem #1: 5+5 = 10
Problem #2: 1+1 = 2
Problem #3: 8+3 = 11
Problem #4: 1+2 = 3
Problem #5: 8+6 = 1
Problem #6: 3+1 = 1
Problem #7: 1+4 = 1
Problem #8: 5+1 = 1
Problem #9: 2+3 = 1
Problem #10: 3+3 = 1
Problem #11: 2+4 = 1
Problem #12: 5+2 = 1

You scored 4 of 12.
``````

But I am stuck with Part 2, I mean not about the code itself, but about what tools do I need to use?

Could you please critic my `Part 1` and suggest where to move according to Part 2 description.

I guess I need to use `Task.yield()` but I am not sure to be honest.

Thanks a lot in advance!

There may be easier solutions, but this seems like a use case for GenServer

For example, the below is a “quiz” where each “question” is just an integer, and the correct answer is the same integer. You kick it off by calling `Quiz.start_quiz` with a path and optional timeout. That in turn will call `GenServer.start_link/2`, which will end up triggering the `init/1` callback in the `# Server` part of the code. In that init callback you can initialize the state of your server by reading in the CSV file. From there it just kicks off a loop through all the questions. The server will terminate either after all questions have been answer or the timeout, which ever comes first (the scheduling of the timeout is handled in `handle_call{:next, ..., ...)`

Whenever the server terminates, we print out the results in the `terminate/2` callback. But note from the docs

… it is not guaranteed that `terminate/2` is called when a `GenServer` exits. For such reasons, we usually recommend important clean-up rules to happen in separated processes either by use of monitoring or by links themselves

I am sure there is plenty of room for improvement in this implementation. For instance, if you are in the middle of a question when the server times out, the results will be printed to the screen but the prompt for the next answer remains active. I’m not sure how to fix that at the moment.

``````defmodule Quiz do
use GenServer

# Client

def start_quiz(path, timeout \\ 30_000) do
{:ok, pid} = GenServer.start_link(__MODULE__, %{path: path, timeout: timeout})

IO.gets("press enter to continue\n")

loop(pid)
end

defp loop(pid) do
pid
|> get_next_question()
|> get_response()
|> loop()
end

defp get_response(question) do
IO.gets("#{question} = \n")
|> String.trim()
end

defp get_next_question(pid) do
GenServer.call(pid, :next)
end

defp answer_current_question(response, pid) do
end

# Server

@impl true
def init(%{path: path, timeout: timeout}) do
IO.inspect("starting quiz with path #{path}")
questions = [1, 2, 3, 4, 5]
total = length(questions)

{:ok,
%{timeout: timeout, correct: 0, total: total, current_question: nil, questions: questions}}
end

@impl true
def terminate(:normal, %{correct: correct, total: total}) do
IO.puts("Quiz over.  #{correct} questions answered correctly out of a total of #{total}")
end

@impl true
def handle_call(:next, _from, state) do
# if current_question is nil, that means this is
# the first question, so we schedule the timer now
if state.current_question == nil do
schedule_timer(state)
end

handle_next_question(state)
end

@impl true
def handle_call({:answer, response}, _from, %{current_question: question} = state) do
new_state =
case is_correct(response, question) do
true -> Map.update!(state, :correct, &(&1 + 1))
false -> state
end

end

@impl true
def handle_info(:end_quiz, state) do
{:stop, :normal, state}
end

defp handle_next_question(%{questions: []} = state) do
handle_info(:end_quiz, state)
end

defp handle_next_question(%{questions: [next | remaining]} = state) do
new_state =
state
|> Map.put(:questions, remaining)
|> Map.put(:current_question, next)

end

defp is_correct(response, question) do
response == Integer.to_string(question)
end

defp schedule_timer(state) do
Process.send_after(self(), :end_quiz, state.timeout)
end
end

``````
1 Like