Monday, August 24, 2015

Clean Up Your Code

I'm getting towards the end of my work on the Frog Soccer game for my 404 page, so I thought it was about time I cleaned up the code.

I'm (maybe unfortunately) one of those people who make something work first and make it pretty second. In the first cuts of my programs, the code can be very rough. Not optimal at all. Lots of cut-and-paste repetition. Bad stuff. If I don't end up abandoning the project, I usually take another look through it and make it better.

For Frog Soccer, I cleaned up a few things today.

  1. Needless repetition. Creation of both the home team frog and the opponent frog was 99% identical code. I made a createFrog function that replaced both chunks. Same thing for the goals.
  2. Global variables. Okay, so they're not exactly global but there were a number of variables declared outside of the game functions because they were referenced by multiple of them. In some cases, this was unnecessary. Whenever possible, I replaced the code which referenced the variable in global scope with more "correct" code. Consider the following example:
function update() {

    if (ball.body.velocity.x === 0 && ball.body.velocity.y === 0) {
        anim.stop();
    } else {
        var speed = Math.min(1, Math.max(Math.abs(ball.body.velocity.x),
              Math.abs(ball.body.velocity.y)) / 200) * 9;
        if (anim.isPlaying) {
            anim.speed = speed;
        } else {
            anim.play(speed, true);
        }
    }

    // ... other update stuff ...

}
Here, the variable anim was a global reference to the ball's single animation object. The ball variable is already global, so keeping the anim variable around seemed redundant.
function update() {

    if (ball.body.velocity.x === 0 && ball.body.velocity.y === 0) {
        ball.animations.stop();
    } else {
        var speed = Math.min(1, Math.max(Math.abs(ball.body.velocity.x),
                Math.abs(ball.body.velocity.y)) / 200) * 9;
        if (ball.animations.getAnimation("roll").isPlaying) {
            ball.animations.getAnimation("roll").speed = speed;
        } else {
            ball.animations.play("roll", speed, true);
        }
    }

    // ... other update stuff ...

}
The code above is better because it accesses the ball's roll animation via the provided Phaser animation functions. It's also more clear when looking at the code that this section is dealing with ball animation. Who was to know what anim referred to before? 
  1. Timers. Ugh. This one was terrible. When the ball was tossed back in from being out-of-bounds, I had a timer to determine when "regular" play should start again. I had it figured out how long it would take for the ball to re-enter the play area based on its location and speed - and I hard-coded a timer based on that. Well, that was ugly and wrong. When playing on my phone, which ran just a little bit slower, the ball wasn't back in-bounds before play restarted, which immediately resulted in the ball being declared out-of-bounds again. Endless toss-ins ensued. And I knew when I did it originally that it was a hack, but I did it anyway. Shame on me. But I fixed it now, along with the next issue...
  2. Proper encapsulation. Sure, this is functional programming, not OO. But since the ball object couldn't make the out-of-bounds determination itself, fixing the issue of the toss-in timer would have required more duplication of code. Instead, I took the bounds check logic and put it in the ball itself. Now the update function can ask the ball if it is in or out of bounds and, depending on which play mode we're in, decide what to do.
ball.inPlay = function() {
    return (this.position.x > 40 && this.position.y > 30
            && this.position.x < 1640 && this.position.y < 800);
};

function update() {

    // ... other update stuff ...

    if (mode === PLAY_MODE && !ball.inPlay()) {

        // changes mode to TOSS_MODE and then to READY_MODE
        tossBall(ball.position.x, ball.position.y);

    } else if (mode === READY_MODE && ball.inPlay()) {

        mode = PLAY_MODE;

    }

}

I feel much better about the Frog Soccer code now, and anyone who happens to examine it on GitHub might think I'm a little less of a hack. Just a little less. I'm better than the frogs in today's comic.

Amphibian.com comic for 24 August 2015