Objectives
- how to start a project in GO?
- how to use dependencies in GO?
- do we always need the a main.go file?
- how do structs compare with classes in PHP?
- how can we define an interface and implement it?
- Init the module
go mod init github.com/MihaiBlebea/go-quiz
- Add 2 flags:
- Flag for adding the path to the problems file - default to problems.yaml
- Flag for adding the timeout - default to 10 sec
For adding a flag:
fileName := flag.String("file", "problems.yaml", "The name of the file with the problems")
Do not forget to call parse after definiting the flags:
flag.Parse()
Add a dependency package to decode the yaml to struct:
gopkg.in/yaml.v2
Quiz struct:
type Quiz struct {
Problems []Problem `yaml:"problems"`
}
Problem struct:
type Problem struct {
Question string `yaml:"question"`
Answer string `yaml:"answer"`
}
Read the file and parse to Quiz struct
b, err := ioutil.ReadFile(fileName)
if err != nil {
return log.Fatal(err)
}
var quiz Quiz
err = yaml.Unmarshal(b, &quiz)
if err != nil {
return log.Fatal(err)
}
var score int
for i, prob := range quiz.Problems {
fmt.Println(fmt.Sprintf("Problem %d: %s ?", i+1, prob.Question))
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
if input == prob.Answer {
fmt.Println("Correct")
score++
} else {
fmt.Println("Wrong")
}
}
fmt.Println(fmt.Sprintf("Game over! Your score is %d from %d", score, len(quiz.Problems)))
- Add timer outside of the for loop:
t := time.NewTimer(time.Duration(s.limit) * time.Second)
- Add select to check if the timer has expired before the input check
var score int
for i, prob := range quiz.Problems {
fmt.Println(fmt.Sprintf("Problem %d: %s ?", i+1, prob.Question))
select {
case <-t.C:
break
default:
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
if input == prob.Answer {
fmt.Println("Correct")
score++
} else {
fmt.Println("Wrong")
}
}
}
fmt.Println(fmt.Sprintf("Game over! Your score is %d from %d", score, len(quiz.Problems)))
var score int
for i, prob := range quiz.Problems {
fmt.Println(fmt.Sprintf("Problem %d: %s ?", i+1, prob.Question))
// Create answer as type for the channel
type Answer struct {
input string
err error
}
// Create a channel of type Answer
answerChan := make(chan Answer)
// Create a routine to get the user input
go func() {
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
if err != nil {
answerChan <- Answer{
err: err,
}
}
// Trim the space on the input
input = strings.TrimSpace(input)
answerChan <- Answer{
input: input,
}
}()
select {
case <-t.C:
break
case answer := <-answerChan:
if answer.err != nil {
return score, answer.err
}
if answer.input == prob.Answer {
fmt.Println("Correct")
score++
} else {
fmt.Println("Wrong")
}
}
}
fmt.Println(fmt.Sprintf("Game over! Your score is %d from %d", score, len(quiz.Problems)))
Points to touch:
- What is the different between a struct and a package
- What is the different between a method with a receiver and just a function
- How does the constructor works in go
// Quiz _
type Quiz struct {
Problems []Problem `yaml:"problems"`
}
// Problem _
type Problem struct {
Question string `yaml:"question"`
Answer string `yaml:"answer"`
}
// New returns a new Quiz
func New(fileName string) (*Quiz, error) {
b, err := ioutil.ReadFile(fileName)
if err != nil {
return &Quiz{}, err
}
var quiz Quiz
err = yaml.Unmarshal(b, &quiz)
if err != nil {
return &Quiz{}, err
}
return &quiz, nil
}
Create a simple test for the quiz package
func TestLoadProblemsFromFile(t *testing.T) {
quiz, err := New("./../problems.yaml")
if err != nil {
t.Error(err)
return
}
if len(quiz.Problems) != 2 {
t.Errorf("Expected %d, got %d", 2, len(quiz.Problems))
}
if quiz.Problems[0].Question != "What is the capital of UK" {
t.Errorf("Expected %s, got %s", "What is the capital of UK", quiz.Problems[0].Question)
}
if quiz.Problems[0].Answer != "London" {
t.Errorf("Expected %s, got %s", "London", quiz.Problems[0].Answer)
}
}
// Player interface. Replace printing ot the console with the Print method on the player
type Player interface {
Print(string)
Input() (string, error)
}
type service struct {
limit int
quiz quiz.Quiz
player Player
}
// New _
func New(limit int, quiz quiz.Quiz, player Player) Service {
return &service{limit, quiz, player}
}
func (s *service) Run() (int, error) {
// Put all the logic in here
return score, nil
}
Human player
// Human player
type Human struct {
}
// Print _
func (h *Human) Print(output string) {
fmt.Println(output)
}
// Input _
func (h *Human) Input() (string, error) {
reader := bufio.NewReader(os.Stdin)
return reader.ReadString('\n')
}
Computer player
// Computer player
type Computer struct {
Answers []string
counter int
}
// Print _
func (c *Computer) Print(output string) {
fmt.Println(output)
}
// Input _
func (c *Computer) Input() (string, error) {
defer c.incrementCounter()
return c.Answers[c.counter], nil
}
func (c *Computer) incrementCounter() {
c.counter++
}
func main() {
fileName := flag.String("file", "problems.yaml", "The name of the file with the problems")
limit := flag.Int("limit", 10, "The time limit for the quiz in seconds")
flag.Parse()
quiz, err := quiz.New(*fileName)
if err != nil {
log.Fatal(err)
}
// player := player.Human{}
player := player.Computer{Answers: []string{"London", "15"}}
gameService := game.New(*limit, *quiz, &player)
_, err = gameService.Run()
if err != nil {
log.Fatal(err)
}
}