DEV Community

Toul
Toul

Posted on • Edited on • Originally published at automatetheboringstuffwithgo.com

Ch. 3 Flow Control

In the previous chapter, we covered the basics of GO data types and, in a few examples, used flow control to print messages.

In this chapter, we'll use what we learned about comparison operators and boolean values to learn more about flow control.

Elements of Flow control

Programs are like water in a river because they tend to flow in one direction. And like water in a river, parts of it can go off into separate outlets based upon the river's conditions, such as a boulder in the stream or a dam.

However, unlike water in a river, it's much easier to divert the flow of a program.

To do so, we use conditions to evaluate a variable's value or the output of a function and execute a different code block.

Let's use an example with comments to see.

We are using the familiar 'if' from the previous chapter.

// Ex. 1 Tell me if a number is even or odd
package main

import (
    "fmt"
    "math/rand"
)

func main() {
    // use the rand library to generate a random integer.
    n := rand.Intn(100)
    // 'if' condition
    if n%2 == 0 {
        // code block that's executed if the condition is true
        fmt.Printf("Number is even: %d", n)
    }
    // code block that's executed if it isn't true
    fmt.Printf("Number is odd: %d", n)
}
Enter fullscreen mode Exit fullscreen mode

In this program, we're checking whether a 'random' integer number is even or not via the modulo operator %, which is a fancy way of saying 'dividing with remainders'.

If you recall, an even number can be divided in two without any remainders, whereas an odd number cannot.

Don't worry about the math. Just note that the logic of the program changes based on the condition, which is the 'random' value of the n variable.

Now, let's introduce the rest of the gang for flow control.

Flow Control Statements

'if' statements

These are the most used control statements, and you'll almost always use them when adding 'logic' to your programs.

The general pattern is that your program has a chunk of code that will only be executed if the condition is considered 'true' or 'false.'

'else' statements

Technically, in the previous 'if' example, we could've had an 'else' statement to print the 'number is odd:' part, but we kept it cleaner by leaving it out, which is a preference amongst programmers.

// ex. 2 else
package main

import (
    "fmt"
    "math/rand"
)

func main() {
    // use the rand library to generate a random integer.
    n := rand.Intn(100)
    // 'if' condition
    if n%2 == 0 {
        // code block that's executed if the condition is true
        fmt.Printf("Number is even: %d", n)
    } else {
        // code block that's executed if it isn't true
        fmt.Printf("Number is odd: %d", n)
    }

}
Enter fullscreen mode Exit fullscreen mode

Else statements say 'hey, if this piece of logic turns out to be false or something else, then do this.'

Typically, 'else' statements aren't preferred because they aren't explicit and will catch only when the condition is false.

Let's introduce the else's, cousin.

else if

Here, we can communicate what we want to happen 'if' the first condition evaluates to 'false' and explicitly define what should happen.

// ex. 3 else if 
package main

import (
    "fmt"
    "math/rand"
)

func main() {
    // use the rand library to generate a random integer.
    n := rand.Intn(100)
    // 'if' condition
    if n%2 == 0 {
        // code block that's executed if the condition is true
        fmt.Printf("Number is even: %d", n)
    } else if n%2 == 1 {
        // code block that's executed if it isn't true
        fmt.Printf("Number is odd: %d", n)
    }

}
Enter fullscreen mode Exit fullscreen mode

switch

The 'switch' control flow works much the same as using 'if' 'else' or 'else if' but presents differently in the form of 'cases'. A 'case' is the same as a condition and may be preferred by those with a mathematical background. Either style works; use whichever makes sense to you.

// ex. 4 switch
package main

import (
    "fmt"
    "time"
)

func main() {

    // You can use commas to separate multiple expressions
    // in the same `case` statement. We use the optional
    // `default` case in this example as well.
    switch time.Now().Weekday() {
    case time.Saturday, time.Sunday:
        fmt.Println("It's the weekend")
    default:
        fmt.Println("It's a weekday")
    }

    // `switch` without an expression is an alternate way
    // to express if/else logic. Here we also show how the
    // `case` expressions can be non-constants.
    t := time.Now()
    switch {
    case t.Hour() < 12:
        fmt.Println("It's before noon")
    default:
        fmt.Println("It's after noon")
    }
}

Enter fullscreen mode Exit fullscreen mode

In the above, we say 'switch' based upon the following 'case' and pass in some data. The same again can be accomplished by using 'if' and 'else if'

// ex. 5 convert switch to if else
package main

import (
    "fmt"
    "time"
)

func main() {
    if t.Hour() < 12 {
        fmt.Println("before noon")
    } else if t.Hour() > 12 {
        fmt.Println("it's after noon")
    }
}
Enter fullscreen mode Exit fullscreen mode

In general, I prefer using 'switch' and including the optional 'default' case because I find it easier to test my 'cases' and to convey to others what I'm trying to do with my code.

Loops

Now that we have some logic to control a program's flow, it's time to introduce 'loops'. A loop is a way we tell computers to "do work for x amount of times". Let's combine it with loops to make a more intelligent program.

A 'for' loop also uses a condition to do its work, but it works 'until' that condition is satisfied. So, for example, the saying 'a thousand thanks' we'll have a program that will say 'thank you' a thousand times based upon a reader's name.

// ex 5. If you're a reader, then a thousand thanks
package main

import "fmt"

func main() {
   fmt.Println("Enter your name")
   var n string
   fmt.Scan(&n)
   switch {
   case n == "Toul":
         fmt.Printf("The author is %s ",  n)
   default:
     // a thousand thanks if you're not the author
      for i := 0; i < 1000; i++ {
         fmt.Printf("\ni: %d Thank you %s for reading", i, n)
      }
   }
}

Enter fullscreen mode Exit fullscreen mode

In the above the 'for' loop syntax is for initial condition; compare ;if not met then do another { // work to be done }

In most programming languages, it is common to start at '0', so keep this in mind when working with arrays, slices, and initial loop conditions.

Okay, let's have some fun by building a few small games to show your friends and family at the winter holiday parties.

Don't worry if some syntax is unfamiliar. We want to focus on building muscle memory and familiarity with GO. So, try your best to copy the code and get in running. If you have any troubles at all the source code is available

Guess the Number

In this first game we'll have the computer pick a random number and then try to guess it by inputting guesses, but the catch is we only get '3' attempts!

But, we'll give the user clues to increase the chances of winning within 3.

package main

import (
   "fmt"
   "math/rand"
   "time"
)

func main() {
   fmt.Println("Game: Guess a number between 0 and 10")
   fmt.Println("You have three(3) tries ")

   source := rand.NewSource(time.Now().UnixNano())
   randomizer := rand.New(source)
   secretNumber := randomizer.Intn(10)

   var guess int

   for try := 1; try <= 3; try++ {

      fmt.Printf("Attempt: %d\n", try)
      fmt.Println("Enter your number: ")
      fmt.Scan(&guess)

      switch {
      case guess < secretNumber:
         fmt.Printf("Sorry, wrong guess ; number is too small\n ")
      case guess > secretNumber:
         fmt.Printf("Sorry, wrong guess ; number is too large\n ")
      case guess == secretNumber:
         fmt.Printf("You win!")
      case try == 3:
         fmt.Printf("Game over!!\n ")
         fmt.Printf("The correct number is %d\n", secretNumber)
      default:
         fmt.Println("I don't recognize that input. Try a number like '1'")
      }
   }
}
Enter fullscreen mode Exit fullscreen mode

Advanced: Rock, Paper, Scissors

Now, for a more fun and more advanced example to wet your programmer appetite.

package main

import (
   "fmt"
   "math/rand"
   "strings"
   "time"
)

func getInput() string {
   // get input and use strings package to convert to lowercase b/c this is a good practice when working with inputs 
   // in general
   fmt.Print("Pick [r]ock, [p]aper, or [s]cissors:   ")
   var input string
   fmt.Scanln(&input)
   return strings.ToLower(input)
}

type Move int

const (
   Rock Move = iota
   Paper
   Scissors
)

var validInput = map[string]Move{
   "r":        Rock,
   "rock":     Rock,
   "p":        Paper,
   "paper":    Paper,
   "s":        Scissors,
   "scissors": Scissors,
}

var inputs = [...]string{
   Rock:     "Rock",
   Paper:    "Paper",
   Scissors: "Scissors",
}

func checkInput() Move {
   // start a loop until we get valid input
   for {
      // Does the user input match the map validInput?
      if move, ok := validInput[getInput()]; ok {
         fmt.Println("Player Chooses", inputs[move])
         return move
      }

      fmt.Println("I didn't understand your choice, please retry")
   }

}

var compare = [...]string{
   Rock:     "You Tied.",
   Paper:    "You Lost.",
   Scissors: "You Win!",
}

func main() {
   // for {} says do this until the user wants to stop with ctrl+c which is the universal way to kill a program 
   // that's running in the terminal.
   // this is also known as 'while' loop in other languages and is how video games work (while user playing keep 
   // running game engine) 
   for {
      rand.Seed(time.Now().Unix())

      // Get Valid Player Input
      userChoice := checkInput()

      // Randomly Assign Computer Choice
      computerChoice := Move(rand.Intn(3))

      computerInput := inputs[computerChoice]

      fmt.Printf("Computer chooses: %s\n", computerInput)

      // Check to see who won
      fmt.Printf("Result: %s\n",
         compare[((computerChoice-userChoice)%3+3)%3])
   }

}


Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
regiszanandrea profile image
Regis Zanandrea

What do the ellipses represent on [...]string{}?

Collapse
 
terraier profile image
Toul

Hi it means to define an array versus a slice and it does so with the automatic correct length. Arrays can be slightly faster than slices for some operations. :)