Chapter 8

Minecraft

FOR THE UNINITIATED, Minecraft is a game of survival where your character can build things and acquire materials by mining the world around him. It's set in a world of blocks, and each of these blocks can be destroyed to reveal hidden treasures. Figure 8-1 shows a typical Minecraft world.

That's a pretty inadequate description, but it should give you an idea of what's going on. There's a full version that you can play on most computers, and there's a special version unique to the Raspberry Pi. The special thing about the Pi version is that you can control the game through Python. Not only can you move your player, but you can also manipulate the entire world around you.

Figure 8-1: The 3D blocks have become emblematic of this game.

9781118717059-fg0801.tif

Exploring Minecraft

That's enough trying to explain it, though. Minecraft is one of those things that you really have to see to understand. First of all, you'll need to download the software from http://pi.minecraft.net.

You should end up with a file called minecraft-pi-0.1.1.tar.gz. You can extract this archive through the terminal with this command:

tar zxvf minecraft-pi-0.1.1.tar.gz

Then, you can start the game with this command:

mcpi/minecraft-pi

Controlling Your Minecraft World

The first thing to do is start a new game and get a feeling for moving about in the world (use the mouse to change the direction you're facing, the left mouse button to attach something, and the w, a, s, and d keys to move around). You can build things by changing the tool you're using (pressing one of the number keys will select from the items displayed at the bottom of the screen), and then pressing the right mouse button to use it. These are just the very basic Minecraft controls, but they should be enough to give you a feel for how to work in the world.

To control the world, you need to use the Python API. This is included in the archive you downloaded earlier, but that version doesn't support Python 3. Instead, you need to download chapter8-minecraft-api.tar.gz from the book's website.

Once you have the new API, you need to extract it using the terminal command:

tar zxvf chapter8-minecraft.tar.gz

You'll need to change the directory using the terminal command cd into the new folder. Then you should create the programs for this chapter in that folder so they can pick up the Python APIs:

cd mcpi

The programs you write won't run Minecraft themselves, but instead connect to an instance of Minecraft that's currently running on the Pi.

With that in mind, start a new Python interpreter. Remember that it has to be in the chapter8-minecraft directory, and the easiest way to do this is open an LXTerminal session, navigate to chapter8-minecraft (use the previous cd line), and then enter python3.

The following code will connect to the currently running instance of Minecraft:

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

You now have a Minecraft object called mc that you can manipulate. This object holds a player object that you can move around. For example, try the following:

>>> mc.player.setPos(10,10,10)

You should find that the player moves. Your player will move to these coordinates (10,10,10), but since worlds are randomly generated, this may be in the air, in which case your player will fall until he reaches the floor. Alternatively, he may end up underwater, or underground. Try changing the coordinates to find him a spot of land. The first and last coordinates are the player's position on the ground (x and z), while the middle one (y) is his vertical position.

Creating Minecraft Worlds in Python

Once you've found a good position for your player, you can alter the world around him using the setBlock(x,y,z,type) method. x, y, and z are the world coordinates and they are in the same format as for the player. type is the typeID of the block. Every different type of block has a typeID, which you'll need to use whenever you manipulate the world.

So, for example, if your player is at (10, 15, 22), you can create a mushroom next to her with the following:

>>> mc.setBlock(11,15,22, 40)

See Table 8-1 for a list of useful block types.

0801

Building Worlds

So far, you've been working with whatever world Minecraft created for you. However, if you're going to create any sort of reusable program, you need to be able to know where everything is located at the start. To do this, you need to set the world to be how you want it. The following script sets a 200 by 200 square in the centre of the world to be smooth grass (type 2), with air above it (type 0).

import minecraft
mc = minecraft.Minecraft.create()
mc.setBlocks(-100,-1, -100, 100, 0, 100, 2)
mc.setBlocks(-100, 1, -100, 100, 100, 100, 0)

This uses the method setBlocks(x1, y1, z1, x2, y2, z2, type), which sets every block between (x1,y1,z1) and (x2,y2,z2) to be of the appropriate type.

Drawing Pictures

Now you know how to set blocks, you can start doing something useful with them. Were this a full version of Minecraft, you could use this to build some form of ultimate fortress to shelter your character. However, the version of Minecraft on the Pi isn't the full version. Instead, you can use this version for more aesthetic reasons, such as drawing pictures with blocks. The following code does just that (it's on the website as chapter8-art.py):

import minecraft
import sys 


mc = minecraft.Minecraft.create()

height = 10
start_x = 0
start_y = 0
canvas = "horizontal"

r = 246
g = 18
b = 22

picture = [["R", "R", "R"],
           ["G", "G", "G"],
           ["B", "B", "B"]]
 
if len(sys.argv) > 1:
    with open(sys.argv[1]) as f:
        picture = f.readlines()

pic_x = start_x
for line in picture:
    pic_y = start_y
    for block in line:

        if canvas == "horizontal":
            x = pic_y
            y = height
            z = pic_x
        else:
            x = height
            y = pic_y
            z = pic_x

        if block == "R":
            mc.setBlock(x,y,z,r)
        if block == "G":
            mc.setBlock(x,y,z,g)
        if block == "B":
            mc.setBlock(x,y,z,b)
            
        pic_y = pic_y + 1
    pic_x = pic_x + 1

This works in a similar way to the level-loading code from Chapter 4. It uses sys.argv to check if the user has specified a filename at the command line. If she has, it uses the text from that file to make a picture; otherwise, it uses the default, which draws three lines.

There are then two for loops that iterate through every line, and every character in that line. If the loop encounters an R, G, or B it draws a red, green, or blue block, respectively.

However, you don't have complete artistic control over the world. You can only create one of the predetermined block types, and not all of them will hover in the air. This example uses block type 246 (glowing obsidian) for red, type 18 (leaves) for green, and type 22 (lapis lazuli) for blue. Of course, you are free to change these to others if you want to create a different look.

The code for the coordinates is a little convoluted because it has two modes—vertical and horizontal—where it draws the picture in different orientations. Because of this, it has to manage two sets of coordinates: 2D ones for the picture and 3D ones for the Minecraft world. The if block translates between the two depending on which orientation is selected.

Notice that the program ignores any character that isn't an R, G, or B, so you can include them in your picture, but they will be represented as space. For example, the following text will create a smiley face (see Figure 8-2):

-------
-G---G-
-G---G-
---B---
-R---R-
--RRR--

Figure 8-2: Smiling faces are just the start. You can use this method to draw whatever you want in the Minecraft world.

9781118717059-fg0802.tif

Taking Things Further

There are loads of ways you could expand this world. The simplest way is to expand the pallet from three colours up to as many as you have characters and block types for. A more complicated way of expanding this world would be to create 3D drawings. This will require you to change the file structure somehow. We recommend doing it as a series of 2D images with a specific line between them. So, for example, a smiley face two blocks thick would be:

-------
-G---G-
-G-B-G-
---B---
-R---R-
--RRR--
*
-------
-------
-G---G-
---B---
-R---R-
--RRR--

You could also use a similar file structure to define an animation, with each separate block being a separate frame.

Making the Game Snake

A key use of any 3D graphical environment is to make games. Minecraft itself is obviously a game, and you can also use it as a games engine to build more games. In this section, you see how to make the classic game Snake.

If you're unfamiliar with the game, you control a snake that moves around and eats apples. Every time it eats an apple, it grows one square longer. If it hits either itself or the walls of the level, it dies. The aim of the game is to get as long a snake as possible. It was one of the most popular games on mobile phones before the iPhone came along.

Code for the game is as follows (you can find it on the website as chapter8-snake.py).

import minecraft
import pygame
import sys
import time
import random

from pygame.locals import *

mc = minecraft.Minecraft.create()
pygame.init()

#blank world
print("Resetting world")
mc.setBlocks(-100,-1,-100, 100, 0, 100, 2)
mc.setBlocks(-100,1,-100, 100, 100, 100, 0)
mc.player.setPos(0,2,0)

# create control window
display = pygame.display.set_mode((200,200))

#draw maze
print("drawing maze")
height = 10

start_x = 0
start_z = 0

difficulty = 0.5
apple_freq = 2

picture = [["R", "R", "R", "R", "R", "R", "R", "R", "R"],
           ["R", "-", "-", "-", "-", "-", "-", "-", "R"],
           ["R", "-", "-", "-", "-", "-", "-", "-", "R"],
           ["R", "-", "-", "-", "-", "-", "-", "-", "R"],
           ["R", "-", "-", "-", "-", "-", "-", "-", "R"],
           ["R", "-", "-", "-", "-", "-", "-", "-", "R"],
           ["R", "-", "-", "-", "-", "-", "-", "-", "R"],
           ["R", "-", "-", "-", "-", "-", "-", "-", "R"],
           ["R", "-", "-", "-", "-", "-", "-", "-", "R"],
           ["R", "-", "-", "-", "-", "-", "-", "-", "R"],
           ["R", "-", "-", "-", "-", "-", "-", "-", "R"],
           ["R", "-", "-", "-", "-", "-", "-", "-", "R"],
           ["R", "-", "-", "-", "-", "-", "-", "-", "R"],
           ["R", "-", "-", "-", "-", "-", "-", "-", "R"],
           ["R", "-", "-", "-", "-", "-", "-", "-", "R"],
           ["R", "R", "R", "R", "R", "R", "R", "R", "R"]]

if len(sys.argv) > 1:
    with open(sys.argv[1]) as f:
        picture = f.readlines()

posn_z = start_z
for line in picture:
    posn_x = start_x
    for block in line:
        x = height
        y = posn_x
        z = posn_z

        if block == "R":
            mc.setBlock(x,y,z,246)

        posn_x = posn_x + 1
    posn_z = posn_z + 1

snake = [(int((posn_z - start_z)/2),int((posn_x - start_x)/2))]
movement = [-1,0]

finished = False

grow_in = []
apple_in = apple_freq

while not finished:
    for event in pygame.event.get():
        if event.type == QUIT:
            finished = True

    key_state = pygame.key.get_pressed()
    if key_state[K_LEFT]:
        movement = [-1,0]
    if key_state[K_RIGHT]:
        movement = [1,0]
    if key_state[K_UP]:
        movement = [0,1]
    if key_state[K_DOWN]:
        movement = [0,-1]

    next_position = (height, snake[0][1] + movement[1],
                     snake[0][0] + movement[0])
    next_block = mc.getBlock(next_position[0],
                           next_position[1], next_position[2])

    if next_block == 246 or next_block == 22:
        finished = True

    if next_block == 18:
        grow_in.append(len(snake))
        apple_in = apple_freq

    snake.insert(0, (next_position[2], next_position[1]))
    for block in snake:
        mc.setBlock(height, block[1], block[0], 22)

    if 0 not in grow_in:
        remove_block = snake.pop()
        mc.setBlock(height, remove_block[1], remove_block[0], 0)

    grow_in2 = []
    for number in grow_in:
        if number > -1:
            grow_in2.append(number - 1)
    grow_in = grow_in2

    if apple_in == 0:
        apple_y = random.randint(1,int(posn_x - start_x)-1)
        apple_x = random.randint(1,int(posn_z - start_z)-1)
        while mc.getBlock(height, apple_y, apple_x) != 0:
            apple_x = random.randint(1,int(posn_z - start_z)-1)
            apple_y = random.randint(1,int(posn_x - start_x)-1)
        mc.setBlock(height, apple_y, apple_x, 18)

    apple_in = apple_in - 1

    time.sleep(difficulty)

mc.postToChat("Game Over")

There are two Minecraft methods that you haven't seen before: getBlock() and postToChat(). Both of these methods are fairly self-explanatory. The first returns the type of block at a particular location, and the second displays text on the player's screen.

You should recognise the first part of the code because it's almost the same as the code you saw previously to draw pictures. It's slightly simplified because here you need only one colour block for the maze that the snake moves through (we picked red, but you can substitute this for whatever you like).

The second part of the code (which includes the while loop and the five lines above it) contains the mechanics of the game. The snake is stored as a list of tuples. Each tuple contains the 2D coordinates for that section of the snake. These are then transformed into 3D coordinates and drawn on the screen.

Moving the Snake

The movement of the snake is done in two parts. Firstly, a new block is added to the start. This new block is calculated by adding the movement variable to the coordinate of the front of the snake. Secondly, the last position of the snake is removed from the list using pop(), which both takes it out of the list and returns it. The tile at this coordinate is set to 0 (air).

The key control is the same as you saw in Chapter 4. It uses PyGame. The only slight complication is that for this to work, there has to be a PyGame window, which must have focus in order to capture the keypresses. You should see a blank 200 by 200 window open when you run the program, and you'll need to click on it in order to be able to control the snake.

Growing the Snake

Perhaps the most complicated part of the code is the part that controls how the snake grows when it eats apples. This is because the snake doesn't get longer as soon as it eats the apple; instead, it gets longer when its whole body has passed through the block the apple was in.

To control this process, you use a list called grow_in. Every time the snake eats an apple, the length of the snake is added to the list. Each time the main loop is executed, every number in this list is reduced by one and negative numbers are discarded. This means that when the whole snake has passed through the apple, there will be a 0 in this list. The final portion of the snake is removed only when there isn't a 0 in the list. All this is done in the following section:

if 0 not in grow_in:
        remove_block = snake.pop()
        mc.setBlock(height, remove_block[1], remove_block[0], 0)

    grow_in2 = []
    for number in grow_in:
        if number > -1:
            grow_in2.append(number - 1)
    grow_in = grow_in2

Adding the Apples

The only other part left to do is to add the apples. A new apple appears exactly apple_freq loops after the snake ate the previous one. They're created at random locations inside the level with this code:

if apple_in == 0:
    apple_y = random.randint(1,int(posn_x - start_x)-1)
    apple_x = random.randint(1,int(posn_z - start_z)-1)
    while mc.getBlock(height, apple_y, apple_x) != 0:
        apple_x = random.randint(1,int(posn_z - start_z)-1)
        apple_y = random.randint(1,int(posn_x - start_x)-1)
    mc.setBlock(height, apple_y, apple_x, 18)

This generates random positions, and then checks that there isn't already something in that position (if there is, it generates a new random position). The variable apple_in simply counts the number of loops since the previous apple was eaten.

Take a look at Figure 8-3 to see how it looks, or just run it for yourself.

Figure 8-3: You can make the game harder or easier by altering the map for the level. Just make sure there aren't any gaps that the snake can escape out of; otherwise, the snake can rampage unchecked through the Minecraft world.

9781118717059-fg0803.tif

Taking Things Further

As with the game from Chapter 4, this one isn't really complete yet, and we'll leave that up to you. Some ways it could be finished include:

  • Some more graphics. For example, add an explosion when the snake dies, or add a countdown to the start. You could make the snake more graphically appealing, such as by using alternating blocks along its length.
  • A score. This could be the length of the snake.
  • A method to change the difficulty (use the time.sleep() on the loop). This could have a link into the score. It could even get faster as the snake gets longer.
  • Surprises. For example, once you get past a certain length, the snake could change colour.
  • Bonuses. These could appear on the map for a short period of time, and if the snake gets to them, the player gets bonus points.
  • More levels. The maps could be different sizes, or they could have extra walls inside them to make it harder.

You don't have to limit yourself to just improvements to Snake. You could use these same methods to make entirely new games. For example, the same basic mechanics could be used for games like Tron. (This game is inspired by the film. Install it with sudo apt-get xtron to find out more).

You could even build it into a platformer like the one in Chapter 4, or … well, you could do anything. You're limited only by your imagination.

Summary

After reading this chapter, you should understand the following a bit better:

  • Minecraft is a survival game set in a 3D world of blocks.
  • There's a special version of Minecraft for the Raspberry Pi and it allows you to control the world from Python.
  • You can move the player using player.setPos(x,y,z).
  • setBlock() and setBlocks() can be used to manipulate the world.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset