3
\$\begingroup\$

Usage:


// Usage: dndgenerate /e [emhd] [0-9+]
//          dndgenerate /e d 4 4 5 4
// /e   Generate Encounter. Parameter can be one of
//          e - Easy
//          m - Medium
//          h - Hard
//          d - Deadly
//      As well as a list of heroes by their levels
//
// Example:  dndgenerate /e m 4 4 5 4
//      Generate a medium encounter for three level 4 and one level 5 adventurers.

The code to make it happen:


type DifficultyOption = Easy | Medium | Hard | Deadly

type EncounterOptions = {
    difficulty: DifficultyOption option
    heroes: int list
}

let (|Level|_|) (str: string) =
    let mutable intValue = 0

    if System.Int32.TryParse(str, &intValue)
        then match intValue with
             | x when x < 0 -> None
             | x when x > 20 -> None
             | x -> Some x
        else None



let (|Difficulty|_|) (str: string) =
    match str with
    | "e" | "E" -> Some(Easy)
    | "m" | "M" -> Some(Medium)
    | "h" | "H" -> Some(Hard)
    | "d" | "D" -> Some(Deadly)
    | x -> None

let isComplete (options:EncounterOptions) =
    not <| options.heroes.IsEmpty && options.difficulty.IsSome

type VerboseOption = VerboseOutput | TerseOutput

type MiscOptions = {
    verbose: VerboseOption
}

type GeneratorType =
    | E of EncounterOptions
    | Unknown

type ParseMode = TopLevel | Encounter | Plot | Dungeon | Settlement | Error

type CommandLineOptions = {
    misc: MiscOptions
    parseMode: ParseMode
    generatorType: GeneratorType
}

let parseTopLevel arg miscSoFar =
    match arg with
    | "/v" ->
        let newMiscSoFar = { miscSoFar with verbose = VerboseOutput }
        { misc = newMiscSoFar; parseMode = TopLevel; generatorType = Unknown }

    | "/e" -> 
        { misc = miscSoFar; parseMode = Encounter; generatorType = E { difficulty = None; heroes = [] } }

    | x ->
        { misc = miscSoFar; parseMode = Error; generatorType = Unknown }

let parseEncounter arg miscSoFar encounterGenerator =
    match arg with
    | Level x ->
        let builder' = { encounterGenerator with heroes = x :: encounterGenerator.heroes }
        { misc = miscSoFar; parseMode = Encounter; generatorType = E builder' }

    | Difficulty x ->
        let builder' = { encounterGenerator with difficulty = Some x }
        { misc = miscSoFar; parseMode = Encounter; generatorType = E builder' }

    | _ -> 
        { misc = miscSoFar; parseMode = Error; generatorType = E encounterGenerator }

let foldFunction state element =
    match state with
    | { misc = m; parseMode = TopLevel } ->
        parseTopLevel element m

    | { misc = m; parseMode = Encounter; generatorType = E(g) } ->
        parseEncounter element m g

    | { parseMode = Error } ->
        state

    | { misc = m; parseMode = p; generatorType = g } -> 
        printfn "Unexpected constellation of %A %A %A" m p g
        state

let disableIfIncomplete commandLineOptions =
    match commandLineOptions.parseMode with
    | Error ->
        commandLineOptions

    | _ ->
        match commandLineOptions.generatorType with
        | E(encounter) when isComplete encounter ->
            commandLineOptions

        | Unknown -> commandLineOptions

        | _ -> { commandLineOptions with parseMode = Error }

let parseCommandLine args =
    let defaultOptions = { 
        verbose = TerseOutput
    }

    let initialFoldState =
        { misc = defaultOptions; parseMode = TopLevel; generatorType = Unknown }

    let finalFoldState = args |> List.fold foldFunction initialFoldState

    disableIfIncomplete finalFoldState

Based on code from Scott Wlaschin's Parsing command line arguments but extended to include argument groups and error handling. Purposefully avoided FParsec because I'm new to F# and wanted to solve it vanilla-style first.

\$\endgroup\$
1
\$\begingroup\$

I am also new to F#, so I have just 2 small annotations:

The function that parses the string to Int32 can be simplified:

match System.Int32.TryParse(str) with
    | (true, x) when x >= 0 && x <= 20 -> Some x
    | _ -> None

The last case of the function parseTopLevel should be _ -> instead of x -> because the value is not used ('_' expresses exactly that)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.