Maximum PC

Hack Minecraft Pi & Make a Trebuchet

- –JONNI BIDWELL

YOU’LL NEED THIS

RASPBERRY Pi 2 The brilliant mini-computer costs under $45. See www.raspberryp­i.org.

MINECRAFT Download the latest version from http://pi.minecraft.net

ARGUABLY MORE FUN THAN the generously provided Wolfram Mathematic­a: Pi Edition is Mojang’s generously provided Minecraft:PiEdition. The latter is a cut-down version of the popular PocketEdit­ion and, as such, lacks life-threatenin­g gameplay, but it does include more blocks than you can shake a stick at, and three types of saplings from which said sticks can be harvested.

This means that there’s plenty of stuff with which to unleash your creativity, then, but all that clicking is hard work, and by dint of the edition including an elegant Python API, you can bring to fruition blocky versions of your wildest dreams with just a few lines of code.

Assuming you’ve got your Pi [ Image A] up and running, download the latest version from http://pi.minecraft.net to your home directory. The authors stipulate the use of Raspbian, so that’s what we recommend—your mileage may vary with other distros.

1 GET STARTED Minecraft requires the X server to be running, so if you’re a bootto-console type, you’ll have to “startx.” Start LXTerminal, and extract and run the contents of the archive, like so: $ tar -xvzf minecraft-pi-0.1.1.tar.gz $ cd mcpi $ ./minecraft-pi

See how smoothly it runs? Toward the top-left corner, you can see your x, y, and z co-ordinates, which will change as you navigate the block-tastic environmen­t. The x and z axes run parallel to the floor, whereas the y dimension denotes altitude. Each block (or voxel, to use the correct parlance) that makes up the landscape is described by integer co-ordinates and a BlockType. The “floor” doesn’t really have any depth, so is, instead, said to be made of tiles. Empty space has the BlockType AIR, and there are about 90 other more tangible substances, including such delights as GLOWING_ OBSIDIAN and TNT [ Image B]. Your player’s co-ordinates, in contrast to those of the blocks, have a decimal part, since you’re able to move continuous­ly within AIR blocks.

The API enables you to connect to a running Minecraft instance, and manipulate the player and terrain as befits your megalomani­acal tendencies. In order to service these, our first task is to copy the provided library, so that we don’t mess with the vanilla installati­on of Minecraft. We’ll make a special folder for all our mess called “~/picraft,” and put all the API stuff in “~/picraft/ minecraft.” Open LXTerminal and issue the following directives: $ mkdir ~/picraft $ cp -r ~/mcpi/api/python/mcpi ~/picraft/minecraft 2 MOD MINECRAFT Now, without further ado, let’s make our first Minecraft modificati­ons. We’ll start by running an interactiv­e Python session alongside Minecraft. So, open up another tab in LXTerminal, start Minecraft, and enter a world, then Alt-Tab back to the terminal, and open up Python in the other tab. Do the following in the Python tab: import minecraft.minecraft as minecraft import minecraft.block as block mc = minecraft.Minecraft.create() posVec = mc.player.getTilePos() x = posVec.x y = posVec.y z = posVec.z mc.postToChat(str(x)+’ ‘+ str(y) +’ ‘+ str(z))

Behold, our location is emblazoned on the screen for a few moments (if not, you’ve made a mistake). These coordinate­s refer to the current block that your character occupies, and so have no decimal point. Comparing these with the co-ordinates at the top-left, you will see that these are just the result of rounding down those decimals to integers (for example, -1.1 is rounded down to -2). Your character’s co-ordinates are available via “mc.player. getPos(),” so in some ways “getTilePos()” is superfluou­s, but it saves three float to int coercions, so we may as well use it. The API has a nice class called Vec3 for dealing

with three-dimensiona­l vectors, such as our player’s position. It includes all the standard vector operations, such as addition and scalar multiplica­tion, as well as some other more exotic stuff that will help us later on.

We can also get data on what our character is standing on. Go back to your Python session and type: curBlock = mc.getBlock(x, y- 1, z) mc.postToChat(curBlock)

Here, “getBlock()” returns an integer specifying the block type: 0 refers to air, 1 to stone, 2 to grass, and you can find all the other block types in the file “block.py” in the “~/picraft/minecraft” folder we created earlier. We subtract 1 from the y value, because we are interested in what’s going on underfoot—calling “getBlock()” on our current location should always return 0, because otherwise we would be embedded inside something solid or drowning.

As usual, running things in the Python interprete­r is great for playing around, but the grown-up way to do things is to put all your code into a file. Create the file “~/picraft/gps.py” with the following: import minecraft.minecraft as minecraft import minecraft.block as block mc = minecraft.Minecraft.create() oldPos = minecraft.Vec3() while True: playerTile­Pos = mc.player.getTilePos() if playerTile­Pos != oldPos: oldPos = playerTile­Pos x = playerTile­Pos.x y = playerTile­Pos.y z = playerTile­Pos.z t = mc.getBlock(x, y– 1, z) mc.postToChat(str(x) + ‘ ‘ + str(y) + ‘ ‘ + str(z) + ‘ ‘ + str(t)) 3 RUN YOUR PROGRAM Now fire up Minecraft, enter a world, then open up a terminal, and run your program: $ python gps.py

The result should be that your co-ordinates and the BlockType of what you’re standing on are displayed as you move about. Once you’ve memorized all the BlockTypes (joke), Ctrl-C the Python program to quit.

We have covered some of the “passive” options of the API, but these are only any fun when used in conjunctio­n with the more constructi­ve (or destructiv­e) options. Before we move on, we’ll cover a couple of these. As before, start Minecraft and a Python session, import the Minecraft and block modules, and set up the “mc” object: posVec = mc.player.getTilePos() x = posVec.x y = posVec.y z = posVec.z for j in range(5): for k in range(x - 5,x + 5) mc.setBlock(k, j,z + 1, 246)

Behold! A 10 x 5 wall of glowing obsidian has been erected adjacent to your current location. We can also destroy blocks by turning them into air, so we can make a tiny tunnel [ Image C] in our obsidian wall like so (assuming you didn’t move since inputting the previous code):

mc.setBlock(x, y, z + 1, 0)

4 BECOME A HOMEMAKER Now we’re aufait with the basics of the API, it’s time to get creative. Building a house is hard, right? Wrong. With just a few lines of sweet Python, your dream home can be yours. Provided your dream home is a fairly standard box constructi­on. If your dreams are wilder, all it takes is more code. You will never have to worry about building permits, utility connection, or accidental­ly digging up an ancient burial ground (unless you built it first).

It never actually rains in MinecraftP­i, so a flat-roof constructi­on will happily suit our purposes just fine. We kick off proceeding­s by defining two corners for our house: v1 is the block next to us in the x direction, and one block higher than our current altitude, whereas v2 is an aesthetica­lly pleasing distance away: pos = mc.player.getTilePos() v1 = minecraft.Vec3(1,1,0) + pos v2 = v1 + minecraft.Vec3(10,4,6)

Now we create a solid stone cuboid between these vertices, and hollow it out by making a smaller interior cuboid of fresh air: mc.setBlocks(v1.x,v1.y,v1.z,v2.x,v2.y,v2.z,4) mc.setBlocks(v1.x+1,v1.y,v1.z+1,v2.x-1,v2.y,v2.z-1,0)

Great, except our only means of egress and ingress is via the skylight, and a proper floor would be nice [ Image D]. If you’re standing in a fairly flat area, you’ll notice that the walls of your house are hovering one block above ground level. This space is where our floor will go. If your local topography is not so flat, your house may be embedded in a hill, or partly airborne, but don’t worry—the required terraformi­ng or adjustment­s to local gravity will all be taken care of. Let’s make our rustic hardwood floor: mc.setBlocks(v1.x,v1.y-1,v1.z,v2.x,v1.y -1,v2.z,5)

The windows are just another variation on this theme: mc.setBlocks(v1.x,v1.y+1,v1.z+1,v1.x,v1.y+2,v1.z+3,102) mc.setBlocks(v1.x+6,v1.y+1,v1.z,v1.x+8,v1.y+2,v1.z,102) mc.setBlocks(v2.x,v1.y+1,v1.z+1,v2.x,v1.y+2,v1.z+3,102) mc.setBlocks(v1.x+2,v1.y+1,v2.z,v1.x+4,v1.y+2,v2.z,102)

The roof uses the special half block 44, which has a few different types. Setting the blockType makes it wooden: mc.setBlocks(v1.x,v2.y,v1.z,v2.x,v2.y,v2.z,44,2)

The door is more complicate­d—the gory details are in the box opposite—but the following three lines do the job: mc.setBlocks(v1.x+2,v1.y,v1.z,v1.x+3,v1.y,v1.z,64,3) mc.setBlock(v1.x+2,v1.y+1,v1.z,64,8) mc.setBlock(v1.x+3,v1.y+1,v1.z,64,9)

Having lovingly constructe­d our property, the next step is to come up with inventive ways of destroying it. We’ve mentioned that TNT can be made live, so that a gentle swipe with a sword will cause it to detonate. It would be trivial to use setBlocks to fill your house with primed TNT, but we can do better. Allow us to introduce our trebuchet. 5 SIMPLE MECHANICS Rather than simulating a projectile moving through space, we instead trace its parabolic trajectory with

hovering TNT. Detonating the origin of this trajectory will initiate a satisfying chain reaction, culminatin­g in a big chunk of your house being destroyed. First we will cover some basic 2D mechanics. In the absence of friction, a projectile will trace out a parabola determined by the initial launch velocity, the angle of launch, and the gravitatio­nal accelerati­on, which on Earth is about 9.81ms-

As a gentle introducti­on, we will fiddle these constants so that the horizontal distance covered by this arc is exactly 32 blocks, and at its peak it will be 16 blocks higher than its original altitude. If blocks were meters, this fudge would correspond to a muzzle velocity just shy of 18ms- and an elevation of 60 degrees. We will only worry about two dimensions, so the arc will be traced along the z axis, with the x co-ordinate fixed just next to our door. This is all summed up by the simple formula y = z(2- z/16), which we implement this way: for j in range(33): height = v1.y + int(j*(2 – j/16.)) mc.setBlock(v1.x+4,height,v1.z-j,46,1)

The final argument sets the TNT to be live, so have at it with your sword, and enjoy the fireworks. Or maybe not: The explosions will, besides really taxing the Pi’s brain, cause some TNT to fall, interrupti­ng the chain reaction, and preserving our lovely house. We don’t want that, so we instead use the following code: height = v1.y ground = height - 1 j=0 while ground = height:

mc.setBlocks(v1.x + 4,oldheight,v1.z - j,v1.x + 4,height,v1.z - j,46,1) j += 1 oldheight = height height = v1.y + int(j *(2 - j / 16.)) ground = mc.getHeight(v1.x + 4, v1.z - j)

This ensures that our parabola is gap-free, and also mitigates against the TNT arc-en-ciel terminatin­g in mid-air. We have dealt with this latter quandary using the “getHeight()” function to determine ground level at each point in the arc, and stop building when we reach it. Note that we have to make the “getHeight()” call before we place the final TNT block, because the height of the world is determined by the uppermost non-air object, even if that object is hovering. 6 CREATE YOUR TREBUCHET If our constructi­on exceeds the confines of the Minecraft world, you could just build another house in a better situation, or you could change “v1.z - j” to “max(116,v1.z-j)” in the above loop [ Image E], which would make a vertical totem of danger at the edge of the world. Now we have our trajectory, we can add the mighty siege engine: z = v1.z -j - 1 mc.setBlocks(v1.x + 3, oldheight, z + 10, v1.x + 6, oldheight +2, z + 7,85) mc.setBlocks(v1.x + 4, oldheight +2, z + 12, v1.x + 4, oldheight +2, z + 1, 5)

Until now, we’ve aligned everything along a particular axis. Our house (before you blew it up [ Image F]) faces the negative z direction, which might be akin to facing south, and this is also the direction along which our explosive parabola is traced. We could rotate everything 90 degrees, and the code would look much the same, though your house would look funny on its side. Things get complicate­d if we want to shed the yoke of these grids and right angles,

to work with angles of our choosing [ Image G]. The problem is how to approximat­e a straight line when our units are blocks of fixed orientatio­n rather than points. 7 TWO OR THREE DIMENSIONS? A general 3D “drawline()” function will prove invaluable in your creations, enabling you to create diverse configurat­ions, from parallelep­ipeds to pentagrams. What is required is a 3D version of the Bresenham algorithm. Pi guru Martin O’Hanlon’s GitHub contains several wonderful MinecraftP­i Edition projects, including a mighty cannon from which this project takes its inspiratio­n. Martin has a whole Python drawing class, which includes the aforementi­oned 3D line algorithm, but once you understand the 2D version, the generaliza­tion is straightfo­rward.

Let us imagine we are in a Flatland- style Minecraft world, in which we wish to approximat­e the line in the (x,y) plane connecting the points (-2,-2) and (4,1). This line has the equation y = 0.5x - 1. The algorithm requires that the gradient of the line is between 0 and 1, so in this case we are fine. If we wanted a line with a different slope, we can flip the axes to make it conform. The crux of the algorithm is the fact that our pixel line will fill only one pixel (block) per column, but multiple pixels per row. Thus, as we rasterize pixel by pixel in the x direction, our y co-ordinate will either stay the same or increment by 1. Some naive Python would then be: dx = x1 – x0 dy = y1 – y0 y = y0 error = 0 grad = dy/dx for x in (x0,x1): plot(x,y) error = error + grad if error = 0.5: y += 1 error -= 1 where “plot()” is some imaginary plotting function and “grad” is between 0 and 1. Thus we increment y whenever our error term accumulate­s sufficient­ly.

Bresenham’s trick was to reduce all the calculatio­ns to integer operations. Nowadays, we can do floating point calculatio­ns at speed, but it is still nice to appreciate these hacks. The floating point variables “grad” and “error” arise due to the division by dx, so if we multiply everything by this quantity, and work around this scaling, we are good to go.

To get this working in three dimensions is not so much of an abstractiv­e jump. We find which is the dominant axis (the one with the largest change in co-ordinates), and flip things around accordingl­y, moving along the dominant axis one block at a time, and incrementi­ng the co-ordinates of minor axes as required. We have to pay attention to the sign of each co-ordinate change, which we store in the variable “ds.” The “ZSGN()” function returns 1, -1, or 0 if its argument is positive, negative, or zero respective­ly; we have left coding this as an exercise for you. We use a helper function “minorList(a,j),” which returns a copy of the list “a” with the “jth” entry removed. We can code this using a one-liner thanks to lambda functions and list slicing:

minorList = lambda a,j: a[:j] + a[j + 1:] 8 AXES TO GRIND Our function “getLine()” will take two vertices, which we will represent using three-element lists, and return a list of all the vertices in the resulting 3D line. All of this is based on Martin’s code. The first part initialize­s our vertex list and deals with the easy case, where both input vertices are the same. Here our line is just a single block: def getLine(v1, v2): if v1 == v2:

vertices.append([v1])

After this it gets ugly. We set up the list of signs, “ds,” and a list of absolute difference­s (multiplied by two), “a.” The “idx =” line is bad form—we want to find our dominant axis, thus the index of the maximum entry in “a.” Using the “index()” method with “max” means we are looping over our list twice, but as this is such a short list, we shan’t worry—it looks much nicer. We refer to the dominant co-ordinates by “X” and “X2.” Our list “s” is a re-arrangemen­t of “ds,” with the dominant co-ordinate at the beginning. And there are some other lists to keep track of the errors. The variable “aX” refers to the sign of the co-ordinate change along our dominant axis. else: ds = [ZSGN(v2[j] - v1[j]) for j in range(3)] a = [abs(v2[j]-v1[j]) 1 for j in range(3)] idx = a.index(max(a)) X = v1[idx] X2 = v2[idx] delta = a[idx] 1 s = [ds[idx]] + minorList(ds,idx) minor = minorList(v1,idx) aminor = minorList(a,idx) dminor = [j - delta for j in aminor] aX = a[idx] 9 FINAL TOUCHES With all that set up, we can delve into our main loop, in which vertices are added, difference­s along minor axes examined, errors recalculat­ed, and major coordinate­s incremente­d. Then we return a list of vertices. loop = True while(loop): vertices.append(minor[:idx] +[X] + minor[idx:]) if X == X2:

loop = False for j in range(2): if dminor[j] = 0: minor[j] += s[j + 1] dminor[j] -= aX dminor[j] += aminor[j] X += s[0] return vertices

We will test this function by making a mysterious beam of wood next to where we are standing. v1 = mc.player.getTilePos() + minecraft.Vec3(1,1,0) v1 = minecraft.Vec3(1,1,0) + pos v2 = v1 + minecraft.Vec3(5,5,5) bline = getLine([v1.x,v1.y,v1.z],[v2.x,v2.y,v2.z]) for j in bline: mc.setBlock(j[0],j[1],j[2],5)

That’s it! Using MinecraftP­iEdition is a great way to practice coding in Python. You’ll have so much fun [ Image H], you’ll probably not even realize you’re learning!

 ??  ?? A
A
 ??  ??
 ??  ??
 ??  ?? D
D
 ??  ??
 ??  ?? F
F
 ??  ?? H
H
 ??  ??

Newspapers in English

Newspapers from United States