GO: Desafio do Quiz (simplificado)
Olá, leitores do CodeAspiras!
Quem participa do nosso canal no Discord sabe que, de tempos em tempos, nós lançamos alguns desafios de programação no canal #desafios. Alguns são bem simples, outros meio complexos. Eu vou destrinchar o que foi mais famoso até então por aqui pra disseminar conhecimento. Se você gostar e quiser buscar mais desafios, é só chegar junto no Discord e ir fazendo o que estiver por lá.
Ah, um detalhe antes de começar: esse é o desafio simplificado. O original exige um pouco mais de conhecimento, como leitura/escrita em arquivo, decodificar/encodar JSON, etc. Como ainda tem muito estudante no começo ainda, decidi retirar esses obstáculos e deixar o desafio mais simples, gerando o que verá a seguir.
O desafio
Faça um programa que inicializa algumas variáveis de um quiz, faz as perguntas para colher as respostas e imprime o resultado.
Obs.: não está sendo determinado como seu programa deve funcionar!
Impressão esperada no terminal
Pergunta 1: Essa seria a primeira pergunta...?
Opção 1: Sim...?
Opção 2: Não....?
Resposta: 1
--
Pergunta 2: Essa seria a terceira pergunta...?
Opção 1: Sim...?
Opção 2: Não....?
Resposta: 1
--
Resultado: você acertou 1 de 2 perguntas!!
E é isso, não tem mais nenhuma descrição. A ideia é que seu programa inicie já imprimindo:
Pergunta 1: Essa seria a primeira pergunta...?
Opção 1: Sim...?
Opção 2: Não....?
Resposta:
E aguarde o usuário responder. Daí o usuário responde 1
, que é a resposta correta.
Então seu programa continuará o processo, imprimindo uma separação e a próxima pergunta:
--
Pergunta 2: Essa seria a terceira pergunta...?
Opção 1: Sim...?
Opção 2: Não....?
Resposta:
E aí, mais uma vez, irá aguardar o usuário responder. Respondendo 1
, que é a resposta errada, o programa deverá então finalizar e imprimir uma separação e o resultado, informando que acertei apenas uma das duas perguntas:
--
Resultado: você acertou 1 de 2 perguntas!!
As únicas entradas do usuário são as respostas, que são números.
Sacou? Quer tentar desenvolver uma solução aí antes de continuar lendo?
Se for tentar, aprecie esse gif e só role a tela quando terminar!
A solução (em Golang)
A maior dificuldade que as pessoas tem ao tentar solucionar desafios como esse é dar o primeiro passo. É como se você estivesse dirigindo um carro bem velho, dando partida no meio de uma ladeira. Se você tentou pensar em alguma coisa e não conseguiu, espero que possa absorver um pouco da técnica que vou te passar agora, que é infalível para construção de linhas de raciocínio.
Essa técnica se chama Baby Steps, que significa Passos de Bebê, em inglês. A ideia é que, ao invés de tentar montar, na sua cabeça, toda uma história de "começo, meio e fim", você deveria dar um passo de cada vez. Conforme você se torna experiente, será capaz de montar a história inteira na mente antes de colocar a mão na massa, mas enquanto não chega lá, não faz mal algum dar pequenos passos de bebê.
Nesse caso, eu já dei uma ajudinha ali em cima descrevendo que o desafio não está determinando como deve ficar seu código e exibindo, passo-a-passo, como as coisas devem aparecer no terminal.
Nesse caso, você pode simplemente copiar tudo o que foi impresso no exemplo e... imprimir, Kiko?
Sim! Exatamente! Supondo que você ainda está aprendendo a fazer códigos em Golang, vamos por parte:
1 - Imprimindo todos os textos
package main
import "fmt"
func main() {
// Pergunta 1
fmt.Println("Pergunta 1: Essa seria a primeira pergunta...?")
fmt.Println("Opção 1: Sim...?")
fmt.Println("Opção 2: Não....?")
fmt.Print("Resposta: ")
// ler resposta
fmt.Println("") // retirar essa quebra de linha temporaria
fmt.Println("--")
// Pergunta 2
fmt.Println("Pergunta 2: Essa seria a terceira pergunta...?")
fmt.Println("Opção 1: Sim...?")
fmt.Println("Opção 2: Não....?")
fmt.Print("Resposta: ")
// ler resposta
fmt.Println("") // retirar essa quebra de linha temporaria
fmt.Println("--")
// Conclusão
fmt.Println("Resultado: você acertou 1 de 2 perguntas!!")
}
Veja o código no Playground: https://go.dev/play/p/HBrbJPnV_SY
O código acima irá imprimir:
Pergunta 1: Essa seria a primeira pergunta...?
Opção 1: Sim...?
Opção 2: Não....?
Resposta:
--
Pergunta 2: Essa seria a terceira pergunta...?
Opção 1: Sim...?
Opção 2: Não....?
Resposta:
--
Resultado: você acertou 1 de 2 perguntas!!
Nesse momento, nós já estamos fazendo 90% do que foi pedido no desafio, concorda? Falta, literalmente, ler dois números. Caso não saiba como ler a entrada do terminal, basta pesquisar <linguagem> scanf
e você vai achar alguns bons artigos explicando como fazer. Note que é importante ler mais do que um site pra tirar alguma conclusão, hein?
2 - Lendo respostas
No caso do nosso desafio, eu poderia usar o pacote bufio
, que nos permite ter mais controle sobre qual stream de entrada estamos utilizando e tal, mas, para simplificar, quero utilizar a função Scanln
do pacote fmt
. É similar ao scanf
de algumas outras linguagens por aí. Quer ver?
PS.:**daqui para baixo*, para rodar o programa, precisará criar o arquivo localmente e rodando na mão. Pra fazer isso,* baixe o Golang, instale, depois crie uma pasta em qualquer lugar, abra essa pasta pelo terminal e digitego mod init quizsimp
. Daí crie o arquivomain.go
e cole o código nesse arquivo. Para executar, rode:go run .
package main
import "fmt"
func main() {
var resposta string
// Pergunta 1
fmt.Println("Pergunta 1: Essa seria a primeira pergunta...?")
fmt.Println("Opção 1: Sim...?")
fmt.Println("Opção 2: Não....?")
// Resposta 1
fmt.Print("Resposta: ")
if _, err := fmt.Scanln(&resposta); err != nil {
fmt.Printf("Um erro aconteceu ao ler a resposta: %s\n", err)
return
}
fmt.Println("--")
// Pergunta 2
fmt.Println("Pergunta 2: Essa seria a terceira pergunta...?")
fmt.Println("Opção 1: Sim...?")
fmt.Println("Opção 2: Não....?")
// Resposta 2
fmt.Print("Resposta: ")
if _, err := fmt.Scanln(&resposta); err != nil {
fmt.Printf("Um erro aconteceu ao ler a resposta: %s\n", err)
return
}
fmt.Println("--")
// Conclusão
fmt.Println("Resultado: você acertou 1 de 2 perguntas!!")
}
3 - Contabilizando acertos
Agora você está armazenando a resposta do usuário na nova variável resposta
, certo? Só que não estamos fazendo nada com isso!! Que tal adicionarmos uma outra variável acertos
que contabiliza quantas respostas foram certas?
Além disso, podemos mudar a impressão de resultados de Println
para Printf
e formatar o texto, injetando o número de acertos:
package main
import "fmt"
func main() {
var resposta string
acertos := 0
// Pergunta 1
fmt.Println("Pergunta 1: Essa seria a primeira pergunta...?")
fmt.Println("Opção 1: Sim...?")
fmt.Println("Opção 2: Não....?")
// Resposta 1
fmt.Print("Resposta: ")
if _, err := fmt.Scanln(&resposta); err != nil {
fmt.Printf("Um erro aconteceu ao ler a resposta: %s\n", err)
return
}
if resposta == "1" {
acertos++
}
fmt.Println("--")
// Pergunta 2
fmt.Println("Pergunta 2: Essa seria a terceira pergunta...?")
fmt.Println("Opção 1: Sim...?")
fmt.Println("Opção 2: Não....?")
// Resposta 2
fmt.Print("Resposta: ")
if _, err := fmt.Scanln(&resposta); err != nil {
fmt.Printf("Um erro aconteceu ao ler a resposta: %s\n", err)
return
}
if resposta == "2" {
acertos++
}
fmt.Println("--")
// Conclusão
fmt.Printf("Resultado: você acertou %d de 2 perguntas!!\n", acertos)
}
E pronto! Solucionamos o desafio............... Certo?
A verdade é que estamos gerando a mesma saída esperada que coloquei no começo desse desafio. Então, sim, solucionamos, porém não está nada elegante. Se quisermos fazer um Quiz com 100 perguntas, esse código ficaria gigantesco!
Nós podemos separar um pouco as responsabilidades aqui: que tal agruparmos todos os dados de cada pergunta em uma estrutura, deixando, então, o trecho de impressão/leitura fica responsável apenas por isso mesmo, simplificando os deveres?
4 - Organizando dados em estruturas
E o que teria nessa estrutura de pergunta, Kiko?
Bem... A pergunta em si, as opções e a resposta esperada (vulgo gabarito). Seria algo assim:
type QuizItem struct {
Question string
Options map[string]string
RightOptionIndex string
}
E poderíamos organizar a primeira pergunta assim:
QuizItem{
Question: "Essa seria a primeira pergunta...?",
Options: map[string]string{
"1": "Sim...?",
"2": "Não....?",
},
RightOptionIndex: "1",
}
E, com isso, teríamos quase todas as informações para imprimir tudo, concorda?
Como assim, quase todas, Kiko?
É que falta sabermos qual é o número dessa pergunta. Como a ideia é termos uma lista de perguntas, teremos essa informação de outro lugar. Enfim, vamos transformar o código:
package main
import "fmt"
type QuizItem struct {
Question string
Options map[string]string
RightOptionIndex string
}
func main() {
// prepara o quiz
quiz := []QuizItem{
{
Question: "Essa seria a primeira pergunta...?",
Options: map[string]string{
"1": "Sim...?",
"2": "Não....?",
},
RightOptionIndex: "1",
},
{
Question: "Essa seria a terceira pergunta...?",
Options: map[string]string{
"1": "Sim...?",
"2": "Não....?",
},
RightOptionIndex: "2",
},
}
// executa o quiz
acertos := 0
for index, item := range quiz {
// número da pergunta vem do index+1 !
fmt.Printf("Pergunta %d: %s\n", index+1, item.Question)
for num, opt := range item.Options {
fmt.Printf("Opção %s: %s\n", num, opt)
}
fmt.Print("Resposta: ")
var resposta string
if _, err := fmt.Scanln(&resposta); err != nil {
fmt.Printf("Um erro aconteceu ao ler a resposta: %s\n", err)
return
}
if resposta == item.RightOptionIndex {
acertos++
}
fmt.Println("--")
}
// Conclusão
fmt.Printf("Resultado: você acertou %d de 2 perguntas!!\n", acertos)
}
E podemos ir ainda além!! E se quisermos exibir o resultado em forma de gabarito? Bem, só faz sentido se você puder distinguir as opções corretas das erradas, concorda? E pra isso você pode usar símbolos e tal, mas eu prefiro COLORIR.
5 - Exibindo gabarito após resultado
Oi?! Dá pra colorir texto no terminal, Kiko?
Sim, dá! E para isso é só usarmos um comando que altera as propriedades de texto do terminal. Eu não sei exatamente qual é o nome disso, até tentei pesquisar mas parece que cada pessoa chama do jeito que quer, então não me ajudou muito, rs. Então vamos chamar do nosso jeito também! E deixo isso aberto para que alguém me corrija publicamente a vontade, hehe.
Eu chamo esse comando de Editor de Fonte. Ele funciona como uma tag HTML, tendo um comando no início de onde você quer trocar a fonte e outro no final. Em algumas linguagens, você pode acessar esse escape pela inicial \e
, mas em Golang nós acessamos pela inicial \033
:
Começa com:
\033[<nums>m
Termina com:
\033[0m
Nesse caso, a ideia é que, no lugar de <nums>
, você coloque os números que vão definir o estilo de fonte a utilizar no trecho seguinte. Depois que acabar de imprimir o texto no estilo desejado, você reseta o estilo novamente com \033[0m
. Sim, 0
é um dos possíveis códigos a se colocar no lugar de <nums>
. E quando utilizamos mais de um código, nós separamos eles com ponto-e-vírgula (;).
Eu não vou colocar nenhuma tabela dos possíveis estilos aqui, mas o que nos interessa no momento:
cor vermelha:
31
;cor verde:
32
.
Então, se você quiser imprimir uma linha vermelha, é só imprimir:
fmt.Println("\033[31mEsse é o estilo com a cor vermelha\033[0m")
Dito isso, podemos fazer três constantes com as cores e uma função para fazer essa impressão formatada, o que acha?
const (
COLOR_RED = "31"
COLOR_GREEN = "32"
)
func printStyled(text string, styles ...string) {
fmt.Println("\033[" + strings.Join(styles, ";") + "m" + text + "\033[0m")
}
Assim, é só chamar:
printStyled("Opção 1: ...", COLOR_GREEN) // para opções corretas
printStyled("Opção 2: ...", COLOR_RED) // para opções marcadas incorretamente
Mas ainda não estamos prontos, pois... Não salvamos nenhuma resposta do usuário.
Então vamos adicionar uma nova informação no QuizItem
: Answer string
, além de mudar o slice []QuizItem
para []*QuizItem
para usarmos sempre as mesmas referências, alterando e salvando as respostas na própria estrutura:
package main
import (
"fmt"
)
type QuizItem struct {
Question string
Options map[string]string
RightOptionIndex string
Answer string
}
func main() {
// prepara o quiz
quiz := []*QuizItem{
{
Question: "Essa seria a primeira pergunta...?",
Options: map[string]string{
"1": "Sim...?",
"2": "Não....?",
},
RightOptionIndex: "1",
},
{
Question: "Essa seria a terceira pergunta...?",
Options: map[string]string{
"1": "Sim...?",
"2": "Não....?",
},
RightOptionIndex: "2",
},
}
// executa o quiz
acertos := 0
for index, item := range quiz {
fmt.Printf("Pergunta %d: %s\n", index+1, item.Question)
for num, opt := range item.Options {
fmt.Printf("Opção %s: %s\n", num, opt)
}
fmt.Print("Resposta: ")
if _, err := fmt.Scanln(&item.Answer); err != nil {
fmt.Printf("Um erro aconteceu ao ler a resposta: %s\n", err)
return
}
if item.Answer == item.RightOptionIndex {
acertos++
}
fmt.Println("--")
}
// Conclusão
fmt.Printf("Resultado: você acertou %d de 2 perguntas!!\n", acertos)
}
E agora, podemos fazer um novo loop nas perguntas, colorindo as respostas corretas e erradas!!
package main
import (
"fmt"
"strings"
)
const (
COLOR_RED = "31"
COLOR_GREEN = "32"
)
type QuizItem struct {
Question string
Options map[string]string
RightOptionIndex string
Answer string
}
func printStyled(text string, styles ...string) {
fmt.Print("\033[" + strings.Join(styles, ";") + "m" + text + "\033[0m")
}
func main() {
// prepara o quiz
quiz := []*QuizItem{
{
Question: "Essa seria a primeira pergunta...?",
Options: map[string]string{
"1": "Sim...?",
"2": "Não....?",
},
RightOptionIndex: "1",
},
{
Question: "Essa seria a terceira pergunta...?",
Options: map[string]string{
"1": "Sim...?",
"2": "Não....?",
},
RightOptionIndex: "2",
},
}
// executa o quiz
acertos := 0
for index, item := range quiz {
fmt.Printf("Pergunta %d: %s\n", index+1, item.Question)
for num, opt := range item.Options {
fmt.Printf("Opção %s: %s\n", num, opt)
}
fmt.Print("Resposta: ")
if _, err := fmt.Scanln(&item.Answer); err != nil {
fmt.Printf("Um erro aconteceu ao ler a resposta: %s\n", err)
return
}
if item.Answer == item.RightOptionIndex {
acertos++
}
fmt.Println("--")
}
// gabarito
fmt.Println("Gabarito:")
for index, item := range quiz {
fmt.Printf("Pergunta %d: %s\n", index+1, item.Question)
for num, opt := range item.Options {
label := fmt.Sprintf("Opção %s: %s", num, opt)
if num == item.RightOptionIndex {
printStyled(label, COLOR_GREEN)
} else if num == item.Answer {
printStyled(label, COLOR_RED)
} else {
fmt.Print(label)
}
fmt.Println("")
}
fmt.Println("--")
}
// Conclusão
fmt.Printf("Resultado: você acertou %d de 2 perguntas!!\n", acertos)
}
Conclusão
Nesse desafio, nós fizemos um código estritamente imperativo, mas poderíamos evoluir ainda mais. Por exemplo, eu fiz minha versão do desafio com Bubbletea, sendo o desafio completo (lendo arquivos). Enfim, essa é uma excelente atividade para iniciantes! Vocês aprendem a colocar em prática várias coisas de uma vez só. Além disso, você pode brincar com seus colegas fazendo perguntas reais nos programas finais! E se puder aceitar múltiplas respostas? E se for um Quiz de scoring
ao invés de gabarito? No scoring
, não existe opção errada, apenas pontuações. No final, o total de pontuação do usuário varia pelas opções marcadas, encaixando ele em algum grupo de pontuações esperadas. O que acha de desenrolar esse desafio?
E por hoje é só! Curtiu? Comenta e compartilha!! Como falei no início, temos outros desafios lá no canal do Discord. Pode chegar que a casa é aberta, rs. Qualquer coisa, é só falar aí nos comentários!
Abraços...
Inté!