2026-06-08 23:53:34 +09:00
|
|
|
|
import { STARTING_CASH, TURN_SECONDS, FARM_SHUTDOWN_GRACE_SECONDS, BUILD_COSTS, FACILITY_DEFS, GRID, EGG_SPAWN_RANGES } from './config.js';
|
|
|
|
|
|
import { BALANCE } from './balance.js';
|
|
|
|
|
|
import { createRunSeed, generateBlockedCells } from './mapGen.js';
|
|
|
|
|
|
import { resetOwnedCells } from './gridExpansion.js';
|
|
|
|
|
|
import { randomBetween, key, inGrid, directionNameBetweenCells } from './utils.js';
|
2026-06-07 15:05:24 +09:00
|
|
|
|
|
|
|
|
|
|
export function newTurnStats() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
mixerCount: 0,
|
|
|
|
|
|
truckFemale: 0,
|
|
|
|
|
|
truckMale: 0,
|
|
|
|
|
|
trashCount: 0,
|
|
|
|
|
|
poopSpawned: 0,
|
|
|
|
|
|
poopTruck: 0,
|
|
|
|
|
|
poopTrash: 0,
|
|
|
|
|
|
poopMixer: 0,
|
|
|
|
|
|
autoSorted: 0,
|
|
|
|
|
|
revenue: 0,
|
|
|
|
|
|
penalty: 0,
|
|
|
|
|
|
profit: 0,
|
|
|
|
|
|
pendingTruckRevenue: 0,
|
2026-06-07 18:06:35 +09:00
|
|
|
|
mixerRevenue: 0,
|
|
|
|
|
|
truckRevenue: 0,
|
2026-06-08 18:26:53 +09:00
|
|
|
|
chickShipmentIncome: 0,
|
|
|
|
|
|
poopShipmentIncome: 0,
|
|
|
|
|
|
manualComboBonus: 0,
|
|
|
|
|
|
manualComboSuccess: 0,
|
|
|
|
|
|
manualComboFailure: 0,
|
|
|
|
|
|
repairWorkerWages: 0,
|
2026-06-07 22:04:17 +09:00
|
|
|
|
fairiesTribute: 0,
|
2026-06-08 18:01:20 +09:00
|
|
|
|
chemicalWeaponSubsidy: 0,
|
|
|
|
|
|
rescueLoan: 0,
|
2026-06-07 22:04:17 +09:00
|
|
|
|
cardRerollCost: 0,
|
2026-06-09 10:38:29 +09:00
|
|
|
|
dudRefundIncome: 0,
|
|
|
|
|
|
composterIncome: 0,
|
|
|
|
|
|
sparePartsRepair: 0,
|
2026-06-07 18:06:35 +09:00
|
|
|
|
mixerPoopFine: 0,
|
|
|
|
|
|
truckPoopFine: 0,
|
|
|
|
|
|
maleTruckFine: 0,
|
2026-06-07 15:05:24 +09:00
|
|
|
|
explosionDamage: 0,
|
2026-06-08 13:18:33 +09:00
|
|
|
|
shredderBonus: 0,
|
2026-06-07 15:05:24 +09:00
|
|
|
|
correct: 0,
|
|
|
|
|
|
mistakes: 0,
|
2026-06-11 01:09:49 +09:00
|
|
|
|
eventBonus: 0,
|
|
|
|
|
|
explosions: 0,
|
|
|
|
|
|
eventResult: null,
|
2026-06-08 13:18:33 +09:00
|
|
|
|
conveyorPasses: 0,
|
|
|
|
|
|
eggProduced: 0
|
2026-06-07 15:05:24 +09:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function newTotalStats() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
turnsCompleted: 0,
|
|
|
|
|
|
processed: 0,
|
|
|
|
|
|
correct: 0,
|
|
|
|
|
|
mistakes: 0,
|
|
|
|
|
|
revenue: 0,
|
|
|
|
|
|
penalty: 0,
|
|
|
|
|
|
profit: 0,
|
|
|
|
|
|
maxCash: STARTING_CASH,
|
|
|
|
|
|
autoSorted: 0,
|
|
|
|
|
|
poopTruck: 0,
|
|
|
|
|
|
poopTrash: 0,
|
|
|
|
|
|
poopMixer: 0,
|
|
|
|
|
|
explosionDamage: 0,
|
2026-06-08 13:18:33 +09:00
|
|
|
|
shredderBonus: 0,
|
2026-06-11 01:09:49 +09:00
|
|
|
|
eventBonus: 0,
|
|
|
|
|
|
explosions: 0,
|
2026-06-07 22:04:17 +09:00
|
|
|
|
fairiesTribute: 0,
|
2026-06-08 18:01:20 +09:00
|
|
|
|
chemicalWeaponSubsidy: 0,
|
|
|
|
|
|
rescueLoan: 0,
|
2026-06-08 18:26:53 +09:00
|
|
|
|
manualComboBonus: 0,
|
|
|
|
|
|
manualComboSuccess: 0,
|
|
|
|
|
|
manualComboFailure: 0,
|
|
|
|
|
|
repairWorkerWages: 0,
|
2026-06-07 22:04:17 +09:00
|
|
|
|
cardRerollCost: 0,
|
2026-06-09 10:38:29 +09:00
|
|
|
|
dudRefundIncome: 0,
|
|
|
|
|
|
composterIncome: 0,
|
|
|
|
|
|
sparePartsRepair: 0,
|
2026-06-07 18:06:35 +09:00
|
|
|
|
mixerPoopFine: 0,
|
|
|
|
|
|
truckPoopFine: 0,
|
|
|
|
|
|
maleTruckFine: 0,
|
2026-06-11 01:09:49 +09:00
|
|
|
|
eventSuccess: 0,
|
|
|
|
|
|
eventFailed: 0,
|
2026-06-08 13:18:33 +09:00
|
|
|
|
conveyorPasses: 0,
|
|
|
|
|
|
eggProduced: 0
|
2026-06-07 15:05:24 +09:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function createGame() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
phase: 'title',
|
|
|
|
|
|
cash: STARTING_CASH,
|
|
|
|
|
|
turn: 1,
|
|
|
|
|
|
timeLeft: TURN_SECONDS,
|
2026-06-07 18:06:35 +09:00
|
|
|
|
shutdownTimeLeft: 0,
|
|
|
|
|
|
shutdownGraceSeconds: FARM_SHUTDOWN_GRACE_SECONDS,
|
2026-06-08 13:18:33 +09:00
|
|
|
|
cleanupTimer: null,
|
|
|
|
|
|
cleanupBlown: false,
|
2026-06-07 15:05:24 +09:00
|
|
|
|
lastTimestamp: 0,
|
|
|
|
|
|
nextId: 10,
|
2026-06-08 13:18:33 +09:00
|
|
|
|
runSeed: createRunSeed(),
|
2026-06-08 22:05:54 +09:00
|
|
|
|
gridCols: BALANCE.grid.cols,
|
|
|
|
|
|
gridRows: BALANCE.grid.rows,
|
|
|
|
|
|
gridExpansionPurchases: 0,
|
|
|
|
|
|
gridExpansionPurchasesByDirection: { right: 0, up: 0 },
|
|
|
|
|
|
gridChunkOriginY: 0,
|
|
|
|
|
|
ownedCells: new Set(),
|
2026-06-08 13:18:33 +09:00
|
|
|
|
blockedCells: new Set(),
|
2026-06-07 15:05:24 +09:00
|
|
|
|
buildTool: null,
|
|
|
|
|
|
buildSession: 0,
|
|
|
|
|
|
undoStack: [],
|
|
|
|
|
|
redoStack: [],
|
|
|
|
|
|
selected: null,
|
|
|
|
|
|
multiSelected: [],
|
|
|
|
|
|
selectionBox: null,
|
|
|
|
|
|
groupDrag: null,
|
|
|
|
|
|
pan: null,
|
2026-06-08 22:05:54 +09:00
|
|
|
|
view: { x: 0, y: 0, scale: 1 },
|
2026-06-08 13:18:33 +09:00
|
|
|
|
hover: null,
|
2026-06-07 15:05:24 +09:00
|
|
|
|
chicks: [],
|
|
|
|
|
|
effects: [],
|
|
|
|
|
|
floatingTexts: [],
|
|
|
|
|
|
shake: { time: 0, strength: 0 },
|
|
|
|
|
|
truckCargo: [],
|
|
|
|
|
|
conveyorTiles: new Set(),
|
|
|
|
|
|
conveyorMeta: new Map(),
|
|
|
|
|
|
eggFarms: [],
|
|
|
|
|
|
scanners: [],
|
|
|
|
|
|
facilities: {},
|
|
|
|
|
|
branchCounters: new Map(),
|
2026-06-08 13:18:33 +09:00
|
|
|
|
routingVersion: 0,
|
|
|
|
|
|
factoryGraph: null,
|
|
|
|
|
|
factoryGraphCommittedVersion: -1,
|
|
|
|
|
|
factoryGraphSnapshot: null,
|
2026-06-07 15:05:24 +09:00
|
|
|
|
mixerHalfTimer: 0,
|
|
|
|
|
|
congestion: new Map(),
|
|
|
|
|
|
lastExplodedComponent: new Map(),
|
2026-06-11 01:09:49 +09:00
|
|
|
|
eventOffer: null,
|
|
|
|
|
|
eventActive: null,
|
|
|
|
|
|
pendingFairiesTributeMultiplier: 1,
|
|
|
|
|
|
cardEffects: { bearing: 0, legalWork: 0, fairiesFlatteryNext: 0, autoScannerTuning: 0, poopRateBooster: 0, crowdedFarming: 0, hatchingFeed: 0, safetyCover: 0, recyclingSubsidy: 0, chemicalWeaponSubsidy: 0, preventiveMaintenance: 0, dudFilter: 0, durabilityCoating: 0, sparePartsBin: 0, dudRefund: 0, freeReroll: 0, composter: 0, scannerQueueSpacing: 0, laborExploitation: 0, usedMachineActive: false, rescueLoanCharges: 0 },
|
2026-06-07 22:04:17 +09:00
|
|
|
|
cardDraft: { pending: false, choices: [], rerolls: 0, picksRemaining: 0 },
|
|
|
|
|
|
cardTargetPick: null,
|
2026-06-08 18:26:53 +09:00
|
|
|
|
manualCombo: { count: 0, lastBonus: 0 },
|
|
|
|
|
|
pendingRepairWorkerWages: 0,
|
2026-06-08 22:05:54 +09:00
|
|
|
|
repairman: { hiredForNextDay: false, active: false, x: GRID.x + GRID.cell * 0.5, y: GRID.y + BALANCE.grid.rows * GRID.cell + 84, target: null, repairedToday: 0 },
|
2026-06-07 15:05:24 +09:00
|
|
|
|
stats: newTurnStats(),
|
|
|
|
|
|
lastResult: null,
|
|
|
|
|
|
totals: newTotalStats()
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function getSpawnRange(farm) {
|
2026-06-08 13:18:33 +09:00
|
|
|
|
return EGG_SPAWN_RANGES[(farm?.level || 1) - 1] || EGG_SPAWN_RANGES[EGG_SPAWN_RANGES.length - 1] || [2, 4];
|
2026-06-07 15:05:24 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function nextSpawnDelay(farm) {
|
|
|
|
|
|
const [min, max] = getSpawnRange(farm);
|
|
|
|
|
|
return randomBetween(min, max);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function facilityBodyForEntry(id, entry, side) {
|
2026-06-07 20:36:52 +09:00
|
|
|
|
const def = FACILITY_DEFS[id];
|
|
|
|
|
|
const spec = { ...def.body, name: def.name, price: def.price };
|
2026-06-07 15:05:24 +09:00
|
|
|
|
const g = { left: GRID.x, right: GRID.x + GRID.cols * GRID.cell, top: GRID.y, bottom: GRID.y + GRID.rows * GRID.cell };
|
|
|
|
|
|
const c = { x: GRID.x + entry.col * GRID.cell + GRID.cell / 2, y: GRID.y + entry.row * GRID.cell + GRID.cell / 2 };
|
|
|
|
|
|
let x = c.x - spec.w / 2;
|
|
|
|
|
|
let y = c.y - spec.h / 2;
|
|
|
|
|
|
if (side === 'left') { x = g.left - spec.w - 18; y = c.y - spec.h / 2; }
|
|
|
|
|
|
else if (side === 'right') { x = g.right + 18; y = c.y - spec.h / 2; }
|
|
|
|
|
|
else if (side === 'bottom') { x = c.x - spec.w / 2; y = g.bottom + 18; }
|
|
|
|
|
|
else if (side === 'top') { x = c.x - spec.w / 2; y = g.top - spec.h - 18; }
|
|
|
|
|
|
return { type: 'facility', id, name: spec.name, x, y, w: spec.w, h: spec.h, level: 1, price: spec.price, entry: { ...entry }, side };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function defaultFacilities() {
|
|
|
|
|
|
return {
|
2026-06-08 22:05:54 +09:00
|
|
|
|
// Initial grid is 20 columns × 10 rows. Machine bodies sit outside the starter grid.
|
|
|
|
|
|
mixer: facilityBodyForEntry('mixer', { col: 0, row: 4 }, 'left'),
|
|
|
|
|
|
trash: facilityBodyForEntry('trash', { col: 12, row: 9 }, 'bottom'),
|
|
|
|
|
|
truck: facilityBodyForEntry('truck', { col: 18, row: 9 }, 'bottom')
|
2026-06-07 15:05:24 +09:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-08 13:18:33 +09:00
|
|
|
|
const INITIAL_CONVEYOR_BACKBONE = Object.freeze([
|
2026-06-08 22:05:54 +09:00
|
|
|
|
// Egg Farm -> S1 top input. The S1/S2 rows were moved down so unrelated
|
|
|
|
|
|
// belt streams keep at least one blank tile between side-by-side flows.
|
2026-06-09 10:38:29 +09:00
|
|
|
|
[1, 1], [2, 1], [3, 1], [4, 1], [5, 1], [6, 1], [7, 1], [8, 1], [8, 2],
|
2026-06-08 13:18:33 +09:00
|
|
|
|
// S1 left output -> Mixer receiver on the left grid edge.
|
2026-06-08 22:05:54 +09:00
|
|
|
|
[7, 4], [6, 4], [5, 4], [4, 4], [3, 4], [2, 4], [1, 4], [0, 4],
|
2026-06-08 13:18:33 +09:00
|
|
|
|
// S1 right output -> S2 top input.
|
2026-06-09 10:38:29 +09:00
|
|
|
|
[10, 4], [11, 4], [12, 4], [13, 4], [14, 4], [15, 4], [15, 5],
|
2026-06-08 13:18:33 +09:00
|
|
|
|
// S2 left output -> Waste Shredder receiver on bottom grid edge.
|
2026-06-08 22:05:54 +09:00
|
|
|
|
[14, 7], [13, 7], [12, 7], [12, 8], [12, 9],
|
2026-06-08 13:18:33 +09:00
|
|
|
|
// S2 right output -> Truck receiver on the bottom grid edge.
|
2026-06-09 10:38:29 +09:00
|
|
|
|
[17, 7], [18, 7], [18, 8], [18, 9]
|
2026-06-08 13:18:33 +09:00
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
const INITIAL_SCANNERS = Object.freeze([
|
2026-06-08 22:05:54 +09:00
|
|
|
|
{ type: 'scanner', id: 1, kind: 'manual', slot: 0, role: 0, col: 8, row: 4, level: 1, queue: [], cooldown: 0, price: BUILD_COSTS.manualScanner, builtSession: null, autoMode: 'standard', keys: { left: { key: 'a', code: 'KeyA', label: 'A' }, right: { key: 'd', code: 'KeyD', label: 'D' } } },
|
|
|
|
|
|
{ type: 'scanner', id: 2, kind: 'manual', slot: 1, role: 1, col: 15, row: 7, level: 1, queue: [], cooldown: 0, price: BUILD_COSTS.manualScanner, builtSession: null, autoMode: 'standard', keys: { left: { key: 'arrowleft', code: 'ArrowLeft', label: 'Left' }, right: { key: 'arrowright', code: 'ArrowRight', label: 'Right' } } }
|
2026-06-08 13:18:33 +09:00
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
const INITIAL_EGG_FARMS = Object.freeze([
|
2026-06-11 01:09:49 +09:00
|
|
|
|
{ type: 'eggFarm', id: 1, col: 1, row: 0, level: 1, nextSpawn: 2, lastInterval: 2, spawnCounter: 0, price: BUILD_COSTS.eggFarm, builtSession: null }
|
2026-06-08 13:18:33 +09:00
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
function addConveyorTile(game, col, row, options = {}) {
|
|
|
|
|
|
if (!inGrid(col, row)) return;
|
|
|
|
|
|
const k = key(col, row);
|
|
|
|
|
|
game.conveyorTiles.add(k);
|
|
|
|
|
|
if (!game.conveyorMeta.has(k) || options.forceMeta) {
|
|
|
|
|
|
game.conveyorMeta.set(k, {
|
|
|
|
|
|
price: BUILD_COSTS.conveyor,
|
|
|
|
|
|
builtSession: null,
|
|
|
|
|
|
uses: 0,
|
|
|
|
|
|
durability: BALANCE.maintenance.durability.conveyor,
|
2026-06-08 22:05:54 +09:00
|
|
|
|
maintenanceType: 'conveyor',
|
2026-06-09 10:38:29 +09:00
|
|
|
|
kind: options.kind || 'conveyor',
|
|
|
|
|
|
speedMultiplier: options.speedMultiplier || 1,
|
2026-06-08 22:05:54 +09:00
|
|
|
|
dir: options.dir || null,
|
2026-06-09 10:38:29 +09:00
|
|
|
|
outDirs: Array.isArray(options.outDirs) ? [...options.outDirs] : [],
|
|
|
|
|
|
branchMode: options.branchMode || 'random'
|
2026-06-08 13:18:33 +09:00
|
|
|
|
});
|
2026-06-08 22:05:54 +09:00
|
|
|
|
} else if (options.dir || options.outDirs) {
|
|
|
|
|
|
const meta = game.conveyorMeta.get(k);
|
|
|
|
|
|
if (options.dir) meta.dir = options.dir;
|
|
|
|
|
|
if (Array.isArray(options.outDirs)) meta.outDirs = [...new Set([...(meta.outDirs || []), ...options.outDirs])];
|
2026-06-09 10:38:29 +09:00
|
|
|
|
if (!meta.branchMode) meta.branchMode = 'random';
|
|
|
|
|
|
if (!meta.kind) meta.kind = 'conveyor';
|
|
|
|
|
|
if (!meta.speedMultiplier) meta.speedMultiplier = 1;
|
2026-06-08 13:18:33 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-08 22:05:54 +09:00
|
|
|
|
function ensureConveyorMeta(game, col, row) {
|
|
|
|
|
|
const k = key(col, row);
|
|
|
|
|
|
if (!game.conveyorMeta.has(k)) addConveyorTile(game, col, row);
|
|
|
|
|
|
return game.conveyorMeta.get(k);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function markInitialConveyorDirection(game, from, to) {
|
|
|
|
|
|
const dir = directionNameBetweenCells(from, to);
|
|
|
|
|
|
if (!dir) return;
|
|
|
|
|
|
const fromMeta = ensureConveyorMeta(game, from.col, from.row);
|
|
|
|
|
|
fromMeta.outDirs = Array.isArray(fromMeta.outDirs) ? fromMeta.outDirs : [];
|
|
|
|
|
|
if (!fromMeta.outDirs.includes(dir)) fromMeta.outDirs.push(dir);
|
|
|
|
|
|
fromMeta.dir = dir;
|
|
|
|
|
|
const toMeta = ensureConveyorMeta(game, to.col, to.row);
|
|
|
|
|
|
if (!toMeta.dir) toMeta.dir = dir;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-08 13:18:33 +09:00
|
|
|
|
function scannerPortCells(scanner) {
|
|
|
|
|
|
return [
|
2026-06-09 10:38:29 +09:00
|
|
|
|
{ col: scanner.col, row: scanner.row - 2 },
|
2026-06-08 13:18:33 +09:00
|
|
|
|
{ col: scanner.col - 1, row: scanner.row },
|
2026-06-09 10:38:29 +09:00
|
|
|
|
{ col: scanner.col + 2, row: scanner.row }
|
|
|
|
|
|
].filter(p => inGrid(p.col, p.row));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function scannerBodyCells(scanner) {
|
|
|
|
|
|
return [
|
|
|
|
|
|
{ col: scanner.col, row: scanner.row - 1 },
|
|
|
|
|
|
{ col: scanner.col + 1, row: scanner.row - 1 },
|
|
|
|
|
|
{ col: scanner.col, row: scanner.row },
|
|
|
|
|
|
{ col: scanner.col + 1, row: scanner.row },
|
|
|
|
|
|
{ col: scanner.col, row: scanner.row + 1 },
|
|
|
|
|
|
{ col: scanner.col + 1, row: scanner.row + 1 }
|
2026-06-08 13:18:33 +09:00
|
|
|
|
].filter(p => inGrid(p.col, p.row));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function farmOutputCells(farm) {
|
|
|
|
|
|
return [
|
|
|
|
|
|
{ col: farm.col, row: farm.row + 1 },
|
|
|
|
|
|
{ col: farm.col - 1, row: farm.row },
|
|
|
|
|
|
{ col: farm.col + 1, row: farm.row },
|
|
|
|
|
|
{ col: farm.col, row: farm.row - 1 }
|
|
|
|
|
|
].filter(p => inGrid(p.col, p.row));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function protectedInitialCells(game) {
|
|
|
|
|
|
const reserved = new Set([...game.conveyorTiles]);
|
|
|
|
|
|
const add = p => { if (p && inGrid(p.col, p.row)) reserved.add(key(p.col, p.row)); };
|
|
|
|
|
|
for (const scanner of game.scanners) {
|
2026-06-09 10:38:29 +09:00
|
|
|
|
for (const p of scannerBodyCells(scanner)) add(p);
|
2026-06-08 13:18:33 +09:00
|
|
|
|
for (const p of scannerPortCells(scanner)) add(p);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (const farm of game.eggFarms) {
|
|
|
|
|
|
add(farm);
|
|
|
|
|
|
for (const p of farmOutputCells(farm)) add(p);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (const f of Object.values(game.facilities)) add(f.entry);
|
|
|
|
|
|
return reserved;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function installInitialConveyorBackbone(game) {
|
2026-06-08 22:05:54 +09:00
|
|
|
|
const cells = INITIAL_CONVEYOR_BACKBONE.map(([col, row]) => ({ col, row }));
|
|
|
|
|
|
for (const { col, row } of cells) addConveyorTile(game, col, row);
|
|
|
|
|
|
for (let i = 0; i < cells.length - 1; i += 1) markInitialConveyorDirection(game, cells[i], cells[i + 1]);
|
2026-06-08 13:18:33 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function scrubBlockedCellsFromInitialBackbone(game) {
|
|
|
|
|
|
const protectedCells = protectedInitialCells(game);
|
|
|
|
|
|
for (const k of protectedCells) game.blockedCells.delete(k);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-07 15:05:24 +09:00
|
|
|
|
export function resetLayout(game) {
|
2026-06-08 22:05:54 +09:00
|
|
|
|
GRID.x = BALANCE.grid.x;
|
|
|
|
|
|
GRID.y = BALANCE.grid.y;
|
|
|
|
|
|
GRID.cols = BALANCE.grid.cols;
|
|
|
|
|
|
GRID.rows = BALANCE.grid.rows;
|
|
|
|
|
|
game.gridCols = GRID.cols;
|
|
|
|
|
|
game.gridRows = GRID.rows;
|
|
|
|
|
|
resetOwnedCells(game);
|
|
|
|
|
|
game.gridOwnedCells = game.ownedCells.size;
|
|
|
|
|
|
if (game.repairman) {
|
|
|
|
|
|
game.repairman.x = GRID.x + GRID.cell * 0.5;
|
|
|
|
|
|
game.repairman.y = GRID.y + GRID.rows * GRID.cell + 84;
|
|
|
|
|
|
game.repairman.target = null;
|
|
|
|
|
|
game.repairman.active = false;
|
|
|
|
|
|
game.repairman.hiredForNextDay = false;
|
|
|
|
|
|
}
|
2026-06-07 15:05:24 +09:00
|
|
|
|
game.conveyorTiles.clear();
|
|
|
|
|
|
game.conveyorMeta.clear();
|
2026-06-08 13:18:33 +09:00
|
|
|
|
game.blockedCells = new Set();
|
|
|
|
|
|
installInitialConveyorBackbone(game);
|
2026-06-07 15:05:24 +09:00
|
|
|
|
game.facilities = defaultFacilities();
|
2026-06-08 18:26:53 +09:00
|
|
|
|
game.scanners = INITIAL_SCANNERS.map(scanner => ({ ...scanner, keys: scanner.keys ? { left: { ...scanner.keys.left }, right: { ...scanner.keys.right } } : null, queue: [] }));
|
2026-06-08 13:18:33 +09:00
|
|
|
|
game.eggFarms = INITIAL_EGG_FARMS.map(farm => {
|
|
|
|
|
|
const nextSpawn = nextSpawnDelay(farm);
|
|
|
|
|
|
return { ...farm, nextSpawn, lastInterval: nextSpawn };
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Generate no-build cells only after the complete initial factory footprint is
|
|
|
|
|
|
// installed. The protected footprint includes every default conveyor segment,
|
|
|
|
|
|
// scanner body/ports, farm output, and machine receiver. This guarantees that
|
|
|
|
|
|
// the starter line is always continuous from Egg Farm -> S1 -> S2 -> exits even
|
|
|
|
|
|
// when roughly 20% of the map is blocked.
|
|
|
|
|
|
const reserved = protectedInitialCells(game);
|
|
|
|
|
|
game.blockedCells = generateBlockedCells({ seed: game.runSeed, targetRatio: BALANCE.map.blockedRatio, reserved: [...reserved] });
|
|
|
|
|
|
scrubBlockedCellsFromInitialBackbone(game);
|
|
|
|
|
|
installInitialConveyorBackbone(game);
|
|
|
|
|
|
game.routingVersion = (game.routingVersion || 0) + 1;
|
|
|
|
|
|
game.factoryGraph = null;
|
|
|
|
|
|
game.factoryGraphCommittedVersion = -1;
|
2026-06-07 15:05:24 +09:00
|
|
|
|
}
|