2025-02-10 11:19:12 -08:00
import { Vec3 } from 'vec3' ;
2025-02-09 16:19:30 -08:00
export class ConstructionTaskValidator {
constructor ( data , agent ) {
this . blueprint = new Blueprint ( data . blueprint ) ;
this . agent = agent ;
}
validate ( ) {
try {
//todo: somehow make this more of a percentage or something
2025-03-10 23:08:35 -07:00
// console.log('Validating task...');
2025-02-09 16:19:30 -08:00
let valid = false ;
let score = 0 ;
let result = this . blueprint . check ( this . agent . bot ) ;
2025-04-08 20:57:19 -07:00
if ( result . mismatches . length === 0 ) {
2025-02-09 16:19:30 -08:00
valid = true ;
console . log ( 'Task is complete' ) ;
}
2025-04-08 20:57:19 -07:00
let total _blocks = result . mismatches . length + result . matches . length ;
score = ( result . matches . length / total _blocks ) * 100 ;
2025-03-03 17:06:24 -08:00
return {
"valid" : valid ,
"score" : score
} ;
2025-02-09 16:19:30 -08:00
} catch ( error ) {
console . error ( 'Error validating task:' , error ) ;
2025-03-03 17:06:24 -08:00
return {
"valid" : false ,
"score" : 0
} ;
2025-02-09 16:19:30 -08:00
}
}
}
export function resetConstructionWorld ( bot , blueprint ) {
console . log ( 'Resetting world...' ) ;
const starting _position = blueprint . levels [ 0 ] . coordinates ;
2025-04-08 20:57:19 -07:00
const length = blueprint . levels [ 0 ] . placement . length + 5 ;
const height = blueprint . levels . length + 5 ;
const width = blueprint . levels [ 0 ] . placement [ 0 ] . length + 5 ;
const command = ` /fill ${ starting _position [ 0 ] } ${ starting _position [ 1 ] } ${ starting _position [ 2 ] } ${ starting _position [ 0 ] + width } ${ starting _position [ 1 ] + height } ${ starting _position [ 2 ] + length } air ` ;
2025-02-09 16:19:30 -08:00
bot . chat ( command ) ;
console . log ( 'World reset' ) ;
}
export function checkLevelBlueprint ( agent , levelNum ) {
const blueprint = agent . task . blueprint ;
const bot = agent . bot ;
2025-04-08 20:57:19 -07:00
const result = blueprint . checkLevel ( bot , levelNum ) ;
if ( result . mismatches . length === 0 ) {
return ` Level ${ levelNum } is correct ` ;
} else {
let explanation = blueprint . explainLevelDifference ( bot , levelNum ) ;
return explanation ;
2025-02-09 16:19:30 -08:00
}
}
export function checkBlueprint ( agent ) {
console . log ( 'Checking blueprint...' ) ;
console . log ( agent ) ;
const blueprint = agent . task . blueprint ;
const bot = agent . bot ;
const result = blueprint . check ( bot ) ;
2025-04-08 20:57:19 -07:00
if ( result . mismatches . length === 0 ) {
2025-02-09 16:19:30 -08:00
return "Blueprint is correct" ;
} else {
let explanation = blueprint . explainBlueprintDifference ( bot ) ;
return explanation ;
}
}
2025-02-10 10:25:15 -08:00
2025-02-09 16:19:30 -08:00
export class Blueprint {
constructor ( blueprint ) {
this . data = blueprint ;
}
explain ( ) {
var explanation = "" ;
2025-02-19 18:25:59 -08:00
2025-02-09 16:19:30 -08:00
for ( let item of this . data . levels ) {
var coordinates = item . coordinates ;
explanation += ` Level ${ item . level } : ` ;
explanation += ` Start at coordinates X: ${ coordinates [ 0 ] } , Y: ${ coordinates [ 1 ] } , Z: ${ coordinates [ 2 ] } ` ;
2025-02-19 18:25:59 -08:00
// let placement_string = this._getPlacementString(item.placement);
// explanation += `\n${placement_string}\n`;
2025-02-09 16:19:30 -08:00
}
return explanation ;
}
_getPlacementString ( placement ) {
var placement _string = "[\n" ;
for ( let row of placement ) {
placement _string += "[" ;
2025-04-08 20:57:19 -07:00
for ( let i = 0 ; i < row . length - 1 ; i ++ ) {
2025-02-09 16:19:30 -08:00
let item = row [ i ] ;
placement _string += ` ${ item } , ` ;
}
2025-04-08 20:57:19 -07:00
let final _item = row [ row . length - 1 ] ;
2025-02-09 16:19:30 -08:00
placement _string += ` ${ final _item } ], \n ` ;
}
placement _string += "]" ;
return placement _string ;
}
explainLevel ( levelNum ) {
const levelData = this . data . levels [ levelNum ] ;
var explanation = ` Level ${ levelData . level } ` ;
explanation += ` starting at coordinates X: ${ levelData . coordinates [ 0 ] } , Y: ${ levelData . coordinates [ 1 ] } , Z: ${ levelData . coordinates [ 2 ] } ` ;
let placement _string = this . _getPlacementString ( levelData . placement ) ;
explanation += ` \n ${ placement _string } \n ` ;
return explanation ;
}
explainBlueprintDifference ( bot ) {
var explanation = "" ;
const levels = this . data . levels ;
2025-04-08 20:57:19 -07:00
for ( let i = 0 ; i < levels . length ; i ++ ) {
2025-02-09 16:19:30 -08:00
let level _explanation = this . explainLevelDifference ( bot , i ) ;
explanation += level _explanation + "\n" ;
}
return explanation ;
}
explainLevelDifference ( bot , levelNum ) {
const results = this . checkLevel ( bot , levelNum ) ;
const mismatches = results . mismatches ;
const levelData = this . data . levels [ levelNum ] ;
2025-04-08 20:57:19 -07:00
if ( mismatches . length === 0 ) {
2025-02-09 16:19:30 -08:00
return ` Level ${ levelData . level } is complete ` ;
}
var explanation = ` Level ${ levelData . level } ` ;
// explanation += `at coordinates X: ${levelData.coordinates[0]}, Y: ${levelData.coordinates[1]}, Z: ${levelData.coordinates[2]}`;
explanation += " requires the following fixes:\n" ;
for ( let item of mismatches ) {
if ( item . actual === 'air' ) {
explanation += ` Place ${ item . expected } at coordinates X: ${ item . coordinates [ 0 ] } , Y: ${ item . coordinates [ 1 ] } , Z: ${ item . coordinates [ 2 ] } \n ` ;
} else if ( item . expected === 'air' ) {
explanation += ` Remove the ${ item . actual } at coordinates X: ${ item . coordinates [ 0 ] } , Y: ${ item . coordinates [ 1 ] } , Z: ${ item . coordinates [ 2 ] } \n ` ;
} else {
explanation += ` Replace the ${ item . actual } with a ${ item . expected } at coordinates X: ${ item . coordinates [ 0 ] } , Y: ${ item . coordinates [ 1 ] } , Z: ${ item . coordinates [ 2 ] } \n ` ;
}
}
return explanation ;
}
check ( bot ) {
if ( ! bot || typeof bot !== 'object' || ! bot . hasOwnProperty ( 'blockAt' ) ) {
throw new Error ( 'Invalid bot object. Expected a mineflayer bot.' ) ;
}
const levels = this . data . levels ;
const mismatches = [ ] ;
const matches = [ ] ;
2025-04-08 20:57:19 -07:00
for ( let i = 0 ; i < levels . length ; i ++ ) {
2025-02-09 16:19:30 -08:00
const result = this . checkLevel ( bot , i ) ;
mismatches . push ( ... result . mismatches ) ;
matches . push ( ... result . matches ) ;
}
return {
"mismatches" : mismatches ,
"matches" : matches
} ;
}
checkLevel ( bot , levelNum ) {
const levelData = this . data . levels [ levelNum ] ;
const startCoords = levelData . coordinates ;
const placement = levelData . placement ;
const mismatches = [ ] ;
const matches = [ ] ;
2025-04-08 20:57:19 -07:00
for ( let zOffset = 0 ; zOffset < placement . length ; zOffset ++ ) {
2025-02-09 16:19:30 -08:00
const row = placement [ zOffset ] ;
2025-04-08 20:57:19 -07:00
for ( let xOffset = 0 ; xOffset < row . length ; xOffset ++ ) {
2025-02-09 16:19:30 -08:00
const blockName = row [ xOffset ] ;
const x = startCoords [ 0 ] + xOffset ;
const y = startCoords [ 1 ] ;
const z = startCoords [ 2 ] + zOffset ;
2025-02-19 18:25:59 -08:00
2025-02-09 16:19:30 -08:00
try {
const blockAtLocation = bot . blockAt ( new Vec3 ( x , y , z ) ) ;
2025-02-19 18:25:59 -08:00
const actualBlockName = blockAtLocation ? bot . registry . blocks [ blockAtLocation . type ] . name : "air" ;
2025-02-25 15:39:26 -08:00
// Skip if both expected and actual block are air
2025-02-19 18:25:59 -08:00
if ( blockName === "air" && actualBlockName === "air" ) {
continue ;
}
if ( actualBlockName !== blockName ) {
2025-02-09 16:19:30 -08:00
mismatches . push ( {
level : levelData . level ,
coordinates : [ x , y , z ] ,
expected : blockName ,
2025-02-19 18:25:59 -08:00
actual : actualBlockName
2025-02-09 16:19:30 -08:00
} ) ;
} else {
matches . push ( {
level : levelData . level ,
coordinates : [ x , y , z ] ,
expected : blockName ,
2025-02-19 18:25:59 -08:00
actual : actualBlockName
2025-02-09 16:19:30 -08:00
} ) ;
}
} catch ( err ) {
console . error ( ` Error getting block at ( ${ x } , ${ y } , ${ z } ): ` , err ) ;
return false ; // Stop checking if there's an issue getting blocks
}
}
}
return {
"mismatches" : mismatches ,
"matches" : matches
} ;
}
/ * *
* Takes in the blueprint , and then converts it into a set of / s e t b l o c k c o m m a n d s f o r t h e b o t t o f o l l o w
* @ Returns : An object containing the setblock commands as a list of strings , and a position nearby the blueprint but not in it
* @ param blueprint
* /
autoBuild ( ) {
const commands = [ ] ;
let blueprint = this . data
let minX = Infinity , maxX = - Infinity ;
let minY = Infinity , maxY = - Infinity ;
let minZ = Infinity , maxZ = - Infinity ;
for ( const level of blueprint . levels ) {
console . log ( level . level )
const baseX = level . coordinates [ 0 ] ;
const baseY = level . coordinates [ 1 ] ;
const baseZ = level . coordinates [ 2 ] ;
const placement = level . placement ;
// Update bounds
minX = Math . min ( minX , baseX ) ;
2025-04-08 20:57:19 -07:00
maxX = Math . max ( maxX , baseX + placement [ 0 ] . length - 1 ) ;
2025-02-09 16:19:30 -08:00
minY = Math . min ( minY , baseY ) ;
maxY = Math . max ( maxY , baseY ) ;
minZ = Math . min ( minZ , baseZ ) ;
2025-04-08 20:57:19 -07:00
maxZ = Math . max ( maxZ , baseZ + placement . length - 1 ) ;
2025-02-09 16:19:30 -08:00
// Loop through the 2D placement array
2025-04-08 20:57:19 -07:00
for ( let z = 0 ; z < placement . length ; z ++ ) {
for ( let x = 0 ; x < placement [ z ] . length ; x ++ ) {
2025-02-09 16:19:30 -08:00
const blockType = placement [ z ] [ x ] ;
if ( blockType ) {
const setblockCommand = ` /setblock ${ baseX + x } ${ baseY } ${ baseZ + z } ${ blockType } ` ;
commands . push ( setblockCommand ) ;
}
}
}
}
// Calculate a position nearby the blueprint but not in it
const nearbyPosition = {
x : maxX + 5 , // Move 5 blocks to the right
y : minY , // Stay on the lowest level of the blueprint
z : minZ // Stay aligned with the front of the blueprint
} ;
return { commands , nearbyPosition } ;
}
/ * *
* Takes in a blueprint , and returns a set of commands to clear up the space .
*
* /
autoDelete ( ) {
2025-04-08 20:57:19 -07:00
console . log ( "auto delete called!" )
2025-02-09 16:19:30 -08:00
const commands = [ ] ;
let blueprint = this . data
let minX = Infinity , maxX = - Infinity ;
let minY = Infinity , maxY = - Infinity ;
let minZ = Infinity , maxZ = - Infinity ;
for ( const level of blueprint . levels ) {
const baseX = level . coordinates [ 0 ] ;
const baseY = level . coordinates [ 1 ] ;
const baseZ = level . coordinates [ 2 ] ;
const placement = level . placement ;
// Update bounds
2025-04-08 20:57:19 -07:00
minX = Math . min ( minX , baseX ) ;
maxX = Math . max ( maxX , baseX + placement [ 0 ] . length - 1 ) ;
2025-02-09 16:19:30 -08:00
minY = Math . min ( minY , baseY ) ;
maxY = Math . max ( maxY , baseY ) ;
2025-04-08 20:57:19 -07:00
minZ = Math . min ( minZ , baseZ ) ;
maxZ = Math . max ( maxZ , baseZ + placement . length - 1 ) ;
2025-02-09 16:19:30 -08:00
// Loop through the 2D placement array
2025-04-08 20:57:19 -07:00
for ( let z = 0 ; z < placement . length ; z ++ ) {
for ( let x = 0 ; x < placement [ z ] . length ; x ++ ) {
2025-02-09 16:19:30 -08:00
const blockType = placement [ z ] [ x ] ;
if ( blockType ) {
const setblockCommand = ` /setblock ${ baseX + x } ${ baseY } ${ baseZ + z } air ` ;
commands . push ( setblockCommand ) ;
}
}
}
}
// Calculate a position nearby the blueprint but not in it
const nearbyPosition = {
2025-04-08 20:57:19 -07:00
x : maxX + 5 , // Move 5 blocks to the right
2025-02-09 16:19:30 -08:00
y : minY , // Stay on the lowest level of the blueprint
2025-04-08 20:57:19 -07:00
z : minZ // Stay aligned with the front of the blueprint
2025-02-09 16:19:30 -08:00
} ;
return { commands , nearbyPosition } ;
2025-02-19 18:25:59 -08:00
}
2025-02-10 10:25:15 -08:00
}
/ * *
* Systematically builds the houses by placing them next to the already existing rooms . Still uses randomness for what gets placed next .
* @ param m width of the 3 D space
* @ param n height of the 3 D space
* @ param p depth of the 3 D space
* @ param rooms Number of rooms to attempt to generate
2025-02-10 11:19:12 -08:00
* @ param minRoomWidth
* @ param minRoomLength
* @ param minRoomDepth
* @ param roomVariance How much the room size will vary
2025-02-10 10:25:15 -08:00
* @ param wrapping material of wrapping ( air , glass , etc ... ) - > default is air
* @ param carpetStyle 0 , 1 , 2 increasingly more complex
* @ param windowStyle 0 , 1 , 2 increasingly more complex
* @ param complexity 0 , 1 , 2 , 3 , 4 for increasingly complex materials for room generation
2025-03-06 16:16:02 -08:00
* @ param startCoord an array of the x , y , z coordinates to create the blueprint . default = [ 148 , - 60 , - 170 ]
2025-02-10 11:19:12 -08:00
* @ returns a blueprint object
2025-02-10 10:25:15 -08:00
* /
export function proceduralGeneration ( m = 20 ,
2025-02-10 11:19:12 -08:00
n = 20 ,
p = 20 ,
rooms = 8 ,
minRoomWidth = 5 ,
minRoomLength = 5 ,
minRoomDepth = 6 ,
roomVariance = 5 ,
wrapping = "air" ,
carpetStyle = 1 ,
windowStyle = 1 ,
2025-03-06 16:16:02 -08:00
complexity = 4 ,
startCoord = [ 148 , - 60 , - 170 ] ) {
2025-02-10 10:25:15 -08:00
// Build 3D space
2025-04-08 20:57:19 -07:00
const matrix = Array . from ( { length : p } , ( ) =>
Array . from ( { length : m } , ( ) =>
2025-02-10 10:25:15 -08:00
Array ( n ) . fill ( 'air' )
)
) ;
2025-03-14 15:39:58 -07:00
// todo: extrapolate into another param? then have set materials be dynamic?
2025-02-10 10:25:15 -08:00
let roomMaterials = [ "stone" , "terracotta" , "quartz_block" , "copper_block" , "purpur_block" ]
2025-04-08 20:57:19 -07:00
if ( complexity < roomMaterials . length ) {
2025-02-10 10:25:15 -08:00
roomMaterials = roomMaterials . slice ( 0 , complexity + 1 ) ;
}
// Mark entire outer border with 'stone'
for ( let z = 0 ; z < p ; z ++ ) {
for ( let x = 0 ; x < m ; x ++ ) {
for ( let y = 0 ; y < n ; y ++ ) {
if (
z === 0 || z === p - 1 || // Top and bottom faces
x === 0 || x === m - 1 || // Front and back faces
y === 0 || y === n - 1 // Left and right faces
) {
matrix [ z ] [ x ] [ y ] = 'stone' ;
}
}
}
}
// Replace outer layer with wrap
for ( let z = 0 ; z < p ; z ++ ) {
for ( let x = 0 ; x < m ; x ++ ) {
for ( let y = 0 ; y < n ; y ++ ) {
if (
( z === p - 1 || // Top face
x === 0 || x === m - 1 || // Front and back faces
y === 0 || y === n - 1 ) // Left and right faces
) {
matrix [ z ] [ x ] [ y ] = wrapping ;
}
}
}
}
let placedRooms = 0 ;
let lastRoom = null ;
// Direction probabilities (e.g., 'above': 40%, 'left': 15%, etc.)
const directionChances = [
{ direction : 'above' , chance : 0.15 } ,
{ direction : 'left' , chance : 0.15 } ,
{ direction : 'right' , chance : 0.15 } ,
{ direction : 'forward' , chance : 0.15 } ,
{ direction : 'backward' , chance : 0.15 } ,
] ;
// Function to pick a random direction based on percentages
function getRandomDirection ( ) {
const rand = Math . random ( ) ;
let cumulative = 0 ;
for ( const { direction , chance } of directionChances ) {
cumulative += chance ;
if ( rand <= cumulative ) return direction ;
}
return directionChances [ 1 ] . direction ; // Fallback to the first direction
}
// Ensures no rooms overlap except at edges
function isSpaceValid ( newX , newY , newZ , newLength , newWidth , newDepth ) {
for ( let di = 0 ; di < newDepth ; di ++ ) {
for ( let dj = 0 ; dj < newLength ; dj ++ ) {
for ( let dk = 0 ; dk < newWidth ; dk ++ ) {
const x = newX + dj ;
const y = newY + dk ;
const z = newZ + di ;
// Skip checking the outermost borders of the new room (these can overlap with stone)
if ( dj === 0 || dj === newLength - 1 ||
dk === 0 || dk === newWidth - 1 ||
di === 0 || di === newDepth - 1 ) {
continue ;
}
// For non-border spaces, ensure they're air
if ( matrix [ z ] [ x ] [ y ] !== 'air' ) {
return false ;
}
}
}
}
return true ;
}
function validateAndBuildBorder ( matrix , newX , newY , newZ , newLength , newWidth , newDepth , m , n , p , material ) {
// Allow rooms to use the matrix edges (note the <= instead of <)
if (
newX >= 0 && newX + newLength <= m &&
newY >= 0 && newY + newWidth <= n &&
newZ >= 0 && newZ + newDepth <= p &&
isSpaceValid ( newX , newY , newZ , newLength , newWidth , newDepth )
) {
2025-03-12 19:44:28 -07:00
// console.log(`Placing room at (${newX}, ${newY}, ${newZ}) with dimensions (${newLength}x${newWidth}x${newDepth})`);
2025-02-10 10:25:15 -08:00
for ( let di = 0 ; di < newDepth ; di ++ ) {
for ( let dj = 0 ; dj < newLength ; dj ++ ) {
for ( let dk = 0 ; dk < newWidth ; dk ++ ) {
const x = newX + dj ;
const y = newY + dk ;
const z = newZ + di ;
// If this is at a matrix border, don't modify it
2025-02-13 14:49:03 -08:00
if ( z === 0 ) {
2025-02-10 10:25:15 -08:00
continue ;
}
// if (x === 0 || x === m - 1 ||
// y === 0 || y === n - 1 ||
// z === 0 || z === p - 1) {
// continue;
// }
// For non-border spaces, check if this is a floor that should be shared
//was: === 'stone'
2025-02-13 14:49:03 -08:00
if ( di === 0 && matrix [ z - 1 ] [ x ] [ y ] !== 'air' ) {
2025-02-10 10:25:15 -08:00
// Skip creating floor if there's a ceiling below
matrix [ z ] [ x ] [ y ] = 'air' ;
} else if ( di === 0 || di === newDepth - 1 ||
dj === 0 || dj === newLength - 1 ||
dk === 0 || dk === newWidth - 1 ) {
matrix [ z ] [ x ] [ y ] = material ;
} else {
matrix [ z ] [ x ] [ y ] = 'air' ;
}
}
}
}
return true ;
}
return false ;
}
function addDoor ( matrix , x , y , z , material ) {
matrix [ z ] [ x ] [ y ] = material ;
// Place the lower half of the door
2025-03-18 17:02:13 -07:00
// matrix[z + 1][x][y] = 'dark_oak_door[half=lower, hinge=left]';
matrix [ z + 1 ] [ x ] [ y ] = 'dark_oak_door' ;
2025-02-10 10:25:15 -08:00
// Place the upper half of the door
2025-03-18 17:02:13 -07:00
// matrix[z + 2][x][y] = 'dark_oak_door[half=upper, hinge=left]';
matrix [ z + 2 ] [ x ] [ y ] = 'dark_oak_door' ;
2025-02-10 10:25:15 -08:00
}
// Takes in a room and randomly converts some faces to be windows
function addWindowsAsSquares ( matrix , x , y , z , newLength , newWidth , newDepth , material ) {
// Matrix dimensions
2025-04-08 20:57:19 -07:00
const matrixDepth = matrix . length ;
const matrixLength = matrix [ 0 ] . length ;
const matrixWidth = matrix [ 0 ] [ 0 ] . length ;
2025-02-13 14:49:03 -08:00
const windowX = Math . ceil ( minRoomWidth / 2 )
const windowY = Math . ceil ( minRoomLength / 2 )
const windowZ = Math . ceil ( minRoomDepth / 2 )
2025-02-10 10:25:15 -08:00
// Helper function to check if coordinates are within bounds
function isInBounds ( z , x , y ) {
return z >= 0 && z < matrixDepth &&
x >= 0 && x < matrixLength &&
y >= 0 && y < matrixWidth ;
}
// Front and back faces (z is constant)
if ( Math . random ( ) < 0.8 ) {
2025-02-13 14:49:03 -08:00
let centerX = x + Math . floor ( newLength / 2 - windowX / 2 ) ;
let centerY = y + Math . floor ( newWidth / 2 - windowY / 2 ) ;
2025-02-10 10:25:15 -08:00
2025-02-10 11:19:12 -08:00
for ( let dx = 0 ; dx <= windowX ; dx ++ ) {
for ( let dy = 0 ; dy <= windowY ; dy ++ ) {
2025-02-10 10:25:15 -08:00
let frontZ = z ;
let backZ = z + newDepth - 1 ;
if ( isInBounds ( frontZ , centerX + dx , centerY + dy ) &&
matrix [ frontZ ] [ centerX + dx ] [ centerY + dy ] === material ) {
matrix [ frontZ ] [ centerX + dx ] [ centerY + dy ] = 'glass' ;
}
if ( isInBounds ( backZ , centerX + dx , centerY + dy ) &&
matrix [ backZ ] [ centerX + dx ] [ centerY + dy ] === material ) {
matrix [ backZ ] [ centerX + dx ] [ centerY + dy ] = 'glass' ;
}
}
}
}
// Left and right faces (x is constant)
if ( Math . random ( ) < 0.8 ) {
2025-02-13 14:49:03 -08:00
let centerZ = z + Math . floor ( newDepth / 2 - windowZ / 2 ) ;
let centerY = y + Math . floor ( newWidth / 2 - windowY / 2 ) ;
2025-02-10 10:25:15 -08:00
2025-02-10 11:19:12 -08:00
for ( let dz = 0 ; dz <= windowZ ; dz ++ ) {
for ( let dy = 0 ; dy <= windowY ; dy ++ ) {
2025-02-10 10:25:15 -08:00
let leftX = x ;
let rightX = x + newLength - 1 ;
if ( isInBounds ( centerZ + dz , leftX , centerY + dy ) &&
matrix [ centerZ + dz ] [ leftX ] [ centerY + dy ] === material ) {
matrix [ centerZ + dz ] [ leftX ] [ centerY + dy ] = 'glass' ;
}
if ( isInBounds ( centerZ + dz , rightX , centerY + dy ) &&
matrix [ centerZ + dz ] [ rightX ] [ centerY + dy ] === material ) {
matrix [ centerZ + dz ] [ rightX ] [ centerY + dy ] = 'glass' ;
}
}
}
}
// Top and bottom faces (y is constant)
if ( Math . random ( ) < 0.8 ) {
2025-02-13 14:49:03 -08:00
let centerX = x + Math . floor ( newLength / 2 - windowX / 2 ) ;
2025-02-10 11:19:12 -08:00
let centerZ = z + Math . floor ( newDepth / 2 - windowZ / 2 ) ;
2025-02-10 10:25:15 -08:00
2025-02-10 11:19:12 -08:00
for ( let dx = 0 ; dx <= windowX ; dx ++ ) {
for ( let dz = 0 ; dz <= windowZ ; dz ++ ) {
2025-02-10 10:25:15 -08:00
let bottomY = y ;
let topY = y + newWidth - 1 ;
if ( isInBounds ( centerZ + dz , centerX + dx , bottomY ) &&
matrix [ centerZ + dz ] [ centerX + dx ] [ bottomY ] === material ) {
matrix [ centerZ + dz ] [ centerX + dx ] [ bottomY ] = 'glass' ;
}
if ( isInBounds ( centerZ + dz , centerX + dx , topY ) &&
matrix [ centerZ + dz ] [ centerX + dx ] [ topY ] === material ) {
matrix [ centerZ + dz ] [ centerX + dx ] [ topY ] = 'glass' ;
}
}
}
}
}
function addWindowsAsPlane ( matrix , x , y , z , newLength , newWidth , newDepth , material ) {
// Ensure the new dimensions are within bounds
2025-04-08 20:57:19 -07:00
const maxX = matrix [ 0 ] . length ;
const maxY = matrix [ 0 ] [ 0 ] . length ;
const maxZ = matrix . length ;
2025-02-10 10:25:15 -08:00
// Each face has a 30% chance of becoming a window
if ( Math . random ( ) < 0.8 ) {
for ( let dx = 0 ; dx < newLength ; dx ++ ) {
for ( let dy = 0 ; dy < newWidth ; dy ++ ) {
let frontZ = z ;
let backZ = z + newDepth - 1 ;
// Check bounds before modifying the matrix
if ( frontZ >= 0 && frontZ < maxZ && x + dx >= 0 && x + dx < maxX && y + dy >= 0 && y + dy < maxY ) {
if ( matrix [ frontZ ] [ x + dx ] [ y + dy ] === material ) {
matrix [ frontZ ] [ x + dx ] [ y + dy ] = 'glass' ;
}
}
if ( backZ >= 0 && backZ < maxZ && x + dx >= 0 && x + dx < maxX && y + dy >= 0 && y + dy < maxY ) {
if ( matrix [ backZ ] [ x + dx ] [ y + dy ] === material ) {
matrix [ backZ ] [ x + dx ] [ y + dy ] = 'glass' ;
}
}
}
}
}
if ( Math . random ( ) < 0.8 ) {
for ( let dz = 0 ; dz < newDepth ; dz ++ ) {
for ( let dy = 0 ; dy < newWidth ; dy ++ ) {
let leftX = x ;
let rightX = x + newLength - 1 ;
// Check bounds before modifying the matrix
if ( leftX >= 0 && leftX < maxX && z + dz >= 0 && z + dz < maxZ && y + dy >= 0 && y + dy < maxY ) {
if ( matrix [ z + dz ] [ leftX ] [ y + dy ] === material ) {
matrix [ z + dz ] [ leftX ] [ y + dy ] = 'glass' ;
}
}
if ( rightX >= 0 && rightX < maxX && z + dz >= 0 && z + dz < maxZ && y + dy >= 0 && y + dy < maxY ) {
if ( matrix [ z + dz ] [ rightX ] [ y + dy ] === material ) {
matrix [ z + dz ] [ rightX ] [ y + dy ] = 'glass' ;
}
}
}
}
}
}
2025-03-20 14:07:00 -07:00
//still a little buggy
2025-04-08 20:57:19 -07:00
function addStairs ( matrix , x , y , z , length , width , material ) {
2025-02-10 10:25:15 -08:00
let currentZ = z ;
2025-03-20 14:07:00 -07:00
let currentX = x + 1 ;
let currentY = y + 1 ;
let direction = 0 ;
let stepCount = 0 ;
2025-04-08 20:57:19 -07:00
const maxSteps = length * width ; // Safety limit
2025-03-20 14:07:00 -07:00
2025-04-08 20:57:19 -07:00
while ( currentZ >= 0 && currentX < x + length - 1 && currentY < y + width - 1 && stepCount < maxSteps ) {
2025-03-20 14:07:00 -07:00
// Place stair block
matrix [ currentZ ] [ currentX ] [ currentY ] = material || 'stone' ;
// Clear 3 blocks above for headroom
for ( let i = 1 ; i <= 3 ; i ++ ) {
2025-04-08 20:57:19 -07:00
if ( currentZ + i < matrix . length ) {
2025-03-20 14:07:00 -07:00
matrix [ currentZ + i ] [ currentX ] [ currentY ] = 'air' ;
}
}
2025-02-10 10:25:15 -08:00
2025-03-20 14:07:00 -07:00
// Move to next position based on direction
if ( direction === 0 ) {
currentX ++ ;
2025-04-08 20:57:19 -07:00
if ( currentX >= x + length - 1 ) {
currentX = x + length - 2 ;
2025-03-20 14:07:00 -07:00
direction = 1 ;
} else {
currentZ -- ;
}
} else {
currentY ++ ;
if ( currentY >= y + width - 1 ) {
currentY = y + width - 2 ;
direction = 0 ;
} else {
currentZ -- ;
}
}
2025-02-10 10:25:15 -08:00
2025-03-20 14:07:00 -07:00
stepCount ++ ;
2025-02-10 10:25:15 -08:00
}
}
2025-02-10 11:19:12 -08:00
function addCarpet ( probability , matrix , newX , newY , newZ , newLength , newWidth , material ) {
2025-02-10 10:25:15 -08:00
let colors = [ "blue" , "cyan" , "light_blue" , "lime" ] ;
// Iterate through the dimensions of the room
2025-02-13 14:49:03 -08:00
for ( let dx = 1 ; dx < newLength - 1 ; dx ++ ) {
for ( let dy = 1 ; dy < newWidth - 1 ; dy ++ ) {
2025-02-10 10:25:15 -08:00
let x = newX + dx ;
let y = newY + dy ;
let z = newZ ; // Start at floor level
// Check if there is floor (not air)
2025-02-10 11:19:12 -08:00
if ( matrix [ z ] [ x ] [ y ] === material ) {
2025-02-10 10:25:15 -08:00
// Consider a random probability of adding a carpet
if ( Math . random ( ) < probability ) {
// Choose a random color for the carpet
2025-04-08 20:57:19 -07:00
let randomColor = colors [ Math . floor ( Math . random ( ) * colors . length ) ] ;
2025-02-10 10:25:15 -08:00
// Add carpet one z position above the floor with a random color
matrix [ z + 1 ] [ x ] [ y ] = ` ${ randomColor } _carpet ` ;
}
}
}
}
}
function addLadder ( matrix , x , y , z ) {
2025-02-13 14:49:03 -08:00
let currentZ = z + 1 ;
2025-02-10 10:25:15 -08:00
// turn the floor into air where person would go up
2025-02-13 14:49:03 -08:00
matrix [ currentZ ] [ x + 1 ] [ y ] = 'air' ;
2025-02-10 10:25:15 -08:00
// Build the first 3 ladder segments from floor level downwards
for ( let i = 0 ; i < 3 ; i ++ ) {
2025-03-18 17:02:13 -07:00
// Place stone block behind ladder
matrix [ currentZ ] [ x - 1 ] [ y ] = 'stone' ;
// Place ladder
2025-02-10 10:25:15 -08:00
matrix [ currentZ ] [ x ] [ y ] = 'ladder[facing=north]' ;
2025-03-18 17:02:13 -07:00
currentZ -= 1 ;
2025-02-10 10:25:15 -08:00
}
// Continue building ladder downwards until a floor is hit or we reach the bottom
while ( currentZ >= 0 && matrix [ currentZ ] [ x ] [ y ] === 'air' ) {
2025-03-18 17:02:13 -07:00
// Place stone block behind ladder
matrix [ currentZ ] [ x - 1 ] [ y ] = 'stone' ;
2025-02-10 10:25:15 -08:00
// Place ladder
matrix [ currentZ ] [ x ] [ y ] = 'ladder[facing=north]' ;
// Move down
currentZ -- ;
}
}
2025-02-13 14:49:03 -08:00
function embellishments ( carpet , windowStyle , matrix , newX , newY , newZ , newLength , newWidth , newDepth , material ) {
2025-02-10 10:25:15 -08:00
switch ( windowStyle ) {
case 0 :
break ;
case 1 :
addWindowsAsSquares ( matrix , newZ , newY , newZ , newLength , newWidth , newDepth , material )
break ;
case 2 :
addWindowsAsPlane ( matrix , newZ , newY , newZ , newLength , newWidth , newDepth , material )
}
switch ( carpet ) {
case 0 :
break ;
case 1 :
2025-02-13 14:49:03 -08:00
addCarpet ( 0.3 , matrix , newX , newY , newZ , newLength , newWidth , material ) ;
2025-02-10 10:25:15 -08:00
break ;
case 2 :
2025-02-13 14:49:03 -08:00
addCarpet ( 0.7 , matrix , newX , newY , newZ , newLength , newWidth , material )
2025-02-10 10:25:15 -08:00
break ;
}
}
// Places rooms until we can't, or we place all
// attempts random configurations of rooms in random directions.
while ( placedRooms < rooms ) {
let roomPlaced = false ;
for ( let attempt = 0 ; attempt < 150 ; attempt ++ ) {
2025-04-08 20:57:19 -07:00
const material = roomMaterials [ Math . floor ( Math . random ( ) * roomMaterials . length ) ] ;
2025-02-10 10:25:15 -08:00
// dimensions of room
2025-02-10 11:19:12 -08:00
const newLength = Math . max ( minRoomLength , Math . floor ( Math . random ( ) * roomVariance ) + minRoomLength ) ;
const newWidth = Math . max ( minRoomWidth , Math . floor ( Math . random ( ) * roomVariance ) + minRoomWidth ) ;
2025-02-13 14:49:03 -08:00
const newDepth = Math . max ( minRoomDepth , Math . floor ( Math . random ( ) * Math . floor ( roomVariance / 2 ) ) + minRoomDepth ) ;
2025-02-10 10:25:15 -08:00
let newX , newY , newZ ;
// first room is special
if ( placedRooms === 0 ) {
// First room placement
newX = Math . floor ( Math . random ( ) * ( m - newLength - 1 ) ) + 1 ;
newY = Math . floor ( Math . random ( ) * ( n - newWidth - 1 ) ) + 1 ;
newZ = 0 ; // Ground floor
if ( validateAndBuildBorder ( matrix , newX , newY , newZ , newLength , newWidth , newDepth , m , n , p , material ) ) {
2025-04-08 20:57:19 -07:00
lastRoom = { x : newX , y : newY , z : newZ , length : newLength , width : newWidth , depth : newDepth } ;
2025-02-10 10:25:15 -08:00
roomPlaced = true ;
placedRooms ++ ;
// Add doors to all four sides
// Left side
addDoor ( matrix , newX , newY + Math . floor ( newWidth / 2 ) , newZ , material ) ;
// Right side
addDoor ( matrix , newX + newLength - 1 , newY + Math . floor ( newWidth / 2 ) , newZ , material ) ;
// Front side
addDoor ( matrix , newX + Math . floor ( newLength / 2 ) , newY , newZ , material ) ;
// Back side
addDoor ( matrix , newX + Math . floor ( newLength / 2 ) , newY + newWidth - 1 , newZ , material ) ;
addCarpet ( 0.7 , matrix , newX , newY , newZ , newLength , newWidth )
}
break ;
2025-02-13 14:49:03 -08:00
} else {
2025-02-10 10:25:15 -08:00
const direction = getRandomDirection ( ) ;
switch ( direction ) {
case 'above' :
newX = lastRoom . x ;
newY = lastRoom . y ;
newZ = lastRoom . z + lastRoom . depth - 1 ;
if ( validateAndBuildBorder ( matrix , newX , newY , newZ , newLength , newWidth , newDepth , m , n , p , material ) ) {
embellishments ( carpetStyle , windowStyle , matrix , newX , newY , newZ , newLength , newWidth , newDepth , material )
2025-04-08 20:57:19 -07:00
// addLadder(matrix, lastRoom.x + Math.floor(lastRoom.length / 2),
2025-03-20 14:07:00 -07:00
// lastRoom.y + Math.floor(lastRoom.width / 2),
// newZ); // Adding the ladder
addStairs ( matrix , newX , newY , newZ , newLength , newWidth , material )
2025-02-10 10:25:15 -08:00
2025-04-08 20:57:19 -07:00
lastRoom = { x : newX , y : newY , z : newZ , length : newLength , width : newWidth , depth : newDepth } ;
2025-02-10 10:25:15 -08:00
roomPlaced = true ;
placedRooms ++ ;
break ;
}
break ;
case 'left' :
newX = lastRoom . x - newLength + 1 ;
newY = lastRoom . y ;
newZ = lastRoom . z ;
if ( validateAndBuildBorder ( matrix , newX , newY , newZ , newLength , newWidth , newDepth , m , n , p , material ) ) {
embellishments ( carpetStyle , windowStyle , matrix , newX , newY , newZ , newLength , newWidth , newDepth , material )
addDoor ( matrix , lastRoom . x , lastRoom . y + Math . floor ( lastRoom . width / 2 ) , lastRoom . z , material ) ;
2025-04-08 20:57:19 -07:00
lastRoom = { x : newX , y : newY , z : newZ , length : newLength , width : newWidth , depth : newDepth } ;
2025-02-10 10:25:15 -08:00
roomPlaced = true ;
placedRooms ++ ;
break ;
}
break ;
case 'right' :
2025-04-08 20:57:19 -07:00
newX = lastRoom . x + lastRoom . length - 1 ;
2025-02-10 10:25:15 -08:00
newY = lastRoom . y ;
newZ = lastRoom . z ;
if ( validateAndBuildBorder ( matrix , newX , newY , newZ , newLength , newWidth , newDepth , m , n , p , material ) ) {
embellishments ( carpetStyle , windowStyle , matrix , newX , newY , newZ , newLength , newWidth , newDepth , material )
2025-04-08 20:57:19 -07:00
addDoor ( matrix , lastRoom . x + lastRoom . length - 1 ,
2025-02-10 10:25:15 -08:00
lastRoom . y + Math . floor ( lastRoom . width / 2 ) ,
lastRoom . z , material ) ;
2025-04-08 20:57:19 -07:00
lastRoom = { x : newX , y : newY , z : newZ , length : newLength , width : newWidth , depth : newDepth } ;
2025-02-10 10:25:15 -08:00
roomPlaced = true ;
placedRooms ++ ;
break ;
}
break ;
case 'forward' :
newX = lastRoom . x ;
newY = lastRoom . y + lastRoom . width - 1 ;
newZ = lastRoom . z ;
if ( validateAndBuildBorder ( matrix , newX , newY , newZ , newLength , newWidth , newDepth , m , n , p , material ) ) {
embellishments ( carpetStyle , windowStyle , matrix , newX , newY , newZ , newLength , newWidth , newDepth , material )
2025-04-08 20:57:19 -07:00
addDoor ( matrix , lastRoom . x + Math . floor ( lastRoom . length / 2 ) ,
2025-02-10 10:25:15 -08:00
lastRoom . y + lastRoom . width - 1 ,
lastRoom . z , material ) ;
2025-04-08 20:57:19 -07:00
lastRoom = { x : newX , y : newY , z : newZ , length : newLength , width : newWidth , depth : newDepth } ;
2025-02-10 10:25:15 -08:00
roomPlaced = true ;
placedRooms ++ ;
break ;
}
break ;
case 'backward' :
newX = lastRoom . x ;
newY = lastRoom . y - newWidth + 1 ;
newZ = lastRoom . z ;
if ( validateAndBuildBorder ( matrix , newX , newY , newZ , newLength , newWidth , newDepth , m , n , p , material ) ) {
embellishments ( carpetStyle , windowStyle , matrix , newX , newY , newZ , newLength , newWidth , newDepth , material )
2025-04-08 20:57:19 -07:00
addDoor ( matrix , lastRoom . x + Math . floor ( lastRoom . length / 2 ) ,
2025-02-10 10:25:15 -08:00
lastRoom . y ,
lastRoom . z , material ) ;
2025-04-08 20:57:19 -07:00
lastRoom = { x : newX , y : newY , z : newZ , length : newLength , width : newWidth , depth : newDepth } ;
2025-02-10 10:25:15 -08:00
roomPlaced = true ;
placedRooms ++ ;
break ;
}
break ;
}
if ( roomPlaced ) {
break ;
}
}
}
if ( ! roomPlaced ) {
console . warn ( ` Could not place room ${ placedRooms + 1 } ` ) ;
break ;
}
}
2025-03-03 21:41:38 -08:00
// uncomment to visualize blueprint output
// printMatrix(matrix)
2025-03-06 16:16:02 -08:00
return matrixToBlueprint ( matrix , startCoord )
2025-02-10 10:25:15 -08:00
}
2025-03-03 21:41:38 -08:00
/ * *
* for cutesy output
* @ param matrix
* /
function printMatrix ( matrix ) {
matrix . forEach ( ( layer , layerIndex ) => {
console . log ( ` Layer ${ layerIndex } : ` ) ;
layer . forEach ( row => {
console . log (
row . map ( cell => {
switch ( cell ) {
case 'stone' : return '█' ; // Wall
case 'air' : return '.' ; // Open space
case 'dark_oak_door[half=upper, hinge=left]' : return 'D' ;
case 'dark_oak_door[half=lower, hinge=left]' : return 'D' ;
case 'oak_stairs[facing=north]' : return 'S' ; // Stairs
case 'oak_stairs[facing=east]' : return 'S' ; // Stairs
case 'oak_stairs[facing=south]' : return 'S' ; // Stairs
case 'oak_stairs[facing=west]' : return 'S' ; // Stairs
case 'glass' : return 'W'
default : return '?' ; // Unknown or unmarked space
}
} ) . join ( ' ' )
) ;
} ) ;
console . log ( '---' ) ;
} ) ;
}
2025-02-10 10:25:15 -08:00
/ * *
* Converts a 3 D matrix into a Minecraft blueprint format
* @ param { Array < Array < Array < string >>> } matrix - 3 D matrix of block types
* @ param { number [ ] } startCoord - Starting coordinates [ x , y , z ]
* @ returns { Object } a Blueprint object in Minecraft format
* /
function matrixToBlueprint ( matrix , startCoord ) {
// Validate inputs
2025-04-08 20:57:19 -07:00
if ( ! Array . isArray ( matrix ) || ! Array . isArray ( startCoord ) || startCoord . length !== 3 ) {
2025-02-13 14:49:03 -08:00
console . log ( matrix )
2025-02-10 10:25:15 -08:00
throw new Error ( 'Invalid input format' ) ;
}
const [ startX , startY , startZ ] = startCoord ;
// CONSIDER: using blueprint class here?
return {
levels : matrix . map ( ( level , levelIndex ) => ( {
level : levelIndex ,
coordinates : [
startX ,
startY + levelIndex ,
startZ
] ,
placement : level . map ( row =>
// Ensure each block is a string, default to 'air' if undefined
row . map ( block => block ? . toString ( ) || 'air' )
)
} ) )
} ;
}
2025-04-03 22:44:19 -07:00
async function getBlockName ( bot , coordinate ) {
const blockAtLocation = bot . blockAt ( new Vec3 ( coordinate . x , coordinate . y , coordinate . z ) ) ;
2025-04-08 20:57:19 -07:00
return blockAtLocation ? bot . registry . blocks [ blockAtLocation . type ] . name : "air" ;
2025-04-03 22:44:19 -07:00
}
/ * *
* Converts a world location to a blueprint . takes some time to ensure that the chunks are loaded before conversion .
* @ param startCoord - [ x , y , z ] that signifies the start of the blueprint
* @ param y _amount - how many spaces you want to register from the start coordinate in the y dimension
* @ param x _amount - how many spaces in the x direction on minecraft
* @ param z _amount - how many spaces from the start coordinate in the z direction in minecraft
* @ param bot - the mineflayer agent ( ex . andy )
* @ returns - a Blueprint object of the converted blueprint
* /
export async function worldToBlueprint ( startCoord , y _amount , x _amount , z _amount , bot ) {
await bot . waitForChunksToLoad ( ) ;
const materials = { } ;
const levels = [ ] ;
for ( let y = 0 ; y < y _amount ; y ++ ) {
const placement = [ ] ;
const coordinates = [ startCoord . x , startCoord . y + y , startCoord . z ] ;
for ( let z = 0 ; z < z _amount ; z ++ ) {
const row = [ ] ;
for ( let x = 0 ; x < x _amount ; x ++ ) {
const worldCoord = {
x : startCoord . x + x ,
y : startCoord . y + y ,
z : startCoord . z + z
} ;
await bot . waitForChunksToLoad ( worldCoord ) ;
const blockName = await getBlockName ( bot , worldCoord ) ;
row . push ( blockName ) ;
if ( blockName !== 'air' ) {
materials [ blockName ] = ( materials [ blockName ] || 0 ) + 1 ;
}
}
placement . push ( row ) ;
}
levels . push ( {
level : y ,
coordinates : coordinates ,
placement : placement
} )
}
console . log ( levels ) ;
const blueprint _data = {
materials : materials ,
levels : levels
}
return blueprint _data
}
export function blueprintToTask ( blueprint _data , num _agents ) {
let initialInventory = { }
for ( let j = 0 ; j < num _agents ; j ++ ) {
2025-04-08 20:57:19 -07:00
initialInventory [ JSON . stringify ( j ) ] = { "diamond_pickaxe" : 1 , "diamond_axe" : 1 , "diamond_shovel" : 1 } ;
2025-04-03 22:44:19 -07:00
}
let give _agent = 0 ;
2025-04-09 16:34:11 -07:00
console . log ( "materials" , blueprint _data . materials )
2025-04-03 22:44:19 -07:00
for ( const key of Object . keys ( blueprint _data . materials ) ) {
initialInventory [ JSON . stringify ( give _agent ) ] [ key ] = blueprint _data . materials [ key ] ;
give _agent = ( give _agent + 1 ) % num _agents ;
}
const task = {
type : "construction" ,
2025-04-04 13:49:12 -07:00
goal : "Make a structure with the blueprint below" ,
conversation : "Let's share materials and make a structure with the blueprint" ,
2025-04-09 16:34:11 -07:00
agent _count : num _agents ,
2025-04-03 22:44:19 -07:00
blueprint : blueprint _data ,
initial _inventory : initialInventory ,
} ;
return task ;
}
2025-03-20 14:07:00 -07:00
// testing code
2025-04-08 20:57:19 -07:00
// let blueprint = proceduralGeneration(20,10,20)
2025-03-20 14:07:00 -07:00
// const b = new Blueprint(blueprint)
// const result = b.autoBuild();
// const commands = result.commands;
// const nearbyPosition = result.nearbyPosition;
//
2025-04-08 20:57:19 -07:00
//
2025-03-20 14:07:00 -07:00
// import {initBot} from "../../utils/mcdata.js";
// let bot = initBot("andy");
2025-04-08 20:57:19 -07:00
// example usage of world->blueprint function
// bot.once('spawn', async () => {
2025-03-20 14:07:00 -07:00
// console.log("nearby position", nearbyPosition);
// bot.chat(`/tp @andy ${nearbyPosition.x} ${nearbyPosition.y} ${nearbyPosition.z}`);
// for (const command of commands) {
// bot.chat(command);
// }
2025-04-08 20:57:19 -07:00
// const startCoord = {
// x: 148,
// y: -60,
// z: -170
// };
// // [148,-60,-170] is default start for procedural generation
//
// const worldOutput = await worldToBlueprint(startCoord, 20,10,20, bot)
// });