/tinyletter

The Programs of the Week of ⚡ Cyber Monday ⚡

This Week’s Program: Nov 28 - Dec 1

Last year at this time I said, “Truly, all of our days our Cyber.” This year that feels like a nightmare prophecy come true.

Here’s my plan for the rest of the year. hive-city is not coming with me into 2017. I’m going to make as much progress as I can in that time, but I’ll probably be wrapping up and picking up some work outside this project as December winds down. I’d call this project a success. Here’s what I lay out in the README:

  • The Elm language, tools, and ecosystem
  • The Elm Architecture and the ongoing evolution of Functional Reactive Programming
  • Making a game
  • Modeling entities and state in a computer game inspired by a physical counterpart
  • And getting to immerse myself in a funky, esoteric game from the 90’s

I did those things. I really have loved my time spent in Elm, though it’s still such a nascent set of tools. I’ve embraced the Elm architecture. I’m making a freaking game and I enjoyed the hell out of it. I’m coding alongside the Necromunda rulebook. I’m immersed. Feels great, and I would love to revisit the rules of these skirmish wargames in a future project.

So here’s what I’m thinking about doing next: Next week I’m going to prepare to set up a new Elm project for the Ludum Dare game competition. I want to do something in Racket, and I’m particular interested in building tools for the Twitch ecosystem and HTTP Live Streaming. I want to get started with Unity development and continue my gamedev education.

13a6a06dd938468ed10e136a73a0b5137fe8dad5

Tabletop wargames are essentially dice games, so what’s a dice game without dice!? This commit introduces the Dice module and type to hive-city. Elm’s Random module is one of my favorites. Purely functional generators force sane data-flows. The Dice module is just a Generator (List Int).

One thing I miss in the transition to Elm 0.18 is being able to use backticks to make any function an inline operator. When I imagined the dice API, I imagined being able to do something like:

2 `d` 6

To make a 2D6 dice roll. Now, that syntax has been removed, and reversing the arugments and using the pipe operator is encouraged. So we get this:

2 |> d 6

I also make a D3 from the rules of the game. We take our list of dice and:

Random.map (List.map (toFloat >> (*) 0.5 >> ceiling))

I do a Random.map because this is a generator and the value is not yet realized, but we know it will be a List Int. Inside the Random.map I map over the List with a function we compose: convert the Int to a Float, divide it in half (I multiply 0.5 because multiplication is commutative), and then round up.

f25b42190e3b033bdfa8e335ef5d016f534715f3

Back in Player.execute, I change the type signature from returning a pair with a Task to one that returns a Cmd. You get a Cmd by performing a Task, so it’s pretty quick work to make that modification. The reason I do this is because Random.generate returns a Cmd, so we can execute instructions through the same function regardless of whether or not they require Random effects.

Also the new (as of Elm 0.18) Tuple.mapFirst and Tuple.mapSecond functions make shaping the return value of execute very easy.

74e9660cc543be825032a8832f22a004b362542f

I add a helper value to Dice: oneD6, and a new function roll. Roll is just an alias for Random.generate, but I pull it in here so that anything that imports Dice doesn’t also need to import Random.

cf523121e17e81d385839c8af7186858709cb4b8

Using that new Dice.roll function, I can just slot that in to make it so that the Shooting instruction will roll some dice. I ignore the dice roll for now.

13444a2f66272eb98dc1cd7a061d2b90af318e1d

Using a previously created, but underutilized type called Failure, I change the Complete Msg to accept a Result Failure Action. I simplify the Player.execute function because I can use Tuple.mapSecond along with Cmd.map to make sure that execute’s return value can slot into what the Elm Architecture expects. Using a Result means we encode that some instructions can actually have values that are failure modes eg. a shot can miss.

d642280934eeb222b4d82852636140ff5eae0141

A Shot can either be a Hit or a Miss. That Shot can be converted to a Result with Weapons.toResult. I also create Model.shoot, which accepts a List Die (I introduce Die as an alias for Int). This List Die is generated from Dice.roll and contains the values of the dice we rolled. We sum them up and compare them against the attacker’s ballistic skill to determine whether the shot was a Hit or a Miss. The TODO here notes that I still need to add modifiers.

ab2fb346157ea5bcd485e645757aa2d37da9645d

Now I hook everything up. When the Shooting instruction is executed, the dice is rolled, and the results of the roll are passed into Model.shoot, which will convert the returned Shot into a Result of either an Err, holding the MissedShot type or an Ok. The Main update loop will then feed that into the Complete Msg to resolve effects.

520365c646b60bcbe58061061ac9551c3c45db44

Here’s a view that presents an icon of Dice. When you click it, it’s going to do something.

53b890d92d30882b15a994ec808ed26ae382aa57

Here’s a new type alias. A DiceRoll is a tuple of the number of dice, the value of those dice, and an Instruction we’re rolling for.

9121c53f0523d840e96951cd7cb1cc97acd2c595

Glueing all of this together is this commit. A new piece of state is added, rolling, which is Maybe an aforementioned DiceRoll. Roll is a new Msg that executes a given instruction.

In the area of the UI where we display the action controls, if our rolling state is set, we display that Dice view. When clicked, it will Roll the dice for the instruction.

A lot of magic happens in the Action.Shoot command, where we use Maybe.andThen to combine the attacker and shooter into a pair, and then set up rolling for the Shooting instruction.

Now, whenever a target is in range of the shooter, the message “Target Acquired” is displayed, and the dice roll control appears. When you click the dice, it will roll the dice and the result is either Ok or Err if the attack is successful.

Now, to resolve wounds! This code is looking hideous. I need to do some refactoring and reorganization.

🎲 Mark