import fs from 'fs';
/ The logical opposing pairs for bidirectional routing
const COMPASS_PAIRS = [
{ fwd: 'NORTH', rev: 'SOUTH' },
{ fwd: 'EAST', rev: 'WEST' },
{ fwd: 'UP', rev: 'DOWN' },
{ fwd: 'IN', rev: 'OUT' },
// Backups if the room gets highly connected
{ fwd: 'NORTHEAST', rev: 'SOUTHWEST' },
{ fwd: 'NORTHWEST', rev: 'SOUTHEAST' }
];
function parseCartographer(input) {
const rooms = {};
const blocks = input.split('---').map(b => b.trim()).filter(Boolean);
for (const block of blocks) {
const lines = block.split('
');
let id = '', desc = '', exits = [];
for (const line of lines) {
if (line.startsWith('ROOM:')) id = line.replace('ROOM:',
'').trim();
if (line.startsWith('DESC:')) desc = line.replace('DESC:',
'').trim();
if (line.startsWith('EXITS:')) {
const exitsRaw = line.replace('EXITS:', '');
exits = exitsRaw.split(',').map(e => e.trim()).filter(Boolean);
}
}
if (id) {
rooms[id] = { id, desc, exits, routes: {} };
}
}
return rooms;
}
function assignCompassDirections(rooms) {
// Keep track of assigned connections to enforce bidirectionality
const assignedLinks = new Set();
for (const [id, room] of Object.entries(rooms)) {
for (const targetId of room.exits) {
// Skip if the target room doesn't actually exist
if (!rooms[targetId]) continue;
// Skip if we already mapped this connection from the other side
const linkKey1 = `${id}->${targetId}`;
const linkKey2 = `${targetId}->${id}`;
if (assignedLinks.has(linkKey1)) continue;
// Find an available compass pair
const targetRoom = rooms[targetId];
const usedInRoomA = Object.keys(room.routes);
const usedInRoomB = Object.keys(targetRoom.routes);
let chosenPair = COMPASS_PAIRS.find(pair =>
!usedInRoomA.includes(pair.fwd) && !usedInRoomB.includes(pair.r
ev)
);
// Fallback just in case a room has crazy connectivity
if (!chosenPair) chosenPair = { fwd: 'PATH', rev: 'PATH' };
// Assign the forward route
room.routes[chosenPair.fwd] = targetId;
assignedLinks.add(linkKey1);
// Enforce the reverse route automatically (no traps!)
targetRoom.routes[chosenPair.rev] = id;
assignedLinks.add(linkKey2);
}
}
}
function buildFSO(rooms) {
let fsoCode = `LBL start_game
GLOBAL "HELP" handle_help
`;
// Set the first room dynamically as the spawn point
const firstRoomId = Object.keys(rooms)[0];
if (firstRoomId) fsoCode += `JMP room_${firstRoomId.toLowerCase()}
`;
for (const [id, room] of Object.entries(rooms)) {
const lblName = `room_${id.toLowerCase()}`;
fsoCode += `LBL ${lblName}
`;
fsoCode += `ENV_FLUSH
`;
// Escape quotes to prevent compilation crashes
const safeDesc = room.desc.replace(/"/g, '\\"');
fsoCode += `DESC "${safeDesc}"
`;
for (const [direction, targetId] of Object.entries(room.routes)) {
fsoCode += `EXIT "${direction}" room_${targetId.toLowerCase()}
`;
}
fsoCode += `AWAIT
`;
}
// Add the global help handler
fsoCode += `LBL handle_help
`;
fsoCode += `OUT "Type directions (like NORTH or EAST) to move around."
`;
fsoCode += `AWAIT
`;
return `\`\`\`fso-state
VAR INVENTORY ARR '[]'
\`\`\`
\`\`\`fso-code
${fsoCode.trim()}
\`\`\``;
}
/ CLI Execution
const inputFile = process.argv[2] || 'map.txt';
if (!fs.existsSync(inputFile)) {
console.error(`[FAULT] Could not find ${inputFile}`);
process.exit(1);
}
const rawText = fs.readFileSync(inputFile, 'utf-8');
const rooms = parseCartographer(rawText);
assignCompassDirections(rooms);
const outputMd = buildFSO(rooms);
fs.writeFileSync('game_idea.md', outputMd);
console.log(`[SYS] Topology successfully routed! Compiled to game_idea.md`);