2

我想在选择时突出显示一个按钮(浅蓝色背景)。为此,我编写了以下代码:

players : Input Int
players = input 1

playerSelection : Element
playerSelection = flow right [ 
      color (buttonPressed 1) (button players.handle 1 "Solo")
    , color (buttonPressed 2) (button players.handle 2 "2P")
    , color (buttonPressed 3) (button players.handle 3 "3P")
    , color (buttonPressed 4) (button players.handle 4 "4P")
    ]

buttonPressed : Int -> Color
buttonPressed p = if players.signal == p then lightBlue else white

当我沿着我的其余代码运行它时,我收到这个错误消息:

   if | players.signal == p -> lightBlue
      | True -> white

  Expected Type: Int
    Actual Type: Signal.Signal Int

问题是 player.signal 是一个 Signal Int。如果我将其替换为正常数字,我的程序将正常工作。但是,我需要一个按钮在选择时变为浅蓝色,在选择另一个按钮时再次变为白色。有没有办法将信号转换为普通整数以进行比较?我已经尝试创建一个外部比较方法并将信号提升到该方法,但是这种方法的结果将是一个我无法使用的 Signal Bool。为了说明我做了什么:

buttonPressed : Int -> Color
buttonPressed p = if equals p <~ players.signal then lightBlue else white

equals : Int -> Int -> Bool
equals a b = a == b

更新

我试图将信号“更高”移动到调用函数,但问题仍然存在。我不妨展示所有的代码(它并不多,我应该首先这样做)。

heartbeat = every (second/50)

data Player = One | Two | Three | Four

-- STARTING SCREEN
players : Input Int
players = input 1

beginState : Int -> Element
beginState ps = collage 600 600 [
      move (0,270) (toForm (playerSelection ps))
    ]

playerSelection : Int -> Element
playerSelection ps = flow right [
      color (buttonPressed 1 ps) (button players.handle 1 "Solo")
    , color (buttonPressed 2 ps) (button players.handle 2 "2P")
    , color (buttonPressed 3 ps) (button players.handle 3 "3P")
    , color (buttonPressed 4 ps) (button players.handle 4 "4P")
    ]

buttonPressed : Int -> Int -> Color
buttonPressed p ps = if p == ps then lightBlue else white

playState : Element
playState = draw

draw : Element
draw = collage 1024 768 [
      filled black (rect 1024 768)
    ]

endState : String -> Element
endState p = plainText (show p ++ " has won!")

startGame : Signal Element
startGame = foldp (\_ _ -> draw) (beginState <~ players.signal) Keyboard.space

main : Signal Element
main = startGame

我将首先解释我的最终目标是什么。游戏在开始屏幕 (beginState) 中开始。在此屏幕中,玩家可以自定义:
- 昵称(返回字符串的文本字段)
- 控件(wasd、箭头键、用户定义的任何方向键)
- 玩家数量

输入空格键后,游戏开始,这些参数不能再更改。这可以在startGame中看到。实际游戏的运作方式与本主题无关,但重要的是要知道我希望将开始屏幕的自定义选项转移到游戏机制中。因此,每个玩家的某些操作的输入键必须与控件文本字段中选择的字符一致。选择的玩家数量将决定在游戏中创建哪些对象。此游戏的 playState 目前已替换为 holder draw

关于问题:
我正在慢慢地了解到,我不能简单地通过调用来询问玩家的数量,players.signal因为我会得到一个 Signal Int 而不是 Int。这意味着我将无法调用,例如controls.signal,检索要放入的字符Keyboard.directions(因为我将得到一个信号字符而不是字符)。问题基本上是,我如何像使用其他编程语言中的变量一样存储和传递来自信号的信息?

我在这段代码上工作得越多,我就越意识到我不明白信号是如何工作的。在您的示例(Apanatshka)中,我看到您将信号提升到 playerSelection,它需要一个 Int 作为参数而不是 Signal Int。当我尝试应用它时(请参阅此更新部分中的代码),我收到以下错误:

Type error on line 76, column 35 to 63:
       beginState <~ players.signal

  Expected Type: Signal.Signal Graphics.Element.Element
    Actual Type: Graphics.Element.Element

我大致了解可能出了什么问题。一个函数正在发送两个信号,它只需要一个信号。老实说,我更愿意看到信号传播到更相关的位置,比如在 beginState 中:

beginState : Element
beginState = collage 600 600 [
      move (0,270) (toForm (playerSelection <~ players.signal))
    ]

但是随后 toForm 得到了一个 Signal Element 参数而不是 Element,这又带来了问题。从技术上讲,我可以将信号一直放在 main 中,但我对此感觉非常糟糕,因为players.signal一旦游戏开始并且信号作为 Int 参数传递就不再需要它了。

我希望我很清楚我想要完成什么。并快速总结我的问题:
- 如果不将players.signal我的所有信号都支撑到主信号,我的代码在哪里适合?
- 我将如何存储和传递来自信号的信息?

4

1 回答 1

2

回答“更新”

我认为Elm 网站的Learn页面对于您提出的一般问题有一些很好的资源:

  1. 使用信号
  2. 模块化架构

我会尝试做一个简短的回顾并添加我的意见,因为这些只是官方文档的链接。

类型错误说明

首先,来自 Elm 编译器的类型错误可能会令人困惑。预期与实际通常以不直观的顺序给出。但是你得到的类型错误来自于给出foldp一个信号的基值。的类型foldp : (a -> b -> b) -> b -> Signal a -> Signal b表明如果b= Signal Element,那么 的结果foldpSignal (Signal b)。这通常是当您“过早”开始在代码中使用信号时遇到的问题情况。

使用信号

我了解您认为信号是可变变量,并且只想在需要其当前值的任何地方提及它们。但正如您所指出的,这基本上就是信号与可变变量不同的地方。因此,简单架构的总体思路是将所有输入组合到一个数据结构中:

data Input =
    Players Int
  | Control String

input : Signal Input
input = merge (Players <~ players.signal) (Control <~ control.signal)

您构建另一个数据结构来保存程序所需的所有状态并定义一个initialState.
然后你定义一个通用的更新函数,给定一个新的输入你可以更新状态。
您还可以创建一个view函数来显示您的状态。

然后你创建main

main = view <~ foldp update initialState input

这基本上是第一个链接中描述的常见模式。我认为这回答了您的问题我将如何存储和传递来自信号的信息?:foldp
它回答了我的代码在哪里players.signal适合而不将我的所有信号都支撑到主信号?因为这里的答案是:你必须将所有信号都支持到main. 但是这个答案不能令人满意,并且可能无法扩展,所以这就是我包含第二个链接的原因。

模块化架构

单单foldp保存你所有状态的模式似乎有点过于严格了。1但它有效.. 直到您开始扩大规模并希望对不同组件进行一些关注点分离。

所以架构页面的基本思想是通过在一个模块中定义它们的状态、输入、更新、视图来创建不交互的组件。然后你的主程序导入不同的模块,创建数据结构来组合不同的状态/输入/更新/视图,并能够foldp再次使用单个,最后,在main.


1我实际上认为它太严格并且可以放松,但这是我不想进入的切线。这是一个不平凡的想法,如果要实施,还需要做更多的工作,所以它不是推荐新手的完全合适的材料。但这是我的宠物项目(不是我有时间),所以你去吧:[无耻的插件]


“更新”之前的原始答案

大概的概念

你在思考正确的方向。这个想法是完全players.signal脱离这个buttonPressed功能。s的一般建议Signal是尽量不要使用它们,然后在最后一刻 ---in main--- 使用它们。如果main变得太复杂,那么即使这意味着在其他函数中使用信号,也要进行重构。但试着从非信号函数开始。

解决方案

仅在 main 中使用信号,因此要使用players.signalinbuttonPressed您需要将其作为参数传递(注意参数是Int,而不是Signal Int):

buttonPressed : Int -> Int -> Color
buttonPressed ps p = if ps == p then lightBlue else white

您需要将其IntplayerSelectionto传递buttonPressed,因此也将其作为参数添加:

playerSelection : Int -> Element
playerSelection ps = flow right [ 
      color (buttonPressed ps 1) (button players.handle 1 "Solo")
    , color (buttonPressed ps 2) (button players.handle 2 "2P")
    , color (buttonPressed ps 3) (button players.handle 3 "3P")
    , color (buttonPressed ps 4) (button players.handle 4 "4P")
    ]

现在你可以这样写 main :

main = playerSelection <~ players.signal

可执行示例

import Graphics.Input (Input, input, button)

players : Input Int
players = input 1

playerSelection : Int -> Element
playerSelection ps = flow right [ 
      color (buttonPressed ps 1) (button players.handle 1 "Solo")
    , color (buttonPressed ps 2) (button players.handle 2 "2P")
    , color (buttonPressed ps 3) (button players.handle 3 "3P")
    , color (buttonPressed ps 4) (button players.handle 4 "4P")
    ]

buttonPressed : Int -> Int -> Color
buttonPressed ps p = if ps == p then lightBlue else white

main = playerSelection <~ players.signal
于 2014-12-09T08:08:31.830 回答