feat: Implement ExploringState class for turtle state machine to autonomously explore and discover the world map
This commit is contained in:
177
server/states/ExploringState.js
Normal file
177
server/states/ExploringState.js
Normal file
@@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user