如何让用户在控制台F#中再次玩

时间:2016-11-16 12:39:19

标签: f#

好的,所以我现在正在fsharp的控制台中制作策划者,我试图找出如何询问用户是否想要再玩一次。

let main() =
  choosePuzzleMaker() 
  puzzleGuess() 
 c <- guess b [([],(0,0))] 
  while a <> c && d <> 8 do
    c <- guess b [(c, validate a c)] 
    d <- d+1 
  if d <> 8 then
    printfn "GZ! FUCKING MASTERMIND! You completed in %A turns and the code was %A" d a
  else
    printfn "That didn't go well...?"
    printfn "Game Over!"
PlayAgain()
main()

我尝试将PlayAgain()定义为:

let rec PlayAgain() =
  printfn "Do you want to play again? Please type:
1: Yes
2: No\n"
  match System.Console.ReadLine() with
  | "1"|"yes"|"Yes" -> printfn "Alright!!!"
                       choosePuzzleMaker()

  | "2"|"no"|"No" -> printfn "The game is over!"

  | _   -> printfn "Invalid option! Please try again!"
           (PlayAgain())

然而,这并没有奏效,所以我的问题是:

你如何让控制台回答是/否并让程序重新开始?

1 个答案:

答案 0 :(得分:2)

看起来您的问题是一个简单的缩进错误。与Python一样,F#通过缩进来定义代码块。让我举个例子:

// Some variables
let x = 5
let y = 3
let z = 1

let add1_wrong x =
    printfn "Adding 1 to %d produces..." x
printfn "The wrong answer: %d" (x + 1)  // Oops! This is wrong

let add1_correct x =
    printfn "Adding 1 to %d produces..." x
    printfn "The right answer: %d" (x + 1)  // This is correct

add1_wrong x
add1_wrong y
add1_wrong z
add1_correct x
add1_correct y
add1_correct z

尝试在F#Interactive中运行它,您将获得以下输出:

The wrong answer: 6
Adding 1 to 5 produces...
Adding 1 to 3 produces...
Adding 1 to 1 produces...
Adding 1 to 5 produces...
The right answer: 6
Adding 1 to 3 produces...
The right answer: 4
Adding 1 to 1 produces...
The right answer: 2

注意&#34;错误答案:6&#34;在您调用add1_wrong函数之前立即打印?编写代码的方式,看起来像作者意图printfn "The wrong answer"行放在add1_wrong函数中,但是他做了一个缩进错误并把它放在在外面而不是功能。因此,它与设置xyz变量并调用add1_wrongadd1_right的其他代码同时运行。

如果您还不了解该示例代码中的内容,立即停止阅读并继续阅读,直至您理解为止。 (或者问一个后续问题,如果你在两三次阅读后仍然不理解它,因为这意味着我没有很好地解释它)。在继续操作之前,您在示例代码中看到缩进错误非常重要,因为您发布的代码中存在相同的错误。实际上,你有两个缩进错误,但只有其中一个导致你问我们的问题。

这里是你的main()函数,就像你在这个问题中键入它一样,其中有两个缩进错误:

let main() =
  choosePuzzleMaker() 
  puzzleGuess() 
 c <- guess b [([],(0,0))] 
  while a <> c && d <> 8 do
    c <- guess b [(c, validate a c)] 
    d <- d+1 
  if d <> 8 then
    printfn "GZ! FUCKING MASTERMIND! You completed in %A turns and the code was %A" d a
  else
    printfn "That didn't go well...?"
    printfn "Game Over!"
PlayAgain()
main()

现在,这里有相同的功能,两个缩进错误都解决了:

let main() =
  choosePuzzleMaker() 
  puzzleGuess() 
  c <- guess b [([],(0,0))] 
  while a <> c && d <> 8 do
    c <- guess b [(c, validate a c)] 
    d <- d+1 
  if d <> 8 then
    printfn "GZ! FUCKING MASTERMIND! You completed in %A turns and the code was %A" d a
  else
    printfn "That didn't go well...?"
    printfn "Game Over!"
    PlayAgain()
main()

c <- guess b [([],(0,0))]行已缩进以匹配其他行,并且末尾的PlayAgain()调用已缩进为 in 您的main()函数,而不是之外,就像你最初编写它一样。

这就是马克·西曼在评论中所写的意思,&#34;该程序只调用PlayAgain一次&#34;。您编写它的方式是PlayAgain函数在main()结束时调用而非。相反,您将其称为一次,然后调用main()一次,然后退出您的程序。

顺便说一句,我(以及其他有经验的F#程序员)会在您的代码中建议做出不同的其他事情 - 例如,名称ab,{{ 1}}和c不是好名字,因为他们不会给你一些关于这些名字应该是什么的线索。我建议将它们重命名如下:

  • d应该被称为a
  • correctAnswer应该被称为......实际上,我还不知道b是什么。我知道它被传递到b函数,但我不知道它是如何被使用的。顺便说一下,这就是为什么它是一个糟糕的变量名称。即使我没有看到任何使用它的代码,单独的名称应该为我提供关于如何使用它的线索。
  • guess应该被称为c
  • thisGuess应该被称为drounds

希望能帮助你找出(并修复)你的错误。如果您需要进一步的帮助,请告诉我们。

更新:要回答您的最新评论,您可以找到问题的简单解决方案,以及一个聪明的解决方案。我将向您展示两者,因为聪明的解决方案将教您一种非常有价值的编程技术,它在函数编程语言(如F#)中一直使用

首先,简单的解决方案。在F#中,如果你需要有两个相互调用的函数,那就叫做相互递归,你可以使用两个关键字来支持它:{{ 1}}和numberOfGuesses个关键字。它看起来像这样:

rec

and关键字告诉F#编译器&#34;我定义的函数将在某个时刻直接或间接地调用自身 - 所以请在其中提供其名称。功能本身。&#34; // Note that these two functions would form an infinite loop! let rec f x = g (x + 1) and g x = f (x * 2) 关键字会创建一个,这些所有的名称彼此可用。

因此可以解决此问题的一种方法是执行以下操作:

rec

那会有效,但我建议第二种解决方案。函数式编程的一个关键思想是将函数视为&#34;事物&#34;你可以操纵。也就是说,您可以将函数存储在列表或数组中,将它们作为参数传递给其他函数,等等。这为我们提供了一种非常强大的技术,可以使用像and函数这样的函数,并使其更通用和可重用。如果像let rec PlayAgain() = // ... and main() = // ... 这样的函数具有一般结构&#34;做一些计算或做出决定。然后,根据结果,接下来做A或B&#34; - 那你要做的是制作功能的A和B 参数 !换句话说,你将它从一个不带参数的函数转换为一个带有一个或两个参数的函数,其中参数是&#34;下一步做什么&#34;功能。 (通常情况下,您在两个方案之间选择一个函数中的两个参数。但对于您的PlayAgain函数,其中一个&#34;接下来要做什么&#34;步骤是& #34;什么都不做&#34;,所以让它只需要一个参数就行了。)这被称为延续传递风格 - &#34;延续&#34;是任何&#34;下一步做什么的传统功能编程术语&#34;步骤

这就是看起来的样子:

PlayAgain

那就是它!我所做的就是给PlayAgain一个参数,然后在适当的地方调用该参数。现在我们重写您的let rec PlayAgain whatToDoNext = printfn "Do you want to play again? Please type: 1: Yes 2: No\n" match System.Console.ReadLine() with | "1"|"yes"|"Yes" -> printfn "Alright!!!" whatToDoNext() | "2"|"no"|"No" -> printfn "The game is over!" | _ -> printfn "Invalid option! Please try again!" (PlayAgain()) 函数,如下所示(仅更改最后一行,并使用PlayAgain,以便在main()函数中可以使用名称let rec

main

通过这种方式,您已经避免使用main(),您发现了一种强大的新编程技术。我强烈建议F#初学者尽可能避免使用let rec main() = choosePuzzleMaker() puzzleGuess() c <- guess b [([],(0,0))] while a <> c && d <> 8 do c <- guess b [(c, validate a c)] d <- d+1 if d <> 8 then printfn "GZ! FUCKING MASTERMIND! You completed in %A turns and the code was %A" d a else printfn "That didn't go well...?" printfn "Game Over!" PlayAgain main 关键字,因为它往往会增加不必要的复杂性以便稍后阅读代码。并且,在这种情况下,通常可以通过简单地制作&#34;接下来要做什么来避免它。步骤参数,这也意味着and函数将更容易在以后的程序中重复使用。