List Processing in CoffeeScript
So you’re coding your CoffeeScript and you have a list that you need to deal with. In JavaScript there is a wide array (pun intended) of methods you can use to make your life easier, but CoffeeScript gives you the advantage of a concise and consistent syntax. Rather than a mashup of loops, jQuery forEach()
calls, and calls to the (excellent) Underscore library, you can get everything done in a succinct and readable manner.
If you start in the middle, you’d be forgiven for thinking you were reading a Python tutorial. As a matter of fact, if you’re a Python veteran you could skip this article, start guessing, and probably do pretty well for yourself. But, for the rest of us, here’s a rundown of my three favorite techniques for dealing with lists in Python CoffeeScript.
Slice and Dice
Let’s say you’re creating a hybrid racing/shopping game, Cario Mart. During the game, you let people buy mushrooms and shells to use in their cars (don’t ask). To make the game more fun and competitive you want to make things cheaper for the players in 6th, 7th, and 8th place (the players who are losing). Here’s one way to do that in CoffeeScript, assuming that the list of players is ordered according to what place they are in:
if player == players[5] or player == players[6] or player == players[7]
blueShroom.cheaperFor(player)
Essentially, you are comparing the player in question with the three players in last place. However, you’re repeating yourself. Here’s a slightly better implementation that only mentions the player in question once:
if player in [players[5], players[6], players[7]]
blueShroom.cheaperFor(player)
But here’s an implementation that takes full advantage of CoffeeScript syntax:
if player in players[5..7]
blueShroom.cheaperFor(player)
Now, if you want to change anything about this (which player you’re comparing, which list of players you’re using, which places you want affected) you only have to change one thing in one place. One last optimization – let’s say your game suddenly expands to nine people instead of eight. Currently, players in places 6-8 will benefit, leaving the poor 9th place player to suffer. Let’s fix that before we move on, making it so that anyone in 6th place and lower benefits:
if player in players[5..]
blueShroom.cheaperFor(player)
List Comprehensions
So you’re in 7th place and you just bought a blue mushroom. Now you want to use it. A blue mushroom, in this case, gets in everyone else’s gas tank and makes their car randomly combust. Ouch! That’ll set them back a few seconds. What you need from your code now is a way to attack everyone except yourself.
Here’s a version that only takes advantage of CoffeeScript’s syntax in the form of the unless
and is
keywords and the loading of conditionals at the end of a statement, but which doesn’t do much in the way of lists.
players.forEach (target) ->
blueShroom.poison(target) unless target is buyer
You’ll also notice that the second line is an anonymous function. CoffeeScript almost makes you forget that. Here’s a version that obscures the code a bit further, bringing it closer to English.
for target in players
blueShroom.poison target unless target is buyer
And here’s one that uses list comprehensions:
blueShroom.poison target for target in players unless target is buyer
Wow, that’s the most English-like code yet! If you’re not sure what’s happening, look back to the first version, which looked like code. Here’s one that uses a few more CoffeeScript keywords that flows slightly better in English:
blueShroom.poison target for target in players when target isnt buyer
Note that this syntax won’t work with the first two versions, only the third version that uses list comprehensions.
As awesome as this is, there are two pitfalls to list comprehensions:
- List comprehensions return a list (in this case, the list of poisoned targets). This is great if you want to “stack” list comprehensions, but if you stack more than two it can quickly become confusing. This is especially true if you’re leaving off the optional parentheses and brackets.
- Because this can look so much like English, you may sometimes write a valid English sentence and be surprised that it doesn’t compile. As much effort as CoffeeScript makes to make your code English-like, if you make the mistake of thinking they’re the same thing, you will be swiftly brought back to reality.
Splats
What a funny word. It’s pretty relevant to our situation, too. You see, we’re trying to make our Cario Mart game work over the network, and we want a way to easily send messages to multiple players. Here’s a function we’ve written to help us:
tellPlayers = (message) ->
player.socket.emit(message) for player in players
Some things to note. First, nice use of list comprehensions! Uses for these things will pop up in a surprising number of places. Second, what we’re doing here is sending a message to each connected player on a WebSocket. You don’t have to worry about exactly what’s going on here or why we do it like this, but the basics are that there are up to eight clients per game, all connected to a central server. This code is on the server, and is sending a message to all of the game’s players (clients).
This works great if the message is simple, like gameEnded
. But what if when the game ends we want to send the name of the winner to everyone? We could write it like this:
tellPlayers = (message, argument) ->
player.socket.emit(message, argument) for player in players
That works now, but what if we send a ‘splat’ message whenever someone gets hit by a weapon (like a blue shroom)? Here, we want to know the weapon, the player hit, and the player who sent the attack. That’s three arguments! The way to get around this is through splats.
tellPlayers = (message, arguments...) ->
player.socket.emit(message, arguments...) for player in players
When you put the three dots afterwards, that means that the argument is actually a list of arguments. There can be 1, 3, 0, or any number of arguments. It’s like you threw the list and all of the individual arguments splatted all over a wall.
Here’s another example. We’re sending the list of players, ordered by race standing, over the socket to each client. The server side code will look like this:
tellPlayers('listOfPlayers', players...)
This calls our previous function. On the client side we have the following:
socket.on 'listOfPlayers', (first, second, third, rest....) ->
celebrate(first)
celebrate(second)
celebrate(third)
hands.clap() for hands in rest
The entire list of players is passed over the socket using a splat, then when it is received it picks out the first three for celebration, and the rest...
splat absorbs the rest.
It’s important that when you’re calling tellPlayers
you use the splat for players
. Otherwise, it will stick the entire list as the first argument, leading to the client celebrating all eight players, then celebrating two null
values, then having no one clap their hands. We can’t have that!
Your Choice
This article has introduced you to three of the most popular list processing techniques that CoffeeScript has to offer. There’s plenty of other little shortcuts and syntactical joybombs awaiting you in CoffeeScript. Visit the homepage and try them out!
In addition, SitePoint has just released the latest in its Jump Start series: Jump Start: CoffeeScript. Learn how to make an awesome ninja-filled HTML5 2D platformer using CoffeeScript … all in one weekend!
Finally, over on Learnable, Paul Annesley will walk you through a short JumpCast video on CoffeeScript. A heady brew!