When programming if something is hard it means that you are not programming at the correct level of abstraction. Remember your ABCs (Always Be Coding-at-the-correct-level-of-abstraction).
What is the correct level of abstraction? The correct level of abstraction is the level that perfectly matches your problem domain. If you are creating an application about painting and geometry then you’ll probably be using points, lines, colors, areas, blending and more. Be sure to use those in your code as well, because doing cross products by hand is for mathletes, not painters.
I recently put this into practice when I was creating the menu screen for Red Ice:
gamestate "Tournament", MapState gamestate "Versus", MatchSetupState submenu "Mini-Games", minigame "Zamboni Defense" minigame "PushOut" minigame "Paint" submenu "Options", item "Config", ->
The structure looks like a menu, with submenus being obvious by their indentation. There’s not a lot of extra syntax junk in the trunk. It’s clean an to the point.
I knew that I wanted the menu to be a simple list of strings and functions. I started out by just using object literals, but noticed much redundancy. In the natural process of drying up the code by removing redundancy and naming functions simply I ended up creating a DSL.
The starting point was creating a function called item
.
item = (text, fn) -> text: text.toUpperCase() action: fn
At that point the menu became a list of item
s, which was a big improvement, but several of the functions were similar, like opening a submenu or changing game states. So I decided to add more functions whenever anything was duplicated. These new methods were able to build on the item
method I had created before, making them simpler as well.
gamestate = (name, state) -> item name, -> engine.setState(state()) minigame = (name) -> item name, -> engine.setState(Minigames[name]())
The submenu
was a little more interesting. Because I made it a function, it was easy to automatically add a back
button to each submenu just by sticking it in the function. Additionally implementing the back button was very simple by using the functions I had previously defined. One benefit of CoffeeScript is that I was able to use splats to allow any length of arguments to the submenu
function, with each argument after the name being an item in the submenu.
back = item "Back", popSubMenu submenu = (name, menuOptions...) -> item name, -> I.menus.push [back].concat(menuOptions)
Making nested submenus is also a breeze, not that you’d want to nest things deeply, but hey good DSLs make everything easy!
submenu "Mini-Games", submenu "Survival", minigame "Zamboni Defense" minigame "Mutant Attack" minigame "PushOut" submenu "Cooperative", minigame "Paint"
The biggest advantage of DSLs, correct abstractions, and higher-level languages, is that their power compounds. This would have been possible in pure JavaScript, but probably such a pain that it wouldn’t have been worth doing. Once you have a solid abstraction you can then build on it further and even combine different components in new and interesting ways.
If all you have is a hammer it’s going to take you a damn long time to build a house.