Gábor Fekete
Gábor Fekete

Reputation: 1358

Update model at each iteration of list

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
        }

Upvotes: 1

Views: 283

Answers (1)

farmio
farmio

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

Related Questions