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 = (text, fn) -> text: text.toUpperCase() action: fn
At that point the menu became a list of
items, 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]())
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"
If all you have is a hammer it’s going to take you a damn long time to build a house.