Randomness in Elm

Buildings as Actors

In working towards making buildings work, I realised I already have in place the structure I need for them. Fundamentally, a building is just another actor, and it produces resources in the same way as the player does. I already have a loop for that – in this case almost all building actions will happen every game tick, but that’s fine.

Hence I’ve been thinking about my player actions to see how I can make those more interesting. Initially, I had decided on a model where the player does a Gather action and this takes some time, and produces some herbs. This was easy to extend to Gather Herb action, and a Gather Flower and Gather Mushroom, and suddenly I have a load of new actions. Each of these has different weightings, so you might get six herbs in 10 ticks, but only 2 mushrooms. Now the player has a genuine decision – what resource is the most important to them right now?

But what about some randomness? How about I add another “Forage” action. This would take longer, but give you more resources in total; however, you don’t have a definite choice of what you get. This means you’d usually use this, but if you want to work towards a particular target this is less useful. As I add more resources for forage, this becomes very inefficient for specific gathering – which is great as there’s a definite choice of strategy here.

Forage First Steps

The first thing I did for forage was thread it all through as a new ActionResult

type ActionResult 
= Gather ResourceType
| Forage
| Idle

The thoughts here was that once the action is complete, the model would be updated with some random values. This worked out reasonably – Elm does work quite nicely for changing things like this and I quickly got to the point where I had a complete action function similar to

 case actor.action.result of
   Gather resourceType -> 
     { model | resources = addResource (resourceType, action.action.amount) model.resources }
   Forage -> addRandomResources model
   Idle -> model

But to implement addRandomResources, I hit a wall…because there is no random() function in Elm. Or at least not one that I’m used to.

Random Number Usage, Imperative Style

Coming from the imperative world, I think there is a lot less structure in the language. The idea that a method may return a different value depending on the outside world is perfectly reasonable. After all, at some point you have to accept user input, or request the state of a sensor, or make a network connection.

This behaviour then follows for randomness. I can easily and quickly request a random number, and if I were using an imperative language style I would expect my code would be something like

addRandomResources model = 
  { model | resources = addResource (randomResource, randomAmount) model.resources }

However, the functional paradigm used in Elm states that the results of a function are always the same for any given input. This helps testing, for example, as any function test will always have a consistent result. In my function above, the results would depend entirely on randomResource and randomAmout, and if these must always produce the same result in the Elm architecture, they’re not going to be random!

Randomness in Elm

If I can’t get a random number directly in Elm, then I must have to get it from outside, and this is where Elm’s main update : Msg -> Model -> (Model, Cmd Msg) function comes into play. Instead of generating a number on the spot and manipulating it, I describe how I want the number to be generated in advance, and send it off to the Elm runtime. That comes back with another update, and I use that number then.

It’s a bit more sophisticated than that, though. Rather than just numbers, I can create a generator which makes a random resource, with some weighting:

randomGather : Random.Generator ResourceType
randomGather =
  Random.weighted
  (60, Herb)
  [ (10, Mushroom)
  , (20, Flower)
  ]

Since I want more than one of these, I convert this into a list of random resources – I want this in a separate function as it can then live next to all the other functions that generate things. When it comes to balancing, I want everything in the same place.

createForageResultGenerator : Random.Generator (List ResourceType)
createForageResultGenerator = 
  (Random.list 20 randomGather)

Finally then I create a new Update message

 MakeRandomPlayerAction Forage x ->
  ( model
  , Random.generate (\result -> SetPlayerForageAction result) createForageResultGenerator
  )

I have most of the pieces now РI have a random generator that will be called when I want a Forage action, this will return to the SetPlayerForageAction with the random list of resouces. That action, when complete, will add the random resources.

Model Changes

Once I was here, there was another model change that seemed appropriate. Initally, I had an action contain an “amount”, but for Forage actions (and Idle!) the amount is not used, and for forage I needed a new type of amount. The way this seems to be done in Elm is to extend the ActionResult type to include an appropriate amount in it.

type ActionResult 
  = Gather ResourceType Float
  | Forage (List ResourceType)
  | Idle

This means now when I pass through the ActionResult it has all the appropriate information for updating the model. I can then remove the amount field from the action type, and everything is a bit tidier.

There is one thing left that I’m not sure about. When I need to describe what the button for setting an action does, it has to create a ActionResult with a dummy value:

div []
  [ button [ onClick (SetPlayerAction (Gather Herb 0)) ] [ text "Gather Herbs" ] 
  , button [ onClick (SetPlayerAction (Gather Mushroom 0)) ] [ text "Gather Mushrooms" ] 
  , button [ onClick (SetPlayerAction (Gather Flower 0)) ] [ text "Gather Flowers" ] 
  , button [ onClick (MakeRandomPlayerAction (Forage [])) ] [ text "Forage" ] 
  ]

This doesn’t really feel right.

Conclusion and Next Steps

I have managed to thread the random list of resource types into my model, and now Forage has a list of random resources attached to it. These are added to the model when the action completes.

I’m not sure that I like the way random in Elm works entirely – it does seem overly complex and I’m left carrying around a list which seems a bit wasteful. But there’s not much choice here – and we’ll see if I come around to this method in the future.

I will get to buildings soon! That is really the next thing to do. Then we have a basic game, and I think we’ll be looking at saving and loading. I also need to think about progression in the game – what do you do with the herbs? How do you get to be better witch? What about a reset mechanism?


Leave a Reply

Your email address will not be published. Required fields are marked *