Update model at each iteration of list - elm

Update, see below
I'm currently learning Elm and this is my first functional language to begin with.
I have a List Resource and a List Converter in my model. A Converter takes in a List Resource and outputs another List Resource. The conversion only happens when there are enough inputs. I then want to iterate through each Converter and reduce/increase the number of resources.
I couldn't wrap my head around a solution. I have a feeling that List.foldr is what I should use but I'm not sure. How should I update the model so that it will get updated each iteration?
This is what I have so far:
type alias Resource =
{ resourcetype : ResourceType
, quantity: Int
}
type ResourceType
= Energy
| Water
| Metal
type alias Converter =
{ convertertype : ConverterType
, intakes : List (ResourceType, Int)
, outputs: List (ResourceType, Int)
}
type ConverterType
= SolarCollector
| Humidifer
| MetalCollector
type alias Model =
{ resources : List Resource
, converters : List Converter
}
type Msg
= Tick Time
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Tick time ->
(updateGame model, Cmd.none)
convert : List Resource -> Converter -> List Resource
convert resources converter =
let
intakes = converter.intakes
outputs = converter.outputs
-- TODO, remove intakes from resources and add outputs to resources
in
resources
updateGame : Model -> Model
updateGame model =
-- TODO, call convert on each converter and update model
model
subscriptions : Model -> Sub Msg
subscriptions model =
Time.every second Tick
Examples:
Resources won't deplete:
--Initial converters
[ SolarCollector [] [Energy 2]
, MetalCollector [Energy 1] [Metal 1]
]
--Initial resources
[ Energy 10, Metal 0]
--After 5 ticks
[ Energy 15, Metal 5]
Resources will deplete:
--Initial converters
[ MetalCollector [Energy 2] [Metal 1]
, SolarCollector [] [Energy 1]
]
--Initial resources
[ Energy 4, Metal 0]
--Tick 1
[ Energy 3, Metal 1] -- MetalCollector uses 2 Energy to get 1 Metal, SolarCollector will generate 1 Energy
--Tick 2
[ Energy 2, Metal 2] -- MC -2E,+1M; SC +1E
--Tick 3
[ Energy 1, Metal 3] -- MC -2E,+1M; SC +1E
--Tick 4
[ Energy 2, Metal 3] -- SC +1E
-- Notice how this tick the MetalCollector didn't run because of list order.
--Tick 5
[ Energy 1, Metal 4] -- MC -2E,+1M; SC +1E
Update:
I got it working! The order of the Converters matters now and it takes the right amount of resources at each tick. Here's the final, working code, thank you for helping me! Here you can try it out: https://ellie-app.com/RB3YsxwbGja1/0
module Main exposing (..)
import Html exposing (Html, div, text, program, ul, li)
import Time exposing (Time, second)
type alias Resources =
{ energy : Float
, water : Float
, metal : Float
}
resourcesToList : Resources -> List Float
resourcesToList resources =
[resources.energy, resources.water, resources.metal]
noResource : Resources
noResource = Resources 0 0 0
energyResource : Float -> Resources
energyResource energy =
{ noResource | energy = energy }
waterResource : Float -> Resources
waterResource water =
{ noResource | water = water }
metalResource : Float -> Resources
metalResource metal =
{ noResource | metal = metal }
type alias Converter =
{ convertertype : ConverterType
, quantity : Int
, intakes : Resources
, outputs: Resources
}
type ConverterType
= SolarCollector
| Humidifer
| MetalCollector
initialResources : Resources
initialResources =
{ noResource | energy = 10}
initialConverters : List Converter
initialConverters =
[ { convertertype = MetalCollector
, quantity = 2
, intakes = energyResource 1
, outputs = metalResource 1
}
, { convertertype = SolarCollector
, quantity = 2
, intakes = noResource
, outputs = energyResource 1
}
, { convertertype = Humidifer
, quantity = 1
, intakes = energyResource 1
, outputs = waterResource 1
}
]
convert : Converter -> Resources -> Resources
convert converter resources =
let
activatedQuantity =
toFloat (getActiveConverterQuantity converter resources)
getActiveConverterQuantity : Converter -> Resources -> Int
getActiveConverterQuantity converter resources =
let
resourcesList = resourcesToList resources
intakesList = resourcesToList converter.intakes
finalList =
List.map2 (,) resourcesList intakesList
|> List.filter (\(r,i) -> i > 0)
|> List.map (\(r,i) -> floor (r/i))
in
case List.maximum finalList of
Just q ->
min q converter.quantity
Nothing ->
converter.quantity
subtractIntakes : Converter -> Resources -> Resources
subtractIntakes converter resources =
{ resources
| energy = resources.energy - activatedQuantity * converter.intakes.energy
, water = resources.water - activatedQuantity * converter.intakes.water
, metal = resources.metal - activatedQuantity * converter.intakes.metal
}
addOutputs : Converter -> Resources -> Resources
addOutputs converter resources =
{ resources
| energy = resources.energy + activatedQuantity * converter.outputs.energy
, water = resources.water + activatedQuantity * converter.outputs.water
, metal = resources.metal + activatedQuantity * converter.outputs.metal
}
in
resources
|> subtractIntakes converter
|> addOutputs converter
-- MODEL
type alias Model =
{ resources : Resources
, converters : List Converter
}
init : ( Model, Cmd Msg )
init =
( { resources = initialResources
, converters = initialConverters
}
, Cmd.none
)
-- MESSAGES
type Msg
= Tick Time
-- VIEW
view : Model -> Html Msg
view model =
div []
[ div[] [text (toString model.resources)]
, div[]
[ model.converters
|> List.map
(\c -> li [] [text (toString (c.convertertype,c.quantity,c.intakes))])
|> ul []
]
]
-- UPDATE
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Tick time ->
(updateGame model, Cmd.none)
updateGame : Model -> Model
updateGame model =
let
newResources = model.converters |> List.foldr convert model.resources
in
{ model | resources = newResources }
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
Time.every second Tick
-- MAIN
main : Program Never Model Msg
main =
program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}

I'd expect your model.resources will not have to hold multiple values of one ResourceType so a Record will make things easier for that ( -> assuming resources = [energy 2, water 1, energy 2] would make no sense )
type alias Resources =
{ energy : Int
, water : Int
, metal : Int
}
type alias Model =
{ resources : Resources
, converters : List Converter
}
now you can fold the intakes of every converter and accumulate the resources
convert : Converter -> Resources -> Resources
convert {intakes, outputs} resources =
let
substractResource : (ResourceType, Int) -> Resources -> Resources
substractResource (rType, quantity) res =
case rType of
Energy ->
{ res | energy = energy - quantity }
-- TODO: same for Water and Metal
-- TODO: addResource function
hasEnoughResources newRes =
case negativeResources newRes of
-- TODO: function to check if any value of the new resources Record is negative
True -> -- return original resources if converter can not run
resources
False -> -- add outputs to res and return new recources
outputs |> List.foldr addResource newRes
in
intakes
|> List.foldr substractResource resources
|> hasEnoughResources
Bonus: combine add and substract functions to 1 function of type : (Int -> Int) -> (ResourceType, Int) -> Resources -> Resources and call it like calcResource << (+)
updateGame : Model -> Model
updateGame model =
let
newResources = model.converters |> List.foldr convert model.resources
in
{ model | resources = newResources }

Related

How to use interfaces with parameterized tuple?

I have Coord function that transforms an n-dimensional size to the type of coordinates bounded by given size: Coord [2,3] = (Fin 2, Fin 3).
import Data.Fin
import Data.List
Size : Type
Size = List Nat
Coord : Size -> Type
Coord [] = ()
Coord s#(_ :: _) = foldr1 (,) $ map Fin s
I'd like to use show and other functions like (==) with Coord s:
foo : Coord s -> String
foo x = show x
Error: While processing right hand side of foo. Can't find an implementation for Show (Coord s).
22 | foo : Coord s -> String
23 | foo x = show x
^^^^^^
Earlier I tried to implement Show (Coord s), but looks like it's impossible. Here is linked question about it.
You can make your own list like data type:
data Coords : List Nat -> Type where
Nil : Coords []
(::) : Fin x -> Coords xs -> Coords (x :: xs)
toList : Coords xs -> List Nat
toList [] = []
toList (x::xs) = finToNat x :: toList xs
example : Coords [2, 3]
example = [1, 2]
Show (Coords xs) where
show cs = show $ toList cs
You can also try using Data.Vect.Quantifiers.All or Data.List.Quantifiers.All:
import Data.Vect
import Data.Vect.Quantifiers
example : All Fin [1, 2, 3]
example = [0, 1, 2]
-- not sure why this is isn't included with Idris
export
All (Show . p) xs => Show (All p xs) where
show pxs = "[" ++ show' "" pxs ++ "]"
where
show' : String -> All (Show . p) xs' => All p xs' -> String
show' acc #{[]} [] = acc
show' acc #{[_]} [px] = acc ++ show px
show' acc #{_ :: _} (px :: pxs) = show' (acc ++ show px ++ ", ") pxs
string : String
string = show example

Partition list into more than 2 parts

So I want to partitision a List ItemModel in Elm into List (List ItemModel). List.partition only makes the list into two lists.
I wrote some code that makes the list into the parts I want (code below).
But it's not as nice of a solution as I'd like, and since it seems like an issue many people would have, I wonder are there better examples of doing this?
partition : List (ItemModel -> Bool) -> List ItemModel -> List (List ItemModel)
partition filters models =
let
filterMaybe =
List.head filters
in
case filterMaybe of
Just filter ->
let
part =
Tuple.first (List.partition filter models)
in
part :: (partition (List.drop 1 filters) models)
Nothing ->
[]
The returned list maps directly from the filters parameter, so it's actually pretty straightforward to do this using just List.map and List.filter (which is what you're really doing since you're discarding the remainder list returned from List.partition):
multifilter : List (a -> Bool) -> List a -> List (List a)
multifilter filters values =
filters |> List.map(\filter -> List.filter filter values)
Repeated partitioning needs to use the leftovers from each step as the input for the next step. This is different than simple repeated filtering of the same sequence by several filters.
In Haskell (which this question was initially tagged as, as well),
partitions :: [a -> Bool] -> [a] -> [[a]]
partitions preds xs = go preds xs
where
go [] xs = []
go (p:ps) xs = let { (a,b) = partition p xs } in (a : go ps b)
which is to say,
partitions preds xs = foldr g (const []) preds xs
where
g p r xs = let { (a,b) = partition p xs } in (a : r b)
or
-- mapAccumL :: (acc -> x -> (acc, y)) -> acc -> [x] -> (acc, [y])
partitions preds xs = snd $ mapAccumL (\xs p -> partition (not . p) xs) xs preds
Testing:
> partitions [ (<5), (<10), const True ] [1..15]
[[1,2,3,4],[5,6,7,8,9],[10,11,12,13,14,15]]
unlike the repeated filtering,
> [ filter p xs | let xs = [1..15], p <- [ (<5), (<10), const True ]]
[[1,2,3,4],[1,2,3,4,5,6,7,8,9],[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]]

Maximum in List of Records

Say I have a List of records in elm:
[ { id = 1, magnitude = 100 }
, { id = 3, magnitude = 300 }
, { id = 2, magnitude = 200 } ]
and I want to get the record with the greatest magnitude value (300). What is a good way of doing this?
The docs gives an example of using the "maximum" -method, but it uses a simple list of integers. How is it done with records?
Update based on recommendation from #robertjlooby
There is a function called maximumBy which does exactly this in elm-community/list-extra. Example:
List.Extra.maximumBy .magnitude list
Original Answer
There are a few ways to achieve this.
This first way is more concise but it involves sorting the whole list, reversing it, then taking the head.
maxOfField : (a -> comparable) -> List a -> Maybe a
maxOfField field =
List.head << List.reverse << List.sortBy field
If you want something that's more efficient and only traverses the list once, here's a more efficient version:
maxOfField : (a -> comparable) -> List a -> Maybe a
maxOfField field =
let f x acc =
case acc of
Nothing -> Just x
Just y -> if field x > field y then Just x else Just y
in List.foldr f Nothing
An example of it in use:
list =
[ { id = 1, magnitude = 100 }
, { id = 3, magnitude = 300 }
, { id = 2, magnitude = 200 } ]
main =
text <| toString <| maxOfField .magnitude list
Here is a version that uses foldl and a default record:
bigger =
let
choose x y =
if x.magnitude > y.magnitude then
x
else
y
in
List.foldl choose {id = 0, magnitude = 0} items
Sebastian's answer add an arbitrary start value which could cause a problem if all your magnitudes were negative. I would adjust to
bigger items =
case items of
[] -> []
(h :: []) -> h
(h :: tail) ->
let
choose x y =
if x.magnitude > y.magnitude then
x
else
y
in
List.foldl choose h tail

ELM: Prevent game elements from colliding / positioning over each other

I'm looking at creating a game with 2 'ships'.
Ships can move up, down, left or right. One ship is controlled by arrows, the other WASD.
However I want to prevent ships from being positioned over each other.
So in my example, the blue ship should not position itself on top of the red ship. I would expect that if the red ship moved right and hit the blue ship - neither ship would move.
Any help is appreciated.
GIST
module Game (..) where
import Graphics.Element exposing (..)
import Graphics.Collage exposing (..)
import Color exposing (red, blue, gray, green)
import Keyboard
import Window
-- ALIAS
type alias Model =
{ color : Color.Color
, isFiring : Bool
, name : String
, y : Int
, x : Int
}
-- MODEL
initialShip : String -> Model
initialShip name =
let
color =
if name == "ship1" then
red
else
blue
in
{ color = color
, isFiring = False
, name = name
, y = 0
, x = 0
}
-- POSITIONS
moveLeft : Model -> Model
moveLeft model =
{ model | x = model.x - 1 }
moveRight : Model -> Model
moveRight model =
{ model | x = model.x + 1 }
moveDown : Model -> Model
moveDown model =
{ model | y = model.y - 1 }
moveUp : Model -> Model
moveUp model =
{ model | y = model.y + 1 }
-- ACTIONS
type Action
= NoOp
| Left
| Right
| Down
| Up
-- UPDATE
update : Action -> Model -> Model
update action model =
case action of
NoOp ->
model
Left ->
moveLeft model
Right ->
moveRight model
Down ->
moveDown model
Up ->
moveUp model
-- View
drawGame : Float -> Float -> Form
drawGame w h =
rect w h
|> filled gray
drawShip : Float -> Model -> Form
drawShip gameHeight ship =
let
shipColor =
if ship.isFiring then green else ship.color
initialPosition =
if ship.name == "ship1" then
(toFloat (ship.x - 50))
else
(toFloat (ship.x + 50))
in
ngon 3 30
|> filled shipColor
|> rotate (degrees 90)
|> move (initialPosition, (toFloat ship.y + 50))
view : (Int, Int) -> Model -> Model -> Element
view (w, h) ship1 ship2 =
let
(w', h') = (toFloat w, toFloat h)
in
collage w h
[ drawGame w' h'
, drawShip h' ship1
, drawShip h' ship2
]
-- SIGNALS
direction : Signal { x : Int, y : Int } -> Signal Action
direction input =
let
position =
Signal.map (\{ x, y } -> { x = x, y = y }) input
delta =
Time.fps 120
toAction { x, y } =
if x < 0 then
Left
else if x > 0 then
Right
else if y < 0 then
Down
else if y > 0 then
Up
else
NoOp
actions =
Signal.map toAction position
in
Signal.sampleOn delta actions
ship1 : Signal Model
ship1 =
Signal.foldp update (initialShip "ship1") (direction Keyboard.wasd)
ship2 : Signal Model
ship2 =
Signal.foldp update (initialShip "ship2") (direction Keyboard.arrows)
-- MAIN
main : Signal Element
main =
Signal.map3 view Window.dimensions ship1 ship2
I recommend having a single model encompassing both ships, then making a data type that has both arrows and wasd, then mapping both the arrows signal and the wasd signal and merging them into a single foldp.

Elm drawing over list

I am trying to draw a list of objects but cannot get it to work.
Secondary question is how do I make a "for loop" using ELM.
I have a
type Object a = { a | x:Float, y:Float, vx:Float, vy:Float }
type Car = Object {}
type Cars = [Car]
displayCar = move (car.x,car.y) (filled white (rect 30 20))
displayCars = ?????
I am trying to get somethign liek this to work
collage 100 100 [displayCar (head cars) -- does work
, displayCars cars -- does not work
]
In particular, the collage has multiple things it needs to plot:
[ filled pongGreen (rect gameWidth gameHeight)
, displayObjHouse (game.houses !! 0) -- so ugly code
, displayObjHouse (game.houses !! 1) -- so ugly code
, displayObjHouse (game.houses !! 2) -- so ugly code
, displayObjHouse (game.houses !! 3) -- so ugly code
, displayObjHouse (game.houses !! 4) -- so ugly code
, displayCars cars -- does not work
]
You are looking for the function map.
map : (a -> b) -> [a] -> [b]
This means that you can apply some function to a list of things and get back a list of the results.
You are very close with what you have here. I've filled in some blanks to help you keep making progress! Good luck!
type Object a = { a | x:Float, y:Float, vx:Float, vy:Float }
type Car = Object {}
type House = Object { residents : Int }
displayCar : Car -> Form
displayCar car = move (car.x,car.y) (filled black (rect 30 20))
displayCars : [Car] -> [Form]
displayCars cars = map displayCar cars
-- map : (a -> b) -> [a] -> [b]
-- In our particular example, we plug in our functions
-- displayCar : Car -> Form
-- since display car is the first argument to map, all a's become Car
-- and all b's become Form
-- So the type of map in this instance is ((Car -> Form) -> [Car] -> [Form]
someCars : [Car]
someCars = [ { x = 100, y = 10, vx = 0, vy = 0 }
, { x = 35, y = 100, vx = 0, vy = 0 }
, { x = 0, y = 0, vx = 0, vy = 0 }
]
someHouses : [House]
someHouses = [ { x = 20, y = -100, vx = 0, vy = 0, residents = 3 }
, { x = -20, y = -50, vx = 0, vy = 0, residents = 3 }
, { x = 160, y = -150, vx = 0, vy = 0, residents = 3 }
]
displayHouse : House -> Form
displayHouse house = move (house.x, house.y) (filled blue (rect 30 50))
main : Element
main =
let houses = map displayHouse someHouses
cars = map displayCar someCars
in collage 400 400 (houses ++ cars)