“In theory, theory and practice are the same. In practice, they’re not.”
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 ascala.util.Random
instance, it’s also best to pass aRandom
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 immutableval
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
- Modify the game so you can play a new game by pressing 'n'
- After adding the ability to play a new game, modify the program to keep a history of all previously-played games