/** * explorer_bot.js — Claude explores the world through a mineflayer bot's eyes. * * Connects to the server, reports what it sees, and accepts movement commands * via stdin. Outputs structured JSON observations. * * Usage: node explorer_bot.js [host] [port] */ const mineflayer = require('mineflayer'); const { Vec3 } = require('vec3'); const readline = require('readline'); const host = process.argv[2] || '192.168.0.244'; const port = parseInt(process.argv[3] || '25567', 10); const bot = mineflayer.createBot({ host, port, username: 'ClaudeBot', auth: 'offline', version: '1.21.11', }); // ── Observation functions ── function observe() { if (!bot.entity) return '{"error": "not spawned"}'; const pos = bot.entity.position; const obs = { position: { x: Math.floor(pos.x), y: Math.floor(pos.y), z: Math.floor(pos.z) }, health: bot.health, food: bot.food, gamemode: bot.game.gameMode, time: bot.time.timeOfDay, isRaining: bot.isRaining, }; // Block below and around obs.blockBelow = blockName(pos.offset(0, -1, 0)); obs.blockAt = blockName(pos); obs.blockAbove = blockName(pos.offset(0, 2, 0)); // Look in all 4 directions for interesting blocks obs.surroundings = {}; const dirs = { north: [0, 0, -1], south: [0, 0, 1], east: [1, 0, 0], west: [-1, 0, 0] }; for (const [name, [dx, dy, dz]] of Object.entries(dirs)) { const blocks = []; for (let dist = 1; dist <= 8; dist++) { const b = blockName(pos.offset(dx * dist, 0, dz * dist)); if (b !== 'air') blocks.push({ dist, block: b }); } if (blocks.length) obs.surroundings[name] = blocks; } // Nearby entities obs.entities = []; for (const entity of Object.values(bot.entities)) { if (entity === bot.entity) continue; const dist = entity.position.distanceTo(pos); if (dist <= 20) { obs.entities.push({ type: entity.type === 'player' ? 'player' : (entity.name || entity.type), name: entity.username || null, distance: Math.round(dist), position: { x: Math.floor(entity.position.x), y: Math.floor(entity.position.y), z: Math.floor(entity.position.z), }, }); } } // Scan a 5x5 area at foot level for terrain overview obs.terrain = []; for (let dx = -5; dx <= 5; dx++) { for (let dz = -5; dz <= 5; dz++) { // Find surface block (scan down from y+3) for (let dy = 3; dy >= -3; dy--) { const b = bot.blockAt(pos.offset(dx, dy, dz)); if (b && b.name !== 'air') { obs.terrain.push({ dx, dz, y: Math.floor(pos.y) + dy, block: b.name }); break; } } } } return JSON.stringify(obs); } function blockName(vec) { const b = bot.blockAt(vec); return b ? b.name : 'unloaded'; } // ── Movement functions ── async function walkTo(x, y, z) { const goal = new Vec3(x, y, z); console.log(JSON.stringify({ action: 'walking', target: { x, y, z } })); // Simple movement: look at target and walk forward await bot.lookAt(goal); bot.setControlState('forward', true); // Walk until close or timeout return new Promise((resolve) => { const check = setInterval(() => { const dist = bot.entity.position.distanceTo(goal); if (dist < 2) { bot.setControlState('forward', false); clearInterval(check); resolve(true); } }, 200); setTimeout(() => { bot.setControlState('forward', false); clearInterval(check); resolve(false); }, 10000); // 10s max walk }); } async function lookAround() { const snapshots = []; const yaws = [0, Math.PI / 2, Math.PI, -Math.PI / 2]; // N, E, S, W const names = ['north', 'east', 'south', 'west']; for (let i = 0; i < 4; i++) { await bot.look(yaws[i], 0, true); await new Promise(r => setTimeout(r, 500)); // What do I see in this direction? const pos = bot.entity.position; const dx = Math.round(Math.sin(yaws[i])); const dz = Math.round(-Math.cos(yaws[i])); const visible = []; for (let dist = 1; dist <= 16; dist++) { const b = bot.blockAt(pos.offset(dx * dist, 0, dz * dist)); const bUp = bot.blockAt(pos.offset(dx * dist, 1, dz * dist)); const bDown = bot.blockAt(pos.offset(dx * dist, -1, dz * dist)); if (b && b.name !== 'air') visible.push({ dist, block: b.name, level: 'eye' }); if (bDown && bDown.name !== 'air' && bDown.name !== (b && b.name)) visible.push({ dist, block: bDown.name, level: 'ground' }); } snapshots.push({ direction: names[i], blocks: visible.slice(0, 8) }); } return snapshots; } // ── Event handlers ── bot.on('login', () => { console.log(JSON.stringify({ event: 'login', username: bot.username })); }); bot.on('spawn', async () => { console.log(JSON.stringify({ event: 'spawn', ...JSON.parse(observe()) })); // Initial look around const views = await lookAround(); console.log(JSON.stringify({ event: 'look_around', views })); }); bot.on('chat', (username, message) => { if (username === bot.username) return; console.log(JSON.stringify({ event: 'chat', from: username, message })); }); bot.on('health', () => { console.log(JSON.stringify({ event: 'health', health: bot.health, food: bot.food })); }); bot.on('death', () => { console.log(JSON.stringify({ event: 'death' })); }); bot.on('kicked', (reason) => { console.log(JSON.stringify({ event: 'kicked', reason: typeof reason === 'object' ? JSON.stringify(reason) : reason })); }); bot.on('error', (err) => { console.error(JSON.stringify({ event: 'error', message: err.message })); }); // ── Command interface (stdin) ── const rl = readline.createInterface({ input: process.stdin }); rl.on('line', async (line) => { const cmd = line.trim(); if (cmd === 'observe' || cmd === 'look') { console.log(observe()); } else if (cmd === 'around') { const views = await lookAround(); console.log(JSON.stringify({ action: 'look_around', views })); } else if (cmd.startsWith('walk ')) { const parts = cmd.split(' '); const x = parseInt(parts[1]), y = parseInt(parts[2]), z = parseInt(parts[3]); const arrived = await walkTo(x, y, z); console.log(JSON.stringify({ action: 'walk_result', arrived, ...JSON.parse(observe()) })); } else if (cmd === 'forward') { bot.setControlState('forward', true); setTimeout(() => { bot.setControlState('forward', false); console.log(observe()); }, 3000); } else if (cmd === 'jump') { bot.setControlState('jump', true); setTimeout(() => bot.setControlState('jump', false), 500); setTimeout(() => console.log(observe()), 1000); } else if (cmd.startsWith('say ')) { bot.chat(cmd.slice(4)); } else if (cmd === 'quit') { bot.end(); process.exit(0); } else { console.log(JSON.stringify({ error: 'unknown command', commands: ['observe', 'around', 'walk X Y Z', 'forward', 'jump', 'say MSG', 'quit'] })); } });