Quick answer
This final beginner script keeps three fixed names: Harvester1 fills Spawn and Extensions, Upgrader1 spends Energy on the Controller, and Builder1 builds, repairs, then upgrades. The main loop also tries to spawn any missing Creep in that order. It checks objects before using them, stores simple state in Creep Memory, and logs unexpected return codes.
It is a learning script, not a mature room framework.
What the code manages
| Name | Primary work | State field |
|---|---|---|
Harvester1 | Harvest and deliver to Spawn or Extension. | memory.delivering |
Upgrader1 | Harvest and upgrade the Controller. | memory.upgrading |
Builder1 | Harvest, build, repair, then upgrade. | memory.working |
Before replacing your current code
- Save a copy of the current working version.
- Replace
Spawn1with the exact Spawn name. - Confirm the room can normally reach at least 200 spawning Energy.
- Understand that existing same-name Creeps will be reused.
- Use this only for the single-room fixed-name exercise.
- Do not mark Console or live testing as complete until you observe it in your own room.
Complete beginner room code
State impact: This code can spawn Creeps, move them, harvest and transfer Energy, upgrade the Controller, build sites, repair Structures, and write three boolean Memory fields.
const SPAWN_NAME = 'Spawn1';
const BODY = [WORK, CARRY, MOVE];
function moveToTarget(creep, target, label) {
const moveResult = creep.moveTo(target, {
reusePath: 5
});
if (moveResult !== OK &&
moveResult !== ERR_TIRED) {
console.log(
'[' +
creep.name +
'] moveTo(' +
label +
') returned ' +
moveResult +
'.'
);
}
return moveResult;
}
function updateWorkState(creep, field) {
const usedEnergy =
creep.store.getUsedCapacity(RESOURCE_ENERGY) || 0;
const freeEnergy =
creep.store.getFreeCapacity(RESOURCE_ENERGY) || 0;
if (usedEnergy === 0) {
creep.memory[field] = false;
}
if (freeEnergy === 0) {
creep.memory[field] = true;
}
return creep.memory[field] === true;
}
function runHarvest(creep, source) {
const harvestResult = creep.harvest(source);
if (harvestResult === ERR_NOT_IN_RANGE) {
moveToTarget(creep, source, 'Source');
} else if (harvestResult !== OK &&
harvestResult !== ERR_NOT_ENOUGH_RESOURCES) {
console.log(
'[' +
creep.name +
'] harvest() returned ' +
harvestResult +
'.'
);
}
return harvestResult;
}
function getFirstSource(creep) {
const source = creep.room.find(FIND_SOURCES)[0];
if (!source) {
console.log(
'[' +
creep.name +
'] no visible Source was found.'
);
return null;
}
return source;
}
function runHarvester(creep) {
const source = getFirstSource(creep);
if (!source) {
return;
}
const delivering =
updateWorkState(creep, 'delivering');
if (!delivering) {
runHarvest(creep, source);
return;
}
const targets =
creep.room.find(FIND_MY_STRUCTURES, {
filter: function (structure) {
const acceptsEnergy =
structure.structureType === STRUCTURE_SPAWN ||
structure.structureType === STRUCTURE_EXTENSION;
return acceptsEnergy &&
structure.store.getFreeCapacity(
RESOURCE_ENERGY
) > 0;
}
});
const target = targets[0];
if (!target) {
creep.say('waiting');
return;
}
const transferResult =
creep.transfer(target, RESOURCE_ENERGY);
if (transferResult === ERR_NOT_IN_RANGE) {
moveToTarget(creep, target, 'Energy target');
} else if (transferResult !== OK &&
transferResult !== ERR_FULL) {
console.log(
'[' +
creep.name +
'] transfer() returned ' +
transferResult +
'.'
);
}
}
function runUpgrader(creep) {
const source = getFirstSource(creep);
const controller = creep.room.controller;
if (!source) {
return;
}
if (!controller) {
console.log(
'[' +
creep.name +
'] no Controller was found.'
);
return;
}
const upgrading =
updateWorkState(creep, 'upgrading');
if (!upgrading) {
runHarvest(creep, source);
return;
}
const upgradeResult =
creep.upgradeController(controller);
if (upgradeResult === ERR_NOT_IN_RANGE) {
moveToTarget(creep, controller, 'Controller');
} else if (upgradeResult !== OK) {
console.log(
'[' +
creep.name +
'] upgradeController() returned ' +
upgradeResult +
'.'
);
}
}
function findRepairTarget(creep) {
return creep.room.find(FIND_STRUCTURES, {
filter: function (structure) {
const repairable =
structure.my === true ||
structure.structureType === STRUCTURE_ROAD ||
structure.structureType === STRUCTURE_CONTAINER;
const excluded =
structure.structureType === STRUCTURE_WALL ||
structure.structureType === STRUCTURE_RAMPART;
return repairable &&
!excluded &&
structure.hits < structure.hitsMax;
}
})[0];
}
function runBuilder(creep) {
const source = getFirstSource(creep);
if (!source) {
return;
}
const working =
updateWorkState(creep, 'working');
if (!working) {
runHarvest(creep, source);
return;
}
const site =
creep.room.find(FIND_MY_CONSTRUCTION_SITES)[0];
if (site) {
const buildResult = creep.build(site);
if (buildResult === ERR_NOT_IN_RANGE) {
moveToTarget(creep, site, 'Construction Site');
} else if (buildResult !== OK) {
console.log(
'[' +
creep.name +
'] build() returned ' +
buildResult +
'.'
);
}
return;
}
const damaged = findRepairTarget(creep);
if (damaged) {
const repairResult = creep.repair(damaged);
if (repairResult === ERR_NOT_IN_RANGE) {
moveToTarget(creep, damaged, 'damaged Structure');
} else if (repairResult !== OK) {
console.log(
'[' +
creep.name +
'] repair() returned ' +
repairResult +
'.'
);
}
return;
}
const controller = creep.room.controller;
if (!controller) {
console.log(
'[' +
creep.name +
'] no Controller was found.'
);
return;
}
const upgradeResult =
creep.upgradeController(controller);
if (upgradeResult === ERR_NOT_IN_RANGE) {
moveToTarget(creep, controller, 'Controller');
} else if (upgradeResult !== OK) {
console.log(
'[' +
creep.name +
'] upgradeController() returned ' +
upgradeResult +
'.'
);
}
}
function trySpawnNamedCreep(spawn, name) {
const dryRunResult = spawn.spawnCreep(
BODY,
name,
{ dryRun: true }
);
if (dryRunResult !== OK) {
if (dryRunResult !== ERR_NOT_ENOUGH_ENERGY &&
dryRunResult !== ERR_BUSY &&
dryRunResult !== ERR_NAME_EXISTS) {
console.log(
'Dry run for ' +
name +
' returned ' +
dryRunResult +
'.'
);
}
return dryRunResult;
}
const spawnResult = spawn.spawnCreep(
BODY,
name
);
if (spawnResult === OK) {
console.log(name + ' started spawning.');
} else {
console.log(
'spawnCreep() for ' +
name +
' returned ' +
spawnResult +
'.'
);
}
return spawnResult;
}
module.exports.loop = function () {
const spawn = Game.spawns[SPAWN_NAME];
if (!spawn) {
console.log(
SPAWN_NAME +
' was not found. Existing Creeps will still run.'
);
} else if (!spawn.spawning) {
if (!Game.creeps['Harvester1']) {
trySpawnNamedCreep(spawn, 'Harvester1');
} else if (!Game.creeps['Upgrader1']) {
trySpawnNamedCreep(spawn, 'Upgrader1');
} else if (!Game.creeps['Builder1']) {
trySpawnNamedCreep(spawn, 'Builder1');
}
}
const harvester = Game.creeps['Harvester1'];
const upgrader = Game.creeps['Upgrader1'];
const builder = Game.creeps['Builder1'];
if (harvester && !harvester.spawning) {
runHarvester(harvester);
}
if (upgrader && !upgrader.spawning) {
runUpgrader(upgrader);
}
if (builder && !builder.spawning) {
runBuilder(builder);
}
};
How the spawning flow works
- Find the named Spawn.
- Skip creation while it is busy.
- Check missing Creeps in Harvester, Upgrader, Builder order.
- Validate the 200-Energy body with
dryRun. - Start at most one spawning request.
- Continue running any Creeps that already exist even if the Spawn name is wrong.
Harvester1 flow
Harvester1 harvests until full, then finds the first owned Spawn or Extension with free Energy capacity. When all such targets are full, it keeps its Energy and waits.
Upgrader1 flow
Upgrader1 harvests until full and upgrades until empty. The code checks the Source and Controller before submitting actions.
Builder1 flow
Builder1 harvests until full, then builds the first owned site, otherwise repairs the first selected damaged Structure, otherwise upgrades the Controller.
What to observe after deployment
- The correct Spawn name does not trigger repeated missing-object logs.
- Missing Creeps are requested in the expected order.
- Harvester1 fills Spawn and Extensions.
- Upgrader1 increases Controller progress.
- Builder1 follows build, repair, upgrade priority.
- Each Creep returns to a Source after using its Energy.
- Memory state fields switch only at empty and full boundaries.
- Unexpected action results appear in the Console.
Known failure boundaries
- All three Creeps select the first Source result.
- Each role has one fixed name.
- No replacement is spawned before a Creep dies.
- A total workforce loss can be unrecoverable when spawning Energy is below 200.
- Harvester1 fills only Spawn and Extensions.
- No nearest-target selection or assignment.
- No CPU optimization, caching, modules, or multi-room control.
- Real Console and live multi-tick behavior remain pending until observed.
Debugging checklist
- Back up the previous code.
- Replace the Spawn name.
- Confirm the room normally reaches 200 spawning Energy.
- Check every fixed Creep name.
- Confirm Sources and Controller are visible.
- Inspect Spawn and Extension free capacity.
- Inspect all three Memory state fields.
- Inspect action and movement return codes.
- Observe several complete Source-to-target trips.
- Do not call the script production-ready.
What to learn after the beginner route
The next useful improvements are Memory-based role data, dynamic body generation, dead-Creep Memory cleanup, and separate modules. Add them one at a time instead of turning this article into a complete architecture.
Use the English knowledge map, return-code reference, and room diagnostic tool while extending the script.
Frequently asked questions
Is this production-ready?
No. It is a fixed-name, single-room learning script.
Why can the room fail after every Creep dies?
The example body costs 200 Energy. With less Energy and no living supplier, the Spawn cannot create the first replacement.
Why does Harvester1 stop when targets are full?
Spawn and Extensions are the only delivery targets in this script.
Why do all Creeps use the first Source?
Source assignment is intentionally left for the next learning stage.