2025-02-09 16:19:30 -08:00
import { Vec3 } from 'vec3' ;
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
console . log ( 'Validating task...' ) ;
let valid = false ;
let score = 0 ;
let result = this . blueprint . check ( this . agent . bot ) ;
if ( result . mismatches . length === 0 ) {
valid = true ;
console . log ( 'Task is complete' ) ;
}
let total _blocks = result . mismatches . length + result . matches . length ;
score = ( result . matches . length / total _blocks ) * 100 ;
console . log ( ` Task is ${ score } % complete ` ) ;
return valid ;
} catch ( error ) {
console . error ( 'Error validating task:' , error ) ;
return false ;
}
}
}
export function resetConstructionWorld ( bot , blueprint ) {
console . log ( 'Resetting world...' ) ;
const starting _position = blueprint . levels [ 0 ] . coordinates ;
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 ` ;
bot . chat ( command ) ;
console . log ( 'World reset' ) ;
}
export function checkLevelBlueprint ( agent , levelNum ) {
const blueprint = agent . task . blueprint ;
const bot = agent . bot ;
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 ;
}
}
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 ) ;
if ( result . mismatches . length === 0 ) {
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 = "" ;
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 ] } ` ;
let placement _string = this . _getPlacementString ( item . placement ) ;
explanation += ` \n ${ placement _string } \n ` ;
}
return explanation ;
}
_getPlacementString ( placement ) {
var placement _string = "[\n" ;
for ( let row of placement ) {
placement _string += "[" ;
for ( let i = 0 ; i < row . length - 1 ; i ++ ) {
let item = row [ i ] ;
placement _string += ` ${ item } , ` ;
}
let final _item = row [ row . length - 1 ] ;
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 ;
for ( let i = 0 ; i < levels . length ; i ++ ) {
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 ] ;
if ( mismatches . length === 0 ) {
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 = [ ] ;
for ( let i = 0 ; i < levels . length ; i ++ ) {
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 = [ ] ;
for ( let zOffset = 0 ; zOffset < placement . length ; zOffset ++ ) {
const row = placement [ zOffset ] ;
for ( let xOffset = 0 ; xOffset < row . length ; xOffset ++ ) {
const blockName = row [ xOffset ] ;
const x = startCoords [ 0 ] + xOffset ;
const y = startCoords [ 1 ] ;
const z = startCoords [ 2 ] + zOffset ;
try {
const blockAtLocation = bot . blockAt ( new Vec3 ( x , y , z ) ) ;
if ( ! blockAtLocation || blockAtLocation . name !== blockName ) {
mismatches . push ( {
level : levelData . level ,
coordinates : [ x , y , z ] ,
expected : blockName ,
actual : blockAtLocation ? bot . registry . blocks [ blockAtLocation . type ] . name : 'air' // Assuming air if no block
} ) ;
} else {
matches . push ( {
level : levelData . level ,
coordinates : [ x , y , z ] ,
expected : blockName ,
actual : blockAtLocation ? bot . registry . blocks [ blockAtLocation . type ] . name : 'air' // Assuming air if no block
} ) ;
}
} 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 ) ;
maxX = Math . max ( maxX , baseX + placement [ 0 ] . length - 1 ) ;
minY = Math . min ( minY , baseY ) ;
maxY = Math . max ( maxY , baseY ) ;
minZ = Math . min ( minZ , baseZ ) ;
maxZ = Math . max ( maxZ , baseZ + placement . length - 1 ) ;
// Loop through the 2D placement array
for ( let z = 0 ; z < placement . length ; z ++ ) {
for ( let x = 0 ; x < placement [ z ] . length ; x ++ ) {
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 ( ) {
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
minX = Math . min ( minX , baseX ) ;
maxX = Math . max ( maxX , baseX + placement [ 0 ] . length - 1 ) ;
minY = Math . min ( minY , baseY ) ;
maxY = Math . max ( maxY , baseY ) ;
minZ = Math . min ( minZ , baseZ ) ;
maxZ = Math . max ( maxZ , baseZ + placement . length - 1 ) ;
// Loop through the 2D placement array
for ( let z = 0 ; z < placement . length ; z ++ ) {
for ( let x = 0 ; x < placement [ z ] . length ; x ++ ) {
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 = {
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 } ;
}
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
* @ 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
* @ returns a blueprint matrix
* /
export function proceduralGeneration ( m = 20 ,
n = 20 ,
p = 20 ,
rooms = 8 ,
wrapping = "air" ,
carpetStyle = 1 ,
windowStyle = 1 ,
complexity = 4 ) {
// Build 3D space
const matrix = Array . from ( { length : p } , ( ) =>
Array . from ( { length : m } , ( ) =>
Array ( n ) . fill ( 'air' )
)
) ;
// set materials
let roomMaterials = [ "stone" , "terracotta" , "quartz_block" , "copper_block" , "purpur_block" ]
if ( complexity < roomMaterials . length ) {
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 )
) {
console . log ( ` Placing room at ( ${ newX } , ${ newY } , ${ newZ } ) with dimensions ( ${ newLength } x ${ newWidth } x ${ 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 ;
// If this is at a matrix border, don't modify it
if ( z === 0 ) {
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'
if ( di === 0 && matrix [ z - 1 ] [ x ] [ y ] !== 'air' ) {
// 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
matrix [ z + 1 ] [ x ] [ y ] = 'dark_oak_door[half=lower, hinge=left]' ;
// Place the upper half of the door
matrix [ z + 2 ] [ x ] [ y ] = 'dark_oak_door[half=upper, hinge=left]' ;
}
// Takes in a room and randomly converts some faces to be windows
// todo if time: the centering of windows is still kinda buggy
function addWindowsAsSquares ( matrix , x , y , z , newLength , newWidth , newDepth , material ) {
// Matrix dimensions
const matrixDepth = matrix . length ;
const matrixLength = matrix [ 0 ] . length ;
const matrixWidth = matrix [ 0 ] [ 0 ] . length ;
// 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 ) {
let centerX = x + Math . floor ( newLength / 2 ) ;
let centerY = y + Math . floor ( newWidth / 2 ) ;
for ( let dx = - 1 ; dx <= 0 ; dx ++ ) {
for ( let dy = - 1 ; dy <= 0 ; dy ++ ) {
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 ) {
let centerZ = z + Math . floor ( newDepth / 2 ) ;
let centerY = y + Math . floor ( newWidth / 2 ) ;
for ( let dz = - 1 ; dz <= 0 ; dz ++ ) {
for ( let dy = - 1 ; dy <= 0 ; dy ++ ) {
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 ) {
let centerX = x + Math . floor ( newLength / 2 ) ;
let centerZ = z + Math . floor ( newDepth / 2 ) ;
for ( let dx = - 1 ; dx <= 0 ; dx ++ ) {
for ( let dz = - 1 ; dz <= 0 ; dz ++ ) {
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
const maxX = matrix [ 0 ] . length ;
const maxY = matrix [ 0 ] [ 0 ] . length ;
const maxZ = matrix . length ;
// 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' ;
}
}
}
}
}
}
// out of commission
function addStairs ( matrix , x , y , z , direction ) {
let dz = 0 ; // Change in Z direction
let dx = 0 ; // Change in X direction
let facing = '' ;
// Determine direction and facing
switch ( direction ) {
case 'north' :
dz = - 1 ;
facing = 'oak_stairs[facing=north]' ;
break ;
case 'south' :
dz = 1 ;
facing = 'oak_stairs[facing=south]' ;
break ;
case 'east' :
dx = 1 ;
facing = 'oak_stairs[facing=east]' ;
break ;
case 'west' :
dx = - 1 ;
facing = 'oak_stairs[facing=west]' ;
break ;
default :
console . error ( 'Invalid stair direction' ) ;
return ;
}
// Bore stair pattern downwards until we hit a floor or the matrix edge
let currentZ = z ;
while ( currentZ > 0 && matrix [ currentZ - 1 ] [ x ] [ y ] === 'air' ) {
// Place stone as foundation
matrix [ currentZ - 1 ] [ x ] [ y ] = 'stone' ;
// Place stair above the stone
matrix [ currentZ ] [ x ] [ y ] = facing ;
// Move down diagonally
x += dx ;
y += dz ;
currentZ -- ;
// Check if we've hit the edge
if ( x < 0 || x >= matrix [ 0 ] . length || y < 0 || y >= matrix [ 0 ] [ 0 ] . length ) break ;
}
}
function addCarpet ( probability , matrix , newX , newY , newZ , newLength , newWidth ) {
let colors = [ "blue" , "cyan" , "light_blue" , "lime" ] ;
// Iterate through the dimensions of the room
for ( let dx = 1 ; dx < newLength - 1 ; dx ++ ) {
for ( let dy = 1 ; dy < newWidth - 1 ; dy ++ ) {
let x = newX + dx ;
let y = newY + dy ;
let z = newZ ; // Start at floor level
// Check if there is floor (not air)
if ( matrix [ z ] [ x ] [ y ] === 'stone' ) {
// Consider a random probability of adding a carpet
if ( Math . random ( ) < probability ) {
// Choose a random color for the carpet
let randomColor = colors [ Math . floor ( Math . random ( ) * colors . length ) ] ;
// 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 ) {
let currentZ = z + 1 ;
// turn the floor into air where person would go up
matrix [ currentZ ] [ x + 1 ] [ y ] = 'air' ;
// Build the first 3 ladder segments from floor level downwards
for ( let i = 0 ; i < 3 ; i ++ ) {
matrix [ currentZ ] [ x ] [ y ] = 'ladder[facing=north]' ;
currentZ -= 1
}
// Continue building ladder downwards until a floor is hit or we reach the bottom
while ( currentZ >= 0 && matrix [ currentZ ] [ x ] [ y ] === 'air' ) {
// Place ladder
matrix [ currentZ ] [ x ] [ y ] = 'ladder[facing=north]' ;
// Move down
currentZ -- ;
}
}
function embellishments ( carpet , windowStyle , matrix , newX , newY , newZ , newLength , newWidth , newDepth , material ) {
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 :
addCarpet ( 0.3 , matrix , newX , newY , newZ , newLength , newWidth ) ;
break ;
case 2 :
addCarpet ( 0.7 , matrix , newX , newY , newZ , newLength , newWidth )
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 ++ ) {
const material = roomMaterials [ Math . floor ( Math . random ( ) * roomMaterials . length ) ] ;
// dimensions of room
const newLength = Math . max ( 6 , Math . floor ( Math . random ( ) * 6 ) + 4 ) ;
const newWidth = Math . max ( 6 , Math . floor ( Math . random ( ) * 6 ) + 4 ) ;
const newDepth = Math . max ( 5 , Math . floor ( Math . random ( ) * 5 ) + 2 ) ;
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 ) ) {
lastRoom = { x : newX , y : newY , z : newZ , length : newLength , width : newWidth , depth : newDepth } ;
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 ;
}
else {
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 )
addLadder ( matrix , lastRoom . x + Math . floor ( lastRoom . length / 2 ) ,
lastRoom . y + Math . floor ( lastRoom . width / 2 ) ,
newZ ) ; // Adding the ladder
lastRoom = { x : newX , y : newY , z : newZ , length : newLength , width : newWidth , depth : newDepth } ;
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 ) ;
lastRoom = { x : newX , y : newY , z : newZ , length : newLength , width : newWidth , depth : newDepth } ;
roomPlaced = true ;
placedRooms ++ ;
break ;
}
break ;
case 'right' :
newX = lastRoom . x + lastRoom . length - 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 . length - 1 ,
lastRoom . y + Math . floor ( lastRoom . width / 2 ) ,
lastRoom . z , material ) ;
lastRoom = { x : newX , y : newY , z : newZ , length : newLength , width : newWidth , depth : newDepth } ;
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 )
addDoor ( matrix , lastRoom . x + Math . floor ( lastRoom . length / 2 ) ,
lastRoom . y + lastRoom . width - 1 ,
lastRoom . z , material ) ;
lastRoom = { x : newX , y : newY , z : newZ , length : newLength , width : newWidth , depth : newDepth } ;
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 )
addDoor ( matrix , lastRoom . x + Math . floor ( lastRoom . length / 2 ) ,
lastRoom . y ,
lastRoom . z , material ) ;
lastRoom = { x : newX , y : newY , z : newZ , length : newLength , width : newWidth , depth : newDepth } ;
roomPlaced = true ;
placedRooms ++ ;
break ;
}
break ;
}
if ( roomPlaced ) {
break ;
}
}
}
if ( ! roomPlaced ) {
console . warn ( ` Could not place room ${ placedRooms + 1 } ` ) ;
break ;
}
}
return matrix
}
/ * *
* 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
if ( ! Array . isArray ( matrix ) || ! Array . isArray ( startCoord ) || startCoord . length !== 3 ) {
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' )
)
} ) )
} ;
}