3
\$\begingroup\$

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:

  1. Can the type declarations be improved/extended? Is it possible to introduce constraints? IE. Strings of length 5
  2. Can the nested if-statements be simplified using pattern matching?
  3. Is my solution considered readable? How can I improve?
  4. 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.

\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.