我想在选中时突出显示一个按钮(浅蓝色背景)。为此,我编写了以下代码:
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
问题是players.signal是一个Signal Int。如果我将其替换为正常数字,我的程序将按预期工作。但是,我需要一个按钮在选中时变为淡蓝色,并在选择另一个按钮时再次变白。有没有办法将信号转换为正常整数以进行比较?我已经尝试创建一个外部比较方法并将信号提升到该方法,但这种方法的结果将是一个我不能使用的信号布尔。为了说明我的所作所为:
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目前由持有者draw
取代。
关于问题:
我正在慢慢地知道我不能简单地通过调用players.signal
来询问玩家的数量,因为我会得到一个Signal Int而不是Int。这意味着我将无法调用controls.signal
来检索要放入Keyboard.directions
的字符(因为我将获得一个Signal Char而不是Char)。问题基本上是,我如何在信号中存储和传递信息,就像我在其他编程语言中使用变量一样?
我对这段代码的处理越多,我就意识到我越不明白信号是如何工作的。在你的例子中(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中,但我对此感觉很糟糕,因为一旦游戏开始并且信号作为Int参数传递,就不再需要players.signal
。
我希望我能清楚自己想要完成的事情。并快速总结我的问题:
- players.signal
在哪里不适合我的代码而不支持我的所有信号到主?
- 我如何存储和传递信号中的信息?
答案 0 :(得分:2)
我认为Learn的Elm website页面在您提出的一般性问题上有一些很好的资源:
我会尝试简短回顾并添加我的观点,因为这些只是官方文档的链接。
首先,来自Elm编译器的类型错误可能令人困惑。预期与实际通常以不直观的顺序给出。但是你得到的类型错误来自给foldp
一个基本值,即一个信号。 foldp : (a -> b -> b) -> b -> Signal a -> Signal b
的类型显示,如果b
= Signal Element
,则foldp
的结果为Signal (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
在我的代码中不适合我的所有信号到主?,因为这里的答案是:你必须将所有信号都支持到{{1} } 即可。但是这个答案并不令人满意,可能无法扩展,所以这就是我加入第二个链接的原因。
保持所有状态的单个main
的模式似乎有点过于严格。 1 但是它有效..直到你开始扩展它并想要一些关注的分离不同的组件。
因此,Architecture页面的基本思想是通过在模块中定义状态,输入,更新和视图来创建不交互的组件。然后,主程序导入不同的模块,创建数据结构以组合不同的状态/输入/更新/视图,并最终能够再次使用单个foldp
在foldp
中使用。
<小时/> 1 我实际上认为它太严格而且可以放松,但这是我不想进入的切线。这是一个非平凡的想法,如果要实施它还需要更多的工作,因此引用新手并不是完全合适的材料。但这是我的宠物项目(不是我有时间),所以你去了:[shameless plug]。
你在思考正确的方向。我们的想法是将main
完全取消players.signal
功能。对buttonPressed
s的一般建议是尽量不要使用它们,然后在最后一刻---在Signal
中使用它们。如果main
过于复杂,那么重构即使这意味着在其他函数中使用信号。但尝试只从非信号功能开始。
仅在main中使用信号,因此要在main
中使用players.signal
,您需要将其作为参数传递(请注意参数为buttonPressed
,而不是Int
):
Signal Int
您需要将此buttonPressed : Int -> Int -> Color
buttonPressed ps p = if ps == p then lightBlue else white
从Int
传递给playerSelection
,因此请将其作为参数添加:
buttonPressed
现在你可以像这样编写main:
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 = playerSelection <~ players.signal