From fa98e86055949c465476c33bfbf9718431ef141d Mon Sep 17 00:00:00 2001 From: MayaTheShy Date: Fri, 20 Feb 2026 01:45:39 -0500 Subject: [PATCH] feat: Implement ExploringState class for turtle state machine to autonomously explore and discover the world map --- server/states/ExploringState.js | 177 ++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 server/states/ExploringState.js diff --git a/server/states/ExploringState.js b/server/states/ExploringState.js new file mode 100644 index 0000000..4d452ef --- /dev/null +++ b/server/states/ExploringState.js @@ -0,0 +1,177 @@ +/** + * ExploringState - Autonomous exploration to discover the world map + * Similar to mining but focused on discovering blocks rather than mining them + */ +import { BaseState } from './BaseState.js'; + +export class ExploringState extends BaseState { + constructor(turtle, data = {}) { + super(turtle, data); + this.maxDistance = data.maxDistance || 200; + this.minFuel = data.minFuel || 500; + this.blocksDiscovered = 0; + this.stuckCounter = 0; + this.visitedPositions = new Set(); + } + + get name() { + return 'exploring'; + } + + get description() { + return `Exploring - ${this.blocksDiscovered} blocks discovered`; + } + + async *act() { + console.log(`[${this.turtle.id}] Starting exploration`); + + while (!this.cancelled) { + // Safety checks + const fuel = await this.checkFuel(); + if (fuel !== 'unlimited' && fuel < this.minFuel) { + const refueled = await this.tryRefuel(); + if (!refueled) { + this.turtle.setState('goHome', { reason: 'low_fuel' }); + return; + } + } + + // Check distance + if (this._isTooFar()) { + this.turtle.setState('goHome', { reason: 'too_far' }); + return; + } + + // Check inventory + const isFull = await this.isInventoryFull(); + if (isFull) { + this.turtle.setState('goHome', { reason: 'inventory_full', returnState: 'exploring' }); + return; + } + + // Mark position + const pos = this.turtle.position; + if (pos) { + this.visitedPositions.add(`${pos.x},${pos.y},${pos.z}`); + } + + // Comprehensive scan + const scanResult = await this.scanSurroundings(); + if (scanResult) { + this.blocksDiscovered += Object.keys(scanResult).length; + } + + // Mine any valuable ores we find + yield* this._checkAndMineOres(); + + // Exploration movement + yield* this._exploreStep(); + + yield; + await this._sleep(300); + } + } + + async *_checkAndMineOres() { + const valuableOres = new Set([ + 'minecraft:diamond_ore', 'minecraft:emerald_ore', + 'minecraft:deepslate_diamond_ore', 'minecraft:deepslate_emerald_ore', + ]); + + const directions = [ + { check: 'turtle.inspect()', dig: 'turtle.dig()' }, + { check: 'turtle.inspectUp()', dig: 'turtle.digUp()' }, + { check: 'turtle.inspectDown()', dig: 'turtle.digDown()' }, + ]; + + for (const { check, dig } of directions) { + if (this.cancelled) return; + const result = await this.exec(` + local hasBlock, data = ${check} + if hasBlock then return {name = data.name} end + return nil + `); + + if (result && valuableOres.has(result.name)) { + await this.exec(dig); + this.turtle.emit('blockMined', { blockType: result.name }); + yield; + } + } + } + + async *_exploreStep() { + const pos = this.turtle.position; + if (!pos) return; + + // Favor horizontal movement heavily (85%) + const r = Math.random() * 100; + + if (r < 85) { + // Try to find unvisited direction + let moved = false; + + for (let i = 0; i < 4; i++) { + const fwdPos = this.turtle.getBlockPositionInDirection('forward'); + if (fwdPos && !this.visitedPositions.has(`${fwdPos.x},${fwdPos.y},${fwdPos.z}`)) { + const canMove = await this.exec('local h = turtle.inspect(); return not h'); + if (canMove) { + await this.moveForward(false); + moved = true; + this.stuckCounter = 0; + break; + } else { + // Block in the way - dig through if exploring + const success = await this.moveForward(true); + if (success) { + moved = true; + this.stuckCounter = 0; + break; + } + } + } + + await this.exec('turtle.turnRight()'); + this.turtle.facing = (this.turtle.facing + 1) % 4; + } + + if (!moved) { + this.stuckCounter++; + const success = await this.moveForward(true); + if (!success) { + await this.exec('turtle.turnRight()'); + this.turtle.facing = (this.turtle.facing + 1) % 4; + } + } + } else if (r < 93 && pos.y > 10) { + await this.moveDown(true); + } else { + await this.moveUp(true); + } + + if (this.stuckCounter > 6) { + await this.moveUp(true); + this.stuckCounter = 0; + } + + yield; + } + + _isTooFar() { + const pos = this.turtle.position; + const home = this.turtle.homePosition; + if (!pos || !home) return false; + const dist = Math.abs(pos.x - home.x) + Math.abs(pos.y - home.y) + Math.abs(pos.z - home.z); + return dist > this.maxDistance; + } + + getRecoveryData() { + return { + ...super.getRecoveryData(), + data: { + maxDistance: this.maxDistance, + minFuel: this.minFuel, + }, + }; + } +}