Nordic Adventure and the Mediator Pattern

When I wrote Fusion, one of the mistakes that bogged me down was that all objects were on a single layer - the backgrounds, player, turrets, bullets, enemies and explosions all intermingled in the same space. This made it difficult to independently work on each section, since there was always concern that my work would impact another section.

With Nordic, cocos2d-javascript lets me work with layers and scenes in a very fluid way, and I've set up two basic layers so far - the player and the environment. As I built up the system, I began to experience a new problem - the two layers were inappropriately intimate. Take this code sample from an early build related to the collision detection:

// fetch a list of sandbags from the environment
sandbags = this.parent.parent.env.sandbags

// check each one to see if it blocks me
for (var i in sandbags) {
    if (geo.rectOverlapsRect(this.boundingBox, sandbags[i])) {
        // cannot move there
       return false
    }
}

// can move there
return true

The player object is checking to see if it can move into a new location. To do so, it directly looks at the list of sandbags in the environment and compares them to itself. As soon as I wrote this, the bad code smell popped up and I knew I had to change it. Unlike Fusion, where I was willing to write crap code just to get things done, I'm being more strict on myself with Nordic.

Enter the Mediator Pattern. In the book Design Patterns: Elements of Reusable Object Oriented Software, the mediator pattern is defined as:

Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.

This pattern is what I needed to keep the player separated from the environment while still allowing them to communicate. I began to rework the scene so that it acts as a mediator and moved the logic around until I reached this:

Player Layer

// ask the scene if i'm allowed to go to a certain spot
if (this.parent.playerAllowed(tempBox)) {
    var pos = util.copy(this.player.position)
    pos.x += vel.x * this.player.speed * dt
    pos.y += vel.y * this.player.speed * dt
    this.player.velocity = vel
    this.player.position = pos
    this.parent.updatePlayerPosition(this.player.boundingBox)
    return
}

The player layer now only asks its parent (the scene) if it is allowed to move into a new spot. If the scene returns true, it moves there. Otherwise, it does not.

Game Scene (Mediator)

playerAllowed : function(box) {
    // ask the environment for sandbags
    var sandbags = this.environment.getSandbags()
    for (var i in sandbags) {
        // compare sandbags to the position the player requested
        if (geo.rectOverlapsRect(sandbags[i], box)) {
            return false;
        }
    }
 
    return true
}

The scene, acting as a mediator, asks the environment for a list of sandbags. The scene then compares the location given to it by the player to the list of sandbags as reported by the environment. If any collide, the player is told it cannot move to that spot.

Environment Layer

getSandbags : function() {
    var sandbags = [];

    // go through all defined sandbags
    for (var i in this.sandbags) {
        var s = util.copy(this.sandbags[i])
          , pos = s.origin

        // adjust position to be relative to screen
        pos.x += this.position.x
        pos.y += this.position.y

        s.origin = pos

        sandbags.push(s)
    }

    return sandbags
}

The environment returns a list of sandbags when requested. The extra position calculations deal with screen scrolling, so the sandbags returned are positioned relative to the screen, rather than relative to the environment.

Furthermore, decoupling the concept of 'can the player walk here' into 'is this spot accessible to the ground' not only separates the player from the environment, it makes it reusable. This same function can be called upon by any node that is bound to ground movement - enemies, certain weapons/spells, even environment effects like water spilling out of a wall. 

I've never used the mediator pattern before, since it has less obvious applications for web development. But it saved me here, since the original path would require very tedious work to update the player or environment any time I changed how sandbags behave. And considering Nordic is still in alpha phases, those changes can be expected to come often.