简体   繁体   中英

How do i write the classic high/low game in F#?

I was reading up on functional languages and i wondered how i would implement 'tries' in a pure functional language. So i decided to try to do it in F#

But i couldnt get half of the basics. I couldnt figure out how to use a random number, how to use return/continue (at first i thought i was doing a multi statement if wrong but it seems like i was doing it right) and i couldnt figure out how to print a number in F# so i did it in the C# way.

Harder problems is the out param in tryparse and i still unsure how i'll do implement tries without using a mutable variable. Maybe some of you guys can tell me how i might correctly implement this

C# code i had to do last week

using System;

namespace CS_Test
{
    class Program
    {
        static void Main(string[] args)
        {
            var tries = 0;
            var answer = new Random().Next(1, 100);
            Console.WriteLine("Guess the number between 1 and 100");
            while (true)
            {
                var v = Console.ReadLine();
                if (v == "q")
                {
                    Console.WriteLine("you have quit");
                    return;
                }
                int n;
                var b = Int32.TryParse(v, out n);
                if (b == false)
                {
                    Console.WriteLine("This is not a number");
                    continue;
                }
                tries++;
                if (n == answer)
                {
                    Console.WriteLine("Correct! You win!");
                    break;
                }
                else if (n < answer)
                    Console.WriteLine("Guess higher");
                else if (n > answer)
                    Console.WriteLine("Guess lower");
            }
            Console.WriteLine("You guess {0} times", tries);
            Console.WriteLine("Press enter to exist");
            Console.ReadLine();
        }
    }
}

The very broken and wrong F# code

open System;

let main() =
    let tries = 0;
    let answer = (new Random()).Next 1, 100
    printfn "Guess the number between 1 and 100"
    let dummyWhileTrue() =
        let v = Console.ReadLine()
        if v = "q" then
            printfn ("you have quit")
            //return
        printfn "blah"
        //let b = Int32.TryParse(v, out n)
        let b = true;
        let n = 3
        if b = false then
            printfn ("This is not a number")
            //continue;
        //tries++
        (*
        if n = answer then
            printfn ("Correct! You win!")
            //break;
        elif n < answer then
            printfn ("Guess higher")
        elif n>answer then
            printfn ("Guess lower")
        *)
    dummyWhileTrue()
    (Console.WriteLine("You guess {0} times", tries))
    printfn ("Press enter to exist")
    Console.ReadLine()
main()

Welcome to F#!

Here's a working program; explanation follows below.

open System

let main() = 
    let answer = (new Random()).Next(1, 100)
    printfn "Guess the number between 1 and 100" 
    let rec dummyWhileTrue(tries) = 
        let v = Console.ReadLine() 
        if v = "q" then 
            printfn "you have quit"
            0
        else
            printfn "blah" 
            let mutable n = 0
            let b = Int32.TryParse(v, &n) 
            if b = false then 
                printfn "This is not a number"
                dummyWhileTrue(tries)
            elif n = answer then 
                printfn "Correct! You win!"
                tries
            elif n < answer then 
                printfn "Guess higher"
                dummyWhileTrue(tries+1) 
            else // n>answer
                printfn "Guess lower"
                dummyWhileTrue(tries+1) 
    let tries = dummyWhileTrue(1) 
    printfn "You guess %d times" tries
    printfn "Press enter to exit"
    Console.ReadLine()  |> ignore
main() 

A number of things...

If you're calling methods with multiple arguments (like Random.Next ), use parens around the args ( .Next(1,100) ).

You seemed to be working on a recursive function ( dummyWhileTrue ) rather than a while loop; a while loop would work too, but I kept it your way. Note that there is no break or continue in F#, so you have to be a little more structured with the if stuff inside there.

I changed your Console.WriteLine to a printfn to show off how to call it with an argument.

I showed the way to call TryParse that is most like C#. Declare your variable first (make it mutable, since TryParse will be writing to that location), and then use &n as the argument (in this context, &n is like ref n or out n in C#). Alternatively, in F# you can do like so:

let b, n = Int32.TryParse(v)

where F# lets you omit trailing-out-parameters and instead returns their value at the end of a tuple; this is just a syntactic convenience.

Console.ReadLine returns a string, which you don't care about at the end of the program, so pipe it to the ignore function to discard the value (and get rid of the warning about the unused string value).

Here's my take, just for the fun:

open System

let main() = 
    let answer = (new Random()).Next(1, 100)
    printfn "Guess the number between 1 and 100" 
    let rec TryLoop(tries) = 
        let doneWith(t) = t
        let notDoneWith(s, t) = printfn s; TryLoop(t) 
        match Console.ReadLine() with
        | "q" -> doneWith 0
        | s -> 
            match Int32.TryParse(s) with
            | true, v when v = answer -> doneWith(tries)
            | true, v when v < answer -> notDoneWith("Guess higher", tries + 1) 
            | true, v when v > answer -> notDoneWith("Guess lower", tries + 1)
            | _ -> notDoneWith("This is not a number", tries)

    match TryLoop(1) with
        | 0 -> printfn "You quit, loser!"
        | tries -> printfn "Correct! You win!\nYou guessed %d times" tries

    printfn "Hit enter to exit"
    Console.ReadLine()  |> ignore
main() 

Things to note:

  • Pattern matching is prettier, more concise, and - I believe - more idiomatic than nested ifs
  • Used the tuple-return-style TryParse suggested by Brian
  • Renamed dummyWhileTrue to TryLoop , seemed more descriptive
  • Created two inner functions doneWith and notDoneWith , (for purely aesthetic reasons)

I lifted the main pattern match from Evaluate in @Huusom's solution but opted for a recursive loop and accumulator instead of @Hussom's (very cool) discriminate union and application of Seq.unfold for a very compact solution.

open System
let guessLoop answer = 
    let rec loop tries =
        let guess = Console.ReadLine()
        match Int32.TryParse(guess) with
        | true, v when v < answer -> printfn "Guess higher." ; loop (tries+1)
        | true, v when v > answer -> printfn "Guess lower." ; loop (tries+1)
        | true, v -> printfn "You won." ; tries+1
        | false, _ when guess = "q" -> printfn "You quit." ; tries
        | false, _ -> printfn "Not a number." ; loop tries
    loop 0

let main() =
    printfn "Guess a number between 1 and 100."
    printfn "You guessed %i times" (guessLoop ((Random()).Next(1, 100)))

Also for the fun of if:

open System

type Result =
    | Match
    | Higher
    | Lower
    | Quit
    | NaN

let Evaluate answer guess =
    match Int32.TryParse(guess) with
    | true, v when v < answer -> Higher
    | true, v when v > answer -> Lower
    | true, v -> Match
    | false, _ when guess = "q" -> Quit
    | false, _ -> NaN

let Ask answer =
    match Evaluate answer (Console.ReadLine()) with
    | Match -> 
        printfn "You won."
        None
    | Higher -> 
        printfn "Guess higher."
        Some (Higher, answer)
    | Lower -> 
        printfn "Guess lower."
        Some (Lower, answer)
    | Quit -> 
        printfn "You quit."
        None
    | NaN ->
        printfn "This is not a number."
        Some (NaN, answer)

let main () = 
    printfn "Guess a number between 1 and 100."
    let guesses = Seq.unfold Ask ((Random()).Next(1, 100))
    printfn "You guessed %i times" (Seq.length guesses)

let _ = main()

I use an enumeration for state and Seq.unfold over input to find the result.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM