Lucy in the Sky with Diamonds
Joshua HawxwellFor Hack24 I wrote a game in Elm called Lucy in the Sky with Diamonds. It is a simple strategy game for two players, each taking turns to make non-overlapping diamonds between random sets of points. The winner being the one who creates the most. There is a time limit for making your move after which the other player has a chance to go.
Due to the diamond (or kite, as someone pointed out to me) shapes used there are some interesting ways to make moves and deny the use of particular points. I don’t think I’ve played it enough to formulate any concrete guaranteed winning strategies.
This was the first time I’ve written a game in Elm, and in general the first game I’ve made for a while, so I thought I’d write about how I felt it went.
I’ve been writing Elm for a few months now. This has spread to two projects at work being written in Elm, one of which was re-written from Angular 2. It is a joy to write: if I am able to write something that compiles I can trust that it will do what I want most of the time, which is not something I have been able to say of Angular. Having views as functions is what I always wanted jsx to be, and is as far away from having views as big strings with magic template syntax as you could ever hope.
I did encounter a few things that required some work though.
The first issue I came up against was having to reconcile two coördinate systems. I used evancz/elm-graphics to draw the canvas, which places the origin (0, 0) at the center point of the canvas; but elm-lang/mouse places the origin (0, 0) at the top-left of the page. To save time/because I got bored of searching for a solution, I ended up not putting the game in the middle of the page and instead keeping it at the top-left with a fixed margin then doing a (terrible) mouse point to canvas point conversion. Terrible because it actually went more like mouse point to origin at top-left of canvas point to origin at center of canvas point — I really don’t remember why I did that, I was only a few hours in to the Hack.
The other issue — which wasn’t really an issue, more that I expected the package to handle it for me — was detecting collisions between the diamonds. My first implementation used the packages rotation and scale transformations to draw the diamonds correctly, but there was no way to test whether the diamonds overlapped.
diamond : (Float, Float) -> (Float, Float) -> Collage.Form
diamond (startPosX, startPosY) (endPosX, endPosY) =
let
scale = sqrt (((endPosX - startPosX)^2) + ((endPosY - startPosY)^2))
angle = atan2 (endPosY - startPosY) (endPosX - startPosX)
in
Collage.polygon
[ (0, 0)
, (scale * 0.7, scale * 0.25)
, (scale, 0)
, (scale * 0.7, -scale * 0.25)
]
|> Collage.outlined (Collage.solid (Color.hsl (degrees 350) 1 0.5))
|> Collage.move (absoluteToCanvas (startPosX, startPosY))
|> Collage.rotate -angle
This meant having to reinvent the wheel slightly. Although I still think it turned out better than it would have in other JavaScript or TypeScript. Imagine if this were C# or something…
overlap : Diamond -> Diamond -> Bool
overlap a b =
any2 (\x y -> Vector.toInt x == Vector.toInt y) (points a) (points b)
|| any2 (Vector.intersect) (edges a) (edges b)
any2 : (a -> b -> Bool) -> List a -> List b -> Bool
any2 f xs ys =
List.any (\x -> List.any (\y -> f x y) ys) xs
Overall it was a great learning experience, and the game is fun to play.