Dan Hunter view portfolio

Let's canvas over CoffeeScript.

3rd January

Code abstraction is a lovely thing. Being superficial I appreciate the skin-deep benefits of syntax reduction. Being a programmer I appreciate the shorthand notation and additional features they provide. HAML, for example, is just great.

Coffeescript is a new-to-me-but-not-really-new-at-all JavaScript abstraction. It throws in trailing conditions and list comprehension, among other things. Plus it means I might not get RSI from typing 'prototype' over and over and over and over.

A little while ago I was working on a javascript/canvas game. I thought it might be a good experiment to nab one of the more interesting methods from it and write the CoffeeScript equivalent - the one that checks the win case. I'll show you the game first.

The objective of the game should be obvious with a few clicks, but just in case: the idea is to copy the rotating arrangement on the right (the guide circle) by adding pieces to the blank circle on the left (the player circle).

Click to add a piece. Press the space bar to restart the current level if you mess up. Have a go:

Each circle is represented by an array of numbers, the length of the array is equal to the number of segments into which the circle is divided (which is proportional to the level the user is playing), and the values represent the number of pieces added to that segment.

Therefore if the two arrays contain the same numbers in the same order, the arrangements are the same, and the player wins.

The nature of the game throws up one curveball though: the array representing the player's circle may start at any point in the sequence of numbers representing the rotating arrangement on the guide circle.

For example, if the guide arrangement is..

[0, 1, 2, 0, 1];

and the player's arrangement is..

[0, 1, 0, 1, 2];

..it is still correct.

My solution may not be the best way to solve it - but it works. Perhaps a brute-force solution would be easier to follow, one that checks every element of the player's arrangement to see if that element is the first of the guide arrangement, but I thought the increased array operations would be too expensive in comparison.

function compare_arrays(_a, _b) {

  var a = _a.slice();
  var b = _b.slice();

  var key = b[0];

  var occurrences = [];
  var i;

  for(i = 0; i < a.length; i ++) {
    if (a[i] === key) {
      occurrences.push(i);
    }
  }

  for(i = 0; i < occurrences.length; i ++) {

    var wrap = a.splice(0, occurrences[i]);

    a = a.concat(wrap);

    if (a.join("") === b.join("")) {
      return true;
    }
  }

  return false;
}

As you can hopefully see, I find all the occurrences of the first element of the correct solution, then wrap the player's arrangement around each occurrence of that value.

The purpose of the first two lines is to create duplicates of the supplied arrays, otherwise the method would be destructive, as javascript passes pointers to the arrays when calling the function.

The CoffeeScript equivalent is as follows:

compare_arrays = (_a, _b) ->

 a = _a.slice()
 b = _b.slice()

 key = b[0]

 occurrences = (_i for val in a when val == key)

 wrap_leading_elements = (_key) ->
  wrap = a[0.._key]
  a = a[_key..-1].concat(wrap)
  true if a.join("") == b.join("")

 (return true if wrap_leading_elements(o)) for o in occurrences

 false

It might be worth noting a little of my experience.

I've always thought of significant whitespace to be an ill-defined delimitation that relies much too heavily on the formatting than the logic of your code. I can't say I had any problems with it here though. Perhaps using HAML trained an extra layer of awareness that kept my CoffeeScript untroubled. Either way, it seemed a perfectly natural way to write code.

I've since come across opinions that state if a programmer can't be trusted to indent his code, how likely is it that the code will be reliable to start with? It seems like a valid point.

CoffeeScript is definitely a bit of a laugh to play with, I found it very similar to learning regular expressions. Some parts were a little hacky, especially using a variable that will be generated, rather than the value, to ensure the index of an element was returned. I could have used the only surviving low-level loop available, the while loop, but I was enjoying myself too much toying with the language.

I found myself often referring to the generated code to see the effects of the code I was writing though, as opposed to having a perfect idea of what it should be doing based on the CoffeeScript alone, which is really backwards.

The parentheses around (return true if wrap_leading_elements(o)) denote that each element of the loop should return true. Whereas were the parentheses are omitted the loop as a whole would return true. Obvious in hindsight, but only after checking against the generated js did I figure it out.

The following two lines

wrap = a[0.._key]
a = a[_key..-1].concat(wrap)

..are written in this manner due to CoffeeScript's slightly odd way of implementing the splice method. In the conventional JavaScript version I use splice to remove elements from an array, however with CoffeeScript you seem only able to replace elements of an array, not remove them. As such, I was required to slice twice to produce the same result.

It seems that where HAML documents are perfectly readable representations of HTML, easy enough for someone inexperienced with the syntax to understand, CoffeeScript turns a somewhat cryptic block of code into an ugly, bloated abomination.

An extremely compatible, JSLint valid, and very fast abomination though. I like it!