A Functional Game (With a Little Bit of State)

“In theory, theory and practice are the same. In practice, they’re not.”

Yogi Berra

Introduction

Now that I’ve given you a little background about what I think “state” is, let’s build a simple game that requires us to use state. I’ll build the game using recursion, and also immutable state — something I had never heard of when I first starting writing the Scala Cookbook.

Goals

Here are my goals for this lesson:

  • To write our first functional application
  • Show a first example of how to handle “state” in a Scala/FP application

Source code

The best way to understand this lesson is to have its source code open in an IDE as you read it. The source code is available at this Github URL:

Coin Flip: A simple FP game

To get started using state in a Scala application, I’ll build a little game you can play at the command line. The application will flip a coin (a virtual coin), and as the player, your goal is to guess whether the result is heads or tails. The computer will keep track of the total number of flips and the number of correct guesses.

When you start the game, you’ll see this command-line prompt:

(h)eads, (t)ails, or (q)uit: _

This is how the application prompts you for your guess. Enter h for heads, t for tails, or q to quit the game. If you enter h or t, the application will flip a virtual coin, then let you know if your guess was correct or not.

As an example of how it works, I just played the game and made four guesses, and the input/output of that session looks like this:

(h)eads, (t)ails, or (q)uit: h
Flip was Heads. #Flips: 1, #Correct: 1

(h)eads, (t)ails, or (q)uit: h
Flip was Tails. #Flips: 2, #Correct: 1

(h)eads, (t)ails, or (q)uit: h
Flip was Heads. #Flips: 3, #Correct: 2

(h)eads, (t)ails, or (q)uit: t
Flip was Tails. #Flips: 4, #Correct: 3

(h)eads, (t)ails, or (q)uit: q

=== GAME OVER ===
#Flips: 4, #Correct: 3

Admittedly this isn’t the most exciting game in the world, but it turns out to be a nice way to learn how to handle immutable state in a Scala/FP application.

One note before proceeding: The input/output in this game will not be handled in a functional way. I’ll get to that in a future lesson.

On to the game!

Coin Flip game state

Let’s analyze how this game works:

  • The computer is going to flip a virtual coin.
  • You’re going to guess whether that result is heads or tails.
  • You can play the game for as many flips as you want.
  • After each flip the output will look like this:

    Flip was Tails. #Flips: 4, #Correct: 2

These statements tell us a few things about the game state:

  • We need to track how many coin flips there are.
  • We need to track how many guesses the player made correctly.

I could track more information, such as the history of the guess for each coin flip and the actual value, but to keep it simple, all I want to do at this time is to track (a) the number of flips, and (b) the number of correct guesses. As a result, a first stab at modeling the game state looks like this:

case class GameState (
    numFlips: Int,
    numCorrectGuesses: Int
)

Game pseudocode

Next, let’s start working on the game code.

You know you’re going to need some sort of main loop, and in the imperative world, pseudocode for that loop looks like this:

var input = ""
while (input != "q") {
    // prompt the player to select heads, tails, or quit
    // get the player's input
    if (input == "q") {
        print the game summary
        quit
    }
    // flip the coin
    // see if the player guessed correctly
    // print the #flips and #correct
}

I/O functions

Alas, that’s not how I’ll write the loop, but it does give me an idea of some I/O functions I’m going to need. From that pseudocode it looks like I’m going to need these functions:

  • A “show prompt” function
  • A “get user input” function
  • A function to print the number of flips and correct answers

These functions have nothing to do with functional programming — they’re impure I/O functions that connect our application to the outside world — so I’ll write them in a standard Scala/OOP way. Here’s the “show prompt” function:

def showPrompt: Unit = { print("\n(h)eads, (t)ails, or (q)uit: ") }

Next, here’s the “get user input” function:

def getUserInput = readLine.trim.toUpperCase

Prior to Scala 2.11.0, readLine was made available to you without an import statement via Scala’s Predef object, but since then it’s available at scala.io.StdIn.readLine. Notice that I convert all input to uppercase to make it easier to work with later.

Next, while the game is being played I want to print output like this:

Flip was Tails. #Flips: 4, #Correct: 3

and when the game is over I want to print this output:

=== GAME OVER ===
#Flips: 4, #Correct: 3

To accommodate these needs I create these functions:

def printableFlipResult(flip: String) = flip match {
    case "H" => "Heads"
    case "T" => "Tails"
}

def printGameState(printableResult: String, gameState: GameState): Unit = {
    print(s"Flip was $printableResult. ")
    printGameState(gameState)
}

def printGameState(gameState: GameState): Unit = {
    println(s"#Flips: ${gameState.numFlips}, #Correct: ${gameState.numCorrect}")
}

def printGameOver: Unit = println("\n=== GAME OVER ===")

Note that the printGameState functions take the GameState as an input parameter, and use its fields to print the output. The assumption is that these functions always receive the latest, up-to-date GameState instance.

If you know Scala, that’s all fairly standard “print this out” and “read this in” code.

Declaring the Unit return type

Note that in these examples I use :Unit = syntax on the functions that have no return type. Methods that have a Unit return type are called procedures, and the Procedure Syntax in the Scala Style Guide recommends declaring the Unit return type, so I’ve shown it here.

Writing a toin coss function

When you look back at this piece of the original pseudocode:

// flip the coin

you’ll see that one more thing I can get out of the way before writing the main loop is a function to simulate a coin toss.

A simple way to simulate a toin coss is to use a random number generator and limit the generator to return values of 0 and 1, where 0 means “heads” and 1 mean “tails.” This is how you limit Scala’s Random.nextInt method to yield only 0 or 1:

val r = new scala.util.Random
r.nextInt(2)

The r.nextInt(2) code tells nextInt to return integer values that are less than 2, i.e., 0 and 1.

Knowing that, I can write a coin flip function like this:

// returns "H" for heads, "T" for tails
def tossCoin(r: Random) = {
    val i = r.nextInt(2)
    i match {
        case 0 => "H"
        case 1 => "T"
    }
}

Question: Do you think this is a pure function? If so, why do you think so, and if not, why not?

With these functions out of the way, let’s get to the main part of the lesson: how to write the main loop of the program with an immutable game state.

Writing the main loop in FP style

So now we need a “loop” ... how can we write one in an FP style? Using the tools we know so far, the best way to handle this is with our new friend, recursion.

Because you may have never done this before, let me add a few important notes:

  • With recursion the main loop is going to call itself repeatedly (recursively)
  • Because the game state needs to be updated as the game goes along, a GameState instance needs to be passed into each recursive call
  • Because each instance of the loop will simulate the flip of a coin, and because the tossCoin function requires a scala.util.Random instance, it’s also best to pass a Random instance into each recursive call as well

Given that background, I can start writing some code. First, here’s the GameState I showed earlier:

case class GameState (
    numFlips: Int, 
    numCorrectGuesses: Int
)

Next, I know I’m going to need (a) a Scala App, (b) initial GameState and Random instances, and (c) some sort of mainLoop call to get things started. I also know that mainLoop will take the GameState and Random instances, which leads me to this code:

object CoinFlip extends App {
    val s = GameState(0, 0)
    val r = new Random
    mainLoop(s, r)
}

Next, I can sketch the mainLoop function like this:

@tailrec
def mainLoop(gameState: GameState, random: Random) {
    // a) prompt the user for input
    // b) get the user's input
    // c) flip the coin
    // d) compare the flip result to the user's input
    // e) write the output
    // f) if the user didn't type 'h', loop again:
    mainLoop(newGameState, random)
}

If you feel like you understand what I’ve sketched in this mainLoop code, I encourage you to set this book aside and work on filling out mainLoop’s body on your own, using (a) the I/O functions I showed earlier and (b) any other code you might need. That’s all that needs to be done now: fill out the body, and figure out where the recursive mainLoop call (or calls) need to be made.

Writing the skeleton code

The next thing I did to solve this problem was to stub out the following skeleton code:

object CoinFlip extends App {

    val r = Random
    val s = GameState(0, 0)
    mainLoop(s, r)

    @tailrec
    def mainLoop(gameState: GameState, random: Random) {

        // a) prompt the user for input
        showPrompt()

        // b) get the user's input
        val userInput = getUserInput()

        userInput match {
            case "H" | "T" => {
                // c) flip the coin
                val coinTossResult = tossCoin(random)
                val newNumFlips = gameState.numFlips + 1

                // d) compare the flip result to the user's input
                if (userInput == coinTossResult) {
                    // they guessed right
                    // e) write the output
                    // f) if the user didn't type 'h', loop again:
                    mainLoop(newGameState, random)
                } else {
                    // they guessed wrong
                    // e) write the output
                    // f) if the user didn't type 'h', loop again:
                    mainLoop(newGameState, random)
                }
            }
            case _   => {
                // assume they type 'Q'
                println("\n=== GAME OVER ===")
                printGameState(gameState)
                // we return out of the recursion here
            }
        }
    }

}

That code is slightly different than my pseudocode, but it’s in the ballpark.

Now all I need to do is finish off the ‘e’ and ‘f’ portions of the algorithm. I’ll show those sections in the completed code that follows.

The complete source code

The following source code shows the first cut of my solution for this application.

First, I put all of my “utility” functions in a separate object named CoinFlipUtils, in a file named CoinFlipUtils.scala:

package com.alvinalexander.coinflip.v1

import scala.util.Random
import scala.io.StdIn.readLine

object CoinFlipUtils {

    def showPrompt(): Unit = { print("\n(h)eads, (t)ails, or (q)uit: ") }

    def getUserInput(): String = readLine.trim.toUpperCase

    def printableFlipResult(flip: String): String = flip match {
        case "H" => "Heads"
        case "T" => "Tails"
    }

    def printGameState(printableFlipResult: String, gameState: GameState): Unit = {
        print(s"Flip was $printableFlipResult. ")
        printGameState(gameState)
    }

    def printGameState(gameState: GameState): Unit = {
        println(s"#Flips: ${gameState.numFlips}, #Correct: ${gameState.numCorrect}")
    }

    def printGameOver(): Unit = println("\n=== GAME OVER ===")

    // returns "H" for heads, "T" for tails
    def tossCoin(r: Random): String = {
        val i = r.nextInt(2)
        i match {
            case 0 => "H"
            case 1 => "T"
        }
    }

}

I did that to keep the code organized, and also to keep my next file smaller. Here’s the source code for CoinFlip.scala, which primarily consists of the mainLoop:

package com.alvinalexander.coinflip.v1

import CoinFlipUtils._
import scala.annotation.tailrec
import scala.util.Random

case class GameState(numFlips: Int, numCorrect: Int)

object CoinFlip extends App {

    val r = Random
    val s = GameState(0, 0)
    mainLoop(s, r)

    @tailrec
    def mainLoop(gameState: GameState, random: Random) {

        showPrompt()
        val userInput = getUserInput()

        // handle the result
        userInput match {
            case "H" | "T" => {
                val coinTossResult = tossCoin(random)
                val newNumFlips = gameState.numFlips + 1
                if (userInput == coinTossResult) {
                    val newNumCorrect = gameState.numCorrect + 1
                    val newGameState = gameState.copy(numFlips = newNumFlips, numCorrect = newNumCorrect)
                    printGameState(printableFlipResult(coinTossResult), newGameState)
                    mainLoop(newGameState, random)
                } else {
                    val newGameState = gameState.copy(numFlips = newNumFlips)
                    printGameState(printableFlipResult(coinTossResult), newGameState)
                    mainLoop(newGameState, random)
                }
            }
            case _   => {
                printGameOver()
                printGameState(gameState)
                // return out of the recursion here
            }
        }
    }

}

There are a few ways to shorten and refactor that code, but it gives you an idea of what needs to be done for this game.

When the user’s guess is correct

Note that when the user’s guess matches the coin flip, I use this code:

val newNumCorrect = gameState.numCorrect + 1
val newGameState = gameState.copy(numFlips = newNumFlips, numCorrect = newNumCorrect)
printGameState(printableFlipResult(coinTossResult), newGameState)
mainLoop(newGameState, random)

The key here is that when the user’s guess is correct I need to create a new GameState and pass that new instance into the next mainLoop call. I show that code in a long form, but I can remove the newNumCorrect temporary variable:

val newGameState = gameState.copy(
    numFlips = newNumFlips, 
    numCorrect = gameState.numCorrect + 1
)
printGameState(printableFlipResult(coinTossResult), newGameState)
mainLoop(newGameState, random)

When the user’s guess is incorrect

In the case where the user’s guess is incorrect, I only need to update numFlips when creating a new GameState instance, so that block of code looks like this:

val newGameState = gameState.copy(numFlips = newNumFlips)
printGameState(printableFlipResult(coinTossResult), newGameState)
mainLoop(newGameState, random)

When the user wants to quit the game

In the case where the user enters anything other than H or T, I assume they want to quit the game, so I call these procedures:

printGameOver()
printGameState(gameState)

At this point I don’t call mainLoop any more, so the recursion ends, all of the recursive calls unwind, and the game ends.

Summary

At the beginning of this lesson I noted that the goals for this lesson were:

  • To write our first functional application
  • Show a first example of how to handle “state” in an FP application

A few important parts about this lesson that you may not have seen before in traditional imperative code are:

  • The use of an explicit GameState variable
  • Using recursion as a way of looping
  • The recursion let us define the GameState instance as an immutable val field

I’ll come back to this example later in this book and show another way to handle the “main loop” without using recursion, but given what I’ve shown so far, recursion is the only way to write this code using only val fields.

Exercises

  1. Modify the game so you can play a new game by pressing 'n'
  2. After adding the ability to play a new game, modify the program to keep a history of all previously-played games

See also