ONE WAY YOU can make a Minecraft game more interesting is to make it change what it does based on what is going on around your player. As you move around the game world, the choices that you are faced with depend on what you've already done, which makes the game slightly different every time you play it. The Minecraft API allows you to interact with blocks by finding out what block type you are standing on and detecting when you hit a block with your sword.
In this adventure, you first learn the basics of interacting with blocks by writing a magic bridge program. This bridge is special, because as you walk on water or walk into the sky, a bridge magically appears in front of you to keep you safe. Soon your Minecraft world will fill up with bridges. Version 2 of your magic bridge builder uses a Python list to remember where you built the bridge and makes it do a disappearing act right before your eyes when you land on safe ground again!
Finally, you learn how to sense that a block has been hit and then build an exciting treasure hunt game using your magic bridge to find and collect treasure that appears randomly in the sky, complete with a homing beacon and a score.
In this adventure you work just like a real software engineer, building up a large program one function at a time, and finally stitching it all together at the end to make an exciting larger program. Fasten your seatbelt and take careful note of the instructions. It’s going to be an exciting journey into the sky!
You learned in Adventure 2 that it is possible to track your player’s position by reading the coordinates with getTilePos()
. These coordinates represent the x, y and z coordinates in the Minecraft world where your player, Steve, is located at the moment. You used these coordinates to sense whether Steve was standing on a magic doormat or, using geo-fencing, whether he was standing in a field.
However, unless your programs maintain a detailed map of exactly where every block is in the Minecraft world, just sensing by position is not going to be flexible enough for you, as your programs become more sophisticated. You could always keep a detailed map of your own — but why go to all that trouble when Minecraft must already have that information in the computer’s memory to display the 3D world on the screen?
Fortunately, the Minecraft API also includes a getBlock()
function. This function gives you full access to the in-memory world map of Minecraft and, by using coordinates, you can use it to tell you about every block — not just the block where Steve is located but every block at every position in the Minecraft world.
You also saw in Adventure 3 that it is possible, through block types, to change any block in the Minecraft world. Fortunately, setBlock()
and getBlock()
work together as a pair, so if you use setBlock()
with a block id and then use getBlock()
immediately after that, you get the same block id back.
Soon you are going to build another exciting game inside Minecraft, but your program will be quite big. The best way to build a big program is to build little programs and then stick them all together once you know they work. Let’s start by bringing this idea to life with a simple program that tells you if your player is standing on something safe or not.
Start Minecraft and IDLE and, if you are working on a PC or a Mac, start the server, too. You should have had a bit of practice with starting everything by now, but refer to Adventure 1 if you need any reminders. You are now going to build a program that gives you important information about your player’s exact location. You need to find out if his feet are on the ground before you can build your magic bridge into the sky.
safeFeet.py
. Remember to store your programs inside the MyAdventures
folder, otherwise they will not work.minecraft
module and, because you are interacting with blocks, also the block
module. You need a small time delay to slow things down, so also import the time
module:
import mcpi.minecraft as minecraft
import mcpi.block as block
import time
mc = minecraft.Minecraft.create()
safeFeet()
. This makes it easier for you to reuse this code later. The first thing this function does is to get the position of your player, Steve:
def safeFeet():
pos = mc.player.getTilePos()
getBlock()
gets the block id of the block at the coordinates you provide. Because pos.x
, pos.y
and pos.z
are the coordinates of your player, you must use pos.y-1
to get the block directly below Steve’s feet:
b = mc.getBlock(pos.x, pos.y-1, pos.z) # note: pos.y-1 ↩
is important!
if b == block.AIR.id or b == block.WATER_STATIONARY.id ↩
or b == block.WATER_FLOWING.id:
mc.postToChat("not safe")
else:
mc.postToChat("safe")
safeFeet()
function. Leave a blank line to remind yourself that it is the end of the function, and start the while
of the game loop without any indent. As in your earlier programs, you put a short delay and, finally, use the new safeFeet()
function which does all the work.
while True:
time.sleep(0.5)
safeFeet()
Now choose File ⇒ Save to save your program and then run it by choosing Run ⇒ Run Module from the Editor menu.
What happens as your player moves around the Minecraft world? You should see the words ‘safe' or ‘not safe' appear on the Minecraft chat as appropriate, as shown in Figure 4-1. Try flying in the air and swimming in the sea to see what happens.
In the previous program, you wrote code that sensed the block directly under your player and posted a message on the chat. This is an interesting tiny step towards a bigger program, but let’s see if you can now turn this experiment into something real by stitching it together with some of the techniques you learned in Adventure 3 with the setBlock()
function.
By making a small change to safeFeet.py
, you can turn it into a magic bridge builder that places a glass bridge under your feet wherever you walk, making sure that your player never falls into the sea or falls out of the sky! You reuse this new function in a later program in this adventure, so make sure you name it correctly.
safeFeet.py
program as magicBridge.py
.safeFeet()
function so that it is now called buildBridge()
, and modify the if
/else
statement as marked in bold by removing the mc.postToChat()
and replacing it with a mc.setBlock()
. Every time your player’s feet are unsafe, this builds a glass bridge under him. Be careful of the long line in the if
statement:
def buildBridge():
pos = mc.player.getTilePos()
b = mc.getBlock(pos.x, pos.y-1, pos.z)
if b == block.AIR.id or b == block.WATER_FLOWING.id ↩
or b==block.WATER_STATIONARY.id:
mc.setBlock(pos.x, pos.y-1, pos.z, block.GLASS.id)
The else
and the mc.postToChat()
have been removed from the code, as they are no longer needed.
buildBridge()
function:
while True:
buildBridge()
Run your program and walk around the world, jumping up into the sky and walking on water. As your player walks around, whenever his feet are not on safe ground, a magic glass bridge appears to keep him from falling, as in Figure 4-2. It is now possible for him to walk on water. It’s a miracle!
In all of the programs you have written up until now, you have used some form of variable to store information that changes as the program runs. Each of these variables has a name and a value, and if you want your programs to remember more than one thing, you use more variables.
However, these variables are a fixed size and each can store only one thing (for example, a number or a string of text). In many of the programs you will write as a Minecraft programmer you need to store an undefined number of items. You won’t always know how many items you will want to store in a program.
Fortunately, like any modern programming language, Python has a feature called a list, which can store varying amounts of data as the program runs.
The best way to understand lists is to experiment with them in the Python Shell.
>>>
prompt, which is where you start typing your interactive commands. Don’t forget that you must stop your existing program running by choosing Shell ⇒ Restart Shell from the Python Shell menu, or pressing CTRL then C. If your previous program is still running, this next section doesn't work!a = [] # an empty list
print(a)
a.append("hello")
print(a)
a.append("minecraft")
print(a)
print(len(a))
print(a[0]) # the [0] is the index of the first item
print(a[1]) # the [1] is the index of the second item
word = a.pop()
print(word)
print(a)
print(len(a))
print(len(word))
word = a.pop()
print(word)
print(a)
print(len(a))
Figure 4-3 shows the output of these experiments in the Python Shell.
You are now going to use your new knowledge about lists to write a bridge builder program, where the bridge vanishes once your player’s feet are safely on the ground again. This program is similar to the magicBridge.py
program, so you could save that as a new name and edit it, but the full program is shown here to make it easier to explain each of the steps. Use copy and paste from your magicBridge.py
program if you want to save a little bit of typing.
vanishingBridge.py
by choosing File ⇒ Save As from the Editor menu.import mcpi.minecraft as minecraft
import mcpi.block as block
import time
mc = minecraft.Minecraft.create()
bridge = []
buildBridge()
function that builds the bridge for you. You use this buildBridge()
function in the final program in this adventure, so make sure you name this function correctly. Most of the code at the start of this function is the same as in the magicBridge.py
program, so you could copy that if you want to save some typing time. Be careful to get the indents correct:
def buildBridge():
pos = mc.player.getTilePos()
b = mc.getBlock(pos.x, pos.y-1, pos.z)
if b == block.AIR.id or b == block.WATER_FLOWING.id ↩
or b == block.WATER_STATIONARY.id:
mc.setBlock(pos.x, pos.y-1, pos.z, block.GLASS.id)
coordinate = [pos.x, pos.y-1, pos.z]
bridge.append(coordinate)
else
statement to check whether he is standing on glass. If he is not, your program starts deleting blocks from the bridge. The program has to check that there is still some bridge left, otherwise it raises an error if you try to pop from an empty list. The elif
is short for else if
, and the !=
means ‘not equal to'. Be careful with the indents here: The elif
is indented once, as it is part of the buildBridge()
function; the next if
is indented twice as it is part of the elif
:
elif b != block.GLASS.id:
if len(bridge) > 0:
These next lines are indented three levels because they are part of the if
that is part of the elif
that is part of the buildBridge()
function! Phew!
Remember that earlier you appended a list of three coordinates to the bridge list? Here, you have to index into that list with coordinate[0]
for x, coordinate[1]
for y and coordinate[2]
for z. Adding the time.sleep()
also makes the bridge vanish slowly, so that you can see it happening:
coordinate = bridge.pop()
mc.setBlock(coordinate[0], coordinate[1], coordinate[2], block.AIR.id)
time.sleep(0.25)
while True:
is not indented at all), so check the indentation very carefully:
while True:
time.sleep(0.25)
buildBridge()
Save your program with File ⇒ Save and then run it with Run ⇒ Run Module. Walk your player around the Minecraft world, walk him off a ledge or into a lake and, then turn and walk him back onto safe ground again. What happens? Figure 4-4 shows the bridge starting to vanish once Steve is on safe ground.
The last sensing ability that you need in your tool-bag for this adventure is the ability to sense when your player hits a block. Block-hit detection enables you to create some really exciting games and programs of your own because it allows your player to interact directly with each and every block inside the Minecraft world. To hit a block you need to right-click while you're holding a sword.
Start a new program for this adventure, as it begins life as a little self-contained experiment but later makes its way into your final game of this adventure.
blockHit.py
.import mcpi.minecraft as minecraft
import mcpi.block as block
import time
mc = minecraft.Minecraft.create()
diamond_pos = mc.player.getTilePos()
diamond_pos.x = diamond_pos.x + 1
mc.setBlock(diamond_pos.x, diamond_pos.y, diamond_pos.z,
block.DIAMOND_BLOCK.id)
checkHit()
. You reuse this function in your final program, so make sure you name it correctly:
def checkHit():
vanishingBridge.py
program earlier:
events = mc.events.pollBlockHits()
for
loop. See the following Digging into the Code sidebar for a more detailed explanation of this new form of the for
loop:
for e in events:
pos = e.pos
if pos.x == diamond_pos.x and pos.y == diamond_pos.y ↩
and pos.z == diamond_pos.z:
mc.postToChat("HIT")
while True:
time.sleep(1)
checkHit()
Save your program with File ⇒ Save and run it using Run ⇒ Run Module from the Editor menu.
Move your player around a bit until you can see the diamond. Now, hit it on each of its faces with a sword. What happens? As Figure 4-6 shows, when you hit the diamond, the message ‘HIT' appears on the Minecraft chat.
For most of this adventure, you have been learning skills and building snippets of program code to test out and experiment with various sensing features in Minecraft. It’s now time for you to knit all of that code into a complete game. The game you are going to write is called ‘Sky Hunt', a treasure hunt in which you have to find diamond blocks hanging randomly in the sky using a homing beacon and hit them to get points.
There is a twist to this game though: Every time you move forward you leave a trail of gold, and this costs you one point off your score per gold block. If you run around aimlessly looking for the treasure, your score rapidly decreases and even becomes negative! You have to use your Minecraft navigation skills to look for the diamond blocks quickly, and try to get to them in as few moves as possible.
When you find each piece of treasure you score points, and the trail of gold magically melts away (possibly leaving holes in the ground for you to trip over, so watch out!).
This program is mostly made up of reusable parts from all the other little experimental programs you have already written in this adventure. You can cut and paste bits of your other programs and modify them to save typing time if you want, but I have included the full program here to make sure you know what is needed.
Professional software engineers often start with a simple framework program built with just print statements and test this first to make sure the structure is correct. Then they add and test new features to it gradually. In this section, you are also going to be a real software engineer and write and test this program in steps. First, let’s get the framework of the game loop in, and some dummy functions that you can flesh out as you go along.
Use the following steps to write the functions and the main game loop:
skyHunt.py
.import mcpi.minecraft as minecraft
import mcpi.block as block
import time
import random
mc = minecraft.Minecraft.create()
RANGE
constant to set how difficult the game is, by setting how far away from the player the random treasure is placed. Set this to a small number to start with, while you are testing, and make the number bigger later when your program is completed:
score = 0
RANGE = 5
print
statement here to print a message. This just acts as a placeholder for code you write later:
treasure_x = None # the x-coordinate of the treasure
def placeTreasure():
print("placeTreasure")
def checkHit():
print("checkHit")
def homingBeacon():
print("homingBeacon")
bridge = []
def buildBridge():
print("buildBridge")
while True:
time.sleep(1)
if treasure_x == None and len(bridge) == 0:
placeTreasure()
checkHit()
homingBeacon()
buildBridge()
The first function you need to write is the one that places treasure at a random position in the sky. You use three global variables for the coordinates of the treasure, and their initial value is None
. The None
value is a special Python value, indicating that the variable is in memory but has nothing stored in it. You use this in your game loop to check whether a new piece of treasure needs to be built.
treasure_x = None
treasure_y = None
treasure_z = None
placeTreasure()
function (and take out the print
statement you put in earlier) with this code:
def placeTreasure():
global treasure_x, treasure_y, treasure_z
pos = mc.player.getTilePos()
random
function to place the treasure at a position no more than RANGE
blocks away from the player, but set the y coordinate so that it is somewhere above the player (which will probably place it in the sky):
treasure_x = random.randint(pos.x, pos.x+RANGE)
treasure_y = random.randint(pos.y+2, pos.y+RANGE)
treasure_z = random.randint(pos.z, pos.z+RANGE)
mc.setBlock(treasure_x, treasure_y, treasure_z, ↩
block.DIAMOND_BLOCK.id)
Run your program and test that a piece of treasure is created up in the sky, near where your player is standing.
Now you use the code from the blockHit.py
program with a few small modifications to detect when the treasure is hit by your player’s sword.
print
statement from the checkHit()
function and replace it with the code shown here. The score
and treasure_x
variables have to be listed as global variables here because the checkHit()
function changes their values. Python requires you to list inside a function any global variables that it changes the value of. If you don’t do this, your program doesn't work:
def checkHit():
global score
global treasure_x
events = mc.events.pollBlockHits()
for e in events:
pos = e.pos
if pos.x == treasure_x and pos.y == treasure_y ↩
and pos.z == treasure_z:
mc.postToChat("HIT!")
score
for hitting the treasure and then delete the treasure so it disappears. Finally, you must remember to set treasure_x
to None
(so that placeTreasure()
can create a new random piece of treasure later). Be careful with the indents here, as this code is part of the body of the if
statement:
score = score + 10
mc.setBlock(treasure_x, treasure_y, treasure_z,
block.AIR.id)
treasure_x = None
Save and run your program, and check that when your player hits the treasure it disappears. You should also find that when you hit the treasure and it disappears, a new piece of treasure is created at a random position close to your player.
The homing beacon displays the score and the approximate distance to the treasure every second on the Minecraft chat. Here is how to add this.
timer
variable. As the main game loop eventually runs 10 times per second, you have to count 10 loops for every second. This timer
helps you to do that. If you change the speed of the game loop, you have to adjust this TIMEOUT
value as well. Make sure you put this code just above the homingBeacon()
function (note, there is no indent at all here):
TIMEOUT = 10
timer = TIMEOUT
print()
from inside the homingBeacon()
function and list the timer
as a global variable, as this function will want to change its value:
def homingBeacon():
global timer
treasure_x
variable has a value in it. You have to check here whether treasure has been created; otherwise you get homing beacon messages on the Minecraft chat when there is no treasure to find:
if treasure_x != None:
timer = timer - 1
if timer == 0:
timer = TIMEOUT
timer
times out (every 10 calls to this function, or once every second), calculate a rough number that tells you how far away from the treasure you are. The abs()
function finds the absolute value (a positive value) of the difference between two positions. By adding all the positive differences together, you get a number that is bigger when you are further away from the treasure, and smaller when you are nearer to it. Check your indents here, as this code all belongs to the body of the most recent if
statement:
pos = mc.player.getTilePos()
diffx = abs(pos.x - treasure_x)
diffy = abs(pos.y - treasure_y)
diffz = abs(pos.z - treasure_z)
diff = diffx + diffy + diffz
mc.postToChat("score:" + str(score) + ↩
" treasure:" + str(diff))
Save and run your program and make sure that the homing beacon and score are displayed on the Minecraft chat. Because you are still testing and developing your program, the game loop is set to run 10 times slower than normal, so you should see messages on the Minecraft chat every 10 seconds at the moment. Make sure this is the case by counting from 1 to 10 in your head. You will still see some of the dummy functions printing out on the Python Shell window every second for now because your program is not quite finished.
You now add the bridge builder from your earlier vanishingBridge.py
program. You only need to modify it a little, so that it checks whether your player is standing on gold and, if not, creates a gold trail.
buildBridge()
function looks like the following. The important lines that have changed from your vanishingBridge.py
program are marked in bold:
bridge = []
def buildBridge():
global score
pos = mc.player.getTilePos()
b = mc.getBlock(pos.x, pos.y-1, pos.z)
if treasure_x == None:
if len(bridge) > 0:
coordinate = bridge.pop()
mc.setBlock(coordinate[0],
coordinate[1],
coordinate[2],
block.AIR.id)
mc.postToChat("bridge:" + str(len(bridge)))
time.sleep(0.25)
elif b != block.GOLD_BLOCK.id:
mc.setBlock(pos.x, pos.y-1, pos.z, block.GOLD_BLOCK.id)
coordinate = [pos.x, pos.y-1, pos.z]
bridge.append(coordinate)
score = score – 1
time.sleep(1)
in the game loop to sleep every 0.1 seconds. This runs the game loop 10 times per second. The timer in homingBeacon
counts 10 of these and therefore only displays a message on the Minecraft chat every second.Save and run your program. Check that the gold trail disappears after you collect the treasure and that your score goes down for every gold block that you spend.
Now all you have to do is enjoy the game! See how hard it is to get a good score by collecting the treasure?
Figure 4-7 shows the score and homing beacon display on the Minecraft chat.
Quick Reference Table | |
Command |
Description |
b = mc.getBlock(10, 5, 2) |
Getting the block type at a position |
hits = mc.events.pollBlockHits() for hit in hits: pos = hit.pos print(pos.x) |
Finding out which blocks have been hit |
a = [] # an empty list a = [1,2,3] # an initialised list |
Creating lists |
a.append("hello") |
Adding to the end of a list |
print(a) |
Printing the contents of a list |
print(len(a)) |
Working out the size of a list |
print(a[0]) # 0=first, 1=second |
Accessing items in a list by their index |
print(a[-1]) |
Accessing the last item in a list |
word = a.pop() # remove last item print(word) # item just removed |
Removing the last item from a list |
for item in a: print(item) |
Looping through all items in a list |
In this adventure, you have learned how to use getBlock()
to sense the block that your player is standing on, and how to use events.pollBlockHits()
to respond when your player hits any face of any block. You’ve built a fantastic and complete game within Minecraft, complete with scoring!
RANGE
constant to a larger number so that treasure is created further away from your player. This makes your game more difficult to play, so try to add a new feature that displays ‘cold', ‘warm' or ‘hot' on the Minecraft chat depending on the distance your player is from the treasure.homingBeacon()
function more accurate. (Psst! Martin covers a little bit about this in Adventure 6, so you could sneak a peek at that adventure and see if you can work it out!)Achievement Unlocked: Expert in defying the laws of gravity and walking on water—two miracles achieved in one adventure!