MC – Code Along – 5

gray kangarooA code-along is a story about coding in which you are encouraged to “code up” all the examples in the story. In a code-along you should not cut-and-paste.

This code-along continues from point where our previous code-alone left off.

In this code-along we will write a Bricklayer program that builds a tower. Our goal is to place this tower in the center of our “island surrounded by a lava-filled moat” which we developed in the previous code-along. This code-along shows how to use the following Bricklayer functions.

  • The setPieceMapping function allows customization of Bricklayer’s translation between Minecraft blocks and LEGO bricks.
  • The setBricklayerOrigin function allows the origin of Bricklayer’s virtual space to be moved away from the minecraftOrigin. This is done in relative terms using an offset.
  • The showAsAir function causes Bricklayer to build an artifact exclusively out of Air blocks. This is useful for “erasing” an artifact.
  • The setSuppressOrientationMarker functions allows program-level control over whether the orientation marker, which Bricklayer automatically creates, to be suppressed (i.e., turned off/on).

Step 1 – Use LDD to Develop a Prototype

The program below creates a tower whose shaft consists of Prismarine blocks and whose roof is made of SeaLantern blocks. Note that the tower shaft contains (1) a ring of Glowstone blocks for lighting purposes, (2) an OakDoor, and (3) a large window made of Glass blocks. On the floor of the tower is a ring of Torch blocks (except for in front of the door) and a beacon. The center of the roof is made of LightBlueGlass blocks. This lets the beam created by the beacon to pass through the roof.

A high-resolution rendering of the LDD rendition of tower created is shown next to the code. This high-resolution image is created using a tool called LDD to POV-Ray Converter. This converter creates wonderful high-resolution images. However, artifacts that contain transparent/clear bricks take extremely long to render (e.g., several days). If we want more reasonable rendering times, then the LDD rendition of the tower should not contain any clear bricks. Unfortunately (in this case), Bricklayer’s block-to-brick translation function maps Glass blocks to TRANSPARENT bricks and LightBlueGlass blocks to CLEARLIGHTBLUE bricks. In order to create our high-resolution image we need to change this mapping. Bricklayer provides a function, called setPieceMapping, that allows the mappings that Bricklayer uses to be changed. The setPieceMapping function takes a list as its argument. Each element in this list is a brick-block (or block-brick) pair. The following function call is made on the program shown below.

setPieceMapping [ (Glass,BLUE), (LightBlueGlass,LIGHTBLUE) ]

This instructs Bricklayer to map Glass blocks to BLUE bricks and LightBlueGlass blocks to LIGHTBLUE bricks. Through this mapping we are able to create an LDD artifact containing no clear bricks.

open Level_4;

(* ================================================ *)
(* The function tower will construct a cylindrical 
   tower. The input (x,y,z) denotes the lower-left 
   corner of the tower's bounding box.              *)
(* ================================================ *)

fun tower (x,y,z) =   
  let
    val roofRadius   = 8;
    val roofDiameter = 2*roofRadius + 1
        
    val shaftHeight  = 10;

    val roofOverhang = 2;
                
    val xCenter      = x + roofRadius;
    val zCenter      = z + roofRadius;        

    (* --------------------------------------- *)
    fun beacon (x,y,z) =
        (
          put (3,1,3) Iron   (x,y,z);
          put (1,1,1) Beacon (x+1,y+1,z+1)
        );
         
    (* --------------------------------------- *) 
    (* (x,y,z) is lower-left corner of the 
       artifact's bounding box *)    
    fun shaft (x,y,z) =
        let        
          val shaftRadius = roofRadius - roofOverhang;
          val xCenter     = x + shaftRadius;
          val zCenter     = z + shaftRadius;       
        in
          ringY shaftRadius 1 shaftHeight 
                [Prismarine] 
                (xCenter,y,zCenter);
          ringY shaftRadius 1 1 
                [Glowstone] 
                (xCenter,y + shaftHeight div 2,zCenter);                             
          beacon (xCenter-1,y,zCenter-1);

          (* Torch can only be placed on top 
             of a block (not on its side) *)
          ringY (shaftRadius-1) 1 1 
                [Torch] 
                (xCenter,y,zCenter);  
                
          put (1,2,1) OakDoor (xCenter,y,z);
          (* erase Torch *)                         
          put (1,1,1) Air (xCenter,y,z+1);  
              
          put (3,shaftHeight div 5 * 3,1) 
              Glass 
              (xCenter-1,y+shaftHeight div 4 + 1,z)
      end;
            
    (* --------------------------------------- *)
    fun roof y  = 
        (
          coneY roofRadius 1 
                [SeaLantern] 
                (xCenter,y,zCenter);
          put (1,2,1) 
              LightBlueGlass 
              (xCenter,y+roofRadius-1,zCenter)
        );            
    in
        shaft (x+roofOverhang,y,z+roofOverhang);
        roof  (y+shaftHeight)        
    end;
      
(* ================================================ *)
build(64,64,64);

setPieceMapping [ (Glass,BLUE), 
                  (LightBlueGlass,LIGHTBLUE) ];

tower (0,0,0);

show "A Tower";

mc-ca-5-02-tower


Aside. Aside from high-resolution rendering, when developing Minecraft prototypes (using LDD) the setPieceMapping can be extremely useful. Note that there are many more Minecraft blocks than there are LEGO bricks. This means that certain kinds of distinctions that are visually apparent in Minecraft will not be possible in LDD. For example, Bricklayer’s block-to-brick mapping function will map an OakSlab, a DoubleOakSlab, OakStairs, and an OakDoor to the MEDIUMNOUGAT brick. This is a significant loss of information. Consider constructing a house made of DoubleOakSlab blocks having an OakDoor. Using Bricklayer’s default block-to-brick mapping an LDD prototype will be created where the doors and walls cannot be distinguished from one another. In such a case, the setPieceMapping can be used to customize the block-to-brick mapping. For instance, an OakDoor might be mapped to the BROWN brick, which would then visually distinguish doors from walls in the house prototype.


Step 2 – Migrate Artifact to Minecraft

After developing an acceptable prototype we are ready to build our artifact in Minecraft. The following changes need to be made to the program shown in the previous step.

  1. Calls to the functions setSpawnPoint and setMinecraftOrigin are added to set the minecraftOrigin to the desired location.
  2. A call to the function setPlayerOffset is added to position the avatar at an appropriate distance from the tower.
  3. The output function is changed from show to showMC.
open Level_4;

(* ================================================ *)
(* The function tower will construct a cylindrical 
   tower. The input (x,y,z) denotes the lower-left 
   corner of the tower's bounding box.              *)
(* ================================================ *)

val spawnPoint       = (~172,62,256);
val minecraftOrigin  = (~330,64,~12);  

(* ================================================ *)
fun tower (x,y,z) =  
  let
    val roofRadius = 8;
    val roofDiameter = 2*roofRadius + 1
        
    val shaftHeight  = 10;

    val roofOverhang = 2;
                
    val xCenter      = x + roofRadius;
    val zCenter      = z + roofRadius;        

    (* --------------------------------------- *)
    fun beacon (x,y,z) =
        (
          put (3,1,3) Iron   (x,y,z);
          put (1,1,1) Beacon (x+1,y+1,z+1)
        );
        
    (* --------------------------------------- *)
    (* (x,y,z) is lower-left corner of the 
       artifact's bounding box *)    
    fun shaft (x,y,z) =
        let        
          val shaftRadius = roofRadius - roofOverhang;
          val xCenter     = x + shaftRadius
          val zCenter     = z + shaftRadius           
        in
          ringY shaftRadius 1 shaftHeight 
                [Prismarine] 
                (xCenter,y,zCenter);
          ringY shaftRadius 1 1 
                [Glowstone] 
                (xCenter,y + shaftHeight div 2,zCenter);       
          beacon (xCenter-1,y,zCenter-1);

          (* Torch can only be placed on top of 
             a block (not on its side) *)
          ringY (shaftRadius-1) 1 1 
                [Torch] 
                (xCenter,y,zCenter);  
                
          put (1,2,1) OakDoor (xCenter,y,z);
          (* erase Torch *)                
          put (1,1,1) Air (xCenter,y,z+1);
              
          put (3,shaftHeight div 5 * 3,1) 
              Glass 
              (xCenter-1,y+shaftHeight div 4 + 1,z)
        end;
            
    (* --------------------------------------- *)
    fun roof y  = 
        (
          coneY roofRadius 1 
                [SeaLantern] 
                (xCenter,y,zCenter);
          put (1,2,1) 
              LightBlueGlass 
              (xCenter,y+roofRadius-1,zCenter)
        );             
  in
    shaft (x+roofOverhang,y,z+roofOverhang);
    roof  (y+shaftHeight)        
  end;       
      
(* ================================================ *)
build(64,64,64);

setSpawnPoint      spawnPoint;
setMinecraftOrigin minecraftOrigin;
setPlayerOffset   (~5,5,~5);

tower (0,0,0);

showMC "A Tower";

 

The screenshots below show the tower that is created in Minecraft. Note that the tower is improperly positioned, it needs to be place in the center of the island. The best way to do this is to shift the origin of Bricklayer’s virtual space. Step 3 shows how this can be done. However, first we must erase the tower that has been built. Bricklayer provides an output function, called showAsAir, that does just that. To erase our (mispositioned) tower all we need do is change the output function of the above program from showMC to showAsAir. When this modified program is executed, it will replace all the blocks in our artifact with Air blocks.

Step 3 – Final Form

The program shown below is the our final version. It uses the Bricklayer function setBricklayerOrigin to define the bricklayerOrigin as an offset from the minecraftOrigin. It also uses the function call, supressOrientationMarker true to stop Bricklayer from outputting its orientation marker.

open Level_4;

(* ================================================ *)
(* The function tower will construct a cylindrical 
   tower. The input (x,y,z) denotes the lower-left 
   corner of the tower's bounding box.              *)
(* ================================================ *)

val delta      = 31; (* moatWidth = 63 *)
val roofRadius = 8;
val offset     = delta - roofRadius;

val spawnPoint       = (~172,62,256);
val minecraftOrigin  = (~330,64,~12);
val bricklayerOrigin = (offset,~1,offset);   

(* ================================================ *)
fun tower (x,y,z) =  
  let
    val roofDiameter = 2*roofRadius + 1
        
    val shaftHeight  = 10;

    val roofOverhang = 2;
                
    val xCenter      = x + roofRadius;
    val zCenter      = z + roofRadius;        

    (* --------------------------------------- *)
    fun beacon (x,y,z) =
        (
          put (3,1,3) Iron   (x,y,z);
          put (1,1,1) Beacon (x+1,y+1,z+1)
        );
            
    (* --------------------------------------- *)
    (* (x,y,z) is lower-left corner of the 
       artifact's bounding box *)
    fun shaft (x,y,z) =
        let        
          val shaftRadius = roofRadius - roofOverhang;            
          val xCenter     = x + shaftRadius
          val zCenter     = z + shaftRadius           
        in
          ringY shaftRadius 1 shaftHeight 
                [Prismarine] 
                (xCenter,y,zCenter);
          ringY shaftRadius 1 1 
                [Glowstone] 
                (xCenter,y + shaftHeight div 2,zCenter);                 
          beacon (xCenter-1,y,zCenter-1);

          (* Torch can only be placed on top of 
             a block (not on its side) *)
          ringY (shaftRadius-1) 1 1 
                [Torch] 
                (xCenter,y,zCenter);  
                
          put (1,2,1) OakDoor (xCenter,y,z); 

          (* erase Torch *)   
          put (1,1,1) Air (xCenter,y,z+1);          
              
          put (3,shaftHeight div 5 * 3,1) 
              Glass 
              (xCenter-1,y+shaftHeight div 4 + 1,z)
        end;
            
    (* --------------------------------------- *)
    fun roof y  = 
        (
          coneY roofRadius 1  
                [SeaLantern] 
                (xCenter,y,zCenter);
          put (1,2,1) 
              LightBlueGlass 
              (xCenter,y+roofRadius-1,zCenter)
        );      
  in
    shaft (x+roofOverhang,y,z+roofOverhang);
    roof  (y+shaftHeight)        
  end;
            
(* ================================================ *)
build(64,64,64);

setSpawnPoint      spawnPoint;
setMinecraftOrigin minecraftOrigin;
setBricklayerOrigin bricklayerOrigin;
setPlayerOffset   (~5,5,~5);
setSuppressOrientationMarker true;

tower (0,0,0);

showMC "A Tower";

 

The following screenshot shows the final version of our tower which is centered on our island surrounded by a moat of lava.

The inside of the tower is shown below. Note how the Glowstone ring and the Torch ring is used for lighting. Also note how the beacon shines up to the center of the roof. Recall that this part of the roof is constructed from LightBlueGlass which will allow the light from the beacon to shine through (in a blueish form) into the sky.

This last screenshot shows the tower by night. Note how the SeaLantern roof, Glowstone ring, and lava moat provide lighting. Also note the blueish light from the beacon shining into the night sky.