picraft

This package provides an alternate Python API for Minecraft Pi edition on the Raspberry Pi, or Raspberry Juice on the PC for Python 2.7 (or above), or Python 3.2 (or above).

Table of Contents

Installation

Raspbian installation

If you are using the Raspbian distro, it is best to install picraft using the system’s package manager: apt. This will ensure that picraft is easy to keep up to date, and easy to remove should you wish to do so. It will also make picraft available for all users on the system. To install picraft using apt simply:

$ sudo apt-get update
$ sudo apt-get install python-picraft python3-picraft

To upgrade your installation when new releases are made you can simply use apt’s normal upgrade procedure:

$ sudo apt-get update
$ sudo apt-get upgrade

If you ever need to remove your installation:

$ sudo apt-get remove python-picraft python3-picraft

Ubuntu installation

If you are using Ubuntu, it is best to install picraft from the author’s PPA. This will ensure that picraft is easy to keep up to date, and easy to remove should you wish to do so. It will also make picraft available for all users on the system. To install picraft from the PPA:

$ sudo add-apt-repository ppa:waveform/ppa
$ sudo apt-get update
$ sudo apt-get install python-picraft python3-picraft

To upgrade your installation when new releases are made you can simply use apt’s normal upgrade procedure:

$ sudo apt-get update
$ sudo apt-get upgrade

If you ever need to remove your installation:

$ sudo apt-get remove python-picraft python3-picraft

Windows installation

The following assumes you’re using a recent version of Python (like 3.5) which comes with pip, and that you checked the option to “adjust PATH” when installing Python.

Start a command window by pressing Win-R and entering “cmd”. At the command prompt enter:

C:\Users\Dave> pip install picraft

To upgrade your installation when new releases are made:

C:\Users\Dave> pip install -U picraft

If you ever need to remove your installation:

C:\Users\Dave> pip uninstall picraft

Quick Start

Start a Minecraft game and start Python. First, we’ll connect Python to the Minecraft world and post a message to the chat console:

>>> from picraft import *
>>> world = World()
>>> world.say('Hello, 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.

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)

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)

There are also several short-hands for the X, Y, and Z “unit vectors” (vectors with a length of 1 aligned with each axis), and it’s worth noting that most mathematical operations can be applied to vectors:

>>> Vector(y=1)
Vector(x=0, y=1, z=0)
>>> Y
Vector(x=0, y=1, z=0)
>>> 5*Y
Vector(x=0, y=5, z=0)

There’s also a short-hand for Vector (V), and another representing the origin coordinates (O):

>>> V(y=1)
Vector(x=0, y=1, z=0)
>>> O
Vector(x=0, y=0, 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 - Y]
<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 number, 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 - Y] = 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:

>>> p = world.player.tile_pos - Y
>>> world.blocks[p - Vector(1, 0, 1):p + 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:

>>> p
Vector(x=-2, y=14, z=3)
>>> list(vector_range(p - Vector(1, 0, 1), p + 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 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 p which we use for the start of the range, then add 10*Y to it, and finally add one to compensate for the half-open nature of the range:

>>> p = Vector()
>>> list(vector_range(p, p + (10*Y) + 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:

>>> p = world.player.tile_pos
>>> list(vector_range(p - 1, p + 2 - (2*Y)))
[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:

>>> p = world.player.tile_pos
>>> world.blocks[p - 1:p + 2 - (2*Y)] = 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[p - 1:p + 2 - (2*Y)] = [
...     Block('wool', data=i) for i in range(9)]

You should notice that the example above takes longer to process. This can be accomplished considerably more quickly by batching multiple requests together:

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

Finally, the state of the Minecraft world can be saved and restored easily with the checkpoint object:

>>> world.checkpoint.save()
>>> world.blocks[p - 1:p + 2 - (2*Y)] = 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 p then add 3 to it. This moves the vector 3 along each axis (X, Y, and Z). Next, we’ll make the block at p into stone:

>>> p = world.player.tile_pos
>>> p = p + 3
>>> world.blocks[p] = 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[p:p + 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[p:p + (5*X) + 1] = Block('#ff0000')
>>> world.blocks[p:p + (5*Y) + 1] = Block('#00ff00')
>>> world.blocks[p:p + (5*Z) + 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[p:p + 6] = Block('air')
>>> r = vector_range(p, p + 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[p:p + 6] = Block('air')
>>> with world.connection.batch_start():
...     for rv in r:
...         world.blocks[rv] = Block('diamond_block')
_images/quick5.png

Recipes

This section introduces a variety of “recipes”: small scripts that demonstrate how to achieve something using the picraft library. Suggestions for new recipes are gratefully received: please e-mail the author!

Player Position

The player’s position can be easily queried with the pos attribute. The value is a Vector. For example, on the command line:

>>> world = World()
>>> world.player.pos
Vector(x=2.3, y=1.1, z=-0.81)

Teleporting the player is as simple as assigning a new vector to the player position. Here we teleport the player into the air by adding 50 to the Y-axis of the player’s current position (remember that in the Minecraft world, the Y-axis goes up/down):

>>> world.player.pos = world.player.pos + Vector(y=50)

Or we can use a bit of Python short-hand for this:

>>> world.player.pos += Vector(y=50)

If you want the player position to the nearest block use the tile_pos instead:

>>> world.player.tile_pos
Vector(x=2, y=1, z=-1)
_images/dont_look_down.png

Changing the World

The state of blocks in the world can be queried and changed by reading and writing to the blocks attribute. This is indexed with a Vector (or slice of vectors) and returns or accepts a Block instance. For example, on the command line we can find out the type of block we’re standing on like so:

>>> world = World()
>>> p = world.player.tile_pos
>>> world.blocks[p - Y]
<Block "dirt" id=3 data=0>

We can modify the block we’re standing on by assigning a new block type to it:

>>> world.blocks[p - Y] = Block('stone')

We can modify several blocks surrounding the one we’re standing on by assigning to a slice of blocks. Remember that Python slices are half-open so the easiest way to specify the slice is to specify the start and the end inclusively and then simply add one to the end. Here we’ll change p to represent the vector of the block beneath our feet, then set it and all immediately surrounding blocks to stone:

>>> p -= Y
>>> world.blocks[p - (X + Z):p + (X + Z) + 1] = Block('stone')
_images/blocks.png

Auto Bridge

This recipe (and several others in this chapter) was shamelessly stolen from Martin O’Hanlon’s excellent site which includes lots of recipes (although at the time of writing they’re all for the mcpi API). In this case the original script can be found in Martin’s auto-bridge project.

The script tracks the position and likely future position of the player as they walk through the world. If the script detects the player is about to walk onto air it changes the block to diamond:

from __future__ import unicode_literals

import time
from picraft import World, Vector, Block, Y

world = World()
last_pos = None
while True:
    this_pos = world.player.pos
    if last_pos is not None:
        # Has the player moved more than 0.1 units in a horizontal direction?
        movement = (this_pos - last_pos).replace(y=0.0)
        if movement.magnitude > 0.1:
            # Find the next tile they're going to step on
            next_pos = (this_pos + movement.unit).floor() - Y
            world.blocks[next_pos] = Block('diamond_block')
    last_pos = this_pos
    time.sleep(0.01)

Nice, but we can do better. The following script enhances the recipe so that only blocks which are air are changed to diamond, and the bridge “cleans up” after itself:

from __future__ import unicode_literals

import time
from picraft import World, Vector, Block, Y

world = World()
world.say('Auto-bridge active')
try:
    bridge = []
    last_pos = None
    while True:
        this_pos = world.player.pos
        if last_pos is not None:
            # Has the player moved more than 0.1 units in a horizontal direction?
            movement = (this_pos - last_pos).replace(y=0.0)
            if movement.magnitude > 0.1:
                # Find the next tile they're going to step on
                next_pos = (this_pos + movement.unit).floor() - Y
                if world.blocks[next_pos] == Block('air'):
                    with world.connection.batch_start():
                        bridge.append(next_pos)
                        world.blocks[next_pos] = Block('diamond_block')
                        while len(bridge) > 10:
                            world.blocks[bridge.pop(0)] = Block('air')
        last_pos = this_pos
        time.sleep(0.01)
except KeyboardInterrupt:
    world.say('Auto-bridge deactivated')
    with world.connection.batch_start():
        while bridge:
            world.blocks[bridge.pop(0)] = Block('air')

The script uses a list to keep track of the blocks which are present in the bridge, popping off old blocks when the bridge has more than 10 blocks in it. This list is also used to “clean up” the bridge when the script exits.

_images/bridge.png

Events

The auto-bridge recipe above demonstrates a form of reacting to changes, in that case player position changing.

However, the picraft library provides two different ways of working with events; you can select whichever one suits your particular application. The basic way of reacting to events is to periodically “poll” Minecraft for them (with the poll() method). This will return a list of all events that occurred since the last time your script polled the server. For example, the following script prints a message to the console when you hit a block, detailing the block’s coordinates and the face that you hit:

from time import sleep
from picraft import World

world = World()

while True:
    for event in world.events.poll():
        world.say('Player %d hit face %s of block at %d,%d,%d' % (
            event.player.player_id, event.face,
            event.pos.x, event.pos.y, event.pos.z))
    sleep(0.1)

This is similar to the method used by the official mcpi library. It’s fine for simple scripts but you can probably see how more complex scripts that check exactly which block has been hit start to involve long series of if statements which look a bit ugly in code. The following script creates a couple of blocks near the player on startup: a black block (which ends the script when hit), and a white block (which makes multi-colored blocks fall from the sky):

from random import randint
from time import sleep
from picraft import World, X, Y, Z, Vector, Block

world = World()

p = world.player.tile_pos
white_pos = p - 2 * X
black_pos = p - 3 * X

world.blocks[white_pos] = Block('#ffffff')
world.blocks[black_pos] = Block('#000000')

running = True
while running:
    for event in world.events.poll():
        if event.pos == white_pos:
            rain = Vector(p.x + randint(-10, 10), p.y + 20, p.z + randint(-10, 10))
            rain_end = world.height[rain]
            world.blocks[rain] = Block('wool', randint(1, 15))
            while rain != rain_end:
                with world.connection.batch_start():
                    world.blocks[rain] = Block('air')
                    rain -= Y
                    world.blocks[rain] = Block('wool', randint(1, 15))
                    sleep(0.1)
        elif event.pos == black_pos:
            running = False

The alternate method of event handling in picraft is to rely on picraft’s built-in event loop. This involves “tagging” functions which will react to block hits with the on_block_hit() decorator, then running the main_loop() method. This causes picraft to continually poll the server and call the tagged functions when their criteria are matched by a block-hit event:

from random import randint
from time import sleep
from picraft import World, X, Y, Z, Vector, Block

world = World()

p = world.player.tile_pos
white_pos = p - 2 * X
black_pos = p - 3 * X

world.blocks[white_pos] = Block('#ffffff')
world.blocks[black_pos] = Block('#000000')

@world.events.on_block_hit(pos=black_pos)
def stop_script(event):
    world.connection.close()

@world.events.on_block_hit(pos=white_pos)
def make_it_rain(event):
    rain = Vector(p.x + randint(-10, 10), p.y + 20, p.z + randint(-10, 10))
    rain_end = world.height[rain]
    world.blocks[rain] = Block('wool', randint(1, 15))
    while rain != rain_end:
        with world.connection.batch_start():
            world.blocks[rain] = Block('air')
            rain -= Y
            world.blocks[rain] = Block('wool', randint(1, 15))
            sleep(0.1)

world.events.main_loop()

One advantage of this method (other than slightly cleaner code) is that event handlers can easily be made multi-threaded (to run in parallel with each other) simply by modifying the decorator used:

from random import randint
from time import sleep
from picraft import World, X, Y, Z, Vector, Block

world = World()

p = world.player.tile_pos
white_pos = p - 2 * X
black_pos = p - 3 * X

world.blocks[white_pos] = Block('#ffffff')
world.blocks[black_pos] = Block('#000000')

@world.events.on_block_hit(pos=black_pos)
def stop_script(event):
    world.connection.close()

@world.events.on_block_hit(pos=white_pos, thread=True)
def make_it_rain(event):
    rain = Vector(p.x + randint(-10, 10), p.y + 20, p.z + randint(-10, 10))
    rain_end = world.height[rain]
    world.blocks[rain] = Block('wool', randint(1, 15))
    while rain != rain_end:
        with world.connection.batch_start():
            world.blocks[rain] = Block('air')
            rain -= Y
            world.blocks[rain] = Block('wool', randint(1, 15))
            sleep(0.1)

world.events.main_loop()

Now you should find that the rain all falls simultaneously (more or less, given the constraints of the Pi’s bandwidth!) when you hit the white block multiple times.

_images/rain.png

You should also be aware that the picraft library supports a larger range of events than mcpi. Specifically, it has events for player position changes, and “idle” events. See track_players and include_idle respectively.

Shapes

This recipe demonstrates drawing shapes with blocks in the Minecraft world. The picraft library includes a couple of rudimentary routines for calculating the points necessary for drawing lines:

  • line() which can be used to calculate the positions along a single line
  • lines() which calculates the positions along a series of lines

Here we will attempt to construct a script which draws each regular polygon from an equilateral triangle up to a regular octagon. First we start by defining a function which will generate the points of a regular polygon. This is relatively simple: the interior angles of a polygon always add up to 180 degrees so the angle to turn each time is 180 divided by the number of sides. Given an origin and a side-length it’s a simple matter to iterate over each side generating the necessary point:

from __future__ import division

import math
from picraft import World, Vector, O, X, Y, Z, lines

def polygon(sides, center=O, radius=5):
    angle = 2 * math.pi / sides
    for side in range(sides):
        yield Vector(
                center.x + radius * math.cos(side * angle),
                center.y + radius * math.sin(side * angle))

print(list(polygon(3, center=3*Y)))
print(list(polygon(4, center=3*Y)))
print(list(polygon(5, center=3*Y)))

Next we need a function which will iterate over the number of sides for each required polygon, using the lines() function to generate the points required to draw the shape. Then it’s a simple matter to draw each polygon in turn, wiping it before displaying the next one:

from __future__ import division

import math
from time import sleep
from picraft import World, Vector, Block, O, X, Y, Z, lines

def polygon(sides, center=O, radius=5):
    angle = 2 * math.pi / sides
    for side in range(sides):
        yield Vector(
                center.x + radius * math.cos(side * angle),
                center.y + radius * math.sin(side * angle),
                center.z).round()

def shapes(center=O):
    for sides in range(3, 9):
        yield lines(polygon(sides, center=center))

w = World()
for shape in shapes(w.player.tile_pos + 15*Y + 10*Z):
    # Copy the generator into a list so we can re-use
    # the coordinates
    shape = list(shape)
    # Draw the shape
    with w.connection.batch_start():
        for p in shape:
            w.blocks[p] = Block('gold_block')
    sleep(0.5)
    # Wipe the shape
    with w.connection.batch_start():
        for p in shape:
            w.blocks[p] = Block('air')
_images/shapes.png

Models

This recipe demonstrates drawing models defined by object files. This is a venerable file format from Alias|Wavefront. It’s a simple text-based format that defines the vertices, faces, and other aspects of a model, including the materials of the model. The picraft library includes a rudimentary parser and renderer for this format (in the Model class) which can be used to render such models as blocks in the Minecraft world.

Below is an example object file, which defines the walls and ceiling of a house.

# This is an object file describing a house. First we define the
# required vertices with the "v" command, then reference these
# from faces (with the "f" command). Negative indices in the "f"
# command count back from the most recently defined vertices.

usemtl brick_block

g front-wall
v 0 0 0
v 8 0 0
v 8 3 0
v 0 3 0
v 3 0 0
v 5 0 0
v 3 2 0
v 5 2 0
f -8 -4 -2 -1 -3 -7 -6 -5

g back-wall
v 0 0 8
v 8 0 8
v 8 3 8
v 0 3 8
f -1 -2 -3 -4

g left-wall
f -12 -4 -1 -9

g right-wall
f -11 -3 -2 -10

g ceiling
f -10 -9 -1 -2

We can render this model with the following simple code:

from picraft import Model, World, X, Y, Z

with World() as w:
    p = w.player.tile_pos - 3*X + 5*Z
    with w.connection.batch_start():
        for v, b in Model('house.obj').render().items():
            w.blocks[v + p] = b
_images/house.png

By default, the picraft renderer assumes that the material names are Minecraft block types (see Block.NAMES). However, this is frequently not the case, requiring you to “map” the material names to block types yourself. A materials map can be as simple as a dict mapping material names to Block instances. For example:

from picraft import World, Model, Block

print('Loading model airboat.obj')
m = Model('airboat.obj')
print('Model has the following materials:')
print('\n'.join(s or '<None>' for s in m.materials))

materials_map = {
    None:       Block('stone'),
    'bluteal':  Block('diamond_block'),
    'bronze':   Block('gold_block'),
    'dkdkgrey': Block('#404040'),
    'dkteal':   Block('#000080'),
    'red':      Block('#ff0000'),
    'silver':   Block('#ffffff'),
    'black':    Block('#000000'),
    }

with World() as w:
    with w.connection.batch_start():
        for v, b in m.render(materials=materials_map).items():
            w.blocks[v] = b

_images/airboat.png

To find out what materials are defined on a model, you can query the materials attribute. Note that some faces may have no material associated with them, in which case their material is listed as None (not the blank string).

A materials map may also be a function. This will be called with the face being rendered and must return a Block instance or None (if you don’t want that particular face to be rendered). This is useful for quickly previewing a shape without performing any material mapping; simply provide a function which always returns the same block type:

from picraft import World, Model, Block

m = Model('shuttle.obj').render(materials=lambda face: Block('stone'))

with World() as w:
    with w.connection.batch_start():
        for v, b in m.items():
            w.blocks[v + 20*Y] = b

Animation

This recipe demonstrates, in a series of steps, the construction of a simplistic animation system in Minecraft. Our aim is to create a simple stone cube which rotates about the X axis somewhere in the air. Our first script uses vector_range() to obtain the coordinates of all blocks within the cube, then uses the rotate() method to rotate them about the X axis:

from __future__ import division

from time import sleep
from picraft import World, Vector, X, Y, Z, vector_range, Block

world = World()
world.checkpoint.save()
try:
    cube_range = vector_range(Vector() - 2, Vector() + 2 + 1)
    # Draw frame 1
    state = {}
    for v in cube_range:
        state[v + (5 * Y)] = Block('stone')
    with world.connection.batch_start():
        for v, b in state.items():
            world.blocks[v] = b
    sleep(0.2)
    # Wipe frame 1
    with world.connection.batch_start():
        for v in state:
            world.blocks[v] = Block('air')
    # Draw frame 2
    state = {}
    for v in cube_range:
        state[v.rotate(15, about=X).round() + (5 * Y)] = Block('stone')
    with world.connection.batch_start():
        for v, b in state.items():
            world.blocks[v] = b
    sleep(0.2)
    # and so on...
finally:
    world.checkpoint.restore()

As you can see in the script above we draw the first frame, wait for a bit, then wipe the frame by setting all coordinates in that frame’s state back to “air”. Then we draw the second frame and wait for a bit.

Although this approach works, it’s obviously very long winded for lots of frames. What we want to do is calculate the state of each frame in a function. This next version demonstrates this approach; we use a generator function to yield the state of each frame in turn so we can iterate over the frames with a simple for loop.

We represent the state of a frame of our animation as a dict which maps coordinates (in the form of Vector instances) to Block instances:

from __future__ import division

from time import sleep
from picraft import World, Vector, X, Y, Z, vector_range, Block


def animation_frames(count):
    cube_range = vector_range(Vector() - 2, Vector() + 2 + 1)
    for frame in range(count):
        state = {}
        for v in cube_range:
            state[v.rotate(15 * frame, about=X).round() + (5 * Y)] = Block('stone')
        yield state


world = World()
world.checkpoint.save()
try:
    for frame in animation_frames(10):
        # Draw frame
        with world.connection.batch_start():
            for v, b in frame.items():
                world.blocks[v] = b
        sleep(0.2)
        # Wipe frame
        with world.connection.batch_start():
            for v, b in frame.items():
                world.blocks[v] = Block('air')
finally:
    world.checkpoint.restore()

That’s more like it, but the updates aren’t terribly fast despite using the batch functionality. In order to improve this we should only update those blocks which have actually changed between each frame. Thankfully, because we’re storing the state of each as a dict, this is quite easy:

from __future__ import division

from time import sleep
from picraft import World, Vector, X, Y, Z, vector_range, Block


def animation_frames(count):
    cube_range = vector_range(Vector() - 2, Vector() + 2 + 1)
    for frame in range(count):
        yield {
            v.rotate(15 * frame, about=X).round() + (5 * Y): Block('stone')
            for v in cube_range}


def track_changes(states, default=Block('air')):
    old_state = None
    for state in states:
        # Assume the initial state of the blocks is the default ('air')
        if old_state is None:
            old_state = {v: default for v in state}
        # Build a dict of those blocks which changed from old_state to state
        changes = {v: b for v, b in state.items() if old_state.get(v) != b}
        # Blank out blocks which were in old_state but aren't in state
        changes.update({v: default for v in old_state if v not in state})
        yield changes
        old_state = state


world = World()
world.checkpoint.save()
try:
    for state in track_changes(animation_frames(20)):
        with world.connection.batch_start():
            for v, b in state.items():
                world.blocks[v] = b
        sleep(0.2)
finally:
    world.checkpoint.restore()

Note: this still isn’t perfect. Ideally, we would identify contiguous blocks of coordinates to be updated which have the same block and set them all at the same time (which will utilize the world.setBlocks call for efficiency). However, this is relatively complex to do well so I shall leave it as an exercise for you, dear reader!

Minecraft TV

If you’ve got a Raspberry Pi camera module, you can build a TV to view a live feed from the camera in the Minecraft world. Firstly we need to construct a class which will accept JPEGs from the camera’s MJPEG stream, and render them as blocks in the Minecraft world. Then we need a class to construct the TV model itself and enable interaction with it:

from __future__ import division

import io
import time
import picamera
from picraft import World, V, X, Y, Z, Block
from PIL import Image


def track_changes(old_state, new_state, default=Block('#000000')):
    changes = {v: b for v, b in new_state.items() if old_state.get(v) != b}
    changes.update({v: default for v in old_state if not v in new_state})
    return changes


class MinecraftTVScreen(object):
    def __init__(self, world, origin, size):
        self.world = world
        self.origin = origin
        self.size = size
        self.jpeg = None
        self.state = {}
        # Construct a palette for PIL
        self.palette = list(Block.COLORS)
        self.palette_img = Image.new('P', (1, 1))
        self.palette_img.putpalette(
            [c for rgb in self.palette for c in rgb] +
            list(self.palette[0]) * (256 - len(self.palette))
            )

    def write(self, buf):
        if buf.startswith(b'\xff\xd8'):
            if self.jpeg:
                self.jpeg.seek(0)
                self.render(self.jpeg)
            self.jpeg = io.BytesIO()
        self.jpeg.write(buf)

    def close(self):
        self.jpeg = None

    def render(self, jpeg):
        o = self.origin
        img = Image.open(jpeg)
        img = img.resize(self.size, Image.BILINEAR)
        img = img.quantize(len(self.palette), palette=self.palette_img)
        new_state = {
            o + V(0, y, x): Block.from_color(self.palette[img.getpixel((x, y))], exact=True)
            for x in range(img.size[0])
            for y in range(img.size[1])
            }
        with self.world.connection.batch_start():
            for v, b in track_changes(self.state, new_state).items():
                self.world.blocks[v] = b
        self.state = new_state


class MinecraftTV(object):
    def __init__(self, world, origin=V(), size=(12, 8)):
        self.world = world
        self.camera = picamera.PiCamera()
        self.camera.resolution = (64, int(64 / size[0] * size[1]))
        self.camera.framerate = 5
        self.origin = origin
        self.size = V(0, size[1], size[0])
        self.button_pos = None
        self.quit_pos = None
        self.screen = MinecraftTVScreen(
            self.world, origin + V(0, 1, 1), (size[0] - 2, size[1] - 2))

    def main_loop(self):
        try:
            self.create_tv()
            running = True
            while running:
                for event in self.world.events.poll():
                    if event.pos == self.button_pos:
                        if self.camera.recording:
                            self.switch_off()
                        else:
                            self.switch_on()
                    elif event.pos == self.quit_pos:
                        running = False
                time.sleep(0.1)
        finally:
            if self.camera.recording:
                self.switch_off()
            self.destroy_tv()

    def create_tv(self):
        o = self.origin
        self.world.blocks[o:o + self.size + 1] = Block('#ffffff')
        self.world.blocks[
            o + V(0, 1, 1):o + self.size - V(0, 2, 2) + 1] = Block('#000000')
        self.button_pos = o + V(z=3)
        self.quit_pos = o + V(z=1)
        self.world.blocks[self.button_pos] = Block('#0080ff')
        self.world.blocks[self.quit_pos] = Block('#800000')
        self.world.say('Behold the Minecraft TV!')

    def destroy_tv(self):
        o = self.origin
        self.world.blocks[o:o + self.size + 1] = Block('air')

    def switch_on(self):
        self.world.say('Switching TV on')
        self.camera.start_recording(self.screen, format='mjpeg')

    def switch_off(self):
        self.world.say('Switching TV off')
        self.camera.stop_recording()
        o = self.origin
        self.world.blocks[
            o + V(0, 1, 1):o + self.size - V(0, 2, 2) + 1] = Block('#000000')


with World() as world:
    p = world.player.tile_pos
    tv = MinecraftTV(world, origin=p + 8*X + 2*Y, size=(20, 14))
    tv.main_loop()

Don’t expect to be able to recognize much in the Minecraft TV; the resolution is extremely low and the color matching is far from perfect. Still, if you point the camera at obvious blocks of primary colors and move it around slowly you should see a similar result on the in-game display.

The script includes the ability to position and size the TV as you like, and you may like to experiment with adding new controls to it!

_images/tv.png

Turtle Graphics

Turtle graphics is a popular way for introducing programming to kids. It was part of the original Logo programming language developed by Wally Feurzig and Seymour Papert in 1966.

The turtle module in Python’s standard library provides a classic implementation which moves a triangular turtle around a graphical window drawing geometric shapes.

The picraft turtle module is similar, but instead of a two-dimensional graphical window, its canvas is the Minecraft world. The module provides an object-oriented interface for advanced users that want to control multiple turtles and a simpler procedural interface for newer programmers.

When the turtle is initially created or shown, its default position is beneath the player’s feet:

>>> from picraft.turtle import *
>>> showturtle()
_images/turtle1.png

The turtle’s shape indicates its “forward” direction. Various simple commands can be used to control its orientation and motion:

>>> right(180)
>>> forward(5)
_images/turtle2.png

Every operation can be undone, and commands can be built up to construct whole shapes:

>>> undo()
>>> penup()
>>> forward(5)
>>> left(90)
>>> pendown()
>>> fillblock('diamond_block')
>>> fill(True)
>>> forward(3)
>>> left(90)
>>> forward(4)
>>> left(135)
>>> forward(4)
>>> fill(False)
_images/turtle3.png

Overview of available Turtle and TurtleScreen methods

Methods of Turtle and corresponding functions

Turtle motion
picraft.turtle.fd(distance)
picraft.turtle.forward(distance)
Parameters:distance (float) – the number of blocks to move forward.

Move the turtle forward by the specified distance, in the direction the turtle is headed:

>>> position()
Vector(x=2, y=-1, z=13)
>>> forward(5)
>>> position()
Vector(x=2, y=-1, z=18)
>>> forward(-2)
>>> position()
Vector(x=2, y=-1, z=16)
picraft.turtle.back(distance)
picraft.turtle.bk(distance)
picraft.turtle.backward(distance)
Parameters:distance (float) – the number of blocks to move back.

Move the turtle backward by the specified distance, opposite to the direction the turtle is headed. Does not change the turtle’s heading:

>>> heading()
0.0
>>> position()
Vector(x=2, y=-1, z=18)
>>> backward(2)
>>> position()
Vector(x=2, y=-1, z=16)
>>> heading()
0.0
picraft.turtle.lt(angle)
picraft.turtle.left(angle)
Parameters:angle (float) – the number of degrees to turn counter clockwise.

Turns the turtle left (counter-clockwise) by angle degrees:

>>> heading()
90.0
>>> left(90)
>>> heading()
0.0
picraft.turtle.rt(angle)
picraft.turtle.right(angle)
Parameters:angle (float) – the number of degrees to turn clockwise.

Turns the turtle right (clockwise) by angle degrees:

>>> heading()
0.0
>>> right(90)
>>> heading()
90.0
picraft.turtle.up(angle)
Parameters:angle (float) – the number of degrees to increase elevation by.

Turns the turtle’s nose (its elevation) up by angle degrees:

>>> elevation()
-45.0
>>> up(45)
>>> elevation()
0.0
picraft.turtle.dn(angle)
picraft.turtle.down(angle)
Parameters:angle (float) – the number of degrees to reduce elevation by.

Turns the turtle’s nose (its elevation) down by angle degrees:

>>> elevation()
0.0
>>> down(45)
>>> elevation()
-45.0
picraft.turtle.setpos(x, y=None, z=None)
picraft.turtle.setposition(x, y=None, z=None)
picraft.turtle.goto(x, y=None, z=None)
Parameters:
  • x (float) – the new x coordinate or a turtle / triple / Vector of numbers
  • y (float) – the new y coordinate or None
  • z (float) – the new z coordinate or None

Moves the turtle to an absolute position. If the pen is down, draws a line between the current position and the newly specified position. Does not change the turtle’s orientation:

>>> tp = pos()
>>> tp
Vector(x=2, y=-1, z=16)
>>> setpos(4, -1, 16)
>>> pos()
Vector(x=4, y=-1, z=16)
>>> setpos((0, -1, 16))
>>> pos()
Vector(x=0, y=-1, z=16)
>>> setpos(tp)
>>> pos()
Vector(x=2, y=-1, z=16)

If y and z are None, x must be a triple of coordinates, a Vector, or another Turtle.

picraft.turtle.setx(x)
Parameters:x (float) – the new x coordinate

Set the turtle’s first coordinate to x; leave the second and third coordinates unchanged:

>>> position()
Vector(x=2, y=-1, z=16)
>>> setx(5)
>>> position()
Vector(x=5, y=-1, z=16)
picraft.turtle.sety(y)
Parameters:y (float) – the new y coordinate

Set the turtle’s second coordinate to y; leave the first and third coordinates unchanged:

>>> position()
Vector(x=2, y=-1, z=16)
>>> sety(5)
>>> position()
Vector(x=2, y=5, z=16)
picraft.turtle.setz(z)
Parameters:z (float) – the new z coordinate

Set the turtle’s third coordinate to z; leave the first and second coordinates unchanged:

>>> position()
Vector(x=2, y=-1, z=16)
>>> setz(5)
>>> position()
Vector(x=2, y=-1, z=5)
picraft.turtle.seth(to_angle)
picraft.turtle.setheading(to_angle)
Parameters:to_angle (float) – the new heading

Set the orientation of the turtle on the ground plane (X-Z) to to_angle. The common directions in degrees correspond to the following axis directions:

heading axis
0 +Z
90 +X
180 -Z
270 -X
>>> setheading(90)
>>> heading()
90.0
picraft.turtle.sete(to_angle)
picraft.turtle.setelevation(to_angle)
Parameters:to_angle (float) – the new elevation

Set the elevation of the turtle away from the ground plane (X-Z) to to_angle. At 0 degrees elevation, the turtle moves along the ground plane (X-Z). At 90 degrees elevation, the turtle moves vertically upward, and at -90 degrees, the turtle moves vertically downward:

>>> setelevation(90)
>>> elevation()
90.0
picraft.turtle.home()

Move the turtle to its starting position (this is usually beneath where the player was standing when the turtle was spawned), and set its heading to its start orientation (0 degrees heading, 0 degrees elevation):

>>> heading()
90.0
>>> elevation
45.0
>>> position()
Vector(x=2, y=-1, z=16)
>>> home()
>>> position()
Vector(x=0, y=-1, z=0)
>>> heading()
0.0
>>> elevation()
0.0
picraft.turtle.undo()

Undo (repeatedly) the last turtle action(s):

>>> for i in range(4):
...     fd(5)
...     lt(90)
...
>>> for i in range(8):
...     undo()
Tell Turtle’s state
picraft.turtle.position()
picraft.turtle.pos()

Return the turtle’s current location (x, y, z) as a Vector:

>>> pos()
Vector(x=2, y=-1, z=18)
picraft.turtle.towards(x, y=None, z=None)
Parameters:
  • x (float) – the target x coordinate or a turtle / triple / Vector of numbers
  • y (float) – the target y coordinate or None
  • z (float) – the target z coordinate or None

Return the angle between the line from the turtle’s position to the position specified within the ground plane (X-Z):

>>> home()
>>> forward(5)
>>> towards(0, 0, 0)
-180.0
>>> left(90)
>>> forward(5)
>>> towards(0, 0, 0)
135.0

If y and z are None, x must be a triple of coordinates, a Vector, or another Turtle.

picraft.turtle.heading()

Return the turtle’s current heading (its orientation along the ground plane, X-Z):

>>> home()
>>> right(90)
>>> heading()
90.0
picraft.turtle.elevation()

Return the turtle’s current elevation (its orientation away from the ground plane, X-Z):

>>> home()
>>> up(90)
>>> elevation()
90.0
picraft.turtle.xcor()

Return the turtle’s x coordinate:

>>> home()
>>> xcor()
0
>>> left(90)
>>> forward(2)
>>> xcor()
2
picraft.turtle.ycor()

Return the turtle’s y coordinate:

>>> home()
>>> ycor()
-1
>>> up(90)
>>> forward(2)
>>> ycor()
1
picraft.turtle.zcor()

Return the turtle’s z coordinate:

>>> home()
>>> zcor()
0
>>> forward(2)
>>> zcor()
2
picraft.turtle.distance(x, y=None, z=None)
Parameters:
  • x (float) – the target x coordinate or a turtle / triple / Vector of numbers
  • y (float) – the target y coordinate or None
  • z (float) – the target z coordinate or None

Return the distance from the turtle to (x, y, z), the given vector, or the given other turtle, in blocks:

>>> home()
>>> distance((0, -1, 5))
5.0
>>> forward(2)
>>> distance(0, -1, 5)
3.0

Pen control

Drawing state
picraft.turtle.pd()
picraft.turtle.pendown()

Put the “pen” down; the turtle draws new blocks when it moves.

picraft.turtle.pu()
picraft.turtle.penup()

Put the “pen” up; movement doesn’t draw new blocks.

picraft.turtle.isdown()

Returns True if the pen is down, False if it’s up.

Block control
picraft.turtle.penblock(*args)

Return or set the block that the turtle draws when it moves. Several input formats are allowed:

penblock()
Return the current pen block. May be used as input to another penblock or fillblock call.
penblock(Block('grass'))
Set the pen block to the specified Block instance.
penblock('grass')
Implicitly make a Block from the given arguments and set that as the pen block.
>>> penblock()
<Block "stone" id=1 data=0>
>>> penblock('diamond_block')
>>> penblock()
<Block "diamond_block" id=57 data=0>
>>> penblock(1, 0)
>>> penblock()
<Block "stone" id=1 data=0>
picraft.turtle.fillblock(*args)

Return or set the block that the turtle fills shapes with. Several input formats are allowed:

fillblock()
Return the current fill block. May be used as input to another penblock or fillblock call.
fillblock(Block('grass'))
Set the fill block to the specified Block instance.
fillblock('grass')
Implicitly make a Block from the given arguments and set that as the fill block.
>>> fillblock()
<Block "stone" id=1 data=0>
>>> fillblock('diamond_block')
>>> fillblock()
<Block "diamond_block" id=57 data=0>
>>> fillblock(1, 0)
>>> fillblock()
<Block "stone" id=1 data=0>
Filling
picraft.turtle.fill(flag=None)
Parameters:flag (bool) – True if beginning a fill, False if ending a fill.

Call fill(True) before drawing the shape you want to fill, and fill(False) when done. When used without argument: return the fill state (True if filling, False otherwise).

picraft.turtle.begin_fill()

Call just before drawing a shape to be filled. Equivalent to fill(True).

picraft.turtle.end_fill()

Fill the shape drawn after the last call to begin_fill(). Equivalent to fill(False).

More drawing control
picraft.turtle.reset()
picraft.turtle.clear()

Turtle state

Visibility
picraft.turtle.st()
picraft.turtle.showturtle()

Make the turtle visible:

>>> showturtle()
picraft.turtle.ht()
picraft.turtle.hideturtle()

Make the turtle invisible:

>>> hideturtle()
picraft.turtle.isvisible()

Return True if the turtle is shown, False if it’s hidden:

>>> hideturtle()
>>> isvisible()
False
>>> showturtle()
>>> isvisible()
True

Special Turtle methods

picraft.turtle.undobufferentries()

Return number of entries in the undobuffer:

>>> while undobufferentries():
...     undo()
picraft.turtle.getpen()
picraft.turtle.getturtle()

Return the Turtle object itself. Only reasonable use: as a function to return the “anonymous” turtle:

>>> pet = getturtle()
>>> pet.fd(50)
>>> pet
<picraft.turtle.Turtle object at 0x...>
picraft.turtle.getscreen()

Return the TurtleScreen object the turtle is drawing on:

>>> ts = getscreen()
>>> ts
<picraft.turtle.TurtleScreen object at 0x...>
>>> ts.world.say("Hello world!")

Vectors

Vectors are a crucial part of working with picraft; sufficiently important to demand their own section. This chapter introduces all the major vector operations with simple examples and diagrams illustrating the results.

Orientation

Vectors represent a position or direction within the Minecraft world. The Minecraft world uses a right-hand coordinate system where the Y axis is vertical, and Z represents depth. You can think of positive Z values as pointing “out of” the screen, while negative Z values point “into” the screen.

If you ever have trouble remembering the orientation label the thumb, index finger, and middle finger of your right hand as X, Y, Z respectively. Raise your hand so that Y (the index finger) is pointing up. Now spread your thumb and middle finger so they’re at right angles to each other and your index finger, and you’ll have the correct orientation of Minecraft’s coordinate system.

The following illustration shows the directions of each of the axes:

Positive rotation in Minecraft also follows the right-hand rule. For example, positive rotation about the Y axis proceeds anti-clockwise along the X-Z plane. Again, this is easy to see by applying the rule: make a fist with your right hand, then point the thumb vertically (positive direction along the Y axis). Your other fingers now indicate the positive direction of rotation around that axis.

Vector-vector operations

The picraft Vector class is extremely flexible and supports a wide variety of operations. All Python’s built-in operations (addition, subtraction, division, multiplication, modulus, absolute, bitwise operations, etc.) are supported between two vectors, in which case the operation is performed element-wise. In other words, adding two vectors A and B produces a new vector with its x attribute set to A.x + B.x, its y attribute set to A.y + B.y and so on:

>>> from picraft import *
>>> Vector(1, 1, 0) + Vector(1, 0, 1)
Vector(x=2, y=1, z=1)
_images/vector1.png

Likewise for subtraction, multiplication, etc.:

>>> p = Vector(1, 2, 3)
>>> q = Vector(3, 2, 1)
>>> p - q
Vector(x=-2, y=0, z=2)
>>> p * q
Vector(x=3, y=4, z=3)
>>> p % q
Vector(x=1, y=0, z=0)
_images/vector2.png

Vector-scalar operations

Vectors also support several operations between themselves and a scalar value. In this case the operation with the scalar is applied to each element of the vector. For example, multiplying a vector by the number 2 will return a new vector with every element of the original multiplied by 2:

>>> p * 2
Vector(x=2, y=4, z=6)
>>> p + 2
Vector(x=3, y=4, z=5)
>>> p // 2
Vector(x=0, y=1, z=1)
_images/vector3.png

Miscellaneous function support

Vectors also support several of Python’s built-in functions:

>>> abs(Vector(-1, 0, 1))
Vector(x=1, y=0, z=1)
>>> pow(Vector(1, 2, 3), 2)
Vector(x=1, y=4, z=9)
>>> import math
>>> math.trunc(Vector(1.5, 2.3, 3.7))
Vector(x=1, y=2, z=3)

Vector rounding

Some built-in functions can’t be directly supported, in which case equivalently named methods are provided:

>>> p = Vector(1.5, 2.3, 3.7)
>>> p.round()
Vector(x=2, y=2, z=4)
>>> p.ceil()
Vector(x=2, y=3, z=4)
>>> p.floor()
Vector(x=1, y=2, z=3)
_images/vector4.png

Hint

Floor rounding is the method Minecraft uses to convert from a player position to a tile position. Floor rounding may looking simply like truncation, aka “round toward zero”, but becomes different when negative numbers are involved.

Short-cuts

Several vector short-hands are also provided. One for the unit vector along each of the three axes (X, Y, and Z), one for the origin (O), and finally V which is simply a short-hand for Vector itself. Obviously, these can be used to simplify many vector-related operations:

>>> X
Vector(x=1, y=0, z=0)
>>> X + Y
Vector(x=1, y=1, z=0)
>>> p = V(1, 2, 3)
>>> p + X
Vector(x=2, y=2, z=3)
>>> p + 2 * Y
Vector(x=1, y=6, z=3)
_images/vector5.png

Rotation

From the paragraphs above it should be relatively easy to see how one can implement vector translation and vector scaling using everyday operations like addition, subtraction, multiplication and divsion. The third major transformation usually required of vectors, rotation, is a little harder. For this, the rotate() method is provided. This takes two mandatory arguments: the number of degrees to rotate, and a vector specifying the axis about which to rotate (it is recommended that this is specified as a keyword argument for code clarity). For example:

>>> p = V(1, 2, 3)
>>> p.rotate(90, about=X)
Vector(x=1.0, y=-3.0, z=2.0)
>>> p.rotate(180, about=Y)
Vector(x=-0.9999999999999997, y=2, z=-3.0)
>>> p.rotate(180, about=Y).round()
Vector(x=-1.0, y=2.0, z=-3.0)
_images/vector6.png
>>> X.rotate(180, about=X + Y).round()
Vector(x=-0.0, y=1.0, z=-0.0)
_images/vector7.png

A third optional argument to rotate, origin, permits rotation about an arbitrary line. When specified, the axis of rotation passes through the point specified by origin and runs in the direction of the axis specified by about. Naturally, origin defaults to the origin (0, 0, 0):

>>> (2 * Y).rotate(180, about=Y, origin=2 * X).round()
Vector(x=4.0, y=2.0, z=0.0)
>>> O.rotate(90, about=Y, origin=X).round()
Vector(x=1.0, y=0.0, z=1.0)
_images/vector8.png

To aid in certain kinds of rotation, the angle_between() method can be used to determine the angle between two vectors (in the plane common to both):

>>> X.angle_between(Y)
90.0
>>> p = V(1, 2, 3)
>>> X.angle_between(p)
74.498640433063
_images/vector9.png

Magnitudes

The magnitude attribute can be used to determine the length of a vector (via Pythagoras’ theorem), while the unit attribute can be used to obtain a vector in the same direction with a magnitude (length) of 1.0. The distance_to() method can also be used to calculate the distance between two vectors (this is simply equivalent to the magnitude of the vector obtained by subtracting one vector from the other):

>>> p = V(1, 2, 3)
>>> p.magnitude
3.7416573867739413
>>> p.unit
Vector(x=0.2672612419124244, y=0.5345224838248488, z=0.8017837257372732)
>>> p.unit.magnitude
1.0
>>> q = V(2, 0, 1)
>>> p.distance_to(q)
3.0
_images/vector10.png

Dot and cross products

The dot and cross products of a vector with another can be calculated using the dot() and cross() methods respectively. These are useful for determining whether vectors are orthogonal (the dot product of orthogonal vectors is always 0), for finding a vector perpendicular to the plane of two vectors (via the cross product), or for finding the volume of a parallelepiped defined by three vectors, via the triple product:

>>> p = V(x=2)
>>> q = V(z=-1)
>>> p.dot(q)
0
>>> r = p.cross(q)
>>> r
Vector(x=0, y=2, z=0)
>>> area_of_pqr = p.cross(q).dot(r)
>>> area_of_pqr
4
_images/vector11.png

Projection

The final method provided by the Vector class is project() which implements scalar projection. You might think of this as calculating the length of the shadow one vector casts upon another. Or, put another way, this is the length of one vector in the direction of another (unit) vector:

>>> p = V(1, 2, 3)
>>> p.project(X)
1.0
>>> q = X + Z
>>> p.project(q)
2.82842712474619
>>> r = q.unit * p.project(q)
>>> r.round(4)
Vector(x=2.0, y=0.0, z=2.0)
_images/vector12.png

Immutability

Vectors in picraft (in contrast to the Vec3 class in mcpi) are immutable. This simply means that you cannot change the X, Y, or Z coordinate of an existing vector:

>>> v = Vector(1, 2, 3)
>>> v.x += 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>> v.x = 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

Given that nearly every standard operation can be applied to the vector itself, this isn’t a huge imposition:

>>> v + X
Vector(x=2, y=2, z=3)
>>> v += X
>>> v
Vector(x=2, y=2, z=3)

Nevertheless, it may seem like an arbitrary restriction. However, it conveys an extremely important capability in Python: only immutable objects may be keys of a dict or members of a set. Hence, in picraft, a dict can be used to represent the state of a portion of the world by mapping vectors to block types, and set operators can be used to trivially determine regions.

For example, consider two vector ranges. We can convert them to sets and use the standard set operators to determine all vectors that occur in both ranges, and in one but not the other:

>>> vr1 = vector_range(O, V(5, 0, 5) + 1)
>>> vr1 = vector_range(O, V(2, 0, 5) + 1)
>>> vr2 = vector_range(O, V(5, 0, 2) + 1)
>>> set(vr1) & set(vr2)
set([Vector(x=0, y=0, z=2), Vector(x=1, y=0, z=0), Vector(x=2, y=0, z=2),
Vector(x=0, y=0, z=1), Vector(x=1, y=0, z=1), Vector(x=0, y=0, z=0),
Vector(x=2, y=0, z=1), Vector(x=1, y=0, z=2), Vector(x=2, y=0, z=0)])
>>> set(vr1) - set(vr2)
set([Vector(x=1, y=0, z=3), Vector(x=1, y=0, z=4), Vector(x=2, y=0, z=4),
Vector(x=1, y=0, z=5), Vector(x=0, y=0, z=5), Vector(x=0, y=0, z=4),
Vector(x=2, y=0, z=3), Vector(x=2, y=0, z=5), Vector(x=0, y=0, z=3)])
_images/regions1.png

We could use a dict to store the state of the world for one of the ranges:

>>> d = {v: b for (v, b) in zip(vr1, world.blocks[vr1])}

We can then manipulate this using dict comprehensions. For example, to modify the dict to shift all vectors right by two blocks:

>>> d = {v + 2*X: b for (v, b) in d.items()}

Or to rotate the vectors by 45 degrees about the Y axis:

>>> d = {v.rotate(45, about=Y).round(): b for (v, b) in d.items()}

It is also worth noting to that due to their nature, sets and dicts automatically eliminate duplicated coordinates. This can be useful for efficiency, but in some cases (such as the rotation above), can be something to watch out for.

Conversion from mcpi

If you have existing scripts that use the mcpi implementation, and you wish to convert them to using the picraft library, this section contains details and examples covering equivalent functionality between the libraries.

Minecraft.create

Equivalent: World

To create a connection using default settings is similar in both libraries:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()

>>> from picraft import World
>>> w = World()

Creating a connection with an explicit hostname and port is also similar:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create('localhost', 4711)

>>> from picraft import World
>>> w = World('localhost', 4711)

Minecraft.getBlock

See Minecraft.getBlockWithData below.

Minecraft.getBlocks

Equivalent: blocks

This method only works with the Raspberry Juice mod for the PC version of Minecraft. In picraft simply query the blocks attribute with a slice of vectors, just as with the equivalent to Minecraft.setBlocks below:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.getBlocks(0, -1, 0, 0, 5, 0)
[2, 2, 2, 2, 2, 2, 2]

>>> from picraft import World, Vector, Block
>>> w = World()
>>> v1 = Vector(0, -1, 0)
>>> v2 = Vector(0, 5, 0)
>>> w.blocks[v1:v2 + 1]
[<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 "grass" id=2 data=0>,<Block "grass" id=2 data=0>,
<Block "grass" id=2 data=0>]

Note

In picraft, this method will work with both Raspberry Juice and Minecraft Pi Edition, but the efficient getBlocks call will only be used when picraft detects it is connected to a Raspberry Juice server.

Warning

There is currently no equivalent to getBlockWithData that operates over multiple blocks, so blocks returned by querying in this manner only have a valid id field; the data attribute is always 0.

Minecraft.getBlockWithData

Equivalent: blocks

There is no direct equivalent to getBlock, just getBlockWithData (as there’s no difference in operational cost so there’s little point in retrieving a block id without the data). In mcpi this is done by executing a method; in picraft this is done by querying an attribute with a Vector:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.getBlock(0, -1, 0)
2
>>> mc.getBlockWithData(0, -1, 0)
Block(2, 0)

>>> from picraft import World, Vector
>>> w = World()
>>> w.blocks[Vector(0, -1, 0)]
<Block "grass" id=2 data=0>

The id and data can be extracted from the Block tuple that is returned:

>>> b = w.blocks[Vector(0, -1, 0)]
>>> b.id
2
>>> b.data
0

Minecraft.setBlock

Equivalent: blocks

In picraft the same attribute is used as for accessing block ids; just assign a Block instance to the attribute, instead of querying it:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.getBlock(0, -1, 0)
2
>>> mc.setBlock(0, -1, 0, 1, 0)

>>> from picraft import World, Vector, Block
>>> w = World()
>>> w.blocks[Vector(0, -1, 0)]
<Block "grass" id=2 data=0>
>>> w.blocks[Vector(0, -1, 0)] = Block(1, 0)

Minecraft.setBlocks

Equivalent: blocks

The same attribute as for setBlock is used for setBlocks; just pass a slice of vectors instead of a single vector (the example below shows an easy method of generating such a slice by adding 1 to a vector for the upper end of the slice):

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.getBlock(0, -1, 0)
2
>>> mc.setBlocks(0, -1, 0, 0, 5, 0, 1, 0)

>>> from picraft import World, Vector, Block
>>> w = World()
>>> v1 = Vector(0, -1, 0)
>>> v2 = Vector(0, 5, 0)
>>> w.blocks[v]
<Block "grass" id=2 data=0>
>>> w.blocks[v1:v2 + 1] = Block(1, 0)

Minecraft.getHeight

Equivalent: height

Retrieving the height of the world in a specific location is done with an attribute (like retrieving the id and type of blocks). Unlike mcpi, you pass a full vector (of which the Y-coordinate is ignored), and the property returns a full vector with the same X- and Z-coordinates, but the Y-coordinate of the first non-air block from the top of the world:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.getHeight(0, 0)
0

>>> from picraft import World, Vector
>>> w = World()
>>> w.height[Vector(0, -10, 0)]
Vector(x=0, y=0, z=0)

Minecraft.getPlayerEntityIds

Equivalent: players

The connected player’s entity ids can be retrieved by iterating over the players attribute which acts as a mapping from player id to Player instances:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.getPlayerEntityIds()
[1]

>>> from picraft import World
>>> w = World()
>>> list(w.players)
[1]

Minecraft.saveCheckpoint

Equivalent: save()

Checkpoints can be saved in a couple of ways with picraft. Either you can explicitly call the save() method, or you can use the checkpoint attribute as a context manager:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.saveCheckpoint()

>>> from picraft import World
>>> w = World()
>>> w.checkpoint.save()

In the context manager case, the checkpoint will be saved upon entry to the context and will only be restored if an exception occurs within the context:

>>> from picraft import World, Vector, Block
>>> w = World()
>>> with w.checkpoint:
...     # Do something with blocks...
...     w.blocks[Vector()] = Block.from_name('stone')

Minecraft.restoreCheckpoint

Equivalent: restore()

As with saving a checkpoint, either you can call restore() directly:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.saveCheckpoint()
>>> mc.restoreCheckpoint()

>>> from picraft import World
>>> w = World()
>>> w.checkpoint.save()
>>> w.checkpoint.restore()

Or you can use the context manager to restore the checkpoint automatically in the case of an exception:

>>> from picraft import World, Vector, Block
>>> w = World()
>>> with w.checkpoint:
...     # Do something with blocks
...     w.blocks[Vector()] = Block.from_name('stone')
...     # Raising an exception within the block will implicitly
...     # cause the checkpoint to restore
...     raise Exception('roll back to the checkpoint')

Minecraft.postToChat

Equivalent: say()

The postToChat method is simply replaced with the say() method with the one exception that the latter correctly recognizes line breaks in the message:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.postToChat('Hello world!')

>>> from picraft import World
>>> w = World()
>>> w.say('Hello world!')

Minecraft.setting

Equivalent: immutable and nametags_visible

The setting method is replaced with (write-only) properties with the equivalent names to the settings that can be used:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.setting('world_immutable', True)
>>> mc.setting('nametags_visible', True)

>>> from picraft import World
>>> w = World()
>>> w.immutable = True
>>> w.nametags_visible = True

Minecraft.player.getPos

Equivalent: pos

The player.getPos and player.setPos methods are replaced with the pos attribute which returns a Vector of floats and accepts the same to move the host player:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.player.getPos()
Vec3(12.7743,12.0,-8.39158)
>>> mc.player.setPos(12,12,-8)

>>> from picraft import World, Vector
>>> w = World()
>>> w.player.pos
Vector(x=12.7743, y=12.0, z=-8.39158)
>>> w.player.pos = Vector(12, 12, -8)

One advantage of this implementation is that adjusting the player’s position relative to their current one becomes simple:

>>> w.player.pos += Vector(y=20)

Minecraft.player.setPos

See Minecraft.player.getPos above.

Minecraft.player.getTilePos

Equivalent: tile_pos

The player.getTilePos and player.setTilePos methods are replaced with the tile_pos attribute which returns a Vector of ints, and accepts the same to move the host player:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.player.getTilePos()
Vec3(12,12,-9)
>>> mc.player.setTilePos(12, 12, -8)

>>> from picraft import World, Vector
>>> w = World()
>>> w.player.tile_pos
Vector(x=12, y=12, z=-9)
>>> w.player.tile_pos += Vector(y=20)

Minecraft.player.setTilePos

See Minecraft.player.getTilePos above.

Minecraft.player.setting

Equivalent: autojump

The player.setting method is replaced with the write-only autojump attribute:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.player.setting('autojump', False)

>>> from picraft import World
>>> w = World()
>>> w.player.autojump = False

Minecraft.player.getRotation

Equivalent: heading

The player.getRotation method is replaced with the read-only heading attribute:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.player.getRotation()
49.048615

>>> from picraft import World
>>> w = World()
>>> w.player.heading
49.048615

Minecraft.player.getPitch

Equivalent: pitch

The player.getPitch method is replaced with the read-only pitch attribute:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.player.getPitch()
4.3500223

>>> from picraft import World
>>> w = World()
>>> w.player.pitch
4.3500223

Minecraft.player.getDirection

Equivalent: direction

The player.getDirection method is replaced with the read-only direction attribute:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.player.getDirection()
Vec3(0.1429840348766887,-0.3263934845430674,0.934356922711132)

>>> from picraft import World
>>> w = World()
>>> w.player.direction
Vector(x=0.1429840348766887, y=-0.3263934845430674, z=0.934356922711132)

Minecraft.entity.getPos

Equivalent: pos

The entity.getPos and entity.setPos methods are replaced with the pos attribute. Access the relevant Player instance by indexing the players attribute:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.entity.getPos(1)
Vec3(12.7743,12.0,-8.39158)
>>> mc.entity.setPos(1, 12, 12, -8)

>>> from picraft import World, Vector
>>> w = World()
>>> w.players[1].pos
Vector(x=12.7743, y=12.0, z=-8.39158)
>>> w.players[1].pos = Vector(12, 12, -8)

Minecraft.entity.setPos

See Minecraft.entity.getPos above.

Minecraft.entity.getTilePos

Equivalent: tile_pos

The entity.getTilePos and entity.setTilePos methods are replaced with the tile_pos attribute. Access the relevant Player instance by indexing the players attribute:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.entity.getTilePos(1)
Vec3(12,12,-9)
>>> mc.entity.setTilePos(1, 12, 12, -8)

>>> from picraft import World, Vector
>>> w = World()
>>> w.players[1].tile_pos
Vector(x=12, y=12, z=-9)
>>> w.players[1].tile_pos += Vector(y=20)

Minecraft.entity.setTilePos

See Minecraft.entity.getTilePos above.

Minecraft.entity.getRotation

Equivalent: heading

The entity.getRotation method is replaced with the read-only heading attribute:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.entity.getRotation(213)
49.048615

>>> from picraft import World
>>> w = World()
>>> w.players[213].heading
49.048615

Minecraft.entity.getPitch

Equivalent: pitch

The entity.getPitch method is replaced with the read-only pitch attribute:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.entity.getPitch(213)
4.3500223

>>> from picraft import World
>>> w = World()
>>> w.players[213].pitch
4.3500223

Minecraft.entity.getDirection

Equivalent: direction

The entity.getDirection method is replaced with the read-only duration attribute:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.entity.getDirection(213)
Vec3(0.1429840348766887,-0.3263934845430674,0.934356922711132)

>>> from picraft import World
>>> w = World()
>>> w.players[213].direction
Vector(x=0.1429840348766887, y=-0.3263934845430674, z=0.934356922711132)

Minecraft.camera.setNormal

Equivalent: first_person()

The camera attribute in picraft holds a Camera instance which controls the camera in the Minecraft world. The first_person() method can be used to set the camera to view the world through the eyes of the specified player. The player is specified as the world’s player attribute, or as a player retrieved from the players attribute:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.camera.setNormal()
>>> mc.camera.setNormal(2)

>>> from picraft import World
>>> w = World()
>>> w.camera.first_person(w.player)
>>> w.camera.first_person(w.players[2])

Minecraft.camera.setFollow

Equivalent: third_person()

The camera attribute in picraft holds a Camera instance which controls the camera in the Minecraft world. The third_person() method can be used to set the camera to view the specified player from above. The player is specified as the world’s player attribute, or as a player retrieved from the players attribute:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.camera.setFollow()
>>> mc.camera.setNormal(1)

>>> from picraft import World
>>> w = World()
>>> w.camera.third_person(w.player)
>>> w.camera.third_person(w.players[1])

Minecraft.camera.setFixed

Equivalent: pos

The pos attribute can be passed a Vector instance to specify the absolute position of the camera. The camera will be pointing straight down (y=-1) from the given position and will not move to follow any entity:

>>> import mcpi.minecraft as minecraft
>>> mc = minecraft.Minecraft.create()
>>> mc.camera.setFixed()
>>> mc.camera.setPos(0,20,0)

>>> from picraft import World, Vector
>>> w = World()
>>> w.camera.pos = Vector(0, 20, 0)

Minecraft.camera.setPos

See Minecraft.camera.setFixed above.

Minecraft.block.Block

Equivalent: Block

The Block class in picraft is similar to the Block class in mcpi but with one major difference: in picraft a Block instance is a tuple descendent and therefore immutable (you cannot change the id or data attributes of a Block instance).

This may seem like an arbitrary barrier, but firstly its quite rare to adjust the the id or data attribute (it’s rather more common to just overwrite a block in the world with an entirely new type), and secondly this change permits blocks to be used as keys in a Python dictionary, or to be stored in a set.

The Block class also provides several means of construction, and additional properties:

>>> from picraft import Block
>>> Block(1, 0)
<Block "stone" id=1 data=0>
>>> Block(35, 1)
<Block "wool" id=35 data=1>
>>> Block.from_name('wool', data=1).description
u'Orange Wool'
>>> Block.from_color('#ffffff').description
u'White Wool'

Compatibility constants are also provided:

>>> from picraft import block
>>> block.DIAMOND_BLOCK
<Block "diamond_block" id=57 data=0>
>>> block.STONE
<Block "stone" id=1 data=0>

Frequently Asked Questions

Feel free to ask the author, or add questions to the issue tracker on GitHub, or even edit this document yourself and add frequently asked questions you’ve seen on other forums!

Why?

The most commonly asked question at this stage is: why build picraft at all? Doesn’t mcpi work well enough? It certainly works, but it’s inconsistent with PEP-8 (camelCase everywhere, getters and setters, which always leads to questions when we’re teaching it in combination with other libraries), wasn’t Python 3 compatible (when I started writing picraft, although it is now), has several subtle bugs (Block’s hash, Vec3’s floor rounding), and I’m not particularly fond of many of its design choices (mutable vectors being the primary one).

There have been many attempts at extending mcpi (Martin O’Hanlon’s excellent minecraft-stuff library being one of the best known), but none of the extensions could correct the flaws in the core library itself, and I thought several of the extensions probably should’ve been core functionality anyway.

API Reference

The picraft package consists of several modules which permit access to and modification of a Minecraft world. The package is intended as an alternative Python API to the “official” Minecraft Python API (for reasons explained in the Frequently Asked Questions).

The classes defined in most modules of this package are available directly from the picraft namespace. In other words, the following code is typically all that is required to access classes in this package:

import picraft

For convenience on the command line you may prefer to simply do the following:

from picraft import *

However, this is frowned upon in code as it pulls everything into the global namespace, so you may prefer to do something like this:

from picraft import World, Vector, Block

This is the style used in the Recipes chapter. Sometimes, if you are using the Vector class extensively, you may wish to use the short-cuts for it:

from picraft import World, V, O, X, Y, Z, Block

The following sections document the various modules available within the package:

API - The World class

The world module defines the World class, which is the usual way of starting a connection to a Minecraft server and which then provides various attributes allowing the user to query and manipulate that world.

Note

All items in this module are available from the picraft namespace without having to import picraft.world directly.

The following items are defined in the module:

World

class picraft.world.World(host=u'localhost', port=4711, timeout=1.0, ignore_errors=True)[source]

Represents a Minecraft world.

This is the primary class that users interact with. Construct an instance of this class, optionally specifying the host and port of the server (which default to “localhost” and 4711 respectively). Afterward, the instance can be used to query and manipulate the minecraft world of the connected game.

The say() method can be used to send commands to the console, while the player attribute can be used to manipulate or query the status of the player character in the world. The players attribute can be used to manipulate or query other players within the world (this object can be iterated over to discover players):

>>> from picraft import *
>>> world = World()
>>> len(world.players)
1
>>> world.say('Hello, world!')
say(message)[source]

Displays message in the game’s chat console.

The message parameter must be a string (which may contain multiple lines). Each line of the message will be sent to the game’s chat console and displayed immediately. For example:

>>> world.say('Hello, world!')
>>> world.say('The following player IDs exist:\n%s' %
...     '\n'.join(str(p) for p in world.players))
blocks

Represents the state of blocks in the Minecraft world.

This property can be queried to determine the type of a block in the world, or can be set to alter the type of a block. The property can be indexed with a single Vector, in which case the state of a single block is returned (or updated) as a Block object:

>>> world.blocks[g.player.tile_pos]
<Block "grass" id=2 data=0>

Alternatively, a slice of vectors can be used. In this case, when querying the property, a sequence of Block objects is returned, When setting a slice of vectors you can either pass a sequence of Block objects or a single Block object:

>>> world.blocks[Vector(0,0,0):Vector(2,1,1)]
[<Block "grass" id=2 data=0>,<Block "grass" id=2 data=0>]
>>> world.blocks[Vector(0,0,0):Vector(5,1,5)] = Block.from_name('grass')

As with normal Python slices, the interval specified is half-open. That is to say, it is inclusive of the lower vector, exclusive of the upper one. Hence, Vector():Vector(x=5,1,1) represents the coordinates (0,0,0) to (4,0,0). It is usually useful to specify the upper bound as the vector you want and then add one to it:

>>> world.blocks[Vector():Vector(x=1) + 1]
[<Block "grass" id=2 data=0>,<Block "grass" id=2 data=0>]
>>> world.blocks[Vector():Vector(4,0,4) + 1] = Block.from_name('grass')

Finally, you can query an arbitrary collection of vectors. In this case a sequence of blocks will be returned in the same order as the collection of vectors. You can also use this when setting blocks:

>>> d = {
...     Vector(): Block('air'),
...     Vector(x=1): Block('air'),
...     Vector(z=1): Block('stone'),
...     }
>>> l = list(d)
>>> l
[<Vector x=0, y=0, z=0>,<Vector x=1, y=0, z=0>,<Vector x=0, y=0, z=1>]
>>> world.blocks[l]
[<Block "grass" id=2 data=0>,<Block "grass" id=2 data=0>,<Block "grass" id=2 data=0>]
>>> world.blocks[d.keys()] = d.values()

Warning

Querying or setting sequences of blocks can be extremely slow as a network transaction must be executed for each individual block. When setting a slice of blocks, this can be speeded up by specifying a single Block in which case one network transaction will occur to set all blocks in the slice. The Raspberry Juice server also supports querying sequences of blocks with a single command (picraft will automatically use this). Additionally, batch_start() can be used to speed up setting sequences of blocks (though not querying).

camera

Represents the camera of the Minecraft world.

The Camera object contained in this property permits control of the position of the virtual camera in the Minecraft world. For example, to position the camera directly above the host player:

>>> world.camera.third_person(world.player)

Alternatively, to see through the eyes of a specific player:

>>> world.camera.first_person(world.players[2])

Warning

Camera control is only supported on Minecraft Pi edition.

checkpoint

Represents the Minecraft world checkpoint system.

The Checkpoint object contained in this attribute provides the ability to save and restore the state of the world at any time:

>>> world.checkpoint.save()
>>> world.blocks[Vector()] = Block.from_name('stone')
>>> world.checkpoint.restore()
connection

Represents the connection to the Minecraft server.

The Connection object contained in this attribute represents the connection to the Minecraft server and provides various methods for communicating with it. Users will very rarely need to access this attribute, except to use the batch_start() method.

events

Provides an interface to poll events that occur in the Minecraft world.

The Events object contained in this property provides methods for determining what is happening in the Minecraft world:

>>> events = world.events.poll()
>>> len(events)
3
>>> events[0]
<BlockHitEvent pos=1,1,1 face="x+" player=1>
>>> events[0].player.pos
<Vector x=0.5, y=0.0, z=0.5>
height

Represents the height of the Minecraft world.

This property can be queried to determine the height of the world at any location. The property can be indexed with a single Vector, in which case the height will be returned as a vector with the same X and Z coordinates, but a Y coordinate adjusted to the first non-air block from the top of the world:

>>> world.height[Vector(0, -10, 0)]
Vector(x=0, y=0, z=0)

Alternatively, a slice of two vectors can be used. In this case, the property returns a sequence of Vector objects each with their Y coordinates adjusted to the height of the world at the respective X and Z coordinates.

immutable

Write-only property which sets whether the world is changeable.

Warning

World settings are only supported on Minecraft Pi edition.

Note

Unfortunately, the underlying protocol provides no means of reading a world setting, so this property is write-only (attempting to query it will result in an AttributeError being raised).

nametags_visible

Write-only property which sets whether players’ nametags are visible.

Warning

World settings are only supported on Minecraft Pi edition.

Note

Unfortunately, the underlying protocol provides no means of reading a world setting, so this property is write-only (attempting to query it will result in an AttributeError being raised).

player

Represents the host player in the Minecraft world.

The HostPlayer object returned by this attribute provides properties which can be used to query the status of, and manipulate the state of, the host player in the Minecraft world:

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

Represents all player entities in the Minecraft world.

This property can be queried to determine which players are currently in the Minecraft world. The property is a mapping of player id (an integer number) to a Player object which permits querying and manipulation of the player. The property supports many of the methods of dicts and can be iterated over like a dict:

>>> len(world.players)
1
>>> list(world.players)
[1]
>>> world.players.keys()
[1]
>>> world.players[1]
<picraft.player.Player at 0x7f2f91f38cd0>
>>> world.players.values()
[<picraft.player.Player at 0x7f2f91f38cd0>]
>>> world.players.items()
[(1, <picraft.player.Player at 0x7f2f91f38cd0>)]
>>> for player in world.players:
...     print(player.tile_pos)
...
-3,18,-5

On the Raspberry Juice platform, you can also use player name to reference players:

>>> world.players['my_player']
<picraft.player.Player at 0x7f2f91f38cd0>

Checkpoint

class picraft.world.Checkpoint(connection)[source]

Permits restoring the world state from a prior save.

This class provides methods for storing the state of the Minecraft world, and restoring the saved state at a later time. The save() method saves the state of the world, and the restore() method restores the saved state.

This class can be used as a context manager to take a checkpoint, make modifications to the world, and roll them back if an exception occurs. For example, the following code will ultimately do nothing because an exception occurs after the alteration:

>>> from picraft import *
>>> w = World()
>>> with w.checkpoint:
...     w.blocks[w.player.tile_pos - Vector(y=1)] = Block.from_name('stone')
...     raise Exception()

Warning

Checkpoints are only supported on Minecraft Pi edition.

Warning

Minecraft only permits a single checkpoint to be stored at any given time. There is no capability to save multiple checkpoints and no way of checking whether one currently exists. Therefore, storing a checkpoint may overwrite an older checkpoint without warning.

Note

Checkpoints don’t work within batches as the checkpoint save will be batched along with everything else. That said, a checkpoint can be used outside a batch to roll the entire thing back if it fails:

>>> v = w.player.tile_pos - Vector(y=1)
>>> with w.checkpoint:
...     with w.connection.batch_start():
...         w.blocks[v - Vector(2, 0, 2):v + Vector(2, 1, 2)] = [
...             Block.from_name('wool', data=i) for i in range(16)]
restore()[source]

Restore the state of the Minecraft world from a previously saved checkpoint. No facility is provided to determine whether a prior checkpoint is available (the underlying network protocol doesn’t permit this).

save()[source]

Save the state of the Minecraft world, overwriting any prior checkpoint state.

Camera

class picraft.world.Camera(connection)[source]

This class implements the camera attribute.

first_person(player)[source]

Causes the camera to view the world through the eyes of the specified player. The player can be the player attribute (representing the host player) or an attribute retrieved from the players list. For example:

>>> from picraft import World
>>> w = World()
>>> w.camera.first_person(w.player)
>>> w.camera.first_person(w.players[1])
third_person(player)[source]

Causes the camera to follow the specified player from above. The player can be the player attribute (representing the host player) or an attribute retrieved from the players list. For example:

>>> from picraft import World
>>> w = World()
>>> w.camera.third_person(w.player)
>>> w.camera.third_person(w.players[1])
pos

Write-only property which sets the camera’s absolute position in the world.

Note

Unfortunately, the underlying protocol provides no means of reading this setting, so this property is write-only (attempting to query it will result in an AttributeError being raised).

API - The Block class

The block module defines the Block class, which is used to represent the type of a block and any associated data it may have, and the class which is used to implement the blocks attribute on the World class.

Note

All items in this module, except the compatibility constants, are available from the picraft namespace without having to import picraft.block directly.

The following items are defined in the module:

Block

class picraft.block.Block(id, data)[source]

Represents a block within the Minecraft world.

Blocks within the Minecraft world are represented by two values: an id which defines the type of the block (air, stone, grass, wool, etc.) and an optional data value (defaults to 0) which means different things for different block types (e.g. for wool it defines the color of the wool).

Blocks are represented by this library as a namedtuple() of the id and the data. Calculated properties are provided to look up the name and description of the block from a database derived from the Minecraft wiki, and classmethods are defined to construct a block definition from an id or from alternate things like a name or an RGB color:

>>> Block.from_id(0, 0)
<Block "air" id=0 data=0>
>>> Block.from_id(2, 0)
<Block "grass" id=2 data=0>
>>> Block.from_name('stone')
<Block "stone" id=1 data=0>
>>> Block.from_color('#ffffff')
<Block "wool" id=35 data=0>

The default constructor attempts to guess which classmethod to call based on the following rules (in the order given):

  1. If the constructor is passed a string beginning with ‘#’ that is 7 characters long, it calls from_color()
  2. If the constructor is passed a tuple containing 3 values, it calls from_color()
  3. If the constructor is passed a string (not matching the case above) it calls from_name()
  4. Otherwise the constructor calls from_id() with all given parameters

This means that the examples above can be more easily written:

>>> Block(0, 0)
<Block "air" id=0 data=0>
>>> Block(2, 0)
<Block "grass" id=2 data=0>
>>> Block('stone')
<Block "stone" id=1 data=0>
>>> Block('#ffffff')
<Block "wool" id=35 data=0>

Aliases are provided for compatibility with the official reference implementation (AIR, GRASS, STONE, etc):

>>> import picraft.block
>>> picraft.block.WATER
<Block "flowing_water" id=8 data=0>
classmethod from_color(color, exact=False)[source]

Construct a Block instance from a color which can be represented as:

  • A tuple of (red, green, blue) integer byte values between 0 and 255
  • A tuple of (red, green, blue) float values between 0.0 and 1.0
  • A string in the format ‘#rrggbb’ where rr, gg, and bb are hexadecimal representations of byte values.

If exact is False (the default), and an exact match for the requested color cannot be found, the nearest color (determined simply by Euclidian distance) is returned. If exact is True and an exact match cannot be found, a ValueError will be raised:

>>> from picraft import *
>>> Block.from_color('#ffffff')
<Block "wool" id=35 data=0>
>>> Block.from_color('#ffffff', exact=True)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "picraft/block.py", line 351, in from_color
    if exact:
ValueError: no blocks match color #ffffff
>>> Block.from_color((1, 0, 0))
<Block "wool" id=35 data=14>

Note that calling the default constructor with any of the formats accepted by this method is equivalent to calling this method:

>>> Block('#ffffff')
<Block "wool" id=35 data=0>
classmethod from_id(id, data=0)[source]

Construct a Block instance from an id integer. This may be used to construct blocks in the classic manner; by specifying a number representing the block’s type, and optionally a data value. For example:

>>> from picraft import *
>>> Block.from_id(1)
<Block "stone" id=1 data=0>
>>> Block.from_id(2, 0)
<Block "grass" id=2 data=0>

The optional data parameter defaults to 0. Note that calling the default constructor with an integer parameter is equivalent to calling this method:

>>> Block(1)
<Block "stone" id=1" data=0>
classmethod from_name(name, data=0)[source]

Construct a Block instance from a name, as returned by the name property. This may be used to construct blocks in a more “friendly” way within code. For example:

>>> from picraft import *
>>> Block.from_name('stone')
<Block "stone" id=1 data=0>
>>> Block.from_name('wool', data=2)
<Block "wool" id=35 data=2>

The optional data parameter can be used to specify the data component of the new Block instance; it defaults to 0. Note that calling the default constructor with a string that doesn’t start with “#” is equivalent to calling this method:

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

The “id” or type of the block. Each block type in Minecraft has a unique value. For example, air blocks have the id 0, stone, has id 1, and so forth. Generally it is clearer in code to refer to a block’s name but it may be quicker to use the id.

data

Certain types of blocks have variants which are dictated by the data value associated with them. For example, the color of a wool block is determined by the data attribute (e.g. white is 0, red is 14, and so on).

pi

Returns a bool indicating whether the block is present in the Pi Edition of Minecraft.

pocket

Returns a bool indicating whether the block is present in the Pocket Edition of Minecraft.

name

Return the name of the block. This is a unique identifier string which can be used to construct a Block instance with from_name().

description

Return a description of the block. This string is not guaranteed to be unique and is only intended for human use.

COLORS

A class attribute containing a sequence of the colors available for use with from_color().

NAMES

A class attribute containing a sequence of the names available for use with from_name().

Compatibility

Finally, the module also contains compatibility values equivalent to those in the mcpi.block module of the reference implementation. Each value represents the type of a block with no associated data:

AIR FURNACE_ACTIVE MUSHROOM_RED
BED FURNACE_INACTIVE NETHER_REACTOR_CORE
BEDROCK GLASS OBSIDIAN
BEDROCK_INVISIBLE GLASS_PANE REDSTONE_ORE
BOOKSHELF GLOWING_OBSIDIAN SAND
BRICK_BLOCK GLOWSTONE_BLOCK SANDSTONE
CACTUS GOLD_BLOCK SAPLING
CHEST GOLD_ORE SNOW
CLAY GRASS SNOW_BLOCK
COAL_ORE GRASS_TALL STAIRS_COBBLESTONE
COBBLESTONE GRAVEL STAIRS_WOOD
COBWEB ICE STONE
CRAFTING_TABLE IRON_BLOCK STONE_BRICK
DIAMOND_BLOCK IRON_ORE STONE_SLAB
DIAMOND_ORE LADDER STONE_SLAB_DOUBLE
DIRT LAPIS_LAZULI_BLOCK SUGAR_CANE
DOOR_IRON LAPIS_LAZULI_ORE TNT
DOOR_WOOD LAVA TORCH
FARMLAND LAVA_FLOWING WATER
FENCE LAVA_STATIONARY WATER_FLOWING
FENCE_GATE LEAVES WATER_STATIONARY
FIRE MELON WOOD
FLOWER_CYAN MOSS_STONE WOOD_PLANKS
FLOWER_YELLOW MUSHROOM_BROWN WOOL

Use these compatibility constants by importing the block module explicitly. For example:

>>> from picraft import block
>>> block.AIR
<Block "air" id=0 data=0>
>>> block.TNT
<Block "tnt" id=46 data=0>

API - Vector, vector_range, etc.

The vector module defines the Vector class, which is the usual method of representing coordinates or vectors when dealing with the Minecraft world. It also provides functions like vector_range() for generating sequences of vectors.

Note

All items in this module are available from the picraft namespace without having to import picraft.vector directly.

The following items are defined in the module:

Vector

class picraft.vector.Vector(x=0, y=0, z=0)[source]

Represents a 3-dimensional vector.

This namedtuple() derivative represents a 3-dimensional vector with x, y, z components. Instances can be constructed in a number of ways: by explicitly specifying the x, y, and z components (optionally with keyword identifiers), or leaving them empty to default to 0:

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

Shortcuts are available for vectors representing the X, Y, and Z axes:

>>> X
Vector(x=1, y=0, z=0)
>>> Y
Vector(x=0, y=1, z=0)

Note that vectors don’t much care whether their components are integers, floating point values, or None:

>>> Vector(1.0, 1, 1)
Vector(x=1.0, y=1, z=1)
>>> Vector(2, None, None)
Vector(x=2, y=None, z=None)

The class supports simple arithmetic operations with other vectors such as addition and subtraction, along with multiplication and division, raising to powers, bit-shifting, and so on. Such operations are performed element-wise [1]:

>>> v1 = Vector(1, 1, 1)
>>> v2 = Vector(2, 2, 2)
>>> v1 + v2
Vector(x=3, y=3, z=3)
>>> v1 * v2
Vector(x=2, y=2, z=2)

Simple arithmetic operations with scalars return a new vector with that operation performed on all elements of the original. For example:

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

Within the Minecraft world, the X,Z plane represents the ground, while the Y vector represents height.

Note

Note that, as a derivative of namedtuple(), instances of this class are immutable. That is, you cannot directly manipulate the x, y, and z attributes; instead you must create a new vector (for example, by adding two vectors together). The advantage of this is that vector instances can be members of a set or keys in a dict.

[1]I realize math purists will hate this (and demand that abs() should be magnitude and * should invoke matrix multiplication), but the element wise operations are sufficiently useful to warrant the short-hand syntax.
replace(x=None, y=None, z=None)[source]

Return the vector with the x, y, or z axes replaced with the specified values. For example:

>>> Vector(1, 2, 3).replace(z=4)
Vector(x=1, y=2, z=4)
ceil()[source]

Return the vector with the ceiling of each component. This is only useful for vectors containing floating point components:

>>> Vector(0.5, -0.5, 1.2)
Vector(1.0, 0.0, 2.0)
floor()[source]

Return the vector with the floor of each component. This is only useful for vectors containing floating point components:

>>> Vector(0.5, -0.5, 1.9)
Vector(0.0, -1.0, 1.0)
dot(other)[source]

Return the dot product of the vector with the other vector. The result is a scalar value. For example:

>>> Vector(1, 2, 3).dot(Vector(2, 2, 2))
12
>>> Vector(1, 2, 3).dot(X)
1
cross(other)[source]

Return the cross product of the vector with the other vector. The result is another vector. For example:

>>> Vector(1, 2, 3).cross(Vector(2, 2, 2))
Vector(x=-2, y=4, z=-2)
>>> Vector(1, 2, 3).cross(X)
Vector(x=0, y=3, z=-2)
distance_to(other)[source]

Return the Euclidian distance between two three dimensional points (represented as vectors), calculated according to Pythagoras’ theorem. For example:

>>> Vector(1, 2, 3).distance_to(Vector(2, 2, 2))
1.4142135623730951
>>> O.distance_to(X)
1.0
angle_between(other)[source]

Returns the angle between this vector and the other vector on a plane that contains both vectors. The result is measured in degrees between 0 and 180. For example:

>>> X.angle_between(Y)
90.0
>>> (X + Y).angle_between(X)
45.00000000000001
project(other)[source]

Return the scalar projection of this vector onto the other vector. This is a scalar indicating the length of this vector in the direction of the other vector. For example:

>>> Vector(1, 2, 3).project(2 * Y)
2.0
>>> Vector(3, 4, 5).project(Vector(3, 4, 0))
5.0
rotate(angle, about, origin=None)[source]

Return this vector after rotation of angle degrees about the line passing through origin in the direction about. Origin defaults to the vector 0, 0, 0. Hence, if this parameter is omitted this method calculates rotation about the axis (through the origin) defined by about. For example:

>>> Y.rotate(90, about=X)
Vector(x=0, y=6.123233995736766e-17, z=1.0)
>>> Vector(3, 4, 5).rotate(30, about=X, origin=10 * Y)
Vector(x=3.0, y=2.3038475772933684, z=1.330127018922194)

Information about rotation around arbitrary lines was obtained from Glenn Murray’s informative site.

x

The position or length of the vector along the X-axis. In the Minecraft world this can be considered to run left-to-right.

y

The position or length of the vector along the Y-axis. In the Minecraft world this can be considered to run vertically up and down.

z

The position or length of the vector along the Z-axis. In the Minecraft world this can be considered as depth (in or out of the screen).

magnitude

Returns the magnitude of the vector. This could also be considered the distance of the vector from the origin, i.e. v.magnitude is equivalent to Vector().distance_to(v). For example:

>>> Vector(2, 4, 4).magnitude
6.0
>>> Vector().distance_to(Vector(2, 4, 4))
6.0
unit

Return a unit vector (a vector with a magnitude of one) with the same direction as this vector:

>>> X.unit
Vector(x=1.0, y=0.0, z=0.0)
>>> (2 * Y).unit
Vector(x=0.0, y=1.0, z=0.0)

Note

If the vector’s magnitude is zero, this property returns the original vector.

Short-hand variants

The Vector class is used sufficiently often to justify the inclusion of some shortcuts. The class itself is also available as V, and vectors representing the three axes are each available as X, Y, and Z. Finally, a vector representing the origin is available as O:

>>> from picraft import V, O, X, Y, Z
>>> O
Vector(x=0, y=0, z=0)
>>> 2 * X
Vector(x=2, y=0, z=0)
>>> X + Y
Vector(x=1, y=1, z=0)
>>> (X + Y).angle_between(X)
45.00000000000001
>>> V(3, 4, 5).projection(X)
3.0
>>> X.rotate(90, about=Y)
Vector(x=0.0, y=0.0, z=1.0)

vector_range

class picraft.vector.vector_range(start, stop=None, step=None, order=u'zxy')[source]

Like range(), vector_range is actually a type which efficiently represents a range of vectors. The arguments to the constructor must be Vector instances (or objects which have integer x, y, and z attributes).

If step is omitted, it defaults to Vector(1, 1, 1). If the start argument is omitted, it defaults to Vector(0, 0, 0). If any element of the step vector is zero, ValueError is raised.

The contents of the range are largely determined by the step and order which specifies the order in which the axes of the range will be incremented. For example, with the order 'xyz', the X-axis will be incremented first, followed by the Y-axis, and finally the Z-axis. So, for a range with the default start, step, and stop set to Vector(3, 3, 3), the contents of the range will be:

>>> list(vector_range(Vector(3, 3, 3), order='xyz'))
[Vector(0, 0, 0), Vector(1, 0, 0), Vector(2, 0, 0),
 Vector(0, 1, 0), Vector(1, 1, 0), Vector(2, 1, 0),
 Vector(0, 2, 0), Vector(1, 2, 0), Vector(2, 2, 0),
 Vector(0, 0, 1), Vector(1, 0, 1), Vector(2, 0, 1),
 Vector(0, 1, 1), Vector(1, 1, 1), Vector(2, 1, 1),
 Vector(0, 2, 1), Vector(1, 2, 1), Vector(2, 2, 1),
 Vector(0, 0, 2), Vector(1, 0, 2), Vector(2, 0, 2),
 Vector(0, 1, 2), Vector(1, 1, 2), Vector(2, 1, 2),
 Vector(0, 2, 2), Vector(1, 2, 2), Vector(2, 2, 2)]

Vector ranges implement all common sequence operations except concatenation and repetition (due to the fact that range objects can only represent sequences that follow a strict pattern and repetition and concatenation usually cause the resulting sequence to violate that pattern).

Vector ranges are extremely efficient compared to an equivalent list() or tuple() as they take a small (fixed) amount of memory, storing only the arguments passed in its construction and calculating individual items and sub-ranges as requested.

Vector range objects implement the collections.Sequence ABC, and provide features such as containment tests, element index lookup, slicing and support for negative indices.

The default order ('zxy') may seem an odd choice. This is primarily used as it’s the order used by the Raspberry Juice server when returning results from the world.getBlocks call. In turn, Raspberry Juice probably uses this order as it results in returning a horizontal layer of vectors at a time (given the Y-axis is used for height in the Minecraft world).

Warning

Bear in mind that the ordering of a vector range may have affect tests for its ordering and equality. Two ranges with different orders are unlikely to test equal even though they may have the same start, stop, and step attributes (and thus contain the same vectors, but in a different order).

Vector ranges can be accessed by integer index, by Vector index, or by a slice of vectors. For example:

>>> v = vector_range(Vector() + 1, Vector() + 3)
>>> list(v)
[Vector(x=1, y=1, z=1),
 Vector(x=1, y=1, z=2),
 Vector(x=2, y=1, z=1),
 Vector(x=2, y=1, z=2),
 Vector(x=1, y=2, z=1),
 Vector(x=1, y=2, z=2),
 Vector(x=2, y=2, z=1),
 Vector(x=2, y=2, z=2)]
>>> v[0]
Vector(x=1, y=1, z=1)
>>> v[Vector(0, 0, 0)]
Vector(x=1, y=1, z=1)
>>> v[Vector(1, 0, 0)]
Vector(x=2, y=1, z=1)
>>> v[-1]
Vector(x=2, y=2, z=2)
>>> v[Vector() - 1]
Vector(x=2, y=2, z=2)
>>> v[Vector(x=1):]
vector_range(Vector(x=2, y=1, z=1), Vector(x=3, y=3, z=3),
        Vector(x=1, y=1, z=1), order='zxy')
>>> list(v[Vector(x=1):])
[Vector(x=2, y=1, z=1),
 Vector(x=2, y=1, z=2),
 Vector(x=2, y=2, z=1),
 Vector(x=2, y=2, z=2)]

However, integer slices are not currently permitted.

count(value)[source]

Return the count of instances of value within the range (note this can only be 0 or 1 in the case of a range, and thus is equivalent to testing membership with in).

index(value)[source]

Return the zero-based index of value within the range, or raise ValueError if value does not exist in the range.

line

picraft.vector.line(start, end)[source]

Generates the coordinates of a line joining the start and end Vector instances inclusive. This is a generator function; points are yielded from start, proceeding to end. If you don’t require all points you may terminate the generator at any point.

For example:

>>> list(line(O, V(10, 5, 0)))
[Vector(x=0, y=0, z=0),
 Vector(x=1, y=1, z=0),
 Vector(x=2, y=1, z=0),
 Vector(x=3, y=2, z=0),
 Vector(x=4, y=2, z=0),
 Vector(x=5, y=3, z=0),
 Vector(x=6, y=3, z=0),
 Vector(x=7, y=4, z=0),
 Vector(x=8, y=4, z=0),
 Vector(x=9, y=5, z=0),
 Vector(x=10, y=5, z=0)]

To draw the resulting line you can simply assign a block to the collection of vectors generated (or assign a sequence of blocks of equal length if you want the line to have varying block types):

>>> world.blocks[line(O, V(10, 5, 0))] = Block('stone')

This is a three-dimensional implementation of Bresenham’s line algorithm, derived largely from Bob Pendelton’s implementation (public domain).

lines

picraft.vector.lines(points, closed=True)[source]

Generator function which extends the line() function; this yields all vectors necessary to render the lines connecting the specified points (which is an iterable of Vector instances).

If the optional closed parameter is True (the default) the last point in the points sequence will be connected to the first point. Otherwise, the lines will be left disconnected (assuming the last point is not coincident with the first). For example:

>>> points = [O, 4*X, 4*Z]
>>> list(lines(points))
[Vector(x=0, y=0, z=0),
 Vector(x=1, y=0, z=0),
 Vector(x=2, y=0, z=0),
 Vector(x=3, y=0, z=0),
 Vector(x=4, y=0, z=0),
 Vector(x=3, y=0, z=1),
 Vector(x=2, y=0, z=2),
 Vector(x=1, y=0, z=3),
 Vector(x=0, y=0, z=4),
 Vector(x=0, y=0, z=3),
 Vector(x=0, y=0, z=2),
 Vector(x=0, y=0, z=1),
 Vector(x=0, y=0, z=0)]

To draw the resulting polygon you can simply assign a block to the collection of vectors generated (or assign a sequence of blocks of equal length if you want the polygon to have varying block types):

>>> world.blocks[lines(points)] = Block('stone')

To generate the coordinates of a filled polygon, see the filled() function.

circle

picraft.vector.circle(center, radius, plane=Vector(x=0, y=1, z=0))[source]

Generator function which yields the coordinates of a three-dimensional circle centered at the Vector center. The radius parameter is a vector specifying the distance of the circumference from the center. The optional plane parameter (which defaults to the Y unit vector) specifies another vector which, in combination with the radius vector, gives the plane that the circle exists within.

For example, to generate the coordinates of a circle centered at (0, 10, 0), with a radius of 5 units, existing in the X-Y plane:

>>> list(circle(O, 5*X))
[Vector(x=-5, y=0, z=0), Vector(x=-5, y=1, z=0), Vector(x=-4, y=2, z=0),
 Vector(x=-4, y=3, z=0), Vector(x=-5, y=-1, z=0), Vector(x=-4, y=-2, z=0),
 Vector(x=-4, y=-3, z=0), Vector(x=-3, y=4, z=0), Vector(x=-3, y=-4, z=0),
 Vector(x=-2, y=4, z=0), Vector(x=-2, y=-4, z=0), Vector(x=-1, y=4, z=0),
 Vector(x=-1, y=-4, z=0), Vector(x=0, y=5, z=0), Vector(x=0, y=-5, z=0),
 Vector(x=1, y=4, z=0), Vector(x=1, y=-4, z=0), Vector(x=2, y=4, z=0),
 Vector(x=2, y=-4, z=0), Vector(x=3, y=4, z=0), Vector(x=3, y=-4, z=0),
 Vector(x=4, y=3, z=0), Vector(x=4, y=-3, z=0), Vector(x=4, y=2, z=0),
 Vector(x=5, y=1, z=0), Vector(x=5, y=0, z=0), Vector(x=4, y=-2, z=0),
 Vector(x=5, y=-1, z=0)]

To generate another set of coordinates with the same center and radius, but existing in the X-Z (ground) plane:

>>> list(circle(O, 5*X, plane=Z))
[Vector(x=-5, y=0, z=0), Vector(x=-5, y=0, z=1), Vector(x=-4, y=0, z=2),
 Vector(x=-4, y=0, z=3), Vector(x=-5, y=0, z=-1), Vector(x=-4, y=0, z=-2),
 Vector(x=-4, y=0, z=-3), Vector(x=-3, y=0, z=4), Vector(x=-3, y=0, z=-4),
 Vector(x=-2, y=0, z=4), Vector(x=-2, y=0, z=-4), Vector(x=-1, y=0, z=4),
 Vector(x=-1, y=0, z=-4), Vector(x=0, y=0, z=5), Vector(x=0, y=0, z=-5),
 Vector(x=1, y=0, z=4), Vector(x=1, y=0, z=-4), Vector(x=2, y=0, z=4),
 Vector(x=2, y=0, z=-4), Vector(x=3, y=0, z=4), Vector(x=3, y=0, z=-4),
 Vector(x=4, y=0, z=3), Vector(x=4, y=0, z=-3), Vector(x=4, y=0, z=2),
 Vector(x=5, y=0, z=1), Vector(x=5, y=0, z=0), Vector(x=4, y=0, z=-2),
 Vector(x=5, y=0, z=-1)]

To draw the resulting circle you can simply assign a block to the collection of vectors generated (or assign a sequence of blocks of equal length if you want the circle to have varying block types):

>>> world.blocks[circle(O, 5*X)] = Block('stone')

The algorithm used by this function is based on a straight-forward differences of roots method, extended to three dimensions. This produces worse looking circles than the midpoint circle algorithm (also known as a the Bresenham circle algorithm), but isn’t restricted to working in a simple cartesian plane.

Note

If you know of a three dimensional generalization of the midpoint circle algorithm (which handles entirely arbitrary planes, not merely simple X-Y, X-Z, etc. planes), please contact the author!

To create a filled circle, see the filled() function.

sphere

picraft.vector.sphere(center, radius)[source]

Generator function which yields the coordinates of a hollow sphere. The center Vector specifies the center of the sphere, and radius is a scalar number of blocks giving the distance from the center to the edge of the sphere.

For example to create the coordinates of a sphere centered at the origin with a radius of 5 units:

>>> list(sphere(O, 5))

To draw the resulting sphere you can simply assign a block to the collection of vectors generated (or assign a sequence of blocks of equal length if you want the sphere to have varying block types):

>>> world.blocks[sphere(O, 5)] = Block('stone')

The algorithm generates concentric circles covering the sphere’s surface, advancing along the X, Y, and Z axes with duplicate elimination to prevent repeated coordinates being yielded. Three axes are required to eliminate gaps in the surface.

filled

picraft.vector.filled(points)[source]

Generator function which yields the coordinates necessary to fill the space enclosed by the specified points.

This function can be applied to anything that returns a sequence of points. For example, to create a filled triangle:

>>> triangle = [O, 4*X, 4*Z]
>>> list(filled(lines(triangle)))
[Vector(x=0, y=0, z=0), Vector(x=0, y=0, z=1), Vector(x=0, y=0, z=2),
 Vector(x=0, y=0, z=3), Vector(x=0, y=0, z=4), Vector(x=1, y=0, z=2),
 Vector(x=1, y=0, z=1), Vector(x=1, y=0, z=0), Vector(x=1, y=0, z=3),
 Vector(x=2, y=0, z=1), Vector(x=2, y=0, z=0), Vector(x=2, y=0, z=2),
 Vector(x=3, y=0, z=1), Vector(x=3, y=0, z=0), Vector(x=4, y=0, z=0)]

Or to create a filled circle:

>>> list(filled(circle(O, 4*X)))
[Vector(x=-4, y=0, z=0), Vector(x=-3, y=-1, z=0), Vector(x=-3, y=-2, z=0),
 Vector(x=-3, y=0, z=0), Vector(x=-3, y=1, z=0), Vector(x=-3, y=2, z=0),
 Vector(x=-2, y=-1, z=0), Vector(x=-2, y=-2, z=0), Vector(x=-2, y=-3, z=0),
 Vector(x=-2, y=0, z=0), Vector(x=-2, y=1, z=0), Vector(x=-2, y=2, z=0),
 Vector(x=-2, y=3, z=0), Vector(x=-1, y=0, z=0), Vector(x=-1, y=-1, z=0),
 Vector(x=-1, y=-2, z=0), Vector(x=-1, y=-3, z=0), Vector(x=-1, y=1, z=0),
 Vector(x=-1, y=2, z=0), Vector(x=-1, y=3, 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=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=1, y=0, z=0),
 Vector(x=1, y=-1, z=0), Vector(x=1, y=-2, z=0), Vector(x=1, y=-3, z=0),
 Vector(x=1, y=1, z=0), Vector(x=1, y=2, z=0), Vector(x=1, y=3, z=0),
 Vector(x=2, y=0, z=0), Vector(x=2, y=-1, z=0), Vector(x=2, y=-2, z=0),
 Vector(x=2, y=-3, z=0), Vector(x=2, y=1, z=0), Vector(x=2, y=2, z=0),
 Vector(x=2, y=3, z=0), Vector(x=3, y=0, z=0), Vector(x=3, y=-1, z=0),
 Vector(x=3, y=-2, z=0), Vector(x=3, y=1, z=0), Vector(x=3, y=2, z=0),
 Vector(x=4, y=0, z=0), Vector(x=4, y=-1, z=0), Vector(x=4, y=1, z=0)]

To draw the resulting filled object you can simply assign a block to the collection of vectors generated (or assign a sequence of blocks of equal length if you want the object to have varying block types):

>>> world.blocks[filled(lines(triangle))] = Block('stone')

A simple brute-force algorithm is used that simply generates all the lines connecting all specified points. However, duplicate elimination is used to ensure that no point within the filled space is yielded twice.

Note that if you pass the coordinates of a polyhedron which contains holes or gaps compared to its convex hull, this function may fill those holes or gaps (but it will depend on the orientation of the object).

API - Events

The events module defines the Events class, which provides methods for querying events in the Minecraft world, and the BlockHitEvent, PlayerPosEvent, ChatPostEvent, and IdleEvent classes which represent the various event types.

Note

All items in this module are available from the picraft namespace without having to import picraft.events directly.

The following items are defined in the module:

Events

class picraft.events.Events(connection, poll_gap=0.1, include_idle=False)[source]

This class implements the events attribute.

There are two ways of responding to picraft’s events: the first is to poll() for them manually, and process each event in the resulting list:

>>> for event in world.events.poll():
...     print(repr(event))
...
<BlockHitEvent pos=1,1,1 face="y+" player=1>,
<PlayerPosEvent old_pos=0.2,1.0,0.7 new_pos=0.3,1.0,0.7 player=1>

The second is to “tag” functions as event handlers with the decorators provided and then call the main_loop() function which will handle polling the server for you, and call all the relevant functions as needed:

@world.events.on_block_hit(pos=Vector(1,1,1))
def hit_block(event):
    print('You hit the block at %s' % event.pos)

world.events.main_loop()

By default, only block hit events will be tracked. This is because it is the only type of event that the Minecraft server provides information about itself, and thus the only type of event that can be processed relatively efficiently. If you wish to track player positions, assign a set of player ids to the track_players attribute. If you wish to include idle events (which fire when nothing else is produced in response to poll()) then set include_idle to True.

Note

If you are using a Raspberry Juice server, chat post events are also tracked by default. Chat post events are only supported with Raspberry Juice servers; Minecraft Pi edition doesn’t support chat post events.

Finally, the poll_gap attribute specifies how long to pause during each iteration of main_loop() to permit event handlers some time to interact with the server. Setting this to 0 will provide the fastest response to events, but will result in event handlers having to fight with event polling for access to the server.

clear()[source]

Forget all pending events that have not yet been retrieved with poll().

This method is used to clear the list of events that have occurred since the last call to poll() without retrieving them. This is useful for ensuring that events subsequently retrieved definitely occurred after the call to clear().

has_handlers(cls)[source]

Decorator for registering a class as containing picraft event handlers.

If you are writing a class which contains methods that you wish to use as event handlers for picraft events, you must decorate the class with @has_handlers. This will ensure that picraft tracks instances of the class and dispatches events to each instance that exists when the event occurs.

For example:

from picraft import World, Block, Vector, X, Y, Z

world = World()

@world.events.has_handlers
class HitMe(object):
    def __init__(self, pos):
        self.pos = pos
        self.been_hit = False
        world.blocks[self.pos] = Block('diamond_block')

    @world.events.on_block_hit()
    def was_i_hit(self, event):
        if event.pos == self.pos:
            self.been_hit = True
            print('Block at %s was hit' % str(self.pos))

p = world.player.tile_pos
block1 = HitMe(p + 2*X)
block2 = HitMe(p + 2*Z)
world.events.main_loop()

Class-based handlers are an advanced feature and have some notable limitations. For instance, in the example above the on_block_hit handler couldn’t be declared with the block’s position because this was only known at instance creation time, not at class creation time (which was when the handler was registered).

Furthermore, class-based handlers must be regular instance methods (those which accept the instance, self, as the first argument); they cannot be class methods or static methods.

Note

The @has_handlers decorator takes no arguments and shouldn’t be called, unlike event handler decorators.

main_loop()[source]

Starts the event polling loop when using the decorator style of event handling (see on_block_hit()).

This method will not return, so be sure that you have specified all your event handlers before calling it. The event loop can only be broken by an unhandled exception, or by closing the world’s connection (in the latter case the resulting ConnectionClosed exception will be suppressed as it is assumed that you want to end the script cleanly).

on_block_hit(thread=False, multi=True, pos=None, face=None)[source]

Decorator for registering a function/method as a block hit handler.

This decorator is used to mark a function as an event handler which will be called for any events indicating a block has been hit while main_loop() is executing. The function will be called with the corresponding BlockHitEvent as the only argument.

The pos parameter can be used to specify a vector or sequence of vectors (including a vector_range); in this case the event handler will only be called for block hits on matching vectors.

The face parameter can be used to specify a face or sequence of faces for which the handler will be called.

For example, to specify that one handler should be called for hits on the top of any blocks, and another should be called only for hits on any face of block at the origin one could use the following code:

from picraft import World, Vector

world = World()

@world.events.on_block_hit(pos=Vector(0, 0, 0))
def origin_hit(event):
    world.say('You hit the block at the origin')

@world.events.on_block_hit(face="y+")
def top_hit(event):
    world.say('You hit the top of a block at %d,%d,%d' % event.pos)

world.events.main_loop()

The thread parameter (which defaults to False) can be used to specify that the handler should be executed in its own background thread, in parallel with other handlers.

Finally, the multi parameter (which only applies when thread is True) specifies whether multi-threaded handlers should be allowed to execute in parallel. When True (the default), threaded handlers execute as many times as activated in parallel. When False, a single instance of a threaded handler is allowed to execute at any given time; simultaneous activations are ignored (but not queued, as with unthreaded handlers).

on_chat_post(thread=False, multi=True, message=None)[source]

Decorator for registering a function/method as a chat event handler.

This decorator is used to mark a function as an event handler which will be called for events indicating a chat message was posted to the world while main_loop() is executing. The function will be called with the corresponding ChatPostEvent as the only argument.

Note

Only the Raspberry Juice server generates chat events; Minecraft Pi Edition does not support this event type.

The message parameter can be used to specify a string or regular expression; in this case the event handler will only be called for chat messages which match this value. For example:

import re
from picraft import World, Vector

world = World()

@world.events.on_chat_post(message="hello world")
def echo(event):
    world.say("Hello player %d!" % event.player.player_id)

@world.events.on_chat_post(message=re.compile(r"teleport_me \d+,\d+,\d+"))
def teleport(event):
    x, y, z = event.message[len("teleport_me "):].split(",")
    event.player.pos = Vector(int(x), int(y), int(z))

world.events.main_loop()

The thread parameter (which defaults to False) can be used to specify that the handler should be executed in its own background thread, in parallel with other handlers.

Finally, the multi parameter (which only applies when thread is True) specifies whether multi-threaded handlers should be allowed to execute in parallel. When True (the default), threaded handlers execute as many times as activated in parallel. When False, a single instance of a threaded handler is allowed to execute at any given time; simultaneous activations are ignored (but not queued, as with unthreaded handlers).

on_idle(thread=False, multi=True)[source]

Decorator for registering a function/method as an idle handler.

This decorator is used to mark a function as an event handler which will be called when no other event handlers have been called in an iteration of main_loop(). The function will be called with the corresponding IdleEvent as the only argument.

Note that idle events will only be generated if include_idle is set to True.

on_player_pos(thread=False, multi=True, old_pos=None, new_pos=None)[source]

Decorator for registering a function/method as a position change handler.

This decorator is used to mark a function as an event handler which will be called for any events indicating that a player’s position has changed while main_loop() is executing. The function will be called with the corresponding PlayerPosEvent as the only argument.

The old_pos and new_pos parameters can be used to specify vectors or sequences of vectors (including a vector_range) that the player position events must match in order to activate the associated handler. For example, to fire a handler every time any player enters or walks over blocks within (-10, 0, -10) to (10, 0, 10):

from picraft import World, Vector, vector_range

world = World()
world.events.track_players = world.players

from_pos = Vector(-10, 0, -10)
to_pos = Vector(10, 0, 10)
@world.events.on_player_pos(new_pos=vector_range(from_pos, to_pos + 1))
def in_box(event):
    world.say('Player %d stepped in the box' % event.player.player_id)

world.events.main_loop()

Various effects can be achieved by combining old_pos and new_pos filters. For example, one could detect when a player crosses a boundary in a particular direction, or decide when a player enters or leaves a particular area.

Note that only players specified in track_players will generate player position events.

poll()[source]

Return a list of all events that have occurred since the last call to poll().

For example:

>>> w = World()
>>> w.events.track_players = w.players
>>> w.events.include_idle = True
>>> w.events.poll()
[<PlayerPosEvent old_pos=0.2,1.0,0.7 new_pos=0.3,1.0,0.7 player=1>,
 <BlockHitEvent pos=1,1,1 face="x+" player=1>,
 <BlockHitEvent pos=1,1,1 face="x+" player=1>]
>>> w.events.poll()
[<IdleEvent>]
process()[source]

Poll the server for events and call any relevant event handlers registered with on_block_hit().

This method is called repeatedly the event handler loop implemented by main_loop(); developers should only call this method when implementing their own event loop manually, or when their (presumably non-threaded) event handler is engaged in a long operation and they wish to permit events to be processed in the meantime.

include_idle

If True, generate an idle event when no other events would be generated by poll(). This attribute defaults to False.

poll_gap

The length of time (in seconds) to pause during main_loop().

This property specifies the length of time to wait at the end of each iteration of main_loop(). By default this is 0.1 seconds.

The purpose of the pause is to give event handlers executing in the background time to communicate with the Minecraft server. Setting this to 0.0 will result in faster response to events, but also starves threaded event handlers of time to communicate with the server, resulting in “choppy” performance.

track_players

The set of player ids for which movement should be tracked.

By default the poll() method will not produce player position events (PlayerPosEvent). Producing these events requires extra interactions with the Minecraft server (one for each player tracked) which slow down response to block hit events.

If you wish to track player positions, set this attribute to the set of player ids you wish to track and their positions will be stored. The next time poll() is called it will query the positions for all specified players and fire player position events if they have changed.

Given that the players attribute represents a dictionary mapping player ids to players, if you wish to track all players you can simply do:

>>> world.events.track_players = world.players

BlockHitEvent

class picraft.events.BlockHitEvent(pos, face, player)[source]

Event representing a block being hit by a player.

This tuple derivative represents the event resulting from a player striking a block with their sword in the Minecraft world. Users will not normally need to construct instances of this class, rather they are constructed and returned by calls to poll().

Note

Please note that the block hit event only registers when the player right clicks with the sword. For some reason, left clicks do not count.

pos

A Vector indicating the position of the block which was struck.

face

A string indicating which side of the block was struck. This can be one of six values: ‘x+’, ‘x-‘, ‘y+’, ‘y-‘, ‘z+’, or ‘z-‘. The value indicates the axis, and direction along that axis, that the side faces:

player

A Player instance representing the player that hit the block.

PlayerPosEvent

class picraft.events.PlayerPosEvent(old_pos, new_pos, player)[source]

Event representing a player moving.

This tuple derivative represents the event resulting from a player moving within the Minecraft world. Users will not normally need to construct instances of this class, rather they are constructed and returned by calls to poll().

old_pos

A Vector indicating the location of the player prior to this event. The location includes decimal places (it is not the tile-position, but the actual position).

new_pos

A Vector indicating the location of the player as of this event. The location includes decimal places (it is not the tile-position, but the actual position).

player

A Player instance representing the player that moved.

ChatPostEvent

class picraft.events.ChatPostEvent(message, player)[source]

Event representing a chat post.

This tuple derivative represents the event resulting from a chat message being posted in the Minecraft world. Users will not normally need to construct instances of this class, rather they are constructed and returned by calls to poll().

Note

Chat events are only generated by the Raspberry Juice server, not by Minecraft Pi edition.

message

The message that was posted to the world.

player

A Player instance representing the player that moved.

IdleEvent

class picraft.events.IdleEvent[source]

Event that fires in the event that no other events have occurred since the last poll. This is only used if Events.include_idle is True.

API - Connections and Batches

The connection module defines the Connection class, which represents the network connection to the Minecraft server. Its primary purpose for users of the library is to initiate batch sending via the Connection.batch_start() method.

Note

All items in this module are available from the picraft namespace without having to import picraft.connection directly.

The following items are defined in the module:

Connection

class picraft.connection.Connection(host, port, timeout=1.0, ignore_errors=True, encoding=u'ascii')[source]

Represents the connection to the Minecraft server.

The host parameter specifies the hostname or IP address of the Minecraft server, while port specifies the port to connect to (these typically take the values “127.0.0.1” and 4711 respectively).

The timeout parameter specifies the maximum time in seconds that the client will wait after sending a command before assuming that the command has succeeded when ignore_errors is False (see the The Minecraft network protocol section for more information). If ignore_errors is True (the default), act like the mcpi implementation and ignore all errors for commands which do not return data.

Users will rarely need to construct a Connection object themselves. An instance of this class is constructed by World to handle communication with the game server (connection).

The most important aspect of this class is its ability to “batch” transmissions together. Typically, the send() method is used to transmit requests to the Minecraft server. When this is called normally (outside of a batch), it immediately transmits the requested data. However, if batch_start() has been called first, the data is not sent immediately, but merely appended to the batch. The batch_send() method can then be used to transmit all requests simultaneously (or alternatively, batch_forget() can be used to discard the list). See the docs of these methods for more information.

close()[source]

Closes the connection.

This method can be used to close down the connection to the game server. After this method is called, any further requests will raise a ConnectionClosed exception.

send(buf)[source]

Transmits the contents of buf to the connected server.

If no batch has been initiated (with batch_start()), this method immediately communicates the contents of buf to the connected Minecraft server. If buf is a unicode string, the method attempts to encode the content in a byte-encoding prior to transmission (the encoding used is the encoding attribute of the class which defaults to “ascii”).

If a batch has been initiated, the contents of buf are appended to the batch (batches cannot be nested; see batch_start() for more information).

transact(buf)[source]

Transmits the contents of buf, and returns the reply string.

This method immediately communicates the contents of buf to the connected server, then reads a line of data in reply and returns it.

Note

This method ignores the batch mechanism entirely as transmission is required in order to obtain the response. As this method is typically used to implement “getters”, this is not usually an issue but it is worth bearing in mind.

batch_start()[source]

Starts a new batch transmission.

When called, this method starts a new batch transmission. All subsequent calls to send() will append data to the batch buffer instead of actually sending the data.

To terminate the batch transmission, call batch_send() or batch_forget(). If a batch has already been started, a BatchStarted exception is raised.

Note

This method can be used as a context manager (with) which will cause a batch to be started, and implicitly terminated with batch_send() or batch_forget() depending on whether an exception is raised within the enclosed block.

batch_send()[source]

Sends the batch transmission.

This method is called after batch_start() and send() have been used to build up a list of batch commands. All the commands will be combined and sent to the server as a single transmission.

If no batch is currently in progress, a BatchNotStarted exception will be raised.

batch_forget()[source]

Terminates a batch transmission without sending anything.

This method is called after batch_start() and send() have been used to build up a list of batch commands. All commands in the batch will be cleared without sending anything to the server.

If no batch is currently in progress, a BatchNotStarted exception will be raised.

ignore_errors

If False, use the timeout to determine when responses have been successful; this is safer but requires such long timeouts when using remote connections that it’s not the default. If True (the default) assume any response without an expected reply is successful (this is the behaviour of the mcpi implementation; it is faster but less “safe”).

timeout

The length of time in seconds to wait for a response (positive or negative) from the server when ignore_errors is False. Defaults to 1 second.

encoding

The encoding that will be used for messages transmitted to, and received from the server. Defaults to 'ascii'.

server_version

Returns an object (currently just a string) representing the version of the Minecraft server we’re talking to. Presently this is just 'minecraft-pi' or 'raspberry-juice'.

API - Players

The player module defines the Players class, which is available via the players attribute, the Player class, which represents an arbitrary player in the world, and the HostPlayer class which represents the player on the host machine (accessible via the player attribute).

Note

All items in this module are available from the picraft namespace without having to import picraft.player directly.

The following items are defined in the module:

Player

class picraft.player.Player(connection, player_id)[source]

Represents a player within the game world.

Players are uniquely identified by their player_id. Instances of this class are available from the players mapping. It provides properties to query and manipulate the position and settings of the player.

direction

The direction the player is facing as a unit vector.

This property can be queried to retrieve a unit Vector pointing in the direction of the player’s view.

Warning

Player direction is only fully supported on Raspberry Juice. On Minecraft Pi, it can be emulated by activating tracking for the player (see track_players) and periodically calling poll(). However, this will only tell you what direction the player moved in, not necessarily what direction they’re facing.

heading

The direction the player is facing in clockwise degrees from South.

This property can be queried to determine the direction that the player is facing. The value is returned as a floating-point number of degrees from North (i.e. 180 is North, 270 is East, 0 is South, and 90 is West).

Warning

Player heading is only fully supported on Raspberry Juice. On Minecraft Pi, it can be emulated by activating tracking for the player (see track_players) and periodically calling poll(). However, this will only tell you what heading the player moved along, not necessarily what direction they’re facing.

pitch

The elevation of the player’s view in degrees from the horizontal.

This property can be queried to determine whether the player is looking up (values from 0 to -90) or down (values from 0 down to 90). The value is returned as floating-point number of degrees from the horizontal.

Warning

Player pitch is only supported on Raspberry Juice.

player_id

Returns the integer ID of the player on the server.

pos

The precise position of the player within the world.

This property returns the position of the selected player within the Minecraft world, as a Vector instance. This is the precise position of the player including decimal places (representing portions of a tile). You can assign to this property to reposition the player.

tile_pos

The position of the player within the world to the nearest block.

This property returns the position of the selected player in the Minecraft world to the nearest block, as a Vector instance. You can assign to this property to reposition the player.

HostPlayer

class picraft.player.HostPlayer(connection)[source]

Represents the host player within the game world.

An instance of this class is accessible as the Game.player attribute. It provides properties to query and manipulate the position and settings of the host player.

autojump

Write-only property which sets whether the host player autojumps.

When this property is set to True (which is the default), the host player will automatically jump onto blocks when it runs into them (unless the blocks are too high to jump onto).

Warning

Player settings are only supported on Minecraft Pi edition.

Note

Unfortunately, the underlying protocol provides no means of reading a world setting, so this property is write-only (attempting to query it will result in an AttributeError being raised).

direction

The direction the player is facing as a unit vector.

This property can be queried to retrieve a unit Vector pointing in the direction of the player’s view.

Warning

Player direction is only fully supported on Raspberry Juice. On Minecraft Pi, it can be emulated by activating tracking for the player (see track_players) and periodically calling poll(). However, this will only tell you what direction the player moved in, not necessarily what direction they’re facing.

heading

The direction the player is facing in clockwise degrees from South.

This property can be queried to determine the direction that the player is facing. The value is returned as a floating-point number of degrees from North (i.e. 180 is North, 270 is East, 0 is South, and 90 is West).

Warning

Player heading is only fully supported on Raspberry Juice. On Minecraft Pi, it can be emulated by activating tracking for the player (see track_players) and periodically calling poll(). However, this will only tell you what heading the player moved along, not necessarily what direction they’re facing.

pitch

The elevation of the player’s view in degrees from the horizontal.

This property can be queried to determine whether the player is looking up (values from 0 to -90) or down (values from 0 down to 90). The value is returned as floating-point number of degrees from the horizontal.

Warning

Player pitch is only supported on Raspberry Juice.

pos

The precise position of the player within the world.

This property returns the position of the selected player within the Minecraft world, as a Vector instance. This is the precise position of the player including decimal places (representing portions of a tile). You can assign to this property to reposition the player.

tile_pos

The position of the player within the world to the nearest block.

This property returns the position of the selected player in the Minecraft world to the nearest block, as a Vector instance. You can assign to this property to reposition the player.

API - Rendering

The render module defines a series of classes for interpreting and rendering models in the Wavefront object format.

Note

All items in this module are available from the picraft namespace without having to import picraft.render directly.

The following items are defined in the module:

Model

class picraft.render.Model(source, swap_yz=False)[source]

Represents a three-dimensional model parsed from an Alias|Wavefront object file (.obj extension). The constructor accepts a source parameter which can be a filename or file-like object (in the latter case, this must be opened in text mode such that it returns unicode strings rather than bytes in Python 3).

The optional swap_yz parameter specifies whether the Y and Z coordinates of each vertex in the model will be swapped; some models require this to render correctly in Minecraft, some do not.

The faces attribute provides access to all object faces extracted from the file’s content. The materials property enumerates all material names used by the object. The groups mapping maps group names to subsets of the available faces. The bounds attribute provides a range describing the bounding box of the unscaled model.

Finally, the render() method can be used to easily render the object in the Minecraft world at the specified scale, and with a given material mapping.

render(scale=1.0, materials=None, groups=None)[source]

Renders the model as a dict mapping vectors to block types. Effectively this rounds the vertices of each face to integers (after applying the scale multiplier, which defaults to 1.0), then calls filled() and lines() to obtain the complete coordinates of each face.

Each coordinate then needs to be mapped to a block type. By default the material name is simply passed to the Block constructor. This assumes that material names are valid Minecraft block types (see NAMES).

You can override this mechanism with the materials parameter. This can be set to a mapping (e.g. a dict) which maps material names to Block instances. For example:

from picraft import Model, Block

m = Model('airboat.obj')
d = m.render(materials={
    'bluteal':  Block('diamond_block'),
    'bronze':   Block('gold_block'),
    'dkdkgrey': Block((64, 64, 64)),
    'dkteal':   Block('#000080'),
    'red':      Block('#ff0000'),
    'silver':   Block.from_color('#ffffff'),
    'black':    Block(id=35, data=15),
    None:       Block('stone'),
    })

Note

Some object files may include faces with no associated material. In this case you will need to map None to a block type, as in the example above.

Alternatively, materials can be a callable which will be called with the ModelFace being rendered, which should return a block type. The following is equivalent to the default behaviour:

from picraft import Model, Block

m = Model('airboat.obj')
d = m.render(materials=lambda f: Block(f.material))

If you simply want to preview a shape without bothering with any material mapping you can use this method to map any face to a single material (in this case stone):

from picraft import Model, Block

m = Model('airboat.obj')
d = m.render(materials=lambda f: Block('stone'))

If the materials mapping or callable returns None instead of a Block instance, the corresponding blocks will not be included in the result. This is a simple mechanism for excluding parts of a model. The other mechanism for achieving this is the groups parameter which specifies which sub-components of the model should be rendered. This can be specified as a string (indicating that only that sub-component should be rendered) or as a sequence of strings (indicating that all specified sub-components should be rendered).

The result is a mapping of Vector to Block instances. Rendering the result in the main world should be as trivial as the following code:

from picraft import World, Model

w = World()
m = Model('airboat.obj').render(scale=2.0)
with w.connection.batch_start():
    w.blocks[m.keys()] = m.values()

Of course, you may choose to further transform the result first. This can be accomplished by modifying the vectors as you set them:

from picraft import World, Model, Y

w = World()
m = Model('airboat.obj').render(scale=2.0)
with w.connection.batch_start():
    for v, b in m.items():
        w.blocks[v + 10*Y] = b

Alternatively you may choose to use a dict-comprehension:

from picraft import Model, Vector

m = Model('airboat.obj').render(scale=2.0)
offset = Vector(y=10)
m = {v + offset: b for v, b in m.items()}

Note that the Alias|Wavefront object file format is a relatively simple text based format that can be constructed by hand without too much difficulty. Combined with the default mapping of material names to block types, this enables another means of constructing objects in the Minecraft world. For example, see Models.

bounds

Returns a vector range which completely encompasses the model at scale 1.0. This can be useful for determining scaling factors when rendering.

Note

The bounding box returned is axis-aligned and is not guaranteed to be the minimal bounding box for the model.

faces

Returns the sequence of faces that make up the model. Each instance of this sequence is a ModelFace instance which provides details of the coordinates of the face vertices, the face material, etc.

groups

A mapping of group names to sequences of ModelFace instances. This can be used to extract a component of the model for further processing or rendering.

materials

Returns the set of materials used by the model. This is derived from the material assigned to each face of the model.

ModelFace

class picraft.render.ModelFace(vectors, material, groups)[source]

Represents a face belonging to a Model. A face consists of three or more vectors which are all coplanar (belonging to the same two-dimensional plane within the three-dimensional space).

A face also has a material. As Minecraft’s rendering is relatively crude this is simply stored as the name of the material; it is up to the user to map this to a meaningful block type. Finally each face belongs to zero or more groups which can be used to distinguish components of a model from each other.

groups

The set of groups that the face belongs to. By default all faces belong to a Model. However, in additionl to this a face can belong to zero or more “groups” which are effectively components of the model. This facility can be used to render particular parts of a model.

material

The material assigned to the face. This is simply stored as the name of the material as it would be ridiculous to even attempt to emulate the material model of a full ray-tracer as Minecraft blocks.

The Model.render() method provides a simple means for mapping a material name to a block type in Minecraft.

vectors

The sequence of vectors that makes up the face. These are assumed to be coplanar but this is not explicitly checked. Each point is represented as a Vector instance.

API - Turtle

API - Exceptions

The exc module defines the various exception classes specific to picraft.

Note

All items in this module are available from the picraft namespace without having to import picraft.exc directly.

The following items are defined in the module:

Exceptions

exception picraft.exc.Error[source]

Base class for all PiCraft exceptions

exception picraft.exc.NotSupported[source]

Exception raised for unimplemented methods / properties

exception picraft.exc.ConnectionError[source]

Base class for PiCraft errors relating to network communications

exception picraft.exc.ConnectionClosed[source]

Exception raised when an operation is attempted against a closed connection

exception picraft.exc.CommandError[source]

Exception raised when a network command fails

exception picraft.exc.NoResponse[source]

Exception raised when a network command expects a response but gets none

exception picraft.exc.BatchStarted[source]

Exception raised when a batch is started before a prior one is complete

exception picraft.exc.BatchNotStarted[source]

Exception raised when a batch is terminated when none has been started

Warnings

exception picraft.exc.EmptySliceWarning[source]

Warning raised when a zero-length vector slice is passed to blocks

exception picraft.exc.NoHandlersWarning[source]

Warning raised when a class with no handlers is registered with has_handlers()

exception picraft.exc.ParseWarning[source]

Base class for warnings encountered during parsing

exception picraft.exc.UnsupportedCommand[source]

Warning raised when an unsupported statement is encountered

exception picraft.exc.NegativeWeight[source]

Warning raised when a negative weight is encountered

The Minecraft network protocol

This chapter contains details of the network protocol used by the library to communicate with the Minecraft game. Although this is primarily intended to inform future developers of this (or other) libraries, it may prove interesting reading for users to understand some of the decisions in the design of the library.

Specification

Requirements

The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this section are to be interpreted as defined in RFC 2119.

Overall Operation

The Minecraft protocol is a text-based “interactive” line oriented protocol. All communication is initiated by the client and consists of single lines of text which MAY generate a single line of text in response. Lines MUST terminate with ASCII character 10 (line feed, usually shortened to LF or \n).

Protocol implementations MUST use the ASCII encoding (non-ASCII characters are not ignored, or an error, but their effect is undefined).

A Minecraft network session begins by connecting a TCP stream socket to the server, which defaults to listening on port 4711. Protocol implementations SHOULD disable Nagle’s algorithm (TCP_NODELAY) on the socket as the protocol is effectively interactive and relies on many small packets. No “hello” message is transmitted by the client, and no “banner” message is sent by the server. A Minecraft session ends simply by disconnecting the socket.

Commands and responses MUST consist of a single line. The typical form of a command, described in the augmented Backus-Naur Form (ABNF) defined by RFC 5234 is as follows:

command = command-name "(" [ option *( "," option ) ] ")" LF

command-name = 1*ALPHA "." 1*ALPHA [ "." 1*ALPHA ]
option = int-val / float-val / str-val

bool-val = "0" / "1"
int-val = 1*DIGIT
float-val = 1*DIGIT [ "." 1*DIGIT ]
str-val = *CHAR

Note

Note that the ABNF specified by RFC 5234 does not provide for implicit specification of linear white space. In other words, unless whitespace is explicitly specified in ABNF constructions, it is not permitted by the specification.

The typical form of a response (if one is given) is as follows:

response = ( success-response / fail-response ) LF

success-response = int-vector / float-vector
fail-response = "Fail"

int-vector = int-val "," int-val "," int-val
float-vector = float-val "," float-val "," float-val

The general character classes utilised in the ABNF definitions above are as follows:

ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
DIGIT = %x30-39           ; 0-9
CHAR = %x01-09 / %x0B-FF  ; any character except LF
SP = %x20                 ; space
LF = %x0A                 ; line-feed
Client Notes

Successful commands either make no response, or provide a single line of data as a response. Unsuccessful commands either make no response, or provide a single line response containing the string “Fail” (without the quotation marks). The lack of positive (and sometimes negative) acknowledgements provides a conundrum for client implementations: how long to wait before deciding that a command has succeeded? If “Fail” is returned, the client can immediately conclude the preceding command failed. However, if nothing is returned, the client must decide whether the command succeeded, or whether the network or server is simply being slow in responding.

The longer the client waits, the more likely it is to correctly report failed operations (in the case of slow systems). However, the longer the wait, the slower the response time (and performance) of the client.

The mcpi implementation simply ignores errors in commands that produce no response (providing the best performance, but the least safety). The picraft implementation provides a configurable timeout but defaults to ignoring errors like the mcpi implementation because remote connections tend to require such long timeouts that the library’s performance becomes unacceptable.

Clients MAY either ignore errors or implement some form or timeout to determine when operations are successful.

Specific Commands

The following sections define the specific commands supported by the protocol.

camera.mode.setFixed

Syntax:

camera-fixed-command = "camera.mode.setFixed()" LF

The camera.mode.setFixed command fixes the camera’s position at the current location. The camera’s location can subsequently be updated with the camera.setPos command but will not move otherwise. The camera’s orientation is fixed facing down (parallel to a vector along Y=-1).

camera.mode.setFollow

Syntax:

camera-follow-command = "camera.mode.setFollow(" [int] ")" LF

The camera.mode.setFollow command fixes the camera’s position vertically above the player with the specified ID (if the optional integer is specified) or above the host player (if no integer is given). The camera’s position will follow the specified player’s position, but the orientation will be fixed facing down (parallel to a vector along Y=-1).

camera.mode.setNormal

Syntax:

camera-normal-command = "camera.mode.setNormal(" [int] ")" LF

The camera.mode.setNormal command aligns the camera’s position with the “head” of the player with the specified ID (if the optional integer is specified) or the host player (if no integer is given). The camera’s position and orientation will subsequently track the player’s head.

camera.setPos

Syntax:

camera-set-pos-command = "camera.mode.setPos(" float-vector ")" LF

When the camera position has been fixed with camera.mode.setFixed(), this command can be used to alter the position of the camera. The orientation of the camera will, however, remain fixed (parallel to a vector along Y=-1).

chat.post

Syntax:

world-chat-command = "chat.post(" str-val ")" LF

The chat.post command causes the server to echo the message provided as the only parameter to the in-game chat console. The message MUST NOT contain the LF character, but other control characters are (currently) permitted.

entity.getPos

Syntax:

entity-get-pos-command = "entity.getPos(" int-val ")" LF
entity-get-pos-response = player-get-pos-response

The entity.getPos command performs the same action as the player.getPos command for the player with the ID given by the sole parameter; refer to player.getPos for full details.

entity.getTile

Syntax:

entity-get-tile-command = "entity.getTile(" int-val ")" LF
entity-get-tile-command = player-get-tile-response

The entity.getTile command performs the same action as the player.getTile command for the player with the ID given by the sole parameter; refer to player.getTile for full details.

entity.setPos

Syntax:

entity-set-pos-command = "entity.setPos(" int-val "," float-vector ")" LF

The entity.setPos command performs the same action as the player.setPos command for the player with the ID given by the first parameter. The second parameter is equivalent to the first parameter for player.setPos; refer to that command for full details.

entity.setTile

Syntax:

entity-set-tile-command = "entity.setTile(" int-val "," int-vector ")" LF

The entity.setTile command performs the same action as the player.setTile command for the player with the ID given by the first parameter. The second parameter is equivalent to the first parameter for player.setTile; refer to that command for full details.

player.getPos

Syntax:

player-get-pos-command = "player.getPos()" LF
player-get-pos-response = float-vector LF

The player.getPos command returns the current location of the host player in the game world as an X, Y, Z vector of floating point values. The coordinates 0, 0, 0 represent the spawn point within the world.

player.getTile

Syntax:

player-get-tile-command = "player.getTile()" LF
player-get-tile-response = int-vector LF

The player.getTile command returns the current location of the host player in the game world, to the nearest block coordinates, as an X, Y, Z vector of integer values.

player.setPos

Syntax:

player-set-pos-command = "player.setPos(" float-vector ")" LF

The player.setPos command teleports the host player to the specified location in the game world. The floating point values given are the X, Y, and Z coordinates of the player’s new position respectively.

player.setTile

Syntax:

player-set-tile-command = "player.setTile(" int-vector ")" LF

The player.setTile command teleports the host player to the specified location in the game world. The integer values given are the X, Y, and Z coordinates of the player’s new position respectively.

player.setting

Syntax:

player-setting-command = "player.setting(" str-val "," bool-val ")" LF

The player.setting command alters a property of the host player. The property to alter is given as the str-val (note: this is unquoted) and the new value is given as the bool-val (where 0 means “off” and 1 means “on”). Valid properties are:

  • autojump - when enabled, causes the player to automatically jump onto blocks that they run into.
world.checkpoint.restore

Syntax:

world-restore-command = "world.checkpoint.restore()" LF

The world.checkpoint.restore command restores the state of the world (i.e. the id and data of all blocks in the world) from a prior saved state (created by the world.checkpoint.save command). If no prior state exists, nothing is restored but no error is reported. Restoring a state does not wipe it; thus a saved state can be restored multiple times.

world.checkpoint.save

Syntax:

world-save-command = "world.checkpoint.save()" LF

The world.checkpoint.save command can be used to save the current state of the world (i.e. the id and data of all blocks in the world, but not the position or orientation of player entities). Only one state is stored at any given time; any save overwrites any existing state.

The state of the world can be restored with a subsequent world.checkpoint.restore command.

world.getBlock

Syntax:

world-get-block-command = "world.getBlock(" int-vector ")" LF
world-get-block-response = int-val LF

The world.getBlock command can be used to retrieve the current type of a block within the world. The result consists of an integer representing the block type.

See Data Values (Pocket Edition) for a list of block types.

world.getBlocks

Syntax:

world-get-blocks-command = "world.getBlocks(" int-vector "," int-vector ")" LF
world-get-blocks-response = int-val *( "," int-val ) LF

The world.getBlocks command is a Raspberry Juice extension which retrieves the block ids of an entire range of blocks in a single network transaction. The result consists of a list of comma-separated integers representing the ids (but not the data) of all blocks within the cuboid defined by the two vectors inclusively. The ordering of vectors within the range is by z, then x, then y.

world.getBlockWithData

Syntax:

world-get-blockdata-command = "world.getBlockWithData(" int-vector ")" LF
world-get-blockdata-response = int-val "," int-val LF

The world.getBlockWithData command can be used to retrieve the current type and associated data of a block within the world. The result consists of two comma-separated integers which represent the block type and the associated data respectively.

See Data Values (Pocket Edition) for further information.

world.getHeight

Syntax:

world-get-height-command = "world.getHeight(" int-val "," int-val ")" LF
world-get-height-response = int-val LF

In response to the world.getHeight command the server calculates the Y coordinate of the first non-air block for the given X and Z coordinates (first and second parameter respectively) from the top of the world, and returns this as the result.

world.getPlayerIds

Syntax:

world-enum-players-command = "world.getPlayerIds()" LF
world-enum-players-response = [ int-val *( "|" int-val ) LF ]

The world.getPlayerIds command causes the server to a return a pipe (|) separated list of the integer player IDs of all players currently connected to the server. These player IDs can subsequently be used in the commands qualified with entity.

world.setBlock

Syntax:

world-set-block-command = "world.setBlock(" int-vector "," int-val [ "," int-val ] ")" LF

The world.setBlock command can be used to alter the type and associated data of a block within the world. The first three integer values provide the X, Y, and Z coordinates of the block to alter. The fourth integer value provides the new type of the block. The optional fifth integer value provides the associated data of the block.

See Data Values (Pocket Edition) for further information.

world.setBlocks

Syntax:

world-set-blocks-command = "world.setBlock(" int-vector "," int-vector "," int-val [ "," int-val ] ")" LF

The world.setBlocks command can be used to alter the type and associated data of a range of blocks within the world. The first three integer values provide the X, Y, and Z coordinates of the start of the range to alter. The next three integer values provide the X, Y, and Z coordinates of the end of the range to alter.

The seventh integer value provides the new type of the block. The optional eighth integer value provides the associated data of the block.

See Data Values (Pocket Edition) for further information.

world.setting

Syntax:

world-setting-command = "world.setting(" str-val "," bool-val ")" LF

The world.setting command is used to alter global aspects of the world. The setting to be altered is named by the first parameter (the setting name MUST NOT be surrounded by quotation marks), while the boolean value (the only type currently supported) is specified as the second parameter. The settings supported by the Minecraft Pi engine are:

  • world_immutable - This controls whether or the player can alter the world (by placing or destroying blocks)
  • nametags_visible - This controls whether the nametags of other players are visible

Critique

The Minecraft protocol is a text-based “interactive” line oriented protocol. By this, I mean that a single connection is opened from the client to the server and all commands and responses are transmitted over this connection. The completion of a command does not close the connection.

Despite text protocols being relatively inefficient compared to binary (non-human readable) protocols, a text-based protocol is an excellent choice in this case: the protocol isn’t performance critical and besides, this makes it extremely easy to experiment with and debug using nothing more than a standard telnet client.

Unfortunately, this is where the good news ends. The following is a telnet session in which I experimented with various possibilities to see how “liberal” the server was in interpreting commands:

chat.post(foo)
Chat.post(foo)
chat.Post(foo)
chat.post (foo)
chat.post(foo))
chat.post(foo,bar)
chat.post(foo) bar baz
chat.post foo
Fail
  • The first attempt (chat.post(foo)) succeeds and prints “foo” in the chat console within the game.
  • The second, third and fourth attempts (Chat.post(foo), chat.Post(foo), and chat.post (foo)) all fail silently.
  • The fifth attempt (chat.post(foo))) succeeds and prints “foo)” in the chat console within the game (this immediately raised my suspicions that the server is simply using regex matching instead of a proper parser).
  • The sixth attempt (chat.post(foo,bar)) succeeds, and prints “foo,bar” in the chat console.
  • The seventh attempt (chat.post(foo) bar baz) succeeds, and prints “foo” in the console.
  • The eighth and final attempt (chat.post foo) also fails and actually elicits a “Fail” response from the server.

What can we conclude from the above? If one were being generous, we might conclude that the ignoring of trailing junk (bar baz in the final example) is an effort at conforming with Postel’s Law. However, the fact that command name matching is done case insensitively, and that spaces leading the parenthesized arguments cause failure would indicate it’s more likely an oversight in the (probably rather crude) command parser.

A more serious issue is that in certain cases positive acknowledgement, and even negative acknowledgement, are lacking from the protocol. This is a major oversight as it means a client has no reliable means of deciding when a command has succeeded or failed:

  • If the client receives “Fail” in response to a command, it can immediately conclude the command has failed (and presumably raise some sort of exception in response).
  • If nothing is received, the command may have succeeded.
  • Alternatively, if nothing is received, the command may have failed (see the silent failures above).
  • Finally, if nothing is received, the server or intervening network may simply be running slowly and the client should wait a bit longer for a response.

So, after sending a command a client needs to wait a certain period of time before deciding that a command has succeeded or failed. How long? This is impossible to decide given that it depends on the state of the remote system and intervening network.

The longer a client waits, the more likely it is to correctly notice failures in the event of slow systems/networks. However, the longer a client waits the longer it will be before another command can be sent (given that responses are not tied to commands by something like a sequence number), resulting in poorer performance.

The mcpi implementation of the client doesn’t wait at all and simply assumes that all commands which don’t normally provide a response succeed. The picraft implementation provides a configurable timeout, or the option to ignore errors like the mcpi implementation. It defaults to acting in the same manner as the mcpi implementation partly for consistency and partly because such long timeouts are required with remote servers that the library’s performance becomes unacceptable.

What happens with unknown commands? Let’s try another telnet session to find out:

foo
Fail
foo()

It appears that anything without parentheses is rejected as invalid, but anything with parentheses is accepted (even though it does nothing ... is that an error? I’ve no idea!).

What happens when we play with commands which accept numbers?

player.setPos(0.5,60,-60)
player.setPos(0.5,60.999999999999999999999999999999999999,-60)
player.setPos(0.5,0x3c,-60)
player.setPos(5e-1,60,-60)
player.setPos(0.5,inf,-60)
player.setPos(0.5,NaN,nan)
player.setPos(0.5,+60,-60)
player.setPos(0.5,--60,-60)
Fail
player.setPos(   0.5,60,-60)
player.setPos(0.5   ,60,-60)
Fail
player.setPos(0.5,60,-60
player.setPos(0.5,60,-60   foo
player.setPos(0.5  foo,60,-60)
Fail

In each case above, if nothing was returned, the command succeeded (albeit with interesting results in the case of NaN and inf values). So, we can conclude the following:

  • The server doesn’t seem to care if we use floating point literals, decimal integer literals, hex literals, exponent format, or silly amounts of decimals. This suggests to me it’s just splitting the options on ”,” and throwing each resulting string at some generic str2num routine.
  • Backing up the assumption that some generic str2num routine is being used, the server also accepts “NaN” and “inf” values as numbers (albeit with silly results).
  • Leading spaces in options are fine, but trailing ones result in failure.
  • Unless it’s the last option in which case anything goes.
  • Including the trailing parenthesis, apparently.

As we’ve seen above, the error reporting provided by the protocol is beyond minimal. The most we ever get is the message “Fail” which doesn’t tell us whether it’s a client side or server side error, a syntax error, an unknown command, or anything else. In several cases, we don’t even get “Fail” despite nothing occurring on the server.

In conclusion, this is not a well thought out protocol, nor a terribly well implemented server.

A plea to the developers

I would dearly like to see this situation improved and be able to remove this section from the docs! To that end, I would be more than happy to discuss (backwards compatible) improvements in the protocol with the developers. It shouldn’t be terribly hard to come up with something similarly structured (text-based, line-oriented), which doesn’t break existing clients, but permits future clients to operate more reliably without sacrificing (much) performance.

Change log

Release 1.0 (2016-12-12)

The major news in 1.0 is that the API is now considered stable (so I won’t make backwards incompatible changes from here on without lots of warning, deprecation, etc.)

The new features in 1.0 are:

  • The new turtle module implements a classic logo-like turtle in the Minecraft world
  • A rudimentary direction attribute is now available in Minecraft Pi (#20)

The docs have also undergone some re-organization and expansion to make them a bit more friendly.

Release 0.6 (2016-01-21)

Release 0.6 adds some new features:

  • A new sphere() generator function was added (#13)
  • The blocks attribute was updated to permit arbitrary sequences of vectors to be queried and assigned
  • Event decorators can now be used in classes with the new has_handlers() decorator (#14)
  • Installation instructions have been simplified, along with several recipes and code examples throughout the docs (#15, #16)
  • When used with a Raspberry Juice server, chat events can now be monitored and reacted to using event decorators (#19); many thanks to GitHub user wh11e7rue for not just suggesting the idea but providing a fantastically complete pull-request implementing it!

And fixes some bugs:

  • The default for ignore_errors was changed so that picraft’s network behaviour now matches mcpi’s by default (#18)
  • A silly bug in circle() prevented the center parameter from working correctly

Release 0.5 (2015-09-10)

Release 0.5 adds ever more new features:

  • The major news is the new obj loader and renderer in the Model class. This includes lots of good stuff like bounds calculation, scaling, material mapping by map or by callable, sub-component querying by group, etc. It’s also tolerably quick (#10)
  • As part of this work a new function was added to calculate the coordinates necessary to fill a polygon. This is the new filled() function (#12)
  • Lots more doc revisions, including new and fixed recipes, lots more screenshots, and extensions to the chapter documenting vectors.

Release 0.4 (2015-07-19)

Release 0.4 adds plenty of new features:

  • The events system has been expanded considerably to include an event-driven programming paradigm (decorate functions to tell picraft when to call them, e.g. in response to player movement or block hits). This includes the ability to run event handlers in parallel with automatic threading
  • Add support for circle drawing through an arbitrary plane. I’m still not happy with the implementation, and may revise it in future editions, but I am happy with the API so it’s worth releasing for now (#7)
  • Add Raspbian packaging; we’ve probably got to the point where I need to start making guarantees about backward compatibililty in which case it’s probably time to make this more generally accessible by including deb packaging (#8)
  • Lots of doc revisions including a new vectors chapter, more recipes, and so on!

Release 0.3 (2015-06-21)

Release 0.3 adds several new features:

  • Add support for querying a range of blocks with one transaction on the Raspberry Juice server (#1)
  • Add support for rotation of vectors about an arbitrary line (#6)
  • Add bitwise operations and rounding of vectors
  • Lots of documentation updates (fixes to links, new recipes, events documented properly, etc.)

Release 0.2 (2015-06-08)

Release 0.2 is largely a quick bug fix release to deal with a particularly stupid bug in 0.1 (but what are alphas for?). It also adds a couple of minor features:

  • Fix a stupid error which caused block.data and block.color (which make up the block database) to be excluded from the PyPI build (#3)
  • Fix being able to set empty block ranges (#2)
  • Fix being able to set block ranges with non-unit steps (#4)
  • Preliminary implementation of getBlocks support (#1)

Release 0.1 (2015-06-07)

Initial release. This is an alpha version of the library and the API is subject to change up until the 1.0 release at which point API stability will be enforced.

License

Copyright 2013-2015 Dave Jones

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  • Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  • Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Indices and tables