Babyburger
Babyburger

Reputation: 1830

Type error comparing signal int with int [Elm]

I want to make a button highlighted when selected (light blue background). To do so, I wrote the following code:

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

When I run this along the rest of my code, I get this error msg:

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

  Expected Type: Int
    Actual Type: Signal.Signal Int

The problem is that players.signal is a Signal Int. If I replace this to a normal number, my program works as it should. However, I need a button to become light blue when selected and white again when another button is selected. Is there any way to transmute the signal to a normal integer for comparing purposes? I have tried creating an external compare method and lift the signal to that method, but the result of this method would then be a Signal Bool that I cannot use. To illustrate what I did:

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

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

update

I have tried to move the signal 'higher up' to the calling functions, but the problem remains for me. I might as well show all of the code (it's not much, I should have done this in the first place).

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

I'll explain what my end goal is first. The game starts in a beginning screen (beginState). In this screen, the player(s) can customize:
- a nickname (textfield returning a string)
- controls (wasd, arrow keys, any keys the user defines for directions)
- amount of players

When space bar has been entered, the game starts and these parameters can no longer be changed. This can be seen in startGame. How the actual game works is of little relevance to this topic, but it's important to know that I expect the customized options from the start screen to be transferred to the game mechanics. So input keys for certain actions per player must coincide with characters chosen in the controls textfields. The number of players chosen will determine which objects get created in the game. The playState of this game is currently replaced by a holder draw.

On to the problem:
I'm slowly learning that I cannot simply ask for the amount of players by calling players.signal because I'd get a Signal Int instead of an Int. This means I will not be able to call, say controls.signal, to retrieve a char to put in Keyboard.directions (because I'll get a Signal Char instead of Char). The question is basically, how do I store and pass on information from signals like I would with variables in other programming languages?

The more I work on this code, the more I realize I don't understand how signals work. In your example (Apanatshka) I see you lift a signal to playerSelection, which expects an Int as argument instead of a Signal Int. When I try to apply this (see code higher up in this update section), I get the following error:

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

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

I got the general idea what could be wrong. Two signals are being sent from one function and it's expecting only one. Honestly, I'd much rather see the signal propagated to a more relevant position like in beginState:

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

But then toForm gets a Signal Element argument instead of Element, which spells problems again. I could technically put the signal all the way up in main, but I have a very bad feeling about it because players.signal is no longer needed once the game has started and the signal's been passed on as an Int parameter.

I hope I'm clear in what I want to accomplish. And to sum up my issues quickly:
- Where would players.signal fit in my code without propping all of my signals to main?
- How would I store and pass on information from a signal?

Upvotes: 2

Views: 219

Answers (1)

Apanatshka
Apanatshka

Reputation: 5958

Answer to "update"

I think the Learn page of the Elm website has some good resources on the general questions that you're asking:

  1. Using Signals
  2. Modular Architecture

I'll try to give a short recap and add my opinion because these are just links to official docs.

Type errors note

First off, type errors from the Elm compiler can be confusing. Expected vs. Actual is usually given in an unintuitive order. But the type error you're getting is from giving foldp a base value that is a Signal. The type of foldp : (a -> b -> b) -> b -> Signal a -> Signal b shows that if b = Signal Element, then the result of the foldp is Signal (Signal b). This is generally the problematic situation then you get into when you start using signals in your code "too soon".

Using Signals

I understand that you think of signals are mutable variables and just want to mention them wherever you want their current value. But as you have noted, that's basically where signals are different from mutable variables. So the general idea for a simple architecture is to combine all your inputs into one data structure:

data Input =
    Players Int
  | Control String

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

You build another data structure that holds all the state your program needs and define an initialState.
Then you define a general update function, where given a new input you can update the state.
You also create a view function to show your state.

Then you create main:

main = view <~ foldp update initialState input

This is basically what's describe as a common pattern in the first link. I think this answers your question How would I store and pass on information from a signal?: with foldp.
It answers Where would players.signal fit in my code without propping all of my signals to main? because the answer here is: you have to prop all your signals to main. But that answer is unsatisfactory and may not be scalable, so that's why I included the second link.

Modular Architecture

The pattern of a single foldp to hold all your state seems a little overly strict.1 But it works.. Until you start scaling it up and want some separation of concerns for different components.

So the basic idea of the Architecture page is to create components that don't interact by defining their state, input, update, view in a module. Then you main program imports the different modules, creates data structures to combine the different states/inputs/updates/views and be able to use a single foldp again, in the end, to use in main.


1 I actually believe it is too strict and can be loosened, but that's a tangent I didn't want to get into. It's non-trivial idea and needs some more work if it is ever going to be implemented, so it's not exactly appropriate material to refer a newbie to. But it's my pet project (not that I have time for it), so there you go: [shameless plug].


Original answer before the "update"

General idea

You're thinking in the right direction. The idea is to lift players.signal out of the buttonPressed function altogether. The general advice for Signals is try not to use them at all, then at the last moment ---in main--- use them. If main gets too complex, then refactor even if that means using signals in other functions. But try to start out with only non-signal functions.

Solution

Only use a signal in main, so to use players.signal in buttonPressed you need to pass it along as an argument (notice the argument is Int, not Signal Int):

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

You need to pass this Int from playerSelection to buttonPressed, so add it as an argument there as well:

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")
    ]

Now you can write main like so:

main = playerSelection <~ players.signal

Executable example

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

Upvotes: 2

Related Questions