BEGINNER · FIRST ROOM LOOP

How to Combine Your First Screeps Room Loop

Combine fixed-name Harvester1, Upgrader1, and Builder1 logic with spawning, safe target checks, state switches, return-code logging, and explicit beginner limits.

Verification statusChinese source: Read in full · Official documentation: Checked · API and constants: Checked

VERIFICATION

Evidence and test status

Chinese source
Read in full
Official documentation
Checked
API and constants
Checked
JavaScript syntax
Checked
Offline branch review
Passed
Screeps Console
Pending
Live room test
Pending
Last verified
July 24, 2026

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

NamePrimary workState field
Harvester1Harvest and deliver to Spawn or Extension.memory.delivering
Upgrader1Harvest and upgrade the Controller.memory.upgrading
Builder1Harvest, build, repair, then upgrade.memory.working

Before replacing your current code

  • Save a copy of the current working version.
  • Replace Spawn1 with 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

  1. Find the named Spawn.
  2. Skip creation while it is busy.
  3. Check missing Creeps in Harvester, Upgrader, Builder order.
  4. Validate the 200-Energy body with dryRun.
  5. Start at most one spawning request.
  6. 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

  1. The correct Spawn name does not trigger repeated missing-object logs.
  2. Missing Creeps are requested in the expected order.
  3. Harvester1 fills Spawn and Extensions.
  4. Upgrader1 increases Controller progress.
  5. Builder1 follows build, repair, upgrade priority.
  6. Each Creep returns to a Source after using its Energy.
  7. Memory state fields switch only at empty and full boundaries.
  8. 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.

Official documentation

SOURCE AND SCOPE

Review the source, evidence, or next system

This English guide is rewritten for a focused search intent while preserving the technical scope and verification boundaries of its Chinese source. Live-room evidence is claimed only when the verification record says it exists.