Reputation: 1358
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
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
I got it working! The order of the Converter
s 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
}
Upvotes: 1
Views: 283
Reputation: 9593
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 }
Upvotes: 1