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 and its patterns and build some games to show at your winter holiday parties.

Elements of Flow control

Programs are like water in a river. 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)
}

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 the safe go-to for starting. 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.' As shown above.

'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 (it was implicitly handled), which is a preference amongst some programmers.  However, as a newcomer, it's better to do more showing:

// 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)
	}

}

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 as explicit and will catch only when the condition isn't what is expected.

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 the other case 'oddness' is true
		fmt.Printf("Number is odd: %d", n)
	}

}

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. In this Author's opinion, it is the most explicit way to share what the code is training to do.

// ex. 4 switch
package main

import (
	"fmt"
	"time"
)

func main() {

	// `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")
	}
}

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() {
    t := time.Now()
	if t.Hour() < 12 {
		fmt.Println("before noon")
	} else if t.Hour() > 12 {
		fmt.Println("it's after noon")
	}
}

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.

Now that we've added a few more things to our tool belt let's add the final section for this chapter for making fun programs, also known as games.

For 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".

A 'for' loop also uses a condition to do its work, but it works 'until' that condition is satisfied.

So, for example, imagine if we took the common Norwegian saying 'a thousand thanks' literally.

// ex 6. 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)
      }
   }
}

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.

Games

Okay, enough theory and toy examples; let's have 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'")
      }
   }
}

As I hope you can see, using the 'switch' statement clarifies what 'cases' we are evaluating for.  If you want an additional exercise, try writing this with 'if' 'else' and 'else if' statements.  

Advanced: Rock, Paper, Scissors

Now, for a more fun and advanced example. Let's whet your programmer appetite by seeing what fun can be had with loops and control flows.

In this code, you'll see advanced GO things like 'type' and 'maps', which we'll spend a whole section on in 'Data structures'  later.

But, for now, just code out what you see to get familiar with the syntax.

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])
   }

}


Above, you encountered a lot of new things but saw one familiar concept in the 'for' loop. You'll notice no conditions with it, so it will keep running forever. It's pretty neat because a forever-running loop is a basis for making a video game.

Chapter 3. Control Flow