anatomy of a tick

bot class

export function loop(): void {
  getBot().update();
}

this is what my main loop looks like for each arena. the bot gets set up outside of the loop.

the bot instance is responsible for managing all modules in the order we need them to run in. it is basically the abstract class all arena specific bots inherit from:

export abstract class ArenaBot extends Actor {
  public showVisuals = true;

  // snip: some setup

  public visualize(): void {
    renderGameObjectVisuals();
    renderStatUI();
  }

  public abstract onFirstTick(): void;

  public abstract init(): void;

  public update(): void {
    // snip: the things described below
  }
}

modules in general are fairly static. so for example the spawn manager module looks like this:

// snip: imports

// snip: all the caches etc

export class Spawns {
  public static request(request: SpawnRequest, capacity = getEnergyCapacityAvailable()): void {
    // snip
  }

  public static createRequestForRole(playId: string, role: CreepRole, priority: number): SpawnRequest {
    return {
      playId,
      setup: role.getBody(),
      props: role.getDefaultProps(),
      priority
    };
  }

  public static init(): void {
    // snip: reset caches, update utilization
  }

  private static spawnCreep(entry: SpawnParams): number {
    // snip: error handling, the actual call, assigning properties to the new creep
  }

  public static update(): void {
    // snip: spawn next creep
  }
}

and in the bots loop it just gets called by using Spawns.init() (close to the beginning of the loop) and Spawns.update() later on.

intel

information is key! i use a whole bunch of different intel modules. in screeps arena we get perfect information about the game state (well, almost. we need something like getEventLog() from world to answer some of the “how” questions. actually, time to check the forums for this feature request…), so it only makes sense to try and interpret and organize that info in a way that helps the strategy engine make good decisions.

intel ranges from terrain analysis over simple things like current creep and body part counts to more specific things like combat predictions.

strategy

next up, the current strategy engine matches gathered intel with known strategies. a very simple example of strategy detection could be: in basic spawn and swamp, if the hostile spawn is unreachable due to being surrounded by walls and ramparts, we might want to start transitioning to a bunker busting strategy.

so first the strategy engine tries to detect/predict what the opponent is doing, it then picks a strategy from our playbook (which hopefully counters the hostile strategy). lastly, if our strategy changed it handles the transition of the different plays.

plays then get to influence production, for example by requesting creeps. next up each play gets assigned the creeps it requests as best as we can match these requirements to our current set of creeps.

execution

now that we know what we want to do, we run the code that tells our creeps how to do it. each play now tells its respective squad to act. the squad logic results in actual intents! so if we got here, we can assume that finally things will be actually happening in game.

that is also about it for this “phase”. squad implementation dictates how a creep that got assigned to that squad acts. for example a SkirmishSquad that gets a ranged creep assigned might try to micro that creep by kiting around and hitting things with RMA or ranged attacks. each play can have either zero or one squad.

production

here we spawn stuff or build stuff. yup, not too interesting.

i think the way my building planning is abstracted (basically.. not at all) might still need some work. i am thinking about integrating something like an “architect” into the currently running strategy. it really isnt too much of a priority right now though. currently some of the construction sites are even being placed by the creep building and using the structure, which might be just as good of a way to organize some of the structure related things: the logic that places the csite and the logic that handles the csite does not have to live in two different places like this, which should increase future readability.

movement resolver

at the end of the tick the resolver checks each move intent again. since Creep.move() pretty much always returns OK, we can eliminate a whole bunch of cases where we can already predict conflicts. most common problem that gets solved here: multiple creeps might want to be in the same position.

if a creep currently follows a path, it is here where we do stuck detection and invalidation.

visualization

we did all these fancy things. logs can get messy fairly quickly, so lets draw some of the info we have about the fancy things going on on the screen directly. 😊