clone

an rts arena for scripts: submit tribe strategies over the web or mcp, run deterministic matches, watch them

main
tribes / packages / script-host / bots / homesteader.js
homesteader.js 7.1K
1// Homesteader — balanced-economy reference bot for Tribes v2.2// @description: Balanced farm-first economy: granaries, tools before weapons, a modest garrison behind level-1 walls.3//4// Strategy: a broad, self-sufficient economy with food security first. Set5// the whole labor/craft policy ONCE (policies persist — spending gas6// re-issuing them every tick is the classic v2 beginner mistake), then make7// small guarded adjustments: bump farmers when the larder runs thin, queue8// one building at a time, keep exactly one garrison warband once the smithy9// has produced weapons. Tech: milling -> crop_rotation (the food multipliers).10//11// Contract: define a global `function tick(state, memory, api)`.12// state - TribeView JSON snapshot (fog-of-war filtered), fresh each tick13// memory - plain object persisted between ticks (JSON size capped)14// api - policy verbs + api.rng() (seeded stream) + api.log(msg)1516function tick(state, memory, api) {17 // Prestige: try to advance the age when the hoard allows (the engine18 // drops the order if we cannot afford it — a cheap periodic attempt).19 if (state.tick % 50 === 0 && state.age < 3) api.advance();2021 var home = state.settlements[0];22 if (!home) return; // eliminated-in-all-but-name; nothing to govern2324 // Herd management (hysteresis): slaughter only while the flock is fat —25 // hides feed leather and the age hoard — and let a thin flock recover.26 var herds = home.herds || 0;27 if (!memory.slaughterOn && herds >= 16) {28 api.decree("slaughter", 80);29 memory.slaughterOn = 1;30 } else if (memory.slaughterOn && herds < 8) {31 api.decree("slaughter", 0);32 memory.slaughterOn = 0;33 }3435 // True when `building` is already standing or in the build queue at home.36 function hasOrQueued(building) {37 if (home.buildings[building]) return true;38 for (var i = 0; i < home.buildQueue.length; i++) {39 if (home.buildQueue[i].building === building) return true;40 }41 return false;42 }4344 // Standing + queued count of a building at home.45 function countOf(building) {46 var n = home.buildings[building] || 0;47 for (var ci = 0; ci < home.buildQueue.length; ci++) {48 if (home.buildQueue[ci].building === building) n++;49 }50 return n;51 }5253 // ------------------------------------------------------------------54 // One-time policy init. Everything here is PERSISTENT policy: issued55 // once, in force until changed. Sums to 930 permille; the idle56 // remainder feeds population growth.57 // ------------------------------------------------------------------58 if (!memory.init) {59 memory.init = 1;60 // Extraction (tools-hungry, the base of every chain).61 api.allocate("farmer", 280);62 api.allocate("fisher", 90);63 api.allocate("woodcutter", 100);64 api.allocate("miner", 60);65 // Refinement + finished goods.66 api.allocate("miller", 50);67 api.allocate("baker", 70);68 api.allocate("brewer", 30);69 api.allocate("smelter", 40);70 api.allocate("smith", 50);71 api.allocate("carpenter", 30);72 // Infrastructure & services.73 api.allocate("builder", 80);74 api.allocate("hauler", 30);75 api.allocate("scholar", 20);76 // Smithy split: tools keep extraction efficient; a trickle of weapons77 // for the eventual garrison. Grain split: mostly milled for bread.78 api.craft("tools", 750);79 api.craft("weapons", 200);80 api.craft("armor", 50);81 api.craft("mill", 800);82 api.craft("brew", 100);83 api.craft("fodder", 100);84 api.allocate("herder", 30);85 api.decree("rations", 1000);86 api.decree("expansion", 1000); // every acre ploughed: the homestead sprawls // full rations: morale + growth87 api.say("homesteader: tilling the good earth");88 return; // first tick done — keep it cheap and under the action cap89 }9091 // ------------------------------------------------------------------92 // Research: milling then crop_rotation (both multiply the food economy).93 // Re-issuing while idle is safe; the engine drops what it can't start.94 // ------------------------------------------------------------------95 if (state.researching === null) {96 if (state.techs.indexOf("milling") < 0) api.research("milling");97 else if (state.techs.indexOf("crop_rotation") < 0) api.research("crop_rotation");98 }99100 // ------------------------------------------------------------------101 // Food security: if the larder holds less than one unit of food-ish102 // stock per citizen, bump farmers (a guarded ADJUSTMENT — only when the103 // current policy is actually below the target, so this costs gas rarely).104 // ------------------------------------------------------------------105 var larder =106 (home.stores.bread || 0) + (home.stores.grain || 0) + (home.stores.fish || 0);107 if (larder < home.pop && state.allocations.farmer < 360) {108 api.allocate("farmer", 360);109 api.log("homesteader: larder thin (" + larder + "), more farmers");110 }111112 // ------------------------------------------------------------------113 // Construction: one queued building at a time, first missing item of a114 // fixed priority list. Farm/mill/bakery power the bread chain, granary115 // protects the harvest, smithy makes tools/weapons, walls L1 for safety.116 // ------------------------------------------------------------------117 if (home.buildQueue.length === 0) {118 // Growth targets scale with population: more mouths need more farms,119 // more people need more roofs. countOf() checks standing + queued.120 var wantFarms = 1 + Math.floor(home.pop / 60);121 var wantHouses = Math.floor(home.pop / 45);122 var wantCamps = 1 + Math.floor(home.pop / 150);123 if (countOf("farm") < wantFarms) api.build("farm", home.id);124 else if (countOf("lumber_camp") < wantCamps) api.build("lumber_camp", home.id);125 else if (countOf("pasture") < 1 + Math.floor(home.pop / 500)) api.build("pasture", home.id);126 else if (home.pop >= 60 && countOf("library") < 1) api.build("library", home.id);127 else if (countOf("house") < wantHouses) api.build("house", home.id);128 }129 // The strategic plan advances IN PARALLEL with growth (a short queue always130 // has room for one plan item, or growth would crowd it out forever).131 if (home.buildQueue.length < 2) {132 var plan = ["granary", "mill", "bakery", "smithy", "pasture", "walls", "shrine", "library"];133 for (var p = 0; p < plan.length; p++) {134 if (!hasOrQueued(plan[p])) {135 api.build(plan[p], home.id);136 break;137 }138 }139 }140141 // ------------------------------------------------------------------142 // Minimal military: exactly one infantry band, garrisoned at home, once143 // the smithy has turned out enough weapons. The tick-stamp guard stops144 // us re-mustering every tick while the engine processes the first one.145 // ------------------------------------------------------------------146 if (147 state.warbands.length === 0 &&148 (home.stores.weapons || 0) >= 20 &&149 (memory.musterAt === undefined || state.tick - memory.musterAt > 10)150 ) {151 api.muster(home.id, 20, "infantry");152 memory.musterAt = state.tick;153 }154 for (var w = 0; w < state.warbands.length; w++) {155 var band = state.warbands[w];156 if (band.order.type !== "garrison") {157 api.command(band.id, { type: "garrison", target: home.id });158 }159 }160}