As of the time of this post, I have only been learning F# for about 2 weeks. Having previously completed a series of F# koans by Chris Marinos, I was eager to branch out and start building something workable in the language. I remembered when taking a Haskell class in college that one assignment was to create a Mastermind like game that required the player to try and guess a 5-letter word from the English language. We were supplied with a flat file of all 5-letter English words and were tasked with creating the game using Haskell. I thought that trying to do the same would be a great way to learn different aspects and modules within F#.
The first task I wanted to complete was to figure out how to create a game loop within F#. For the game loop to work properly, it should be able to perform the following actions for an infinite amount of time:
- Request the guess from the user for the correct word
- Evaluate the guess
- Return to step 1 if the guess is not correct
For this series of tutorials, I will be hosting the live code on repl.it for anyone that wants to progress through the samples and see a working version.
My first attempt at the game loop was to write a function with tail recursion (although nothing to calculate yet) to eliminate stack overflow exceptions and keep things nice and simple.
This is a pretty straight-forward approach with a game loop that gets the user input from the Console, outputs the input to the console, and then repeats the game loop.
open System let getInput () = printf "guess:>" Console.ReadLine () let output (s:string) = printfn "You typed: %s" s let rec gameLoop() = let input = getInput () output input gameLoop() [<EntryPoint>] let main argv = gameLoop() 0 // return an integer exit code
Knowing that recursion can be difficult sometimes to wrap my brain around, and not wanting to constantly worry about stack overflow bugs, I decided to see if there was another approach utilizing the power of existing F# modules. Having been through the ‘koans’ course listed above, I decided to experiment with using F# sequences to replicate the game loop instead of using a recursive function.
The F# Seq module comes with many handy functions for creating and interacting with sequences. Of particular interest is the ability to create an F# infinite sequence that is lazily evaluated as needed.
For my game loop, I made a few changes in order to take advantage of using an infinite sequence. Each iteration in the sequence prompts for input and then outputs that input back to the Console.
open System let printPrompt () = Console.Write "guess:>" let getLine = fun _ -> printPrompt () System.Console.ReadLine () let writeResponse (s:string) = printfn "You typed: %s" s let progLoop () = let lines = Seq.initInfinite getLine Seq.iter writeResponse lines [<EntryPoint>] let main argv = progLoop () |> ignore 0
In order to allow for the function ‘getLine’ to be used as the infinite sequence generator, the declaration was changed slightly to allow for a wildcard (denoted by the underscore character) parameter. This is needed as the function used for
Seq.initInfinite takes a function that requires the
int index of the sequence to generate. Since it is not needed right now for this sequence generation, it is declared as a wildcard and not an
printPrompt function was also created to separate out the Console prompt writing as a separate function.
let getLine = fun _ -> printPrompt () System.Console.ReadLine ()
progLoop function has now also been changed to use sequences instead of a recursive function. Once the sequence is defined using
Seq.initInfinite, it is iterated over using
Seq.iter. The iterator applies the function
writeResponse to each item in the sequence.
let progLoop _ = let lines = Seq.initInfinite getLine Seq.iter writeResponse lines
Since each item in the sequence pauses to ask for user input, the game loop of prompting for input and performing an action with that input is complete.
So there it is, a simple game loop that continuously prompts for a single line of user input and repeats it back to the Console. The next post in this series will cover processing the input from the user to be able to gracefully exit the game. Stay tuned!