Created
April 11, 2023 18:01
-
-
Save RGBKnights/1bbbb2a020bdd15376b21d122079b586 to your computer and use it in GitHub Desktop.
Screeps Arena - Generators
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
ATTACK, | |
BODYPART_COST, | |
BodyPartConstant, | |
CARRY, | |
ERR_BUSY, | |
ERR_FULL, | |
ERR_INVALID_ARGS, | |
ERR_INVALID_TARGET, | |
ERR_NOT_ENOUGH_ENERGY, | |
ERR_NOT_ENOUGH_RESOURCES, | |
ERR_NOT_IN_RANGE, | |
ERR_NOT_OWNER, | |
ERR_NO_BODYPART, | |
ERR_TIRED, | |
HEAL, | |
MOVE, | |
OK, | |
RESOURCE_ENERGY, | |
ResourceConstant, | |
TOUGH, | |
WORK | |
} from "game/constants"; | |
import { CostMatrix, MoveToOpts } from "game/path-finder"; | |
import { Creep, GameObject, Store, Structure, StructureContainer, StructureSpawn } from "game/prototypes"; | |
import { getCpuTime, getObjectsByPrototype } from "game/utils"; | |
import { arenaInfo } from "game"; | |
class Context { | |
private counter: number; | |
private logCpuTime: boolean; | |
private generators: Map<number, Generator<void, void, void>>; | |
public constructor(logCpuTime = false, iGenerators: Generator<void, void, void>[] = []) { | |
this.counter = 0; | |
this.logCpuTime = logCpuTime; | |
this.generators = new Map<number, Generator<void, void, void>>(iGenerators.map((obj, i) => [i, obj] as [number, Generator<void, void, void>])); | |
} | |
public add(g: Generator<void, void, void>): void { | |
this.generators.set(this.counter++, g); | |
} | |
public update(): boolean { | |
for (const [key, generator] of this.generators) { | |
try { | |
const result = generator.next(); | |
if (result.done === true) this.generators.delete(key); | |
} catch (error: unknown) { | |
this.generators.delete(key); | |
if (error instanceof String) { | |
console.log("Error", error); | |
} else if (error instanceof Error) { | |
console.log(error.name, error.message); | |
} | |
} | |
} | |
if (this.logCpuTime) { | |
const limit = (getCpuTime() / arenaInfo.cpuTimeLimit) * 100; | |
console.log(`Generator CPU: ${limit.toFixed(1)}%`); | |
} | |
return this.generators.size > 0; | |
} | |
} | |
export const context = new Context(true, []); | |
function* awaitProduction(spawn: StructureSpawn): Generator<void, void, void> { | |
while (spawn.spawning) { | |
yield; | |
} | |
} | |
function* awaitEnergy<S extends { store: Store<ResourceConstant> }>(s: S, required: number): Generator<void, void, void> { | |
while (s.store.energy < required) { | |
yield; | |
} | |
} | |
function* queueProduction<C extends Creep>(body: BodyPartConstant[]): Generator<void, C, void> { | |
const spawn = getObjectsByPrototype(StructureSpawn).find(s => s.my === true) as StructureSpawn; | |
const required = body.map(b => BODYPART_COST[b]).reduce((sum, c) => sum + c, 0); | |
yield* awaitEnergy(spawn, required); | |
const result = spawn.spawnCreep(body); | |
switch (result.error) { | |
case ERR_BUSY: | |
throw new Error(`Cant spawn creep [${body.toString()}] because the spawn is already busy`); | |
case ERR_INVALID_ARGS: | |
throw new Error(`Cant spawn creep [${body.toString()}] because body is invalid`); | |
case ERR_NOT_ENOUGH_ENERGY: | |
throw new Error(`Cant spawn creep [${body.toString()}] because not enough energy`); | |
default: | |
yield; | |
break; | |
} | |
yield* awaitProduction(spawn); | |
return result.object as C; | |
} | |
function* withdraw(creep: Creep, target: Structure): Generator<void, void, void> { | |
while (creep.exists && target.exists) { | |
const result = creep.withdraw(target, RESOURCE_ENERGY); | |
switch (result) { | |
case ERR_NOT_OWNER: | |
throw new Error(`Creep ${creep.id} is not mine`); | |
case ERR_INVALID_ARGS: | |
throw new Error(`Creep ${creep.id} dose not have a CARRY body part`); | |
case ERR_INVALID_TARGET: | |
throw new Error(`Target ${target.id} is invalid`); | |
case ERR_NOT_ENOUGH_RESOURCES: | |
case ERR_FULL: | |
return; | |
case ERR_NOT_IN_RANGE: | |
case OK: | |
yield; | |
break; | |
default: | |
throw new Error(`Unexpected Error: ${result}`); | |
} | |
} | |
} | |
function* transfer(creep: Creep, target: Structure): Generator<void, void, void> { | |
while (creep.exists && target.exists) { | |
const result = creep.transfer(target, RESOURCE_ENERGY); | |
switch (result) { | |
case ERR_NOT_OWNER: | |
throw new Error(`Creep ${creep.id} is not mine`); | |
case ERR_INVALID_ARGS: | |
throw new Error(`Creep ${creep.id} dose not have a CARRY body part`); | |
case ERR_INVALID_TARGET: | |
throw new Error(`Creep ${creep.id} has in invalid target ${target.id}`); | |
case ERR_NOT_ENOUGH_RESOURCES: | |
case ERR_FULL: | |
return; | |
case ERR_NOT_IN_RANGE: | |
case OK: | |
yield; | |
break; | |
default: | |
throw new Error(`Unexpected Error: ${result}`); | |
} | |
} | |
} | |
function* move<T extends GameObject>(creep: Creep, target: T, range = 1): Generator<void, void, void> { | |
// TODO: Create path... | |
while (creep.exists && target.exists) { | |
switch (creep.moveTo(target)) { | |
case ERR_NOT_OWNER: | |
throw new Error(`Creep ${creep.id} is not mine`); | |
case ERR_NO_BODYPART: | |
return; | |
case ERR_TIRED: | |
yield; | |
break; | |
case OK: | |
yield; | |
if (creep.getRangeTo(target) > range) { | |
break; | |
} else { | |
return; | |
} | |
default: | |
throw new Error(`Unexpected Error`); | |
} | |
} | |
} | |
function* whenAll(...generators: Generator<void, void, void>[]): Generator<void, void, void> { | |
const ctx = new Context(false, generators); | |
while (ctx.update()) { | |
yield; | |
} | |
} | |
function part(p: BodyPartConstant, a: number, includeMovement = false): BodyPartConstant[] { | |
return [...Array<BodyPartConstant>(a).fill(p), ...Array<BodyPartConstant>(includeMovement ? a : 0).fill(MOVE)]; | |
} | |
const WORKER = [...part(MOVE, 6), ...part(CARRY, 2), ...part(WORK, 1)]; | |
const ATTACKER = [...part(TOUGH, 6), ...part(MOVE, 6), ...part(ATTACK, 8)]; | |
const HEALER = [...part(MOVE, 5), ...part(HEAL, 3)]; | |
function* production(): Generator<void, void, void> { | |
const c1 = yield* queueProduction<Creep>(WORKER); | |
context.add(worker(c1)); | |
for (;;) { | |
const c4 = yield* queueProduction<Creep>(ATTACKER); | |
const c5 = yield* queueProduction<Creep>(ATTACKER); | |
const c6 = yield* queueProduction<Creep>(HEALER); | |
context.add(squad([c4, c5, c6])); | |
} | |
} | |
function* worker(creep: Creep): Generator<void, void, void> { | |
const spawn = getObjectsByPrototype(StructureSpawn).find(s => s.my === true) as StructureSpawn; | |
const containers = getObjectsByPrototype(StructureContainer); | |
const collection = spawn.findInRange(containers, 10); | |
for (const container of collection) { | |
for (; container.exists && container.store.energy > 0; ) { | |
yield* whenAll(move(creep, container), withdraw(creep, container)); | |
yield* whenAll(move(creep, spawn), transfer(creep, spawn)); | |
} | |
} | |
} | |
function* squad(creeps: Creep[]): Generator<void, void, void> { | |
const spawn = getObjectsByPrototype(StructureSpawn).find(s => s.my === false); | |
if (spawn === undefined) throw new Error("No Enemy Spawn"); | |
for (;;) { | |
// Build Cost Matrix for Area | |
const cost = new CostMatrix(); | |
cost.set(0, 0, 0); | |
const options = { | |
flee: false, | |
maxOps: 1000, | |
maxCost: 500, | |
heuristicWeight: 1.2, | |
range: 1, | |
plainCost: 2, | |
swampCost: 10, | |
costMatrix: cost, | |
ignore: [] | |
} as MoveToOpts; | |
for (const creep of creeps) { | |
creep.moveTo(spawn, options); | |
creep.attack(spawn); | |
// creep.attack(creeps) | |
} | |
yield; | |
} | |
} | |
// Default Generators | |
context.add(production()); | |
export function loop(): void { | |
context.update(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment