Painless Estimates

14 April, 2009 (15:22) | Craftsmanship, Software development | 2 comments

How do you determine how long something will take to build?

In an ideal world, many of us would probably like to simply work until the stakeholder/product owner decides we're done, keeping our expense meter running all along. This is (very roughly) the approach taken by agile software development methodologies such as Scrum.

Unfortunately, in the real world we often need to know roughly how long something will take before we start working on it. This is basic product management: the feasability of a new project, product or feature is (in part) derived from the initially estimated cost of building it. "That new nifty feature will take more than four months to add to our product? Maybe we have better things to do with our time, then."

What's the best way of doing this up-front estimation? There's certainly lots of approaches out there. Personally, I've settled on the following method:

  1. Start by getting your bearings somewhat. This means doing enough design and requirement gathering up front. Talk to stakeholders and users. Play around with any new unfamiliar technology that the work will require.   

  2. Extract the concrete tasks that must be done. Each task should take less than eight ideal hours to complete (see below). Tasks larger than this are often opaque "lumps" of many tasks, which are harder to estimate accurately- so divide the work into subtasks as needed. Don't forget work related to polish and testing: integration testing, documentation, release routines, etc.  

  3. Estimate each task in ideal hours: "how many hours would this take if I could focus 100%, no interruptions, closed office?" Another approach is to estimate using abstract or relative units, before translating to actual ideal hours. The Poker Planning Game and the Pomodoro Technique are good examples of this approach.  

  4. Add up total ideal hours. Then multiply by a risk factor, to allow for Murphy's Law. My personal minimum is usually 1.2 (20%) - and that's if I have a very good handle on both the technology, the requirements and most other significant factors.  

  5. Figure out how many ideal hours you actually get done each week. How much effective time do you really have left when you subtract meetings, interruptions, lunch, motivation lapses, etc etc? Divide the hour number from step 4 by these actual hours accomplished each week. Now you know roughly how many actual hours the work will take.  

  6. Finally, take external dependencies into consideration. Will Joe Developer be there week one or is he tied up? Will Sue Tester go for a three week holiday to Hawaii at some point? Set up the project schedule based on these known constraints.  

This method is heavily inspired by Mike Cohn's Agile Estimating and Planning, and I use it both at my dayjob and for private side projects. I've found this to yield quite accurate estimates for small projects - eg. where the scope is less than, say, 3 months with less than four developers.

For larger projects you may find that estimating work up in such fine grained tasks involves a lot of uncertainty. "How can I know exactly what specific tasks will be required all through my project?". Well, yes - for projects with larger scopes, this quickly turns into riskful guesswork.  This is what makes estimating entire large projects up front (and, by extension, taking on large fixed cost projects) such a dicey proposition.

Eggs, Tomatoes and Time Management

30 March, 2009 (16:47) | Craftsmanship, Productivity, Software development | No comments

It's evening, and my home office door is closed. No email or IM clients are running on my computer. My daughter is asleep, my Significant Other is aware that I'm working. I wind up the egg timer, the clock starts ticking. My mind quickly enters a state of Flow. 25 minutes later the timer rings. The feeling is akin to waking up from a dream. I stop working, and jot down a mark in my task list. A solid unit of work accomplished; another egg completed.

eggtimer

I have trouble maintaining a consistent level of productivity - especially for side projects that I work on in my spare time. Both at work and privately I find that my productivity fluctuates wildy. Sometimes I'm able to crank out quality stuff for 10 hours straight, while other days simply vanish in trivial distractions.

A possible remedy to this problem presented itself recently in the Pomodoro Technique ("Tomato Technique"), a time management method I'd never heard of before last week. I've currently only used this system for a few days, but the early results are quite promising.

Getting started is fairly simple:

  1. Set up some sort of timer. A software clock works, but a physical clock is better.
  2. Select a task that you need to accomplish, and remove possible distractions.
  3. Set the timer to 25 minutes.
  4. Work on the task until the timer rings.
  5. One "tomato" is now accomplished for the task - add a mark to the task record.
  6. Take a short break before restarting the timer - 5 minutes or so.
  7. Take a longer break once every four tomatoes.

To me, the most obvious benefit of the technique is that it forces you to timebox and really focus on your work. One resulting effect for me is that I get into the elusive "flow" state much earlier than before. There's more to it, though.

The regular breaks provide an opportunity to periodically step back and look at the current task from a different perspective: "Am I going about this in the wrong way? Maybe I'm even solving the wrong problem?" This is valuable - it's easy to lose sight of the larger picture when you're head down into the specifics of a task.

Over time your ability to estimate future tasks is likely to improve, since you have a more solid appreciation (and written record) of how much effort your past work took. Besides, it's generally a good idea to measure your work in abstract work units rather than directly in clock hours (the Poker Planning Game is another example of this phenomenon).

The final benefit of the system is the moral boost you get from continuously marking down those completed "tomatoes" (I personally refer to them as "eggs", since I use an egg-shaped timer). Think about it; don't you feel good when you complete a feature, article or bug fix? I find that this technique gives me that same satisfaction every 30 minutes - I may not finish that new feature tonight, but I know that every egg takes me closer to completion.

It will be interesting to see what this does for my productivity long-term. The next step in the technique is to actually start estimating future tasks in tomatoes instead of hours; I'll try that once I've gotten used to the basic workflow. I'll probably post an update in a few weeks.

pomodoroTechniqueLogo

If you found this interesting I strongly encourage you to visit the Pomodoro website and read more yourself; there's a nice, free eBook summary available there (pdf).

Invest In Yourself

22 February, 2009 (23:17) | Craftsmanship, Software development | No comments

I wish I could go back in time and give myself career advice.

BackToTheFutureAdvice

Actually, I'd probably hold off on the work-related issues. Essentials first: women, lottery numbers, financial advice. Back To The Future 101, right? But with that out of the way: career tips! What would I say to my younger self?

1. Active knowledge management

Cultivate an active approach to learning and growing as a craftsman. Set and follow a clear personal path - don't just let corporate whims dictate the areas of your expertise. Take charge! If you absolutely have to work with dead-end or irrelevant technology at your day job, then learn what you need in your free time.

2. Timeless skills > buzzwords

On a related note, focus your spare time on diversified and technology agnostic skills, rather than enterprise technology of the week mandated by the corporate overlords.

While there may be something to be said for staying painfully up to date on the latest industry trends / Gartner approved buzzwords, I personally find it more rewarding to go back and improve my core skills. There's always basic stuff I could be much better at. Math, OOP, programming technique: skills which will still endure when the Spring framework draws it last breath (or last download, I guess).

3. Produce in your spare time

When learning a useful new skill in your free time (see above), don't limit yourself to 'Hello World' or a dinky O'Reilly tutorial. Build something concrete. Add new stuff to your portfolio.

You'll learn so much more from building something real and lasting; channel your energy and love of learning into tangible side projects. Create open source software, write articles, start an mISV, work for non-profits/charities... or something else that appeals to you.

4. You != The Company

I wrote blog posts exclusively inside a corporate intranet for a while. I'm kicking myself for that now. Yes, in hindsight I think that what I wrote then was somewhat rubbish. However it would still be nice to have it pad out my own blog archives, rather than the graveyard of some internal company webserver (which is where my old blog posts currently reside).

If you enjoy holding presentations, focus on external meetups and conferences. Don't hide your work in internal company venues (unless your topic concerns company secrets). You want to invest in extracurricular work that benefits both your company and your public profile; when you switch jobs you only benefit from the latter.

Make sure your employment contract doesn't state that your employer owns everything you create outside of work. Why should you freely give away all your potential off-hours creativity to a faceless corporation (unless you're strictly a 9-5 programmer, in which case I suppose it doesn't matter)?

And please think twice before spending your spare time on certificatons mandated by your employer. Certifications can be really useful learning excercises, but spending time outside office hours to study for them?  Only if  it clearly makes you a better craftsman.

Wrapping up

So, in summary: Take charge of your career. Improve your craftsmanship. Produce stuff. Don't freely give away your mind to faceless corporations.

I didn't give the above issues much active thought until these last two years, and I'd love to be able to give my former self some of these pointers. Fortunately, however, there's plenty of time to remedy my previous career coma. Which reminds me: I'd better get back to work on my secret side project. :)

Loosely related stuff

Paul Graham on "How To Do What You Love".

Also, check out the video below. Zed Shaw on intrigue, suspense, corporate autism, stakes and strippers. Very funny talk!

Javascript Tetris Pt 8: Post Mortem & References

28 January, 2009 (21:29) | Gaming, Javascript, Quicktetris, Software development | 7 comments

Part 1, 2, 3, 4, 5, 6, 7, 8

This was a fun project! Time to wrap it up...

What did I take away from all this?

Well, putting together the game (and these blog posts) helped me achieve my goal: I've got a better grip on core Javascript syntax and idioms now. I find I actually like working in the language. It's very flexible, mainly because of its functional aspects and malleable Prototype object system. Ruby is still a more pleasant alternative, though. :)

Development is rapid... as long as I use disciplined regression tests. This is key - the only reason I was able to code (and constantly refactor) this little project relatively quickly was the constant safety net of my tests. Refreshing the browser and quickly hopping through the visual tests took about 10 secs each time, making for fairly rapid dev cycles.

I ended up with a basic but effective dev environment for dynamic web apps. I'm planning on extracting a stripped down template for future projects (with some improvements, see below).

Finally, rapidly building something concrete and visual is a rewarding feeling in and of itself... even for a trivial toy like my dinky little game.

Room for improvement

There are, apart from the myriad candidates for refactoring, some major issues to consider in the finished product as it stands now.

More of the code could be programmatically testdriven. For instance, much of the collision detection and state handling of Piece and Field could be tested and developed without relying on manual, visual tests. I managed without extensive unit tests here, however if the project had been a less trivial one I probably would've been more rigorous.

Piece shape matrices could be expressed in a more compact way if I'd defined each shape once, then used matrix rotation on them as needed. Unfortunately my math is a wee bit rusty, so I opted for "readability" instead. ;)

My use of OOP in this project could be a bit more disciplined. I reach right inside the Field and Piece objects and grab their state variables - so much for encapsulation, eh? I should've defined accessor functions, using closures to hide the actual state variables (this didn't really occur to me until I was close to finishing the project).

Also, all the code should be hidden within the Quicktetris object (our application namespace). One of the quirks of Javascript is its notion of linkage through global variables - every library, function and general flotsam of Javascript code loaded in the current browser window ends up in the the same global namespace. Given the possible amount of library code that a serious application can load, our generic Test, Piece, Field , Graphics names could easily trigger naming conflicts.

How much thought did I put into performance? Nada. Zip. It loads fast enough, and runs fairly smoothly on my laptop. Works for me. However, if general web application performance was more of a concern I'd probably look into some of the most obvious remedies:

  • Compression of the javascript code. All that generous whitespace adds to the size of the transmitted script.
  • Concatenating all javascript into one .js file to avoid overhead of multiple http requests
  • Compressing the image and sound assets. Lower bitrate for sound, reduced color debth for images
  • Using YSlow to profile and suggest further performance tweaks

Using straight DOM scripting combined with styled div elements works for our game. Every visual element is nice and rectangular; because of this we don't need more organic functionality such as lines, circles and curves. More flexible graphics would probably have called for a different approach. There are a few ways of doing this. The canvas approach is one, SVG based graphics another. Unfortunately, Internet Explorer doesn't support either of them out of the box (without installing third party extensions).

Time spent

The code didn't initially leap out in clean increments, as suggested throughout these blog posts. Being a relative newbie to serious Javascript development, I went through quite a few iterations - refactoring the code as I learned more.

Finishing the basic gameplay mechanics took roughly one week, using spare time in evenings and over the weekend. Improved graphics, animation, refactoring and general cleanup was finished after another week. Planning and preparing for this series of companion blog posts added overhead to the whole process.

Tools used:

  • Firebug is an absolute must for debugging Javascript code, enabling you to set breakpoints, step through the code, inspect DOM/CSS/script state at all times... One particularly handy feature it offers is its text console - console.log() is so much nicer for debugging than the old school practice of dumping debug messages in a dialog box with alert().
  • JSLint is, as previously mentioned, the closest you'll get to compiling your Javascript (for now). It checks code for typical Javascript code smells. Like the author of the tool states: "JSLint will hurt your feelings".
  • Rake is my preferred build tool. Writing procedural build scripts in Ruby is very expressive, fast and readable, especially compared to certain other xml-based, declarative monstrosities. :)
  • Beautify Javascript is a handy online tool for prettifying snippets of Javascript code.
  • Any editor will do, but I personally prefer Textmate when I work in OS X. Well worth the money.

Reference material

I usually find the W3Schools pages to be an ok starting point when I research unfamiliar web standards. Their tutorials and examples are uneven, but the site works well for quickly looking up stuff like API details and HTML DOM events.

For Norwegian readers: this is a very nice blog about Javascript and assorted web technologies. Kudos to Christian Johansen for a solid resource!

javascriptgoodpartscover

My main Javascript source, however, is Douglas Crockfords work. Crockford is the author of JsLint, the JSON standard, and general champion of Javascript. Crockford has essentially carved out and evangelized a subset of Javascript / EcmaScript, discouraging use of the more ugly parts in the language spec. You'll find a lot of useful articles at his site, as well as a bunch of videos at the Yahoo YUI site.

I strongly recommend his book, which summarizes much of the material from the articles and videos mentioned above. At 153 pages it's one of the most dense, concise language reference books I've ever seen.


Special thanks

My friend and colleague Alexander Odden (aka Flipside) was kind enough to whip up some sound and music - much appreciated!

I'm grateful to Johannes Brodwall, Christian Johansen, Lars Juel Jensen, Thomas Kristensen, Henrik Storm Ofteland and Bente Storåker for (sometimes heeded) feedback, suggestions and criticism. Thanks guys (and gal)!

Javascript Tetris Pt 7: Gameplay

28 January, 2009 (21:26) | Gaming, Javascript, Quicktetris, Software development | No comments

Full source code can be downloaded from project home at kjeldahlnilsson.net.

Part 1, 2, 3, 4, 5, 6, 7, 8

Today we finally get to strap together the actual game. Most of the work is already done by now - we just need to assemble the components. Let's start by handling transitions between three basic game states; intro screen, playing the game, and game over.

main.js:

var QuickTetris = {
 
    gameModes: {
        titleScreen: "titleScreen",
        gamePlay: "gamePlay",
        gameOver: "gameOver"
    },
 
    gameMode: null,
 
    gotoTitleScreen: function() {
        this.gameMode = this.gameModes.titleScreen;
        Graphics.clearGameContainer();
 
        Graphics.drawString("- PRESS SPACE TO START -", 0, 0);
 
        setKeyReaction(function(keyCode) {
            if (keyCode === SPACE_KEY) {
                QuickTetris.gotoGamePlay();
            }
        });
    },
 
    gotoGameOver: function() {
        this.gameMode = this.gameModes.gamePlay;
        Graphics.clearGameContainer();
 
        Graphics.drawString("- PRESS SPACE TO RETRY -", 0, 0);
 
        setKeyReaction(function(keyCode) {
            if (keyCode === SPACE_KEY) {
                QuickTetris.gotoGamePlay();
            }
        });
    },
 
    gotoGamePlay: function() {
        this.gameMode = this.gameModes.gamePlay;
        Graphics.clearGameContainer();
 
        this.dropSpeed = 3;
 
        var fieldXPos = this.getFieldCenteredXPos();
 
        Field.init("white", "white", fieldXPos, 50);
        Piece.init("white");
 
        setKeyMemory();
    }
 
};



See that final call to setKeyMemory()? Instead of immediately reacting to keypresses, like we did in the tests previously, we instead store the last pressed key in a global variable. Our game then reacts to that stored event regularly during each pass of the game loop, preventing user input from disrupting the flow of the game.

util.js:

var currentKeyPress = null;
function setKeyMemory() {
    document.onkeydown = function(e) {
        if (window.event) // IE
        {
            currentKeyPress = window.event.keyCode;
        }
        else if (e.which) // Netscape/Firefox/Opera
        {
            currentKeyPress = e.which;
        }
    };
    document.onkeyup = function(e) {
        currentKeyPress = null;
    };
}


The heart of a typical game is the game loop. For every pass of this loop we react to user input, move the piece downwards in the playing field, and (sometimes) adjust the difficulty of the game. Let's add that to the Quicktetris object above:

main.js:

    gameLoop: function() {
        if (QuickTetris.gameMode === QuickTetris.gameModes.gamePlay) {
            QuickTetris.reactToKeyPress(currentKeyPress);
            QuickTetris.adjustDifficulty();
            Piece.moveDown(QuickTetris.dropSpeed);
        }
    },
 
    reactToKeyPress: function(keyCode) {
        if (currentKeyPress === null) {
            return;
        }
 
        switch (keyCode) {
        case DIR_KEY_DOWN:
            Piece.moveDown(15);
            break;
        case DIR_KEY_LEFT:
            Piece.moveLeft(Piece.State.tileWidth);
            break;
        case DIR_KEY_RIGHT:
            Piece.moveRight(Piece.State.tileWidth);
            break;
        case SPACE_KEY:
            Piece.rotate(true);
            break;
        }
 
        //Reset key
        currentKeyPress = null;
    }


Finally we need a way of launching the game. We create our default index.html page, which, after loading, calls Quicktetris.startDefaultGameLoop():

main.js:

    startDefaultGameLoop: function() {
        this.gotoTitleScreen();
 
        // Launch game loop - set it to fire every X milliseconds
        setInterval(this.gameLoop, 50); // Attempting 20 FPS
    }


Aaand we're done! Go on, try it out yourself!:)

We'll do a brief post mortem summary of the project in the next and final part.

Javascript Tetris Pt 6: Lights, Action, Music!

28 January, 2009 (21:23) | Gaming, Javascript, Quicktetris, Software development | No comments

Full source code can be downloaded from project home at kjeldahlnilsson.net.

Part 1, 2, 3, 4, 5, 6, 7, 8

The game looks very prototype-ish right now. We're not shooting for blockbuster level presentation here, but we should at least provide a bare minimum of animation and audio feedback. Let's do something about that by adding animation, making the playing field a little more interesting, plus some support for sound effects and music.

First off; some sort animation payoff when the player clears one or more rows. We want a sort of stylized explosion to occur. Let's create that separately first. We'll achieve this by applying a JQuery UI effect to a div while hiding it.

test.js:

    testExplodeAnimation: function() {
        Graphics.clearGameContainer();
        var square = Graphics.createRectangleDiv("black", 400, 400, 100, 100);
        $(square).hide("explode", {},
        1000);
    }



This works, and looks cool. Now we'll finish the Field.doRowClears() method from yesterday, including some animation when we remove the filled rows in the field.

field.js:

    doRowClears: function() {
        var rowsToClear = [];
 
        this.State.gridState.eachRowWithIndex(function(row, rowNumber) {
            var entireRowFilled = true;
 
            // Is row filled?
            for (var tile in row) {
                if (row[tile] === 0) {
                    entireRowFilled = false;
                    break;
                }
            }
 
            // Set row to be cleared
            if (entireRowFilled) {
                rowsToClear.push(rowNumber);
            }
        });
 
        if (rowsToClear.length > 0) {
            this.explodeAndClearRows(rowsToClear);
        }
    },
 
    explodeAndClearRows: function(rowsToExplode) {
 
        // Set up big animation rectangle to cover the disappearing rows
        var topRow = 1000;
        var bottomRow = 0;
        for (var row in rowsToExplode) {
            if (rowsToExplode.hasOwnProperty(row)) {
                if (topRow > rowsToExplode[row]) {
                    topRow = rowsToExplode[row];
                }
                if (bottomRow < rowsToExplode[row]) {
                    bottomRow = rowsToExplode[row];
                }
            }
        }
 
        var rowsTotalTopY = this.State.posY + (topRow * Piece.State.tileHeight);
        var rowsTotalBottomY = this.State.posY + (bottomRow * Piece.State.tileHeight) + Piece.State.tileHeight;
        var rowsTotalHeight = rowsTotalBottomY - rowsTotalTopY;
 
        var explodingRect = Graphics.createRectangleDiv("#C0ADFF", this.State.posX, rowsTotalTopY, Piece.State.tileWidth * this.WIDTH, rowsTotalHeight, 10);
 
        // Clear the actual tiles in grid before animating the large rectangle
        for (row in rowsToExplode) {
            if (rowsToExplode.hasOwnProperty(row)) {
                this.clearRow(rowsToExplode[row]);
            }
        }
 
        // Use JQuery UI effect to "explode" the big rectangle
        $(explodingRect).hide("explode", {},
        1500);
 
        // Clean up
        Graphics.removeNodeFromGameContainer(explodingRect);
 
        // Shuffle remaining higher tiles downwards in field
        for (row in rowsToExplode) {
            if (rowsToExplode.hasOwnProperty(row)) {
                this.shiftTilesDownToRowX(rowsToExplode[row]);
            }
        }
    },
 
    clearRow: function(rowNo) {
        for (x = 0; x < this.State.gridState.length; x++) {
            this.tileOff(x, rowNo);
        }
    },
 
    shiftTilesDownToRowX: function(clearedRowY) {
        for (var y = (clearedRowY - 1); y >= 0; y--) { // Start at bottom to cascade tiles
            for (var x = 0; x < this.State.gridState.length; x++) {
                // Shuffle tile state down
                if (this.isTileOn(x, y)) {
                    this.tileOff(x, y);
                    this.tileOn(x, y + 1);
                }
                else {
                    this.tileOff(x, y + 1);
                }
            }
        }
 
    }


We also want to improve the appearance of the playing field somewhat; solid magenta is handy for raw testing but not exactly visually appealing in a finished game. Let's add a background graphic by updating Graphics.createFieldBackground():

graphics.js:

    createFieldBackground: function(bgcolor, x, y, width, height) {
        var rect = document.createElement('div');
 
        rect.style.position = "absolute";
        rect.style.top = y + "px";
        rect.style.left = x + "px";
        rect.style.zIndex = "-1";
        rect.style.height = height + "px";
        rect.style.width = width + "px";
        rect.style.backgroundColor = bgcolor;
 
        rect.style.backgroundImage = "url('http://www.messynotebook.com/spacer.gif')";
        rect.style.backgroundRepeat = "no-repeat";
        rect.style.backgroundPosition = "0px 0px";
 
        this.getGameContainer().appendChild(rect);
 
        return rect;
    },


collisiontestUpdatedBackground

Much better. The background picture is simply an image pulled from a random "mountains" search on Flickr, then cropped and tweaked a bit in Paint.Net.

We'll wrap up todays installment by adding support for sound and music. I found an elegant little library called Soundmanager. Soundmanager creates and wraps a hidden Flash component in the page, enabling us to seamlessly load and play mp3 files (or other media):

test.js:

    testPlaySound: function() {
	Sound.playLandingSound();
    },
 
    testPlaySoundLooped: function() {
	Sound.playLoopedLandingSound();
    },
 
    testPlayMusic: function() {
	Sound.playAmbientMusic();
    },


We then create our Sound object, wrapping the functionality we need from SoundManager to load and play audio.

sound.js:

var Sound = {
 
    SoundBank: {
        rotation: "rotation",
        landing: "landing",
 
        clearedOneRow: "clearedOneRow",
        clearedTwoRows: "clearedTwoRows",
        clearedThreeRows: "clearedThreeRows",
        clearedFourRows: "clearedFourRows",
 
        ambientMusic: "ambientMusic"
    },
 
    loadSounds: function() {
        soundManager.createSound(this.SoundBank.rotation, 'assets/sound/rotation.mp3');
        soundManager.createSound(this.SoundBank.landing, 'assets/sound/landing.mp3');
        soundManager.createSound(this.SoundBank.clearedOneRow, 'assets/sound/clearedOneRow.mp3');
        soundManager.createSound(this.SoundBank.clearedTwoRows, 'assets/sound/clearedTwoRows.mp3');
        soundManager.createSound(this.SoundBank.clearedThreeRows, 'assets/sound/clearedThreeRows.mp3');
        soundManager.createSound(this.SoundBank.clearedFourRows, 'assets/sound/clearedFourRows.mp3');
        soundManager.createSound(this.SoundBank.ambientMusic, 'assets/sound/ambientMusic.mp3');
    },
 
    playAmbientMusic: function() {
        playLooped(this.SoundBank.ambientMusic);
    },
 
    playRotationSound: function() {
        soundManager.play(this.SoundBank.rotation);
    },
 
    playLandingSound: function() {
        soundManager.play(this.SoundBank.landing);
    },
 
    playLoopedLandingSound: function() {
        playLooped(this.SoundBank.landing);
    },
 
    playClearedSound: function(level) {
        if (!level || level < 4 || level > 1) {
            return;
        }
 
        switch (level) {
        case 1:
            soundManager.play(this.SoundBank.clearedOneRow);
            break;
        case 2:
            soundManager.play(this.SoundBank.clearedTwoRows);
            break;
        case 3:
            soundManager.play(this.SoundBank.clearedThreeRows);
            break;
        case 4:
 
            soundManager.play(this.SoundBank.clearedFourRows);
            break;
        }
    },
 
};
 
// Looping sound support
playLooped: function playLooped(soundID) {
    window.setTimeout(function() {
        soundManager.play(soundID, {
            onfinish: function() {
                playLooped(soundID);
            }
        });
    },
    1);
};
 
soundManager.onload = function() {
    Sound.loadSounds();
}



Sound effects and music can now be launched from the rest of the game logic.

We have most of what we need now; in the next installment we glue it all together to create a playable game!

Javascript Tetris Pt 5: The Life Of A Piece

28 January, 2009 (21:22) | Gaming, Javascript, Quicktetris, Software development | No comments

Full source code can be downloaded from project home at kjeldahlnilsson.net.

Part 1, 2, 3, 4, 5, 6, 7, 8

In this installment we are going to set up the proper four-tile tetris piece and its interaction with the playing field. Be warned; this is the installment where we write most of our code. Grab some coffee before we start. :)

We will add a single functional, visual test for the piece and field interaction. I started out with separate tests for painting the piece, moving it, collision detection and so forth. However, in the end I found a single test sufficed to check all these things - so we'll simply use that, keeping our code walkthrough somewhat brief.

The test sets up the playing piece, the play field, and behavior for user input: movement, rotation and switching the shape of the tetris piece.

test.js:

 testPieceCollision: function() {
        Graphics.clearGameContainer();
 
        Field.init("blue", 400, 100);
        Piece.init();
 
        // Register key mapping
        setKeyReaction(function(keyCode) {
            switch (keyCode) {
            case DIR_KEY_DOWN:
                Piece.moveDown(15);
                break;
            case DIR_KEY_UP:
                Piece.moveUp(15);
                break;
            case DIR_KEY_LEFT:
                Piece.moveLeft(Piece.State.tileWidth);
                break;
            case DIR_KEY_RIGHT:
                Piece.moveRight(Piece.State.tileWidth);
                break;
            case NUM_KEY_ONE:
                Piece.toggleSquareShape();
                break;
            case NUM_KEY_TWO:
                Piece.toggleLineShape();
                break;
            case NUM_KEY_THREE:
                Piece.toggleTeeShape();
                break;
            case NUM_KEY_FOUR:
                Piece.toggleRHookShape();
                break;
            case NUM_KEY_FIVE:
                Piece.toggleLHookShape();
                break;
            case NUM_KEY_SIX:
                Piece.toggleRightLShape();
                break;
            case NUM_KEY_SEVEN:
                Piece.toggleLeftLShape();
                break;
            case SPACE_KEY:
                Piece.rotate(true);
                break;
            default:
                Graphics.drawString('Direction keys moves, space rotates, 1-5 changes piece type', 400, 400);
            }
        });
    },


In Tetris, the playing piece is made up of four tiles. We have seven shapes - seven basic ways of ways of grouping the four tiles.

tetrispiecetypes

All of these piece types can be rotated in ninety degree steps, giving us up to four different shapes per piece type. All piece shapes, no matter their rotation, fit inside a 4*4 grid... another two dimensional array. We'll define the possible shapes of the tetris piece as array literals. I'll limit the code listing to the possible shapes for the "T" piece type, for brevitys sake:

piece.js:

//   x
//  xxx : tee
var tee0dg =
[[0, 0, 1, 0],
 [0, 1, 1, 1],
 [0, 0, 0, 0],
 [0, 0, 0, 0]];
 
var tee90dg =
[[0, 0, 1, 0],
 [0, 0, 1, 1],
 [0, 0, 1, 0],
 [0, 0, 0, 0]];
 
var tee180dg =
[[0, 0, 0, 0],
 [0, 1, 1, 1],
 [0, 0, 1, 0],
 [0, 0, 0, 0]];
 
var tee270dg =
[[0, 0, 1, 0],
 [0, 1, 1, 0],
 [0, 0, 1, 0],
 [0, 0, 0, 0]];
 
var teeRotations = [tee0dg, tee90dg, tee180dg, tee270dg];


Finally we wrap the possible rotated shapes in an array, so we hold all possible rotated shapes of that piece type in one place. We do the same for all other piece shapes.

Now we have everything we need to define the playing piece and its behavior.

piece.js:

var Piece = {
 
    State: {
        piecePos: {
            x: 0,
            y: 0
        },
        color: "black",
        currRotation: squareRotations[0],
        currShape: squareRotations,
        rotationCounter: 0,
        // The four sprites that make up the piece
        tiles: [null, null, null, null],
        tileWidth: 23,
        tileHeight: 30
    },
 
    init: function(color) {
 
        rotationCounter = 0;
 
        var width = this.State.tileWidth;
        var height = this.State.tileHeight;
 
        if (!color) { // Assume colorful diagnostic pattern
            this.State.tiles[0] = Graphics.createRectangleDiv("green", 0, 0, width, height);
            this.State.tiles[1] = Graphics.createRectangleDiv("yellow", 0, 0, width, height);
            this.State.tiles[2] = Graphics.createRectangleDiv("orange", 0, 0, width, height);
            this.State.tiles[3] = Graphics.createRectangleDiv("red", 0, 0, width, height);
        }
        else {
            this.State.tiles[0] = Graphics.createRectangleDiv(color, 0, 0, width, height);
            this.State.tiles[1] = Graphics.createRectangleDiv(color, 0, 0, width, height);
            this.State.tiles[2] = Graphics.createRectangleDiv(color, 0, 0, width, height);
            this.State.tiles[3] = Graphics.createRectangleDiv(color, 0, 0, width, height);
        }
 
        this.reset();
    },
 
    // Reset piece pos to middle of middle, top
    reset: function() {
        this.State.piecePos.x = Field.State.posX + ((Field.WIDTH / 2) * this.State.tileWidth) - (2 * this.State.tileWidth);
        this.State.piecePos.y = Field.State.posY - (this.State.tileHeight * 2);
        this.setRandomShape();
        this.redraw();
    },
 
    drawSingleTile: function(xSlot, ySlot, tileNo) {
        var derivedX = this.State.piecePos.x + (xSlot * this.State.tileWidth);
        var derivedY = this.State.piecePos.y + (ySlot * this.State.tileHeight);
        this.State.tiles[tileNo].style.top = derivedY;
        this.State.tiles[tileNo].style.left = derivedX;
    },
 
    redraw: function() {
        var tileCounter = 0;
        this.State.currRotation.eachWithIndexes(function(element, x, y) {
            if (element === 1) { // Is the matrix slot ticked?
                Piece.drawSingleTile(x, y, tileCounter);
                tileCounter++;
            }
        });
        tileCounter = 0;
    },
 
    move: function(dx, dy) {
        var directionIsDown = (dy > 0);
 
        var collisionCheck = Field.checkCollisions(this.State.piecePos.x, this.State.piecePos.y, this.State.currRotation, directionIsDown, dx, dy);
        if (collisionCheck.collides) {
            if (collisionCheck.sticks) {
                Field.mergeShapeIntoField(this.State.piecePos.x, this.State.piecePos.y, this.State.currRotation);
                this.reset();
                this.isGameOver();
            }
        }
        else {
            this.State.piecePos.x += dx;
            this.State.piecePos.y += dy;
            this.redraw();
        }
    },
 
    moveUp: function(speed) {
        this.move(0, -(speed));
    },
    moveDown: function(speed) {
        this.move(0, speed);
    },
    moveLeft: function(speed) {
        this.move( - (speed), 0);
    },
    moveRight: function(speed) {
        this.move(speed, 0);
    },
 
    setRandomShape: function() {
        var random = Math.floor(Math.random() * 7);
 
        switch (random) {
        case 0:
            this.toggleSquareShape();
            break;
        case 1:
            this.toggleLineShape();
            break;
        case 2:
            this.toggleTeeShape();
            break;
        case 3:
            this.toggleRHookShape();
            break;
        case 4:
            this.toggleLHookShape();
            break;
        case 5:
            this.toggleRightLShape();
            break;
        case 6:
            this.toggleLeftLShape();
            break;
        }
    },
 
    rotate: function(doCollisionCheck) {
        this.State.rotationCounter++;
        if (this.State.rotationCounter == this.State.currShape.length) {
            this.State.rotationCounter = 0;
        }
 
        if (doCollisionCheck) {
            var collisionCheck = Field.checkCollisions(this.State.piecePos.x, this.State.piecePos.y, this.State.currShape[this.State.rotationCounter], false, 0, 0);
            if (collisionCheck.collides) {
                return;
            }
        }
 
        this.State.currRotation = this.State.currShape[this.State.rotationCounter];
        this.redraw();
    },
 
    resetRotation: function() {
        this.State.currRotation = this.State.currShape[0];
        this.State.rotationCounter = 0;
        this.redraw();
    },
 
    toggleSquareShape: function() {
        this.State.currShape = squareRotations;
        this.resetRotation();
    },
 
    toggleLineShape: function() {
        this.State.currShape = lineRotations;
        this.resetRotation();
    },
 
    toggleTeeShape: function() {
        this.State.currShape = teeRotations;
        this.resetRotation();
    },
 
    toggleRHookShape: function() {
        this.State.currShape = rhookRotations;
        this.resetRotation();
    },
 
    toggleLHookShape: function() {
        this.State.currShape = lhookRotations;
        this.resetRotation();
    },
 
    toggleLeftLShape: function() {
        this.State.currShape = leftLRotations;
        this.resetRotation();
    },
 
    toggleRightLShape: function() {
        this.State.currShape = rightLRotations;
        this.resetRotation();
    }
 
};


Whew. The final task is to define how the field behaves when the playing piece touches its borders, or other tiles on the field. In other words, collision detection. The Field object will take the playing piece position, shape, direction and speed and determine two things: will the piece collide with anything? If it collides, does it also stick to that surface? If it does, the Piece will, as defined above, ask Field to merge its shape into the current position in the Field grid.

We add the following members to the Field object literal (which we started defining in yesterdays blog post):

field.js:

    CollisionData: {
        collides: false,
        sticks: false
    },
 
    checkCollisions: function(xPos, yPos, shapeArray, directionIsDown, dx, dy) {
        if (this.State.gridState) {
            var fieldCollision = this.pieceCollidesWithField(xPos, yPos, shapeArray, directionIsDown, dx, dy);
            var boundaryCollision = this.pieceCollidesWithFloorOrWall(xPos, yPos, shapeArray, directionIsDown, dx, dy);
            var collisionResult = Object.create(this.CollisionData);
            collisionResult.collides = fieldCollision.collides || boundaryCollision.collides;
            collisionResult.sticks = fieldCollision.sticks || boundaryCollision.sticks;
            return collisionResult;
        }
    },
 
    pieceCollidesWithField: function(xPos, yPos, shapeArray, directionIsDown, dx, dy) {
        var collision = Object.create(this.CollisionData);
 
        shapeArray.eachWithIndexes(function(element, x, y) {
            if (!element || (collision && collision.collides)) {
                return;
            }
 
            var tileXPos = xPos + (Piece.State.tileWidth * x) + dx;
            var tileYPos = yPos + (Piece.State.tileHeight * y) + 1;
            var movingRect = makeRect(tileXPos, tileYPos, Piece.State.tileWidth, Piece.State.tileHeight);
 
            Field.State.gridTiles.eachWithIndexes(function(tile, arrX, arrY) {
                if (collision.collides) {
                    return;
                }
 
                if (Field.isTileOn(arrX, arrY)) { // Only  collision if tile is actually switched on
                    var tileX = Field.State.posX + (Piece.State.tileWidth * arrX);
 
                    var tileY = Field.State.posY + (Piece.State.tileHeight * arrY);
                    var fieldRect = makeRect(tileX, tileY, Piece.State.tileWidth, Piece.State.tileHeight);
                    collision.collides = intersectRect(movingRect, fieldRect);
 
                    if (collision.collides && directionIsDown) {
                        collision.sticks = true;
 
                    }
                }
            });
        });
 
        return collision;
    },
 
    pieceCollidesWithFloorOrWall: function(xPos, yPos, shapeArray, directionIsDown, dx, dy) {
        var collision;
 
        shapeArray.eachWithIndexes(function(element, x, y) {
            if (!element || (collision && collision.collides)) {
                return;
            }
 
            collision = Object.create(Field.CollisionData);
 
            // Check for floor collision
            var tileYPos = yPos + (Piece.State.tileHeight * y) + 1;
            var tileBottom = tileYPos + Piece.State.tileHeight;
            var fieldBottom = Field.State.posY + (Piece.State.tileHeight * Field.HEIGHT);
            collision.collides = (tileBottom > fieldBottom);
            collision.sticks = directionIsDown;
 
            // Check for wall collision (if no floor collision)
            if (!collision.collides) {
                var tileXPos = xPos + (Piece.State.tileWidth * x) + dx;
                var tileLeft = tileXPos;
                var tileRight = tileXPos + Piece.State.tileWidth;
                var fieldLeft = Field.State.posX;
                var fieldRight = Field.State.posX + (Piece.State.tileWidth * Field.WIDTH);
                collision.collides = (tileLeft < fieldLeft || tileRight > fieldRight);
            }
        });
 
        return collision;
    },
 
    mergeShapeIntoField: function(xPos, yPos, shapeArray) {
        shapeArray.eachWithIndexes(function(element, x, y) {
            if (element) {
                var tileAbsoluteXPos = xPos + (Piece.State.tileWidth * x);
                var tileAbsoluteYPos = yPos + (Piece.State.tileHeight * y);
                var tileXPosInField = tileAbsoluteXPos - Field.State.posX;
                var tileYPosInField = tileAbsoluteYPos - Field.State.posY;
                var tileXLocationInField = tileXPosInField / Piece.State.tileWidth;
                var tileYLocationInField = Math.round(tileYPosInField / Piece.State.tileHeight);
                Field.tileOn(tileXLocationInField, tileYLocationInField);
            }
        });
 
        Field.doRowClears();
    }


Site Map I'm not completely happy with the pieceCollides*() methods. Some of the variable names could be clearer. Checking the entire field for collisions is a little brute force (collision can only occur in the field tiles overlapping and directly surrounding the piece). The calculation of coordinates could be simplified by having Piece use coordinates relative to Field. And methods with more than two levels worth of nested blocks are just asking for an additional method extract. This implementation does the job well enough for now, however.

Once the playing piece has "stuck" and been merged into the field, we check if any rows have been completely filled in the playing field - these should be cleared, and the pieces above should be shuffled down. We'll hold off implementing doRowClears(), however - todays installment is already running a little long. :)

In the next part we'll finish the piece-field interaction. We'll also add some visual and aural interest to the game.

Javascript Tetris Pt 4: Graphics & Input

28 January, 2009 (21:20) | Gaming, Javascript, Quicktetris, Software development, Tools | No comments

Full source code can be downloaded from project home at kjeldahlnilsson.net.

Part 1, 2, 3, 4, 5, 6, 7, 8

In the last installment we set up a "test bench" web page. Now we are going to start implementing user input, as well as the most basic graphics primitives needed for our game.

Square one

Let's start with the simplest thing possible in the graphics department: paint a single tile of color in the browser window.

test.js:

    testDrawSingleSquare: function() {
        Graphics.clearGameContainer();
        var square = Graphics.createRectangleDiv("purple", 400, 400, 20, 20);
    },


We are going to use normal CSS and DOM scripting to accomplish this (there are other approaches to drawing custom graphics in the browser, but they have some drawbacks. We'll discuss this later.)

We add a div element to the body of the testbench html page, naming it "gameContainer". The graphics methods will add, remove and update child div elements in the gameContainer area.

graphics.js:

var Graphics = {
 
    getGameContainer: function() {
        return document.getElementById("gameContainer");
    },
 
    clearGameContainer: function() {
        var node = this.getGameContainer();
 
        if (node.hasChildNodes()) {
            while (node.childNodes.length >= 1) {
                node.removeChild(node.firstChild);
            }
        }
    },
 
    removeNodeFromGameContainer: function(node) {
        try {
            this.getGameContainer().removeChild(node);
        }
        catch(err) {
            // If no such node, fine.
        }
    },
 
    createRectangleDiv: function(bgcolor, x, y, width, height, zIndex) {
        var rect = document.createElement('div');
 
        rect.style.position = "absolute";
        rect.style.top = y + "px";
        rect.style.left = x + "px";
        rect.style.zIndex = "0";
        rect.style.height = height + "px";
        rect.style.width = width + "px";
        rect.style.backgroundColor = bgcolor;
 
        this.getGameContainer().appendChild(rect);
 
        return rect;
    }
};



Running the test yields this tremendously impressive result:

quicktetrissquaretest

Moving it

Moving along to user input, we need to make sure we pick up input events and map some of the keys to specific actions. Our first test simply sets up a visual echo of the key input.

test.js:

    testDetectKeys: function() {
        Graphics.clearGameContainer();
        Graphics.drawString("Press a key", 400, 400);
 
        // Register key mapping
        setKeyReaction(function(keyCode) {
            switch (keyCode) {
            case DIR_KEY_DOWN:
                Graphics.drawString("down key pressed", 400, 400);
                break;
            case DIR_KEY_UP:
                Graphics.drawString("up key pressed", 400, 400);
                break;
            case DIR_KEY_LEFT:
                Graphics.drawString("left key pressed", 400, 400);
                break;
            case DIR_KEY_RIGHT:
                Graphics.drawString("right key pressed", 400, 400);
                break;
            case SPACE_KEY:
                Graphics.drawString("space key pressed", 400, 400);
                break;
            default:
                Graphics.drawString('KeyCode ' + keyCode + ' not handled by test case.', 400, 400);
            }
        });
    },


For this to work we need some graphical output of strings, so we add this method to the Graphics object above:

graphics.js:

    drawnString: null,
    drawString: function(text, x, y) {
 
        if (this.drawnString) {
            this.removeNodeFromGameContainer(drawnString);
        }
 
        drawnString = document.createElement('div');
 
        drawnString.style.top = y + "px";
        drawnString.style.left = x + "px";
        var txtNode = document.createTextNode(text);
        drawnString.appendChild(txtNode);
 
        this.getGameContainer().appendChild(drawnString);
    }


Next up: a reliable cross-browser way of detecting key presses.

util.js:

function setKeyReaction(keyEventHandler) {
    document.onkeydown = function(e) {
        if (window.event) // IE
        {
            keyEventHandler(window.event.keyCode);
        }
        else if (e.which) // Netscape/Firefox/Opera
        {
            keyEventHandler(e.which);
        }
    };
}


The key input test now works, echoing a string representation of the pressed keys in the test bench. Now let's try using direction keys to move a square around the screen.

test.js:

    testMoveSquare: function() {
        Graphics.clearGameContainer();
 
        var dronePos = {
            x: 400,
            y: 400
        };
 
        function moveDrone(dx, dy) {
            dronePos.x += dx;
            dronePos.y += dy;
 
            drone.style.left = dronePos.x;
            drone.style.top = dronePos.y;
        }
 
        // Create the square graphic
        var drone = Graphics.createRectangleDiv("green", dronePos.x, dronePos.y, 20, 20);
 
        // Register key mapping
        setKeyReaction(function(keyCode) {
            switch (keyCode) {
            case DIR_KEY_DOWN:
                moveDrone(0, 5);
                break;
            case DIR_KEY_UP:
                moveDrone(0, -5);
                break;
            case DIR_KEY_LEFT:
                moveDrone( - 5, 0);
                break;
            case DIR_KEY_RIGHT:
                moveDrone(5, 0);
                break;
            default:
                //KeyCode not handled by test case
                Graphics.drawString('Use direction keys to move', 400, 400);
            }
        });
    }


Set the stage

We have a very simple tetris tile to play with now. The next ingredient is the playing field.

test.js:

    testDrawPlayingField: function() {
        Graphics.clearGameContainer();
        Field.init("magenta", "blue", 400, 100); // Set up field state
        Field.tileOn(0, 0);
        Field.tileOn(1, 1);
    }


We want a two dimensional grid of (sometimes visible) colored tiles. Now we start to enjoy those augmentations we did to Array earlier on.

field.js:

var Field = {
 
    // no of tiles
    WIDTH: 12,
    // no of tiles
    HEIGHT: 20,
 
    State: {
        gridState: null,
        gridTiles: null,
        gridBackground: null,
        // Absolute px position of field left border in window
        posX: 0,
        // Absolute px position of field top border in window
        posY: 0
    },
 
    init: function(fieldColor, backgroundColor, posX, posY) {
        this.State.posX = posX;
        this.State.posY = posY;
        this.State.gridBackground = Graphics.createFieldBackground(fieldcolor, this.State.posX, this.State.posY, (Piece.State.tileWidth * this.WIDTH), (Piece.State.tileHeight * this.HEIGHT));
        this.State.gridState = get2dArray(this.WIDTH, this.HEIGHT, 0);
        this.State.gridTiles = this.createHiddenTileArray(this.State.posX, this.State.posY, this.WIDTH, this.HEIGHT, backgroundColor, Piece.State.tileWidth, Piece.State.tileHeight);
    },
 
    createHiddenTileArray: function(posX, posY, matrixWidth, matrixHeight, color, tileWidth, tileHeight) {
        var matrix = get2dArray(matrixWidth, matrixHeight, 0);
        return matrix.map(function(element, x, y) {
            element = Graphics.createRectangleDiv(color, posX + (tileWidth * x), posY + (tileHeight * y), tileWidth, tileHeight);
            element.style.visibility = "hidden";
            return element;
        });
    },
 
    tileOn: function(x, y) {
        if (posWithinField(x, y)) {
            this.State.gridState[x][y] = 1;
            this.State.gridTiles[x][y].style.visibility = "visible";
        }
    },
 
    tileOff: function(x, y) {
        if (posWithinField(x, y)) {
            this.State.gridState[x][y] = 0;
            this.State.gridTiles[x][y].style.visibility = "hidden";
        }
    },
 
    isTileOn: function(x, y) {
        if (posWithinField(x, y)) {
            return (this.State.gridState[x][y]);
        }
    },
 
    posWithinField: function(x, y) {
        return ((x >= 0) && (y>= 0) &&
           (x <= Piece.State.tileWidth * this.WIDTH) &&
           (y <= Piece.State.tileHeight * this.HEIGHT))
    }
};


Voilà:

quicktetrisfieldtest

In our next installment we'll set up interaction between moving pieces and the tetris board.

Javascript Tetris Pt 3: Infrastructure

28 January, 2009 (21:18) | Gaming, Javascript, Quicktetris, Software development, Tools | 8 comments

Full source code can be downloaded from project home at kjeldahlnilsson.net.

Part 1, 2, 3, 4, 5, 6, 7, 8

We'll start by creating some basic infrastructure - just enough to give us a good running start. We need the bare minimum only: personally, I like to evolve my projects to actual needs as I go along. I don't want to invest huge amounts of time in build scripts, test setups, frameworks and support code before I know what I actually need.

Note: The industrious reader may wish to follow along and reimplement the game laid out in this article. Be advised that the source code included in this article is incomplete, and that code snippets sometimes refers to code which is defined further ahead - refer to the full source code if you want the whole picture. I will reference the files containing the code as we move forward.

Unit tests

We are going to write at least some unit tests as we go along, so we need support for writing and running them in our environment. Now, of course, there are several popular unit test frameworks to choose from... but since the goal of our tiny project is to learn Javascript, we'll simply roll our own tiny framework.

We don't need much - some way to assert that tests fail or succeed, some way of defining test cases, and a function to launch and run all the tests.

util.js:

// Assert methods needed by test framework
function assertTrue(boolean, errorMsg) {
    if (boolean === false) {
        throw (errorMsg);
    }
    return;
}
 
function assertFalse(boolean, errorMsg) {
    if (boolean === true) {
        throw (errorMsg);
    }
    return;
}


test.js:

var Test = {
 
    runSuite: function() {
        // Call all methods/testcases in suite
        for (var testFunc in this.Suite) {
            if (this.Suite.hasOwnProperty(testFunc)) { // Don't call any inherited methods
                try {
                    this.Suite[testFunc]();
                }
                catch(err) {
                    alert(testFunc + "() failed: " + err);
                    return;
                }
            }
        }
 
        Graphics.drawString("--All tests in suite passed--", 400, 400);
    },
 
    Suite: {
 
        // Add test cases here
        testAsserts: function() {
            assertTrue(2 === 2, "This should never fail");
            assertFalse(2 === 3, "This should always fail");
        },
 
};


Finally we need some sort of testrunner application. I like having a "test bench" when I develop low level graphical functionality; a context for manually running and observing isolated visual regression tests. We'll create a testbench web page, with a separate button for running our test suite.

test.html:

 
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>QuickTetris test bench</title>
<!-- Load external script -->
<script src="lib/jQuery/jquery-1.2.6.js"></script>
<script src="lib/jQuery/jquery-ui-1.6rc4.js"></script>
<script src="lib/soundmanager/script/soundmanager2.js"></script>
<script src="src/graphics.js"></script>
<script src="src/sound.js"></script>
<script src="src/piece.js"></script>
<script src="src/field.js"></script>
<script src="src/test.js"></script>
<script src="src/util.js"></script>
<script src="src/main.js"></script> 
 
<script>
  window.onload = function() {
    document.getElementById("test0").onclick = function(){ Test.runSuite(); };
    document.getElementById("test1").onclick = function(){ Test.testDrawSingleSquare(); };
    document.getElementById("test2").onclick = function(){ Test.testDetectKeys(); };
    document.getElementById("test3").onclick = function(){ Test.testMoveSquare(); };    
    document.getElementById("test4").onclick = function(){ Test.testDrawPlayingField(); };
    document.getElementById("test5").onclick = function(){ Test.testPieceCollision(); };
    document.getElementById("test6").onclick = function(){ Test.testPlaySound(); };
    document.getElementById("test7").onclick = function(){ Test.testPlaySoundLooped(); };
    document.getElementById("test8").onclick = function(){ Test.testPlayMusic(); };
    document.getElementById("test9").onclick = function(){ Test.testExplodeAnimation(); };
  };
</script>
</head>
<body>
<h3>Programmatic tests</h3>
<input id="test0" type="submit" value="Run test suite"><br/>
<h3>Visual tests</h3>
<input id="test1" type="submit" value="Draw single square"><br/>
<input id="test2" type="submit" value="Detect keyboard input"><br/>
<input id="test3" type="submit" value="Move square based on input"><br/>
<input id="test4" type="submit" value="Draw playing field"><br/>
<input id="test5" type="submit" value="Piece collision"><br/>
<input id="test6" type="submit" value="Play sound"><br/>
<input id="test7" type="submit" value="Play looped sound"><br/>
<input id="test8" type="submit" value="Play music"><br/>
<input id="test9" type="submit" value="Run explosion animation"><br/>
<div id="gameContainer">
</div>
 
</body>
</html>
 


You can run it yourself here. Clicking the top button calls Test.runTestSuite():

quicktetristestbench

I debugged the project using Apache on my own machine. Apache comes preinstalled in recent versions of Mac OS X, you simply need to enable it. Windows users need to download and run the binary installer. After starting Apache, simply dump the project in Apache's /htdocs folder and point your browser to http://localhost/RELATIVE_PROJECT_PATH.

Abstract data types, syntactic sugar

Tetris is basically all about matrices - a grid of tiles where elements appear, move around, and disappear. We are going to store and manipulate a bunch of game state using two dimensional arrays. Javascript provides bare bones support by letting us define arrays of arrays, but we need a little more syntactic sugar for all the grid hopping we're going to do.

I personally really like Ruby's Enumerable idiom, so we want to wire each(), map() etc into the Javascript Array object. We are, of course, not the first people to think of this; the Prototype framework could supply much of this functionality instantly. But again: the object here is to learn the language, so we'll write it ourselves.

The following tests articulate what we want from the Array object:

test.js:

        testArrDimensions: function() {
            var width = 3;
            var height = 2;
            var initValue = "x";
            var arr = get2dArray(width, height, initValue);
 
            assertTrue(arr.getWidth() === width, "Width of array not expected length");
            assertTrue(arr.getHeight() === height, "Height of array not expected length");
 
            for (var x in arr) {
                if (arr.hasOwnProperty(x)) { // Don't call any inherited methods
                    assertTrue(arr[x].length === height, "Height of array not expected length");
                }
            }
        },
 
        testArrEach: function() {
            var arr = [2, 4, 5, 2];
            var length = arr.length;
 
            var elementsVisited = 0;
            arr.each(function(element) {
                assertTrue(element !== null, "Expected all elements to be non-null");
                elementsVisited++;
            });
 
            assertTrue(elementsVisited === length, "Didn't visit " + length + " elements as expected");
        },
 
        testArrEach2d: function() {
            var width = 3;
            var height = 2;
            var initValue = "x";
            var arr = get2dArray(width, height, initValue);
 
            var elementsVisited = 0;
            arr.each(function(element) {
                assertTrue(element === initValue, "Not all slots in array was set to " + initValue);
                elementsVisited++;
            });
 
            assertTrue(elementsVisited === (width * height), "Didn't visit " + (width * height) + " elements as expected");
        },
 
        testArrEachRow: function() {
            var width = 3;
            var height = 4;
            var initValue = "x";
            var arr = get2dArray(width, height, initValue);
 
            var rowsVisited = 0;
            arr.eachRowWithIndex(function(row) {
                assertTrue(row.length === width, "Expected row to be " + width + " elements long");
                rowsVisited++;
            });
 
            assertTrue(rowsVisited === height, "Didn't get " + height + " rows as expected");
        },
 
        testArrMap: function() {
            var width = 2;
            var height = 2;
            var initValue = "2";
            var arr = get2dArray(width, height, initValue);
 
            var mappedArr = arr.map(function(element) {
                return element * 2
            });
 
            var elementsVisited = 0
            mappedArr.each(function(element) {
                assertTrue(element === initValue * 2, "Not all slots in mapped array were transformed to new value");
                elementsVisited++;
            });
 
            assertTrue(elementsVisited === width * height, "Mapped array not same size as original array");
        }
 
    }


We need to augment the Javascript Array object to support this functionality.

util.js:

// Returns two dimensional array, every element initiated to given value
function get2dArray(width, height, initValue) {
    var arr2d = [];
 
    for (var x = 0; x < width; x++) { // For each row
        arr2d[x] = [];
    }
 
    for (x = 0; x < width; x++) {
        for (var y = 0; y < height; y++) {
            arr2d[x][y] = initValue;
        }
    }
 
    return arr2d;
}
 
// D. Crockford idiom for function mixin
Function.prototype.method = function(name, func) {
    this.prototype[name] = func;
    return this;
};
 
// Array mixins for 2d grid functionality
Array.method('getWidth',
function() {
    return this.length;
});
 
Array.method('getHeight',
function() {
    return this[0].length;
});
 
Array.method('isTwoDimensional',
function() {
    return (this[0].constructor == Array);
});
 
Array.method('each',
function(appliedFunction) {
    for (var x = 0; x < this.getWidth(); x++) {
        if (this.isTwoDimensional()) {
            for (var y = 0; y < this.getHeight(); y++) {
                appliedFunction(this[x][y]);
            }
        }
        else {
            appliedFunction(this[x]);
        }
    }
});
 
Array.method('eachWithIndexes',
function(appliedFunction) {
    for (var x = 0; x < this.getWidth(); x++) {
        if (this.isTwoDimensional()) {
 
            for (var y = 0; y < this.getHeight(); y++) {
                appliedFunction(this[x][y], x, y);
            }
        }
        else {
            appliedFunction(this[x], x);
        }
    }
});
 
Array.method('eachRowWithIndex',
function(appliedFunction) {
    for (rowCount = 0; rowCount < this.getHeight(); rowCount++) {
        var row = [];
        for (columnCount = 0; columnCount < this.getWidth(); columnCount++) {
            row[columnCount] = this[columnCount][rowCount];
        }
        appliedFunction(row, rowCount);
    }
});
 
Array.method('map',
function(appliedFunction) {
    var mappedArr = null;
    if (this.isTwoDimensional()) {
        mappedArr = get2dArray(this.getWidth(), this.getHeight(), null);
    }
    else {
        mappedArr = [];
    }
 
    for (var x = 0; x < this.getWidth(); x++) {
        if (this.isTwoDimensional()) {
            for (var y = 0; y < this.getHeight(); y++) {
                mappedArr[x][y] = appliedFunction(this[x][y], x, y);
            }
        }
        else {
            mappedArr[x] = appliedFunction(this[x]);
        }
    }
 
    return mappedArr;
});


A little later I found that Array.map() actually already exists in Javascript. It was, however, still a useful exercise to implement a variant of it myself. It's probably usually not a great idea to monkeypatch over existing core functionality, though. :)

Build environment

We want to set up some sort of automated code verification - especially important since this is a newbie project. Enter JsLint, the closest thing you get to compile-time error checking for Javascript. I chose to run it using the Rhino version (download here). This is our Rake task for running it:

Rakefile:

desc "Run JSLint audit on code and markup"
task :jslint do
 
  lintCommand = "java -classpath ./lib/jsLint/js.jar "+
                  "org.mozilla.javascript.tools.shell.Main ./lib/jsLint/jslint.js";
 
  # Check the .html files in root dir
  lintTargets = Dir.entries(".").reject! do |direntry|
	  if(direntry !~ /(\.html)\z/ ) then
		 true
	  end
  end  
 
  lintTargets.each do |filename|
    echo "Running JSLint on: "+filename
    puts %x{ #{lintCommand} #{filename} };
  end
 
  # Check the .js files in /src dir
  lintTargets = Dir.entries("./src").reject! do |direntry|
	  if(direntry !~ /(\.js)\z/ ) then
		 true
	  end
  end
 
  lintTargets.each do |filename|
    echo "Running JSLint on: "+filename
    puts %x{ #{lintCommand} ./src/#{filename} };
  end
 
  echo "Done running JSLint"
 
end
 
def echo(msg)
  puts("  <Rake build>: "+msg);
end
 


Running it, we see that we have some issues in the code we wrote above:

jslinterror

Easily fixed. Now we have a basic infrastructure in place. In the next part we start looking at graphics and user input.

Javascript Tetris Pt 2: Planning

28 January, 2009 (21:17) | Gaming, Javascript, Quicktetris, Software development | No comments

Part 1, 2, 3, 4, 5, 6, 7, 8

What sort of game should we build?

It has to be something simple, since this is just a learning exercise for me (as a relative Javascript newbie). The scope of the project should be small, preferrably under a week or two using free time in the evenings. This will limit the complexity of the game; we can't support too much input, ouput or complex game state.

We also don't have time to seriously ponder high level game design, so the most practical approach is to just clone an existing game template. Tetris is a nice candidate. Everyone knows how to play it. It has few and simple rules and a nicely static and rectangular playing field. Also, we can get away with very basic and functional presentation. You don't need fancy art direction and killer sound to play tetris.

5 minutes of whiteboarding yields a minimalist game design document:

quicktetrisdesigndoc

We'll dub the game Quicktetris. This reminds us to keep the project small, simple and easily finished. :) Another fifteen minutes spent mindmapping and we have a rough backlog of features that we need to implement:

quicktetrisprojectplanning

(This was the rough, preliminary plan. While I started out with the plan outlined in the mindmap above, the actual implementation work ended up a little different.)

That should be enough planning for our little project; time go get started!