Functional Roles



  • Hi, community!

    I’m totally new to Screeps and I have quite a lot of fun.

    I just wanted to share my approach to creep roles implementation in functional style. The idea is that roles could be implemented as functions, returning boolean value of whether this role is being fulfilled. This way we can define prioritized chain of roles for each creep. For example, a creep with WORK and CARRY can fulfill the role “repair” if it sees something that needs repairing, and fall back to “harvest” role if there is nothing to repair.
    This chain could be gracefully implemented using modern JS features.

    Here is an example of implementation:

    var roles = {};
    
    roles.repair = function() {
        var target = this.pos.findClosestByRange(FIND_MY_STRUCTURES, { // NOTE: FIND_MY_STRUCTURES does not return constructed walls
            filter: (obj) => obj.hits < obj.hitsMax
        });
    
        if (target) {
            if (this.repair(target) == OK) {
                return true;
            }
            if (this.carry.energy < this.carryCapacity) {
                // Go take some energy from base
                var base = Game.spawns.Spawn1;
                if (base.transferEnergy(this) == OK || this.moveTo(base) == OK) {
                    return true;
                }
            }
            if (this.moveTo(target) == OK) {
                return true;
            }
        }
    
        return false; // nothing to repair, role is not fulfilled
    };
    
    roles.harvest = function() {
        // Drop all energy to base
        if (this.carry.energy == this.carryCapacity) {
            var base = Game.spawns.Spawn1;
            if (this.transferEnergy(base) == OK || this.moveTo(base) == OK) {
                return true;
            }
        }
    
        var source = this.pos.findClosestByPath(FIND_SOURCES);
    
        if (source) {
            if (this.harvest(source) == OK || this.moveTo(source) == OK) {
                return true;
            }
        }
    
        return false; // could not approach or harvest from source, role is not fulfilled
    };
    
    module.exports.loop = function() {
    
        // ... Create creep with roles like this. This creep will try to fulfill "repair" role first. If it can't it will do "harvest".
        //  `roles` are best stored in Memory as an array of strings.
        Game.spawns.Spawn1.createCreep([MOVE, WORK, CARRY], 'Worker', { roles: [ 'repair', 'harvest' ] });
    
        // Execute creep roles
        Object.keys(Game.creeps).forEach((creepId) => {
            var creep = Game.creeps[creepId];
            creep.memory.roles.some((role) => roles[role].call(creep)); // will stop on first function returning true
        });
    };
    

    With this approach, creep's behavior can be composed in many different ways. It can also help to build universal creeps that change their roles according to some conditions. Another example is harvesters, that switch to attacking if there are hostile creeps nearby. Or harvesters, that pickup dropped energy if there is any and then resume the harvesting.

    I will gladly welcome comments. And please share if this trick was helpful to you.



  • Oh my. Code format is totally screwed up. But I can't find the Edit Post feature :(.



  • My roles are similar but a bit more complicated. It is an object with three methods:

    fits(creep): boolean // can role be done by this creep?
    run(creep): boolean // do role!
    finished(creep): boolean // is it fulfilled?
    

    And now I consider the next step — add "score" function which tells how good is this particular creep to this role. And assign not just high priority role to free creep, but the role which fits this creep best (with regard of role priority).



  • @xoposhiy: You mean you don't specify the roles when creating creeps, but assign them dynamicaly based on their capabilities? That makes it easier, i like it. Although, this way, you could lose some strategic control.



  • @Blade exaclty. Actually the only strategic control is the priority of roles 🙂



  • I have a different approach. I have my creeps highly diversified, because this way, they are more efficient because they don't need to be allrounders. Then I have classlike objects, which export the body definitions, the priority of the creep (for spawning and executional purposes) and a list of tasks they can perform (the task they performed in the last round is actually stored in their memory).



  • The screeps website doesn't work properly without javascrip, who would have thought? 😄

     

    I have a similar approach. Every time the worker is full of energy he ckecks with the local spawns memory what the max builders, repairer and so on are and then chooses his role accordingly. My creeps work with roles and jobs. The roles are worker, goon and longwayharvesters and such and the jobs are builder, harvester, upgrader, repairer.

    The job memory gets cleared when they switch from getting energy to work and getting reasigned. I think I lose one tick every time that happens though. 

    I have no idea how efficient my code is, the last time I coded anything really was in 2004 😄

     

    module.exports = {
    run: function(creep){

    grid = creep.room.name;
    var gridcontrol = creep.pos.findClosestByRange(FIND_MY_STRUCTURES, {
    filter: (s) => (s.structureType == STRUCTURE_SPAWN)});
    var miniharvester = gridcontrol.memory.harvester;
    var maxrepair = gridcontrol.memory.repair;
    var minibuilder = gridcontrol.memory.builder;
    var maxbuilder = gridcontrol.memory.maxbuilder;
    var numberworkers = creep.room.find(FIND_MY_CREEPS,
    { filter: (s) => (s.memory.role == 'worker')}).length;
    var energyneed = creep.pos.findClosestByPath(FIND_MY_STRUCTURES, {
    filter: (s) => (s.structureType == STRUCTURE_SPAWN || s.structureType == STRUCTURE_EXTENSION || s.structureType == STRUCTURE_TOWER) && s.energy < s.energyCapacity
    });
    //console.log(miniharvester);
    gridcontrol.memory.workeractive = numberworkers;
    //console.log(gridcontrol.memory.workeractive);
    var numberofharvesters = creep.room.find(FIND_MY_CREEPS, { filter: (s) => (s.memory.job == 'harvester')}).length;
    var numberofbuilders = creep.room.find(FIND_MY_CREEPS, { filter: (s) => (s.memory.job == 'builder')}).length;
    var numberofupgraders = creep.room.find(FIND_MY_CREEPS, { filter: (s) => (s.memory.job == 'upgrader')}).length;
    var numberofrepair = creep.room.find(FIND_MY_CREEPS, { filter: (s) => (s.memory.job == 'repair')}).length;
    var constructionSite = creep.pos.findClosestByPath(FIND_CONSTRUCTION_SITES);
    var repair = creep.pos.findClosestByPath(FIND_STRUCTURES, {
    filter: (s) => s.hits < s.hitsMax && s.structureType != STRUCTURE_WALL
    });

    //For when I need feedback, I should write a function for that at some point -.-
    //console.log(" ");
    //console.log("harv: " + numberofharvesters);
    //console.log("build: " +numberofbuilders);
    //console.log("repair: " +numberofrepair);
    //console.log("upgrader: " + numberofupgraders);
    //console.log(energyneed); 

    if ((creep.memory.job == false || creep.memory.job == undefined) == true){
    if (numberofharvesters < miniharvester == true && energyneed != undefined){
    creep.memory.job = 'harvester';
    }
    else if ((numberofrepair < gridcontrol.memory.maxrepair == true) && repair > 0){
    creep.memory.job = 'repair';
    }
    else if (constructionSite != undefined && numberofbuilders < maxbuilder){
    creep.memory.job = 'builder';
    }
    else {
    creep.memory.job = 'upgrader';
    }
    }

    else if (creep.memory.job != false) {
    if(creep.memory.job == 'harvester' && energyneed == undefined){
    creep.memory.job = undefined;
    }
    if(creep.memory.job == 'builder' && constructionSite == undefined){
    creep.memory.job = undefined;
    }
    if(creep.memory.job == 'harvester')
    {jobharvester.run(creep);}

    else if (creep.memory.job == 'repair')
    {jobrepair.run(creep);}

    else if (creep.memory.job == 'builder')
    {jobbuilder.run(creep);}

    else if (creep.memory.job == 'upgrader')
    {jobupgrader.run(creep);}
    }
    }
    };