Sniff is a "Scratch-like" programming language that's designed to help Scratchers move gently from Scratch to more conventional languages. They can start writing programs, without having to learn a new language because Sniff is based on Scratch. They learn a little more about variables, compiling, syntax errors (!), and they can have fun controlling real hardware while they're doing it.

Tuesday, 24 February 2015

Adventures in Minecraft (Chapter 4)

Chapter 4 of the book Adventures in Minecraft, is called "interacting with blocks" and brings together the previous two chapters to make blocks appear and disappear in reaction to the player.

The first example is called safeFeet and simply detects whats under the player to decide if he's standing on something solid.

make world minecraft device
make networkPeer string
make message string
make mcX number
make mcY number
make mcZ number
make blockType number
make blockData number

when start
.set networkPeer to "raspberrypi.local."
..wait 0.5 secs
..broadcast safeFeet and wait

when safeFeet
.tell world to "getPos"
.change mcY by -1
.tell world to "getBlock"
.if blockType = 0 or blockType=8 or blockType=9
..set message to "not safe"
..set message to "safe"
.tell world to "show"

That's pretty simple (and we've put it in a separate script to be "nice"). We simply get the position of the player, and then ask for the block underneath our current position. If that block is either air or water, then we're not safe, otherwise we're safe.

From there its a small step to replace that brick with something else, so that if we're in the air, we create something to stop us falling:

when magicBridge
.tell world to "getPos"
.change mcY by -1
.tell world to "getBlock"
.if blockType = 0 or blockType=8 or blockType=9
..set blockType to 20
..tell world to "setBlock"

For the final trick in this series of exercises, when we create a block we add its position to a list. Then when we get back to safety we go through and delete the blocks:

make bridge list of number

when vanishingBridge
.tell world to "getPos"
.change mcY by -1
.tell world to "getBlock"
.if blockType = 0 or blockType=8 or blockType=9
..set blockType to 20
..tell world to "setBlock"
..add mcX to bridge
..add mcY to bridge
..add mcZ to bridge
..if (not blockType=20) and length of bridge>0
...set mcX to item 1 of bridge
...set mcY to item 2 of bridge
...set mcZ to item 3 of bridge
...delete item 1 of bridge
...delete item 1 of bridge
...delete item 1 of bridge
...set blockType to 0
...tell world to "setBlock"
...wait 0.25 secs

In the Python version we have structures, so we can create a list of points:

def buildBridge():
  pos = mc.player.getTilePos()
  b = mc.getBlock(pos.x, pos.y-1, pos.z)
 if b == or b == or
    mc.setBlock(pos.x, pos.y-1, pos.z,
    coordinate = [pos.x, pos.y-1, pos.z]
  elif b !=
    if len(bridge) > 0:
      coordinate = bridge.pop()
      mc.setBlock(coordinate[0], coordinate[1], coordinate[2],

This is conceptually a little better, as the 3d coordinates are stored in a single element of the "bridge" list, but doesn't really make much difference. Also in the Sniff version we manually add elements to the end, and remove from the beginning, while in Python we use append and pop. Both of these add an extra layer of "something to learn/remember" rather than "something to understand", so they're not  necessarily a clear cut benefit.

There is one further gotcha... Sniff stores the players position to its full sub-block accuracy. This works fine most of the time, but if we're at x=10.9 we're actually still in square 10. When we place a block 10.9 gets rounded to 11, it ends up in the wrong place, and we fall (this is besides the issues of simply not placing the blocks fast enough!). If we add

.set mcX to round (mcX - 0.5)
.set mcY to round (mcY - 0.5)
.set mcZ to round (mcZ - 0.5)

after getPos, then this rounds the value down to the nearest whole block, rather than doing regular up/down rounding, and the bridge works a little better.

The next section deals with hitting things with the sword. This is pretty specific and took me a while to figure out - it HAS to be the sword, and it has to be HITTING (right mouse) not smashing (left).

make diamondX number
make diamondY number
make diamondZ number

when checkHit
.tell world to "getHits"
.if blockType>0
..if mcX=diamondX and mcY=diamondY and mcZ=diamondZ
...set message to join "Hit " [blockData]
...tell world to "show"

when start
.set networkPeer to "raspberrypi.local."
.tell world to "getPos"
.change mcX by 4
.set mcX to round mcX
.set mcY to round mcY
.set mcZ to round mcZ
.set blockType to 57
.tell world to "setBlock"
.set diamondX to mcX
.set diamondY to mcY
.set diamondZ to mcZ
..wait 0.25 secs
..broadcast checkHit and wait

So here we create a diamond (57) block and then wait for the player to hit it. Once again we round the players position to a whole number so we can be sure that we're acctually placing the block where we think we are the block. After placing the block we just keep calling getHit to see if anything has happened. The exact values returned by getHits are a little vague - It's not well documented in the minecraft pi docs what blockType will be, but 0 means we didn't hit anything! blockData is used to tell us which face of the diamond we hit - try it and see.

The chapter concludes with a longer example called Sky Hunt which combines the previous exercises to make larger game. I've not implemented it, as I was keen to move on to chapter 5 which I'll post later in the week.

Once again thanks to the Authors for writing the original Python versions. Go buy the book for a fuller explanation of whats going on here.

No comments:

Post a Comment