Does Text Shadow Increase Text Legibility?

When making Contrasaurus we ran into some trouble with the score counter and health labels. They looked fine on a dark background, but on some levels there were clouds or other light colored and varied backgrounds. In order to increase visibility I added a 1px text shadow to create a distinct edge even against a light colored or varied background. That way the text should be highly visible throughout the game.

It seemed to work well for my situation, so I moved on to mobile optimization and other tasks. That is until this innocuous seeming tweet from Zed Shaw crossed my stream:

text-shadow is the <blink> tag of 2011.

Zed was probably referring to a use more akin to this one, but if I’ve learned anything from the internet it’s that opinions are cheap, evidence is rare, and experience is invaluable. So I respond with my own cheap opinion:

@zedshaw I disagree. A tasteful 1px text shadow can enable text to be better visible on a wide variety of backgrounds, aiding usability.

@STRd6 Got proof? As in, actual usability studies with real people where they read pages and pages of shadowed text that I can replicate?

Whoa, proof? Evidence? I just thought we were throwing opinions around on the internet, but if I’ve learned anything from throwing opinions around on the internet it’s that you never turn down a debate with Zed Shaw.

On a serious note, this is actually a valuable opportunity. I have this hypothesis based on personal, non-scientific data points and I can put it to the test. I actually don’t have much experience testing these hypotheses in a scientifically rigorous way, which makes this opportunity even more valuable. So rather than sit around reading all the opinions and anecdotes about how to create the best usability study, I decided to dive in and iterate.

Null Hypothesis: For people on the internet, the legibility of text is unchanged with 1px text shadow.

Alternative Hypothesis: For people on the internet, a 1px text shadow increases the legibility of text.

To test this I used Amazon’s Mechanical Turk. I’d never used the service before so this was a good learning experience on that front as well. Good statistics requires getting a representative sample of the population in question. I can’t easily get a random sample of “people on the internet”, but I can use Mechanical Turk responders as a proxy.

To test the “1px text shadow increases legibility of text” part I just had a simple and direct question “Choose the image with the most legible text”. I put up three different surveys, each one had one question with two pictures that varied only in whether or not they had 1px text shadow. The three different picture pairs were taken from screenshots of Contrasaurus at three different points in the game.

I offered $0.03 for each response and requested 100 assignments for each pair. All total it came to $10.50, not bad for a crash course in hypothesis testing via Mechanical Turk.

Now the results:

96/100 selected text-shadow was more legible.

55/100 selected text-shadow was more legible.

99/100 selected text-shadow was more legible.

So assuming the null hypothesis “for people on the internet, the legibility of text is unchanged with 1px text shadow” we would expect an outcome as or more extreme than the observed result of 250/300 nearly zero times. We therefore reject the null hypothesis in favor of the alternative hypothesis that “for people on the internet, a 1px text shadow increases the legibility of text”.

A caveat is that this is only as reliable as our research methodology, which admittedly may have some flaws and biases. A few of the ones I can think of straight away:

  • The selection of survey images may not be a representative sample of the conditions in which they appear in the entire game.
  • The selection of respondents may not be a representative sample of the population in question.
  • There may be a bias due to the order in which the options appeared (The no text shadow option was always first as I wasn’t familiar enough with Mechanical Turk to randomize it.)
  • There may be a bias if some respondents responded to multiple surveys thereby making some trials non-independent.
  • There may be a subconscious bias in how I constructed the experiment itself.

Please respond with criticisms and possible solutions. I think there’s quite a bit of promise in the technique of using “real” statistical evidence to back up claims. Even best effort statistical evidence is quite a bit above the standard personal opinion. If I’ve learned anything about statistically significant evidence it’s that it’s quite hard to get right in a bullet proof manner, but like everything, practice and iteration, with feedback, can make it into a valuable skill.

Array#invoke Useful JavaScript Game Extension #24

Array#invoke is a method to make the common task of inoking a method on all elements in an array easier. Though you could use your own anonymous function as a map or each iterator to achieve the same result, this shortcut method makes it clear what the intention is and removes the extra syntax clutter.

What invoke does is to call the named method on each element in the array, passing in any arguments if given.

/**
* Invoke the named method on each element in the array
* and return a new array containing the results of the invocation.
*
* @param {String} method The name of the method to invoke.
* @param [arg...] Optional arguments to pass to the method being invoked.
*
* @type Array
* @returns A new array containing the results of invoking the
* named method on each element.
*/
Array.prototype.invoke = function(method) {
  var args = Array.prototype.slice.call(arguments, 1);

  return this.map(function(element) {
    return element[method].apply(element, args);
  });
};

Here are some examples of it in action:

[1.1, 2.2, 3.3, 4.4].invoke("floor")
=> [1, 2, 3, 4]

['hello', 'world', 'cool!'].invoke('substring', 0, 3)
=> ['hel', 'wor', 'coo']

The result of each invocation is returned in an array. This allows for easy chaining of data transformations:

values.invoke("scale", 4).invoke("subtract", 3)...

Fans of Ruby and Rails will know about Symbol#to_proc, a closely related idiom:

names = people.map &:name

Thanks for reading!

Pixie App Widget Test

I’m testing out embedding a Pixie app widget in my blog. If it doesn’t show up in your RSS feed try it on the main site.

Here’s a little platformer app that I’ve been making in Pixie. I started it a week ago and polished it up during TIGJam.

http://pixie.strd6.com/developer/apps/14/widget

This widget embedding is a great way to share games created in Pixie with the world. Additionally, because it is an embedded widget, it receives updates automatically as you update your game!

Select and Reject – Useful JavaScript Game Extensions #22 and #23

Remember back when we discussed Array#partition? Cool, because having a partition method makes these next two a breeze!

/**
 * Return the group of elements for which the iterator's return value is true.
 *
 * @param {Function} iterator The iterator receives each element in turn as
 * the first agument.
 * @param {Object} [context] Optional context parameter to be
 * used as `this` when calling the iterator function.
 *
 * @type Array
 * @returns An array containing the elements for which the iterator returned true.
 */
Array.prototype.select = function(iterator, context) {
  return this.partition(iterator, context)[0];
};

/**
 * Return the group of elements for which the iterator's return value is false.
 *
 * @param {Function} iterator The iterator receives each element in turn as
 * the first agument.
 * @param {Object} [context] Optional context parameter to be
 * used as `this` when calling the iterator function.
 *
 * @type Array
 * @returns An array containing the elements for which the iterator returned false.
 */
Array.prototype.reject = function(iterator, context) {
  return this.partition(iterator, context)[1];
};

We can cheat quite a bit by delegating the brunt of the work to partition and just return the partition that matches what we want, the true partition in the case of select, and the false partition in the case of reject.

Another thing to note is that in most modern browsers JavaScript already has an equivalent method to select implemented. It’s called filter. It may make sense to use select as an alias for filter, in the cases where it exists.

The important thing to notice is that by implementing robust extensions that are general it opens the door for simpler implementations of specific common cases. This in turn leads to an increase in consistency and a reduction of errors because there is only one method that does the real work instead of several similar methods. When the code changes there is only one place necessary to make the change in rather than many places, and that’s always a good thing.

Array#copy – Useful JavaScript Game Extensions #21

It’s not unheard of to want to copy an array. Though usually you’d prefer to use map, select, or reject, sometimes you just need your own copy. Fortunately the copy method is extremely easy to implement by leveraging the existing Array#concat method.

/**
* Creates and returns a copy of the array. The copy contains
* the same objects.
*
* @type Array
* @returns A new array that is a copy of the array
*/
Array.prototype.copy = function() {
  return this.concat();
}

Array#concat glues arrays together and returns the result. If you call it with many arguments the contents of each argument are appended and returned as a new resulting array. If you call it with zero arguments you still get a new resulting array, just no additional items are appended, so it makes a great copy.

Array#partition – Useful JavaScript Game Extensions #20

Part 20 of 256

There comes a time in every programmer’s life when he must partition an array in twain. Separating apples from oranges, the wheat from the chaff, the yeas from the nays.

/**
 * Partitions the elements into two groups: those for which the iterator returns
 * true, and those for which it returns false.
 * @param {Function} iterator
 * @param {Object} [context] Optional context parameter to be
 * used as `this` when calling the iterator function.
 *
 * @type Array
 * @returns An array in the form of [trueCollection, falseCollection]
 */
Array.prototype.partition = function(iterator, context) {
  var trueCollection = [];
  var falseCollection = [];

  this.each(function(element) {
    if(iterator.call(context, element)) {
      trueCollection.push(element);
    } else {
      falseCollection.push(element);
    }
  });

  return [trueCollection, falseCollection];
};

Let’s see it in action:

// Separating apples from oranges (non-apples)
var result = ["apple", "apple", "orange", "apple", "orange", "orange", "orange", "apple"].partition(function(item) {
  return item == "apple";
});

// result is [["apple", "apple", "apple", "apple"], ["orange", "orange", "orange", "orange"]]

The result of using Array#partition is an array containing to elements, the first is the list of items that the iterator function evaluated to true for, and the second is the list that the iterator function evaluated to false for. You’d be surprised (probably not) how often dividing lists into two categories based on arbitrary criteria comes into play. Additionally this can be used as the foundation for simpler generic methods like select and reject. I’m sure they’ll turn up pretty soon.

JavaScript Mixins

Mixins (a.k.a. modules) are a convenient and useful way to package up pieces of behavior.

  /**
  * Bindable module
  * @name Bindable
  * @constructor
  */
  function Bindable() {

    var eventCallbacks = {};

    return {
      /**
      * The bind method adds a function as an event listener.
      *
      * @name bind
      * @methodOf Bindable#
      *
      * @param {String} event The event to listen to.
      * @param {Function} callback The function to be called when the specified event
      * is triggered.
      */
      bind: function(event, callback) {
        eventCallbacks[event] = eventCallbacks[event] || [];

        eventCallbacks[event].push(callback);
      },
      /**
      * The trigger method calls all listeners attached to the specified event.
      *
      * @name trigger
      * @methodOf Bindable#
      *
      * @param {String} event The event to trigger.
      */
      trigger: function(event) {
        var callbacks = eventCallbacks[event];

        if(callbacks && callbacks.length) {
          var self = this;
          callbacks.each(function(callback) {
            callback(self);
          });
        }
      },
    };
  }

On the face of it this Module only constructs a simple object with two methods bind and trigger. A statically typed language may make use of delegation to forward method calls to a Bindable object created for each class instance, but in dynamic languages we can literally mix this object into the object that wants to make use of it’s capabilities.

function Guy(I) {
  I = I || {};

  $.reverseMerge(I, {
    name: "Sancho"
  });

  var self = {
    sayHi: function() {
      alert("Hi, my name is " + I.name);
      // Make use of the method provided by the Bindable class
      self.trigger("spoke");
    }
  };

  // Mixing it in, just smash the methods of the newly created Bindable onto this object
  $.extend(self, Bindable());

  return self;
}

var guy = Guy();

guy.bind("spoke", function() {
  console.log("This guy spoke");
});

guy.sayHi(); // Alerts and logs to console

Some modules may require a method to be provided by the host. A classic example is an Enumerable module which can provide many additional iterators if given a standard each iterator.

function Enumerable() {
  return {
    partition: function(iterator, context) {
      var trueCollection = [];
      var falseCollection = [];

      this.each(function(element) {
        if(iterator.call(context, element)) {
          trueCollection.push(element);
        } else {
          falseCollection.push(element);
        }
      });

      return [trueCollection, falseCollection];
    },

    select: function(iterator, context) {
      return this.partition(iterator, context)[0];
    },

    reject: function(iterator, context) {
      return this.partition(iterator, context)[1];
    },

    shuffle: function() {
      var shuffledArray = [];

      this.each(function(element) {
        shuffledArray.splice(rand(shuffledArray.length + 1), 0, element);
      });

      return shuffledArray;
    }
  }
}

This Enumerable mixin provides partition, shuffle, select and reject methods. It relies on the host providing the each iterator, and makes use of it to provide partition and shuffle. The select and reject methods in turn make use of the partition method.

// Mixing enumerable into Array.prototype

//Alias forEach as each to provide the method by a name Enumerable knows
Array.prototype.each = Array.prototype.forEach;

// Mix it in
$.extend(Array.prototype, Enumerable());

[1, 2, 3, 4, 5, 6, 7, 8].select(function(n) {return n % 2 == 0}); // => [2, 4, 6, 8]

This covers the classic uses of mixins, though there are some extra cool things that you can do when using instance variables. For now I’ll leave that as an exercise for the reader but later I’ll spell it all out in a follow up post and in my upcoming book Daniel X. Moore’s {SUPER: SYSTEM}.

Contrasaurus Launch

It’s finally here, the day we’ve all been waiting for. A Legendary Hero is born: Contrasaurus.

Contrasaurus

Synopsis

You are a dinosaur. The year is 3,700 B.C. You are summoned into the future to save America from the dark Communist forces that are amassing in Nicaragua. After you complete your mission and become the most decorated top-secret military dinosaur in history, you discover a terrible truth. Finally you seek out those whom you depend on most, only to have them confirm your darkest suspicions.

Gameplay

Experience humanity’s greatest dream: to be a dinosaur covered in weapons blasting through enemies. A carefully modeled and animated T-Rex. Six levels with beautiful parallax backgrounds. Five epic boss battles. A machine gun, flamethrower, laser monocle, and much more!

Technology

This game was painstakingly created with blood, sweat, tears, and HTML5. The core matrix transform library was spun out as Matrix.js. Additionally many of the core language extensions, sprites, sounds, and canvas libraries are working their way into The Pixie Game Platform.

Credits

Programming
Daniel X. Moore
condr

Artwork
Backyard Ninja Design (Sprites)
My Name is Wool (Backgrounds)

Object#reverseMerge – Useful JavaScript Game Extensions #19

Object#reverseMerge a method that complements Object#merge nicely. In regular merge all the properties of the source are copied onto the destination, but in reverseMerge the properties are only copied over if the destination doesn’t already have the property.

Object.prototype.reverseMerge = function(source) {
  for(var key in source) {
    if(source.hasOwnProperty(key) && !(key in this)) {
      this[key] = source[key];
    }
  }

  return this;
};

This allows us to easily have a list of default properties in an object and add them to the destination object only if they aren’t already present.

var iceCreamDefaults = {
  flavor: "vanilla",
  price: 2,
  size: 4
};


var chocolate = {
  flavor: "chocolate"
}.reverseMerge(iceCreamDefaults);

chocolate.price; // 2
chocolate.size; // 4

In the example we have an object containing ice cream default values. These values are merged into the chocolate object we create, but only if they don’t already exist within that object.

Extending Object.prototype is fun and rewarding, but it’s not without it’s pitfalls. For example jQuery is not yet 100% compatible with code that extends Object.prototype, though it has plans to be in the future. In addition, the presence of prototype properties in all your objects is one extra edge case to consider when using for in iteration and the in operator. The primary question is whether the benefit added by Object.prototype extensions exceeds the risk of the extra complexity, and in the end it comes down to what the biggest pain points are in the code in your own specific circumstances. Like most programming practices that involve trade-offs their correct usage depends strongly on the context where they are used.

Be sure to subscribe to get all the latest updates!

Bonus section, an alternate implementation using Object#each:

Object.prototype.reverseMerge = function(source) {
  source.each(function(key, value) {
    if(!(key in this)) {
      this[key] = value;
    }
  }, this); // context argument to ensure that `this` references correctly

  return this;
};

Again, be sure to subscribe so you don’t miss any of this amazing series!