This page is intended for developers trying to get acquainted with the aussenposten source code.

Table of contents

Technologies used

The game is written in JavaScript as CommonJs modules. The webkit (node-webkit) JavaScript runtime is used to provide a native executable appearance. The GUI is built with HTML & CSS and the game graphics are rendered on a HTML5 canvas element.

Dev setup

For development you should serve the aussenposten source directory via http and open index.html in the latest chrome browser. This gives you chrome’s development tools which you can’t easily and conviniently use in the executable (node-webkit).

File and directory overview

Important files and directories:

Code overview: Simulation/model logic

For programmers interested in enhancing or fixing aussenposten.

Unit

A Unit is a state machine moving through the defined dayStates for a particular unit type. The different dayStates (eat, relax, work,…) all have properties attached to them (see JSON below). The state’s properties describe in which buildings it can be accomplished and for how long it is active. In detail, the properties are:

The complete definition of all dayStates for the basic unit type “helot” is listed below:

// `dayState`s of a unit of type "helot"
Unit.STATES.helot = {
   eat: {
      resolveTime: 30 * 1000,
      nextState: 'relax',
      buildings: ['cantine']
   },
   relax: {
      resolveTime: 30 * 1000,
      nextState: 'work',
      buildings: ['living'],
   },
   work: {
      resolveTime: 90 * 1000,
      nextState: 'eat',
      buildings: ['farm', 'mine', 'maintenance', 'storage', 'oremine', 'teleport']
   },
   idle: {
      resolveTime: 10 * 1000,
      nextState: 'work',
      buildings: ['farmarea', 'mineshaft', 'oxygen', 'solar', 'teleport']
   },
   gounderground: {
      resolveTime: 500 * 1000,
      nextState: null,
      buildings: ['elevator']
   }
}

The unit’s state machine only deals with the problem of moving the unit from building to building, and sometimes keeping the unit at the target building for the given amout time (for work or other interactions with the target building).

A unit’s core logic related to dayState is thus:

  1. Move the unit to the next, appropriate building according to the current dayState definition
  2. Stay in this building until the resolveTime is used up
  3. Select and set the next dayState

(add 1): more about how to select the “next and appropriate building” can be found in “Selecting the appropriate building”.)

The above logic is implemented by the method Unit.prototype.updateDayState(). I simplified the code of this method and removed some special cases to make it more readable. It then looks like this:

// the method is passed the amount of time passed since the last invocation has `msTickDuration`
Unit.prototype.updateDayState = function(msTickDuration) {
   // do we have to calculate the route for the current dayState?
   if (this.dayState.route === null) {
      var tilePosition = ...
      var routeInfo = this.dome.getDayStateRoute(tilePosition, this.dayState, this);
      if (routeInfo) {
         this.dayState.route = routeInfo.route;
         this.dayState.building = routeInfo.building;
         this.dayState.building.workerReserve(this);
      } else {
         // -- removed for clarity
         // unit gets idle behaviour
   }

   // wait for resolve time
   this.dayState.resolveTimeRemaining -= msTickDuration;

  // still moving towards building?
   if (this.dayState.route && this.dayState.routeFinished == false) {
      // note that `updatePathfinding()` moves the unit
      this.dayState.routeFinished = this.updatePathfinding(msDuration, 'dayState');

      // we arrived!
      if (this.dayState.routeFinished == true) {
         this.dayState.building.workerEnter(this);
      }
   // we are at the building and the resolve time is used up
   } else if (this.dayState.routeFinished == true && this.dayState.resolveTimeRemaining <= 0)) {
      this.dayState.building.workerExit(this);
      // move on
      this.setDayState(this.dayState.nextState);
   }
}

Remember, how the unit itself (in the above method) mostly deals with route finding to the next building and staying there for some time.

When the unit is in a building, the building takes over. To signal to the building, that it can control the unit, various methods are called on the building to signal changes: when a unit has selected a route to the building and is moving towards it (Building.workerReserve()), when the unit has arrived at the building (Building.workerEnter()) and when he leaves it (Building.workerExit()).

Building

Buildings are active objects themselves.

They have an update method in which they can change several things:

There are a couple of update methods for the different building types. All these update methods start with ´step´ (e.g., stepRelax, stepGather, stepEat, etc.). We will take a closer look at the ´stepProduce()´ method use by production buildings. It does several things:

I extraced the above steps by simplifying the stepProduce() method a bit:

Building.prototype.stepProduce = function() {

   // calculate amount output depending on productivity, numworkers, etc.
   var numWorkers = this.workers.length;
   var productivity = ...
   var out = ...
   this.outputStorage += out;

   this.workers.forEach(function(w) {
      // degrades the fullnes and happiness of the workers.
      w.fullness += this.typeInfo.workerFullness;
      w.happiness += this.typeInfo.workerHappiness;
   }, this)

   // apply damage to the bulding if we produced something
   if (out > 0 && this.typeInfo.updateDamage) {
      this.damage += this.typeInfo.updateDamage * (this.workers.length /this.typeInfo.maxWorkers);
   }
   return {
      resource: this.typeInfo.output.resource,
      amount: out
   };
}

Complex buildings. Moving units while they are working in a building.

The above “production” building type is very simple because the units working for it do not have to leave the building to get work done. Some building types, like the “repair station” or “storage”, require that the workers move between other buildings to get their work done. For example, a storage worker has to fetch resources from a production facility and bring it back to the storage building.

When a unit needs to move while working for a building, the building can set a jobState on the unit. A jobState is very similar to a dayState in that it only deals with moving the unit to a different building and sometimes staying there for a while.

A unit’s jobState can only bet set by the building it is currently in.

For example, the storage building sets the jobState “fetch” on all its workers currently without a jobState. The “fetch” state moves the unit to an appropriate building: a production building which has something in its output storage. The unit moves to this target building and signals it missionEnter() on arrival, and later missionExit(). The missionExit() of the target building then does two things: it hands out the resource the unit came for, and it sets a new jobState “store”, which causes the unit to move back to the storage building.

I extracted the code which deals with a storage workers leaving a target building from the rather long missionExit() method:

Building.prototype.missionExit = function(unit) {

   // unit was here to fetch something? send it back home to store
   // whatever it grabbed.
   if (unit.jobState && unit.jobState.type === 'fetch') {
      // fetch specific type from storage or just anything from outputStorage?
      var res = unit.jobState.storageType;
      var amount = Math.min(15, this.storage[res]);
      this.storage[res] -= amount;
      unit.setJobState('store');
   } else {
      unit.jobState = null;
   }
}

Moving to the “appropriate” building

Which building a unit should target for the current job is determined by two functions, for dayState and jobState:

//@@@ explain

View logic

The view logic is contained in two modules:

The views are stored in a simple tree which is used for draw caching. Views like BuildingView or UnitView have a reference to the model they represent (Building and Unit respectively in this example) but views don’t query the model every time to render themselves but will often output a cached canvas.

The tree of views is composed as follows:

When a model appears or disappears (e.g. building constructed or unit dies) an event is sent to the PlanetView or DomeView which creates the new view and attaches the model.

Other components