3. Quick StartΒΆ

The first thing you need to learn in picraft is vectors, and vector ranges. Er, the two things you need to learn in picraft are vectors, vector ranges, and blocks. The three things ... look, I’ll just come in again.

Firstly, ensure that you have a Minecraft game running on your Pi. Now start a terminal, start Python within the terminal, import the picraft library and start a connection to the Minecraft world:

>>> from picraft import World, Vector, Block
>>> world = World()

The World class is the usual starting point for picraft scripts. It provides access to the blocks that make up the world, the players within the world, methods to save and restore the state of the world, and the ability to print things to the chat console. Let’s start by printing something to the console:

>>> world.say('Hello, world!')

You should see “Hello, world!” appear in the chat console of the Minecraft game. Next, we can query where we’re standing with the pos attribute of the player attribute:

>>> world.player.pos
Vector(x=-2.49725, y=18.0, z=-4.21989)

This tells us that our character is standing at the 3-dimensional coordinates -2.49, 18.0, -4.22 (approximately). In the Minecraft world, the X and Z coordinates (the first and last) form the “ground plane”. In other words you can think of X as going left to right, and Z as going further to nearer. The Y axis represents height (it goes up and down). We can find out our player’s coordinates rounded to the nearest block with the tile_pos attribute:

>>> world.player.tile_pos
Vector(x=-3, y=18, z=-5)

Therefore, we can make our character jump in the air by adding a certain amount to the player’s Y coordinate. To do this we need to construct a Vector with a positive Y value and add it to the tile_pos attribute:

>>> world.player.tile_pos = world.player.tile_pos + Vector(y=5)

We can also use a Python short-hand for this:

>>> world.player.tile_pos += Vector(y=5)

This demonstrates one way of constructing a Vector. We can also construct one by listing all 3 coordinates explicitly:

>>> Vector(y=5)
Vector(x=0, y=5, z=0)
>>> Vector(0, 5, 0)
Vector(x=0, y=5, z=0)

We can use the blocks attribute to discover the type of each block in the world. For example, we can find out what sort of block we’re currently standing on:

>>> world.blocks[world.player.tile_pos - Vector(y=1)]
<Block "grass" id=2 data=0>

We can assign values to this property to change the sort of block we’re standing on. In order to do this we need to construct a new Block instance which can be done by specifying the id manually, or by name:

>>> Block(1)
<Block "stone" id=1 data=0>
>>> Block('stone')
<Block "stone" id=1 data=0>

Now we’ll change the block beneath our feet:

>>> world.blocks[world.player.tile_pos - Vector(y=1)] = Block('stone')

We can query the state of many blocks surrounding us by providing a vector slice to the blocks attribute. To make things a little easier we’ll store the base position first:

>>> v = world.player.tile_pos - Vector(y=1)
>>> world.blocks[v - Vector(1, 0, 1):v + Vector(2, 1, 2)]
[<Block "grass" id=2 data=0>,
 <Block "grass" id=2 data=0>,
 <Block "grass" id=2 data=0>,
 <Block "grass" id=2 data=0>,
 <Block "stone" id=1 data=0>,
 <Block "grass" id=2 data=0>,
 <Block "grass" id=2 data=0>,
 <Block "grass" id=2 data=0>,
 <Block "grass" id=2 data=0>]

Note that the range provided (as with all ranges in Python) is half-open, which is to say that the lower end of the range is inclusive while the upper end is exclusive. You can see this explicitly with the vector_range() function:

>>> v
Vector(x=-2, y=14, z=3)
>>> list(vector_range(v - Vector(1, 0, 1), v + Vector(2, 1, 2)))
[Vector(x=-3, y=14, z=2),
 Vector(x=-3, y=14, z=3),
 Vector(x=-3, y=14, z=4),
 Vector(x=-2, y=14, z=2),
 Vector(x=-2, y=14, z=3),
 Vector(x=-2, y=14, z=4),
 Vector(x=-1, y=14, z=2),
 Vector(x=-1, y=14, z=3),
 Vector(x=-1, y=14, z=4)]

This may seem a clunky way of specifying a range and, in the manner shown above it is. However, most standard infix arithmetic operations applied to a vector are applied to all its elements:

>>> Vector()
Vector(x=0, y=0, z=0)
>>> Vector() + 1
Vector(x=1, y=1, z=1)
>>> 2 * (Vector() + 1)
Vector(x=2, y=2, z=2)

This makes construction of such ranges or slices considerably easier. For example, to construct a vertical range of vectors from the origin (0, 0, 0) to (0, 10, 0) we first assign the origin to v which we use for the start of the range, then add Vector(y=10) to it, and finally add one to compensate for the half-open nature of the range:

>>> v = Vector()
>>> list(vector_range(v, v + Vector(y=10) + 1))
[Vector(x=0, y=0, z=0),
 Vector(x=0, y=1, z=0),
 Vector(x=0, y=2, z=0),
 Vector(x=0, y=3, z=0),
 Vector(x=0, y=4, z=0),
 Vector(x=0, y=5, z=0),
 Vector(x=0, y=6, z=0),
 Vector(x=0, y=7, z=0),
 Vector(x=0, y=8, z=0),
 Vector(x=0, y=9, z=0),
 Vector(x=0, y=10, z=0)]

We can also re-write the example before this (the blocks surrounding the one the player is standing on) in several different ways:

>>> v = world.player.tile_pos
>>> list(vector_range(v - 1, v + 2 - Vector(y=2)))
[Vector(x=-3, y=14, z=2),
 Vector(x=-3, y=14, z=3),
 Vector(x=-3, y=14, z=4),
 Vector(x=-2, y=14, z=2),
 Vector(x=-2, y=14, z=3),
 Vector(x=-2, y=14, z=4),
 Vector(x=-1, y=14, z=2),
 Vector(x=-1, y=14, z=3),
 Vector(x=-1, y=14, z=4)]

We can change the state of many blocks at once similarly by assigning a new Block object to a slice of blocks:

>>> v = world.player.tile_pos
>>> world.blocks[v - 1:v + 2 - Vector(y=2)] = Block('stone')

This is a relatively quick operation, as it only involves a single network call. However, re-writing the state of multiple blocks with different values is more time consuming:

>>> world.blocks[v - 1:v + 2 - Vector(y=2)] = [
...     Block('wool', data=i) for i in range(9)]

You should notice that the example above takes a few seconds to process (each block requires a separate network transaction and due to deficiencies in the Minecraft network protocol, each transaction takes a while to execute). This can be accomplished considerably more quickly by batching multiple requests together:

>>> world.blocks[v - 1:v + 2 - Vector(y=2)] = Block('stone')
>>> with world.connection.batch_start():
...     world.blocks[v - 1:v + 2 - Vector(y=2)] = [
...         Block('wool', data=i) for i in range(9)]

You should notice the example above executes considerably more quickly. Finally, the state of the Minecraft world can be saved and restored easily with the checkpoint object:

>>> world.checkpoint.save()
>>> world.blocks[v - 1:v + 2 - Vector(y=2)] = Block('stone')
>>> world.checkpoint.restore()

In order to understand vectors, it can help to visualize them. Pick a relatively open area in the game world.

_images/quick1.png

We’ll save the vector of your player’s position as v then add 3 to it. This moves the vector 3 along each axis (X, Y, and Z). Next, we’ll make the block at v into stone:

>>> v = world.player.tile_pos
>>> v = v + 3
>>> world.blocks[v] = Block('stone')
_images/quick2.png

Now we’ll explore vector slices a bit by making a line along X+5 into stone. Remember that slices (and ranges) are half-open so we need to add an extra 1 to the end of the slice:

>>> world.blocks[v:v + Vector(x=5) + 1] = Block('stone')
_images/quick3.png

In order to visualize the three different axes of vectors we’ll now draw them each. Here we also use a capability of the Block constructor to create a block with a particular color:

>>> world.blocks[v:v + Vector(x=5) + 1] Block('#ff0000')
>>> world.blocks[v:v + Vector(y=5) + 1] Block('#00ff00')
>>> world.blocks[v:v + Vector(z=5) + 1] Block('#0000ff')
_images/quick4.png

Finally, we can use a vector range to demonstrate patterns. Firstly we wipe out our axes by setting the entire block to “air”. Then we define a vector range over the same block with a step of 2, and iterate over each vector within setting it to diamond:

>>> world.blocks[v:v + 6] = Block('air')
>>> r = vector_range(v, v + 6, Vector() + 2)
>>> for rv in r:
...     world.blocks[rv] = Block('diamond_block')

Once again, we can make use of a batch to speed this up:

>>> world.blocks[v:v + 6] = Block('air')
>>> with world.connection.batch_start():
...     for rv in r:
...         world.blocks[rv] = Block('diamond_block')
_images/quick5.png

This concludes the quick tour of the picraft library. Conversion instructions from mcpi can be found in the next chapter, followed by picraft recipes in the chapter after that. Finally, the API reference can be found at the end of the manual.