I am seeking a review of my command line wordle game.
Having only recently started exploring functional programming, I expect that there will be no shortage of areas to improve. This is my first simple project outside of reading textbooks and solving exercism puzzles.
I am keen to discuss all improvements, however some of my thoughts are listed below:
- Can the type declarations be improved/extended? Is it possible to introduce constraints? IE. Strings of length 5
- Can the nested if-statements be simplified using pattern matching?
- Is my solution considered readable? How can I improve?
- Is there a better pattern/approach I could use to handle game state? Should recursion be avoided?
Solution: NET6
open System
type Result = Incorrect | CorrectLetter | CorrectPosition
type Round = {
Guess: string;
Result: Result seq;
}
type Game = {
Answer: string;
Rounds: Round seq;
}
let loadWords path =
IO.File.ReadAllLines path
let getRandomWord words =
Array.get words (Random().Next(words.Length))
let checkGuess (answer: string) (guess: string) : Result seq =
let grader (a: char, g: char) =
if a = g then Result.CorrectPosition else
if answer.Contains g then Result.CorrectLetter else Result.Incorrect
Seq.map grader (Seq.zip answer guess)
let printColoured (colour: ConsoleColor) (letter: char) =
Console.ForegroundColor <- colour
printf "%c" letter
Console.ResetColor()
let outputResult (letter: char, result: Result) =
match result with
| Incorrect -> printColoured ConsoleColor.Red letter
| CorrectLetter -> printColoured ConsoleColor.Yellow letter
| CorrectPosition -> printColoured ConsoleColor.Green letter
let outputRound (round: Round) =
Seq.iter outputResult (Seq.zip round.Guess round.Result)
printfn ""
let hasWon (results: Result seq) =
Seq.forall (fun result -> result.Equals(Result.CorrectPosition)) results
let containsAlphabetsOnly (text: string) : bool =
text |> Seq.forall (fun c -> System.Char.IsLetter(c))
let rec input (validWords: string seq) : string =
printf "Input your guess: "
let guess = System.Console.ReadLine()
if containsAlphabetsOnly guess && Seq.contains guess validWords then guess else input validWords
[<EntryPoint>]
let main argv =
let possibleAnswers = loadWords "answers.txt"
let answer = getRandomWord possibleAnswers
//printfn "Answer: %s" answer
let (initialState: Game) = { Answer = answer; Rounds = []; }
let rec play (state: Game) =
let validGuesses = Seq.append possibleAnswers (loadWords "guesses.txt")
let guess = input validGuesses
let (round: Round) = { Guess = guess; Result = checkGuess answer guess; }
outputRound round |> ignore
let newRoundState = Seq.append state.Rounds [round]
if hasWon round.Result then state else
if Seq.length state.Rounds >= 5 then state
else play { state with Rounds = newRoundState; }
let result = play initialState
printfn "Finished in: %d rounds" (Seq.length result.Rounds + 1)
printfn "Answer: %s" result.Answer
0
The files referenced above were taken from this reddit post.