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.

Friday, 27 March 2015

Adventures in Minecraft (ch5 - the one with the hardware)

We've been going through the excellent Adventures in Minecraft book and reworking the examples from Python to Sniff. Partly because Minecraft is a fun thing to do, but also as a set of examples of how Python and Sniff differ in their approaches. Generally the Python code is shorter, but depends on more "features" that you have simply remember, or look up rather then actually coding.

In chapter 5, things get a little more interesting as it deals with hooking up real hardware so you can interact with Minecraft from outside the computer. This is a lot of fun, but can be tricky to set up, and it behaves differently if you have a Mac or PC, so we'll just stick to using the Pi (I really want to get the Minecraft libs running natively on Arduino!). To use Sniff with GPIO on the Pi you first need to install WiringPi, and then it should "just work".

Here's the first Python program from the book, which simply flashes an LED:

import time
import RPi.GPIO as GPIO

LED = 15

GPIO.setmode(GPIO.BCM)
GPIO.setup(LED, GPIO.OUT)

def flash(t):
  GPIO.output(LED, True)
  time.sleep(t)
  GPIO.output(LED, False)
  time.sleep(t)

try: # Try to run this code, jump to "finally" if it fails
  while True:
    flash(0.5)
    
finally: # always gets here if your code fails (e.g. you CTRL-C the program)
  GPIO.cleanup()


There's a LOT of stuff there that isn't friendly... Here's the direct equivalent in Sniff:

make led digital output 15

make delay number
when flash
.set led to on
.wait delay secs
.set led to off
.wait delay secs

when start
.forever
..set delay to 0.5
..broadcast flash and wait

For once the Sniff code is actually shorter! This is mainly because Sniff treats GPIO pins like variables - we can simple declare outputs (to give them names) and then assign to them - it just works.

Once we can flash the LED, we can add that to the "doormat" example from previous chapters which used to print "welcome home" when you arrived at a specific location, so that the LED flashes when you get there.

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

make led digital output 15
make delay number

when flash
.set led to on
.wait delay secs
.set led to off
.wait delay secs

when start
.set delay to 0.5
.
.set mcX to 10
.set mcZ to 10
.tell world to "getHeight"
.set blockType to 35
.set blockData to 15
.tell world to "setBlock"
.
.forever
..tell world to "getPos"
..if mcX>10 and mcX<11 and mcZ>10 and mcZ<11
...broadcast flash and wait

This version behaves slightly different to the Python version, as it uses the "getHeight" operation to place a wool block on the ground, even if that is higher or lower than 0. It then checks the position of the player and compares it to the position of the block.


7 Segment Display

The next exercise uses a 7 segment display which will ultimately count down from 9 to 0, before detonating an explosion in Minecraft. However first we have to get it wired up and tested.

This is both a little easier and a little harder in Sniff. It handles the GPIO pins more easily, but you can't have a list of io pins, or io pin numbers. Instead we need to access them individually. This is little more long winded, but its easy enough (and probably a little easier to understand):

make A digital output 10
make B digital output 22
make C digital output 25
make D digital output 8
make E digital output 7
make F digital output 9
make G digital output 11
make P digital output 15

make pattern list of booleans
when display
.set A to item 1 of pattern
.set B to item 2 of pattern
.set C to item 3 of pattern
.set D to item 4 of pattern
.set E to item 5 of pattern
.set F to item 6 of pattern
.set G to item 7 of pattern
.set P to item 8 of pattern

The corresponding Python fragment is:

LED_PINS = [10,22,25,8,7,9,11,15]
for g in LED_PINS:
  GPIO.setup(g, GPIO.OUT)

pattern = [True, True, True, True, True, True, True, False] # ABCDEFG No Dp

for g in range(8):
  if pattern[g]:
    GPIO.output(LED_PINS[g], ON)
  else:
    GPIO.output(LED_PINS[g], not ON)

This is perfectly good code for production use (and scales better - what if we had 100pins?), but the Sniff version is easier to understand.

In the Sniff version we still need to load up the list "pattern" which holds Boolean values (on/off, or if you prefer you can use yes/no or true/false - they all mean the same things in Snff) to indicate which parts of the 7 segment display we want to be lit up and/or turned off, so the main "start" script might be:

when start
.add on to pattern
.add on to pattern
.add on to pattern
.add on to pattern
.add on to pattern
.add on to pattern
.add on to pattern
.add off to pattern
.
.broadcast display and wait

Now to make the display count from 1 to 10 we need to create patterns for each digit, and display that. In the Python than means using a library with makes the code short, but hides what's actually going in. In Sniff lets keep building on what we've already made. Rather than just storing 8 booleans in the list "pattern", lets store 80: 8 for each of the ten digits:

make pattern list of booleans
when loadPatterns
.#0
.add on to pattern
.add on to pattern
.add on to pattern
.add on to pattern
.add on to pattern
.add on to pattern
.add off to pattern
.add off to pattern
.
.#1
.add off to pattern
.add on to pattern
.add on to pattern
.add off to pattern
.add off to pattern
.add off to pattern
.add off to pattern
.add off to pattern
.
.#2
.add on to pattern
.add on to pattern
.add off to pattern
etc

Then we can modify the display script to use not the first 8 values, but use the variable digit to calculate an offset into the list:

make digit number
make base number
when display
.set digit to digit mod 10
.set base to 8*digit
.set A to item base+1 of pattern
.set B to item base+2 of pattern
.set C to item base+3 of pattern
.set D to item base+4 of pattern
.set E to item base+5 of pattern
.set F to item base+6 of pattern
.set G to item base+7 of pattern
.set P to item base+8 of pattern

Now to make it count, just set digit, and broadcast display:
when start
.broadcast loadPatterns
.
.repeat 10 using digit
..broadcast display and wait
..wait 1 secs

(note I've used the "new" repeat using syntax, which executes the block 10 times using digit as a counter - its not necessary but its much neater than just using the Scratch notation).

The final example combines this counter with a button to start a countdown, and then destroy a large area of the world. Most of this is stuff we've covered, so lets just look at the button code:

BUTTON = 4 
GPIO.setup(BUTTON, GPIO.IN)
try: 
  while True:
    time.sleep(0.1)
    if GPIO.input(BUTTON) == False:
      pos = mc.player.getTilePos()
      bomb(pos.x, pos.y, pos.z)
      
finally: 
  GPIO.cleanup()

In Sniff this becomes:

make button digital input 4
.forever
..wait until not button
..tell world to "getPos"
..broadcast bomb and wait

Here we're creating an input, which (like outputs) we can use wherever we would use a boolean, so we can use it in Sniff/Scratch's "wait until" block.  This is a fairly unusual construct as it does nothing until a condition is met. In most languages that would mean wait forever, but Sniff has the same model of doing lots of things at the same time that Scratch does - other scripts may be running, which change the result of the test. It's also very handy when interacting with the outside world.

The code for chapter 5 repeats many of the things we learnt in comparing Python and Sniff in the first few chapters: Python code is generally shorter, but that's due to its more dense syntax, and reliance on external code/modules. However in this case Sniff's good support for GPIO means that the Sniff code is especially clear. Code for all these examples is in the current Sniff distribution.

No comments:

Post a comment