Finding Kitten
Robot Finds Kitten
Before I get into the gory details of learning Elm via robots, I should tell you that my implementation is available for free play (edit: sorry, this has bitrotted too much to be included anymore), and you can also go look at the source code.
Elm
I got a really wonderful introduction to Elm when Evan Czaplicki came to Hacker School in our second week. We got a slightly adapted version of this talk from StrangeLoop 2013, which moved me to make a browser game (something I’ve never wanted to do at any previous point in life). The language seemed elegant and expressive, for lack of less cliched words, and I thought it might be relatively simple to make a succinct Robot Finds Kitten clone.
Real Basic Stuff
Okay, so robot is always finding kitten on the same featureless black background (space? a dark room? are robot and all objects falling continuously into a dark pit? it could be any of these, or all of these). The first thing to do, then, is get a black background the size of the window. That’s pretty simple, and I’m feeling pretty good about myself and my ability to do basic things, so the next thing I try to do is draw a character representing robot. Of course robot should be white text on a black background, and monospace, rather than black text on a white background.
Although there are no objects for robot to investigate currently (and worst of all, there’s no kitten), it still seems like robot should be able to move. So instead of having robot’s state represented by a constant, I make a function to change robot’s position based on keyboard input. I cribbed this fairly heavily from the Mario example on the Elm frontpage, to the degree that the commented-out original code from the “step” function was still in my source until nearly the final commit of the project.
I screwed up trying to write this function a lot. Some excerpts from my notes:
- WHY IS IT SO MOTHERFUCKING HARD TO GET THE FUCKING STEP METHOD TO RETURN A NEW FUCKING ROBOT, JESUS.
- arrrrrrrrrrrrgh
- this looks AN AWFUL FUCKING LOT LIKE THE EXAMPLE CODE but
Parse error at (line 22, column 21)
whyyyyyyyy
Of course, the problem is that I’m doin’ it wrong. Here are snippets of what’s so wrong with what I’m doing:
type Robot = { char : String, x : Int, y : Int }
step (x, y) r = { r | r.char <- "@", r.x <- 0, r.y <- 0 }
input = let delta = lift (\t -> t/20) (fps 25)
in sampleOn delta (lift2 (,) delta Keyboard.arrows)
main =
let r = robot
in lift2 render Window.dimensions (foldp step r input)
First off, I don’t have any type signatures on any of these functions, and so I don’t notice that input
, which I copied wholesale from some example code, is returning something more complex than just keyboard directions. It’s also returning some timing information in a tuple with the keyboard signal. So the type of the value returned by input
is (Signal Time, Signal { x:Int, y:Int })
, not the Signal (x, y)
that I appear to be expecting in the definition of step
, where I look for step (x, y) r
. I could’ve saved a lot of grief here by writing some type signatures rather than trusting the inference engine to do what I meant instead of what I said.
After putting some type signatures on functions and adjusting my view of reality to more closely match the compiler’s, I had a robot that could move around on the empty screen. Success! Well, not really, but getting there.
Robot Finds Something
If there were items, robot would have no way of knowing what they were. Tragic! An item might be kitten, and robot would pass it by! No no no, that just won’t do. I added some functionality to display an item message, although the lack of items means I need to test it with a static message, so it appears that robot is always investigating a very boring item.
I was pairing with Leah, another Hacker Schooler, to implement wrapping for robot (so that robot can’t walk off the edge of the world into a sadly kittenless dimension), when Evan turned around and very kindly corrected some bad misapprehensions we had about how we should be thinking about signals. (As an aside, Hacker School is the only place I have ever received help from the inventor of a language without even having to ask for it, and Evan might be the single kindest human being who has ever helped me with a programming problem.)
Thanks to Evan’s help, robot is now forced to stay vaguely near where kitten might someday be. But there’s still no kitten! I added a few more features and then ran into another major knot of my own misunderstanding when trying to figure out how to model robot’s most recently examined object, and whether robot can be meaningfully said to be examining something at any given point in time.
I initially wanted to model robot’s collisions by adding an optional field to the record type Item
. Elm allows extensible records, which I immediately try to start using like objects by sticking references to other Items in there. This leads me down a pretty bad road, and after a lot of staring into the middle distance and swearing at the compiler I end up with a different solution - I would make a second type, Colliding
, of which only robot was an instance. Colliding
records have a string, collidingWith
, defined, which is just a copy of the description of the object that robot is currently examining. Now robot is both an Item
, with a string representation, location, and character, but it is also Colliding
- either with some item description, or with the empty string. We now have a robot that runs into stuff and when it runs into stuff it tells you what the stuff is!
This is pretty close to the bones of Robot Finds Kitten. I change the only populated item to be kitten and add a couple more features, and robot can now find happiness (and kitten). This is currently the easiest implementation of Robot Finds Kitten ever, but hey, robot can find kitten! I ship the code up to Github and decide not to think about it for a few more days.