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.

Monday, 21 July 2014

SniffBot Jr - with IR control

SniffBot has been a big hit at various STEM events, with kids of all ages having fun driving it. As a follow on, we're developing a kit we can use as the basis for a workshop so participants can actually build their own robot and start programming it.

Part of the challenge of this is COST. Sniffbot is super cheap, but the RC controller it uses isn't. While RC is by far the best way to drive a robot like this, we're hoping to put together a complete system that can do everything the full Sniffbot can do for less that £25. That includes all of the mechanical and electronic components for a programmable, remote control robot (batteries not included, but that's another story!). The RC kit I generally use costs about £60 for the controller, and £15 for the receiver. That's still within reasonable budget if you want to buy one yourself, but not if we're building 5 for a workshop...

IR control is cheap and easy. An IR receiver costs about £1, and for the transmitter you can use any old one one you have around - doesn't everyone have a draw full of them. However if you do need to buy a new one then they're again only about £1.

We can reuse some of the original SniffBot code to drive the motors:

##### NOW CONTROL THE MOTORS #####
make leftDrive number
make rightDrive number

make motorController device
make motor1 number
make motor2 number
make motor3 number
make motor4 number

when updateMotors
.set motor3 to leftDrive
.set motor4 to rightDrive

.tell motorController to "update"


SniffBot Jr will have only 2 driven wheels, so this code isn't really necessary, but it means we can use the same code for both the 2 and 4wd drive versions: set leftDrive and rightDrive and then call updateMotors. It's important to use motor3 for left and motor4 for right, as the timers used by the IR receiver clash with PWM on the M1 and M2 outputs of the Motor Shield.

To receive IR commands we need a receiver which demodulates the signal, such as the TSOP4838. These have three pins: Vcc, Gnd and signal, so they can easily be wired to plug into the servo pins on the Arduino motor controller shield (which appear as inputs D9 and 10).



To use the IR we just need to make an IR device and tell it to read:
make receiveIR device 9
make keyPressed number

when start
.forever
..tell receiveIR to "read"
..if keyPressed > 0
...say [ keyPressed ]



Calling read stores a unique number for the pressed key in the keyPressed variable. If no key is pressed then it has the value 0. Depending on the controller the value of 1 might be used to indicate that the previously pressed key is still being held down.

There are several standards and protocols that IR keys can and should use but to keep things simple in Sniff they're set up so that more or less any key press should return a quasi-unique value. If you run the above code you can test your controller and find out what numbers it generates. Then we can use the values we get to control the motors on the robot:

when start
.forever
..tell receiveIR to "read"
..
..if keyPressed = 55335
...set leftDrive to 1
...set rightDrive to 1
...broadcast updateMotors and wait
..
..if keyPressed = 22695
...set leftDrive to -1
...set rightDrive to -1
...broadcast updateMotors and wait
..
..if keyPressed = 8415
...set leftDrive to -1
...set rightDrive to 1
...broadcast updateMotors and wait
..
..if keyPressed = 24735
...set leftDrive to 1
...set rightDrive to -1
...broadcast updateMotors and wait
..
..if keyPressed = 41055
...set leftDrive to 0
...set rightDrive to 0

...broadcast updateMotors and wait


These values are for a spare, unbranded IR remote I happened to have. I used the previous code to figure out the number for each button I wanted to use, and dropped them in to make a forward, backwards, left, right and stop control.

This is the most basic control you could have, but it could easily be extended so that holding/repeatedly pressing accelerates or slows the robot, and/or that left/right steer, rather than just putting the thing into an on-the-spot spin. However the important thing is we now have control over our robot in a way that's cheap enough to use with a workshop of people.

Tuesday, 15 July 2014

Tom's Giant Thermometer

Another quick bit of fun:

One of the guys who helps out on Sniff is Tom Stacy (@Code_ED on twitter). One of his other projects is an environment sensor using the Arduino Yun. One day he turned up with a fun idea to display temperature by making a giant thermometer out of neopixels! It's pretty easy, as we just need to read the temperature (using a dht or ds18), and then light up the right number of pixels with an appropriate colour.

If you're demoing an experiment to a class you could set one of these up on the wall, and everyone would be able to see the temperature readings your getting.

Lets start by making the devices:
make dht device A1
make temperature number
make humidity number

make neoPixel device A4
make neoShade list of number
make neoColor list of number


We'll also need some info on how we want this configured:
make ledCount number
make minTemp number
make maxTemp number
make ledIndex number

when start
.set ledCount to 10
.set minTemp to 20
.set maxTemp to 30


Now we just need to loop forever, reading the temperature and lighting up the neoPixels:
.forever
..delete all of neoShade
..delete all of neoColor
..
..tell dht to "readDHT11"
..
..set ledIndex to ledCount*(temperature-minTemp)/(maxTemp-minTemp)
..
..repeat ledIndex
...add 0 to neoColor
...add 50 to neoShade
..
..repeat ledCount-ledIndex
...add 180 to neoColor
...add 100 to neoShade
..
..tell neoPixel to "show"
..
..wait 1 secs


NeoPixels are driven by filling in the two lists with the shade, and colour. Colours go from 0-360, where 0 is red, and shades go from 0 (black) though 50 (saturated colour) to 100(white). We start by emptying the lists, and reading the temperature.

Then the clever line... we calculate how many led's to light by calculating the fraction of them to light, and multiplying by the number of LED's.

Then we just fill up the lists with ledIndex of them being red, and the next 1-LED of them being white, then output them to the strip. There's actually a subtle bug here, as ledIndex isn't likely to be an exact integer, so depending on its value we might end up using an extra LED - its a rounding error combined with an off-by-one bug (its also one of the reasons why most programming languages have an int and a float type). However in this case all that happens is an extra LED gets lit, so I'd rather not complicate the code with it (a bit of rounding should solve it).


As set up the code is written for Arduino, and uses 10 neoPixels attached to A4, and a DHT11 attached to A1. It should work fine on a Pi, and you could substitute a ds18 for the dht11 (or 22) as these come in steel cases which let you measure the temperature of liquids and other things you might be interested in.

There is one gotcha though... 10 isn't exactly "giant". I had hoped to use a full strip of NeoPixels. I've run those from the Arduino power pins more or less without problems, but it is on the limit of what is "OK". When I connected the dht11 and the neoPixels I found that the noise introduced into the system by the neoPixels caused erratic readings from the DHT11. I was able to get 10 working reliably. With better smoothing you can probably get a few more, but for best results (and a giant!) thermometer then you'll need to hook up a proper PSU for the neoPixels - this is good practise anyway...


Monday, 7 July 2014

Are you high? (calculating altitude!!!)

Last week I was at a demo of the Engduino. It's an arduino comparable with a whole bunch of extra sensors built in - LEDS, accelerometer, magnetometer, temperature, light sensor, and IR. The big thing they've got going for them is that they have all of that plugged in and working "out of the box". That's a big win  if you're working with inexperienced students, and have a limited time - they don't have to plug anything in except the USB cable! The flip side is that they're pretty expensive, and while they are expandable, hooking up extra hardware is going to be more tricky. As such they're probably great for teaching one off workshops, but for longer term project based teaching, a regular Arduino is probably better...

Anyway - the reason I mention it is that at they explained how at a previous workshop students had been measuring temperature, and logging it/plotting graphs, but then for extra fun they'd gone to the foyer of the building which was 4 stories high, and used a helium balloon to lift one of the boards up, and measure how the temperature changed with height - being indoors, it was (as they predicted) hotter near the top of the building.

I thought that was a fun thing to do, but immediately wanted to take things further - if we want to record the change of temperature in any meaningful way, then we need to know the height. There are two ways to do that - measure the string on the balloon (accurate but slow, and hard to record along with the temperature data), or add an altimeter to the arduino...

It turns out that it's pretty easy to figure out your altitude, within certain limitations. All we need is a BMP180, which will cost you about £1 from our favourite online car boot sale and flea market website. These can measure atmospheric pressure with very high relative accuracy, so they can measure changes in pressure accurate to about 5 Pascals  (1Atmosphere is about 100,000 pascals so that's pretty good percentage error!). At sea level the air pressure drops about 10 Pascals per meter, so if we know the change in pressure we can figure out the change in height. As luck would have it they also include a thermometer so if you want to repeat the Engduino balloon experiment you're good to go.

Hooking up i2c

Hooking up the BMP180 is actually really easy, but can be a bit daunting of you're new to dealing with hardware. It uses i2c (pronounced eye-squared-see) which is simply a way for different electronic components to talk to each other - think of it as USB for chips. The great thing about it is that it makes hooking up lots of hardware really easy once you know what you're doing, but it can be confusing at first.

I2C uses 4 wires. Two of them are power and ground, and the other two  are Data (usually labeled SDA), and Clock (labeled SCL). The clever thing is that you can connect up as many devices as you are likely to need all sharing the same two signal pins (data and clock), which is why I2C is sometimes referred to as TWI (two wire interface). It's able to do this sharing, as most of the devices disconnect themselves from the wires when they're not communicating. The arduino sends a message out to a specific device, then that device turns on, and sends a message back. This is all handled automatically, so you don't really have to worry about it.

What you do need to worry about are voltages, and what happens when all the devices are turned off. This is where it gets confusing, as there are at least 3 ways of doing things - in increasing order of correctness (and complexity). The problem is that the BMP180 is a 3v device, while are Arduino is 5V. Normally we shouldn't mix these, but it turns out that with I2C its OK (ish).

Option 1: wing it hope for the best

BMP Vin to 3V (THIS IS IMPORTANT)
BMP SDA to Uno  A4 (check spec for different Arduino)
BMP SCL to Uno A5 (check spec for different Arduino)
BMP Gnd to Gnd

If we were using a 5V device this would probably be fine (though not ideal). For 3V devices this will work. I've done it. Nothing bad happens. The worst that could happen is that you damage a £1 chip, but you won't. Even then it probably won't blow up or anything. It just might stop working in a few years time. There are a lot of blogs discussion this, and my summary of them would be this is technically incorrect (don't to it in production hardware or assessed projects), but it'll probably work, and no one has actually seen anything bad happen from doing this.

Option 2: cheap and cheerful

Wire as before but add a 4K7 (or 10K) resistor from SDA to 3V, and SCL to 3V. Because of the way I2C works, it gets its positive voltage from these resistors, so the Arduino never sends 5V to our BMP  - instead it just gets the 3V its expecting. As an added bonus 3V is enough (usually) for 5V devices too, so you can connect 5V I2C devices, and 3V I2C devices like this. This is my preferred solution.


I've got a simple breakout board I made from strip board that connects to the sensor shield. The sensor shield breaks out I2C on a 4 pin dupont as 0v,5V,SDL,SCL so soldering a set of 4pin headers makes it easy to connect any number of devices. The three sets of header pins on the left are connected directly for 5V devices like the i2c LCD display. In the centre of the board I break the 5V line, and replace the power with 3V. There are also two 10K pull-ups to 3.3V. These are jumpered just in case I want to remove them. Using this board I can easily connect multiple I2C devices just by plugging them in. About 60% of I2C devices have a pinout which matches this layout, but in the case of the BMP180 I've cut a dupont cable, and resoldered it back together with a couple of twists in so that the pin out matches my board.

Option 3: Do it properly

Option 2 should work fine, and apparently (according to what I've read) is technically within specification. However for about £1 you can buy an i2c level shifter board. These have a chip on them which does all of this stuff properly. If you're building something that's "important", that's going to be sold, or run for a long period of time you should look into these, but today we don't need them.

Lets get on with it

So you've hooked up your BMP180. Now lets code it:
make i2c device
make atmosphere device
make pressure number
make temperature number

when start
.forever
..tell atmosphere to "read"
..say join "Temperature:" [ temperature ]
..say join "Pressure:" [ pressure*0.01 ]
..wait 1 secs

We start by creating an i2c device. We don't use it directly but its needed by the atmosphere device (and all other i2c devices). Then we make an atmosphere device, and the two variables pressure and temperature that its going to use.

To take a reading we just tell the sensor to "read", and it sets the temperature value in Centigrade and the pressure in Pascals (Pa). For historical reasons atmospheric pressure is usually reported in hectoPascals (=100Pa) so we multiply by 0.01 before displaying, and we expect to get a value of around 1000hPa (it could be worse - some backward countries still record pressure in inches!).

Now to figure out the change in pressure, we just record it when we start up, and then we can calculate the difference whenever we need to:

make seaLevelPressure number
make seaLevelTemperature number

when start
.tell atmosphere to "read"
.set seaLevelPressure to pressure
.set seaLevelTemperature to temperature+273.15
.
.forever
..tell atmosphere to "read"
..say join "Temperature:" [ temperature ]
..say join "Pressure:" [ pressure*0.01 ]
..say join "Delta:" [ (pressure-seaLevelPressure)*0.01 ]
..wait 1 secs

I've called the reference pressure sea level, as that's the standard reference point - we assume we start out on the ground, at sea level, though in practise it doesn't really matter. It's just a point to call zero.

So knowing the change in pressure whats the change in altitude? In an ideal world, given enough time and an army of minions class of school children. I might ask them to measure the height of one step on a flight of stairs, count the steps, and calculate the height of each floor in a building, while they measure the pressure at each height. Then get them to come back, and plot a graph, and figure out a linear approximation...

Then I'd google it, and get this equation:


There are a whole bunch of other equations out there, and they should all work fine (and for short distances, linear should be close enough). There are a lot of assumptions about humidity and the way temperature and air density change with height built into this equation, but it doesn't have to be rocket science (unless you're building a rocket - which would be an ideal application for this! in that case it actually is rocket science). Just adding this in should get you accurate to a meter or so, over small height ranges, and if you're changing altitude by more than 100m then you can probably live with less accuracy.

So lets put that together:

make i2c device
make atmosphere device
make pressure number
make temperature number

make altitude number
make seaLevelPressure number
make seaLevelTemperature number

make lcdi2c device
make message string

when start
.tell atmosphere to "read"
.set seaLevelPressure to pressure
.set seaLevelTemperature to temperature+273.15
.
.forever
..tell atmosphere to "read"
..broadcast calculateAltitude and wait
..set message to join "Temperature:" [ temperature ]
..tell lcdi2c to "show"
..set message to join "Pressure:" [ pressure*0.01 ]
..tell lcdi2c to "show"
..set message to join "Delta:" [ (pressure-seaLevelPressure)*0.01 ]
..tell lcdi2c to "show"
..set message to join "Altitude:" [ altitude ]
..tell lcdi2c to "show"
..wait 1 secs



when calculateAltitude
.set altitude to seaLevelPressure/pressure
.set altitude to ln of altitude
.set altitude to altitude * 0.1903
.set altitude to e^ of altitude
.set altitude to seaLevelTemperature * (altitude-1)
.set altitude to altitude/0.0065

As the whole point is to measure changes in height we're going to need to move around, so I've added an LCD device. More specifically I've added an i2C lcd. Since we've already figured out i2c, adding a second i2c device is just a matter of plugging in the 4 wires and off we go.

And by off we go I mean we actually have to get up and walk around now! Plug the Arduino into a battery and its time to go collect some measurements.

 We start out on the ground floor of the Sniff Mansion, and record a baseline pressure. It's a pleasant 21 degrees. There's a change in altitude recorded (17cm) as the sensor only has limited accuracy. We're also recording very small changes in pressure, so over time the pressure will change anyway.
 Moving down to the dungeon (currently closed for refurbishment) we note that not only is it cooler, but that the air pressure has increased by 26Pa. At 10Pa per meter that means we're about 2.5m underground, and the more complex equation confirms this.
 Moving back to the first floor we notice that again we're in a cooler area (the original recording was by the south facing windows, which does get warm). However pressure has conveniently dropped by 38Pa suggesting we're 3.5m higher - the equation gives 3.24m.
There are 15 steps to the first floor of about 21cm or about 3.15m so that's pretty good!
Sniff Labs are in the South tower, where we note its a little hotter again. Preasure has dropped by 72Pa, which is  calculated as 6.15m.

That's 2.9m higher than the first floor, and there are again 15 steps up to the tower. However they're slightly shallower at only 19cm, making  an estimated 2.85m.

That's pretty close! Measureing a step height is probably accurate to about 5% (nearest cm), while both of the results agreed to within 10cm over 3m!


In practise that may be a little optimistic - leaving the arduino sat on my desk, it drifts by about 50cm. However its seems pretty reliable to within 1m. You could improve the accuracy by averaging the samples, but for now the accuracy we've seen is pretty good!

All of this code should run un-changed on a Raspberry Pi if you're so inclined. However the Pi is a bit more tricky to wire up, harder to run off batteries, and much more expensive so an Arduino is a better choice.

This was a really fun experiment to do, and includes lots of good lessons: aside from understanding air pressure, we have to get to grips with the idea of accuracy and precision. We can cross reference different ways of measuring to provide confidence in the two methods, and we can choose an appropriate approximation. All that before we even start to use what we've built. now we can measure altitude we can measure the heights of things for all sorts of cool experiments...

Friday, 4 July 2014

Sniff 101 - the Weather station demo!

It's not much of a claim to say I'm the most experience Sniff programmer! Sniff is a new project, and we're still figuring out what it can do. For example we'll soon be releasing code for the sPhone - a mobile phone written in Sniff on Arduino. You can control equipment by phoning it up or texting it, and have it send out status reports to your phone where ever you are. We think that's pretty exciting, and I can't wait to show that off here...

However, most of you are probably a lot newer to Sniff. While showing off advanced examples is great fun, I thought it would be useful to go back to one of the first things we ever built in Sniff - the first demo where we got really excited, that maybe we were on to something that would make physical computing really accessible.

The weather station project is really easy, but produces great results, and is bizarrely addictive: I had it running on my desk for several months, and was constantly checking in to see what the temperature and humidity was.

We'll assume you've got an Arduino! The only other thing you need is a DHT11 temperature and humidity sensor. These cost £0.99 on ebay. If you're only buying one or two, then I'd recommend you push the boat out and get the "deluxe" DHT22, which costs about £3, as its much more accurate, but the DHT11 works identically, so if you just want to get something working (and need a dozen or so!) then get the cheap ones.

To wire them up, place them on their back (grill holes up), and connect the left pin to 5v, the next pin to the arduino's A1 pin and the right pin to ground. This will leave the third pin unconnected - it doesn't do anything. My preferred way of connecting these (and anything else) is to connect them to a 3pin dupont female header wired as 0v, 5V, signal. (the simplest and cheapest way to do this is to get a female-female cable, and cut it in half).  This is a pretty standard pinout, so you can plug them into breakout boards like the sensor shield, or anything that takes 3pin Grove connectors - You've now build your own equivalent of an £8 grove temperature/humitidy "brick" for about £1.50. 


Plug that into your Arduino - I've chosen to plug it into A1, but any free data pin will work fine. Now we need to code it:

make thermometer dht11 device A1
make humidity number
make temperature number

when start
.forever
..tell thermometer to "read"
..
..say join "Temperature:" [ temperature ]
..say join "Humidity:" [ humidity ]
..

..wait 5 secs

And that's it! First off we make a dht device on pin A1. This is going to use the variables humidity and temperature, so we make these too. Sniff programs are started by broadcasting the start message, so when the arduino powers on, it goes into the forever loop, asking the DHT device to "read". This does the hard work of talking to the hardware, and puts the results into the variables we've prepared.

Then we just print out the results. Say sends the results back to the computer, so you can see them in the Arduino Serial Monitor, or other terminal software (On Pi you can just type "cat </dev/ttyACM0"). Sniff can only print out a single string, so we need to join the label text ("Temperature:") with the result (the variable temperature). However remember Sniff is a typed  language, so the number 35 isn't the same as the string "35". Putting the variable in square brackets is the Sniff notation to say that we want to turn a number into a string.

Once we've printed out the results, we wait 5 seconds - in part this is just so we don't swamp the computer with results (after all air temperature doesn't change that quickly!). However the DHT's have limits on how fast they can measure the air conditions, so 5 seconds is a reasonable waiting time.


That's great but what if we want to actually put this thing somewhere - we don't really want to be carrying around a computer screen! There's an earlier post about all the different display devices you can connect, but the simplest is an LCD/keypad shield. It just plugs in and you're good to go - no wiring required. They cost about £5, so even if you're buying an Arduino, LCD shield and DHT, you can still bring this thing home for less that £15, and all of the parts are reusable for future experiments and projects.


The one disadvantage of the keypad shield is that it covers the Arduino's pins, so they're harder to get to. However it does (usually!) have holes for the pins available, so you can solder on extra header pins to the LCD shield to get access to the Arduino again. However a sensor shield gives you access to the Analog pins, so using them is probably easier (A sensor shield costs about £3, and makes hooking things up a lot easier - especially if you've wired them to a 0v/5v/S dupont header).

To draw something on the LCD we just make an LCD device and tell it to "show" a message:

make lcd device
make message string

when start
.set message to "Hello World"
.tell lcd to "show"

Easy! Now combining that with our earlier code:

make thermometer dht11 device A1
make humidity number
make temperature number

make lcd device
make message string

when start
.forever
..tell dht to "read"
..
..set message to join "Temperature:" [ temperature ]
..tell lcd to "show"
..
..set message to join "Humidity:" [ humidity ]
..tell lcd to "show"
..
..wait 5 secs
A functioning weather station in a few lines of simple Sniff code. You can use this as a jumping off point for all sorts of bigger projects. You could add an SD card, and log data to that. You could add a tft screen and plot the temperature over time (and calculate the dew point, and plot that too!). Adding Ethernet and you could share the data on a website.

You can also easily record more types of data - the BMP180 air pressure sensor (which also has a thermometer build in) is only a couple of pounds, and connects directly. Similarly Maplin sell an anemometer to record wind speed, and a rainfall meter for about £3 each. Both of these are easy to integrate.

Right now its 21 degrees and 35% humidity - that means its a nice warm, sunny day!

Thursday, 3 July 2014

Text Adventures (Part 2)

A few months ago, I wrote about how text adventures could be used to make maps, walk around them, and link computing into a range of project based activities. I introduced SAE - the Scratch Adventure Engine, and explained how you could make use it to implement a simple world with objects.

The map for the SAE demo.
(The woods in the top right are surprisingly confusing)


However one of the great things about text adventures is that a lot of the "puzzles" come down to that basic computer science concept: IF (I have item X) AND (I'm in location Y) THEN (make Z happen). That means that once you've figured out how a problem works in the game, its pretty easy to translate it into code. But before we can put any puzzles into the game, we need a basic understanding of how the engine works.

SAE is pretty long for a Sniff program - about 300 lines. However its pretty simple if you break it down. It all starts with the "when start"!

when start
.set currentRoom to 1
.broadcast setupObjects and wait
.broadcast doLook and wait
.forever
..say ""
..ask "What now?" and wait
..broadcast doAction and wait


I'm a big fan of "programming in the problem domain" - what that means is that your code at the top level should be about things in the game. Even someone who has never programmed, should be able to see what's going on here: we set things up and then loop forever, asking what to do, then doing it.

when doAction
.broadcast splitAnswer and wait
.
.if verb = "look" or verb="l"
..broadcast doLook and wait
..stop script
.
.if verb = "take" or verb="get" or verb="pick"
..broadcast doTake and wait
..stop script


doAction is the longest script in the game, as it needs to understand all the different commands. However most of this is just repeating the same kind of code over and over. Game commands are typically in the form of a "verb" followed by a "noun", as in "get lamp", so the first thing doAction does is split "answer' into a verb (the first word), and a noun (the last word) - this also conveniently handles things like "fight the big dragon".

Another trick in doAction is that we probably want to be able to move around by just entering directions, so the game considers "north" to be a verb. To make this work when we actually enter "go north", we recognise the verb "go" and when we see it we replace the verb with the direction! It's a bit of a hack, but it makes the code a lot easier. doAction then just does a whole load of test, and runs the appropriate script.

.if verb = "go"
..set verb to noun
..set noun to ""
.
.if verb = "north" or verb="n"
..set direction to 1
..broadcast doMove and wait
..stop script
.


To move around we set a direction then call the doMove script:

make direction number
when doMove
.set fileData to join join "rooms/" [ currentRoom ] ".dat"
.tell nativeFile to "startRead"
.if not fileOK
..say "Can't find room data"
..stop script
.
.repeat direction
..tell nativeFile to "readString"
.tell nativeFile to "endRead"
.
.if value of fileData = 0
..say "You can't got that way"
.else
..set currentRoom to value of fileData
..broadcast doLook and wait

In the last post I explained that theres a "dat" file for each room which contains the exits. We read that in, and read in lines till we get to the write one. However remember that we've read in a string, and one of the differences between Scratch and Sniff is that Sniff treats numbers and strings differently, so we need to convert that to a number, using "value of". If the value is 0 that means we can't go that way, other wise we move to that room, and call the doLook script.

when doLook
.set fileData to join join "rooms/" [ currentRoom ] ".txt"
.tell nativeFile to "startRead"
.repeat until not fileOK
..tell nativeFile to "readString"
..if fileOK
...say fileData
.tell nativeFile to "endRead"
.
.set counter to 1
.repeat until counter > length of objectLocations
..if item counter of objectLocations = currentRoom
...say join "You can see a " item counter of objectNames
..change counter by 1

The look script starts by printing out the current rooms description from the text file. However the interesting part is the second part where it looks for objects that are in the room. Objects are held in two lists - one holding their names, the other holding their locations. Here we go through the locations lists and check if the objects location is the current room - in which case we print out that the object is visible.

The "inventory" command works the same way, but checks location 0, as this is used to indicate that the player is carrying an object.

And finally we have any number of further scripts which implement a single command (and get called from doAction). For example if we consider doTake:

when doTake
.broadcast findNounNum and wait
.if nounNumber = 0
..stop script
.if not currentRoom = item nounNumber of objectLocations
..say "I can't see that here"
..stop script
.
.#############
.if noun = "dragon"
..say "He looks a little too heavy to carry!"
..stop script
.#############
.
.say join "You take the " noun
.replace item nounNumber of objectLocations with 0


Most of the commands act on an object. We know its name, but we're probably going to need its number, so findNounNum does this for us. It prints a message and returns 0 if the object is unknown, so  this is the first thing we check.

If we're going to "take" an object it has to be in the current location, so that's the next thing we check. If everything is Ok, then we print a message and set the objects location to 0, to show that we're carrying it.

However we're now more or less ready to start thinking about game logic. This is stuff that's specific to the game. The standard engine (adv. sniff) contains some basic logic, to illustrate how it works, and also because it will probably come in handy. Here we note that dragons are (generally) heavy and you probably don't want to pick them up, so we simply check if the noun is "dragon" and if it is then we print out a suitable message, and stop the script (so we don't pick him up).

make lampIsOn boolean

when doLight
.broadcast findNounNum and wait
.if nounNumber = 0
..stop script
.if not nounAvailable
..say "You can't see that."
..stop script
.
.if noun="lamp"
..say "Done! That'll make it easier to see things in dark places"
..set lampIsOn to yes
..stop script
.
.say "That doesn't really make sense..."

The other main piece of game logic implemented in the default engine is a lamp - there's always a lamp! There's a boolean variable lampIsOn - these hold either yes or no, and when the game starts up the value is set to no. To light the lamp we use the "light" command, and like doTake is uses findNounNum to check that the thing you're trying to light is a valid object. findNounNum also sets up a variable nounAvailable which checks if the chosen object is either in the current location or being carried by the player. Then we simply check if the object is the lamp, and if it is then we set lampIsOn to be yes.

There's some slightly more complex game logic in the file demo.sniff, which is specific to the demo game. In the doLook script there's an extra test:

.##############################################
.#it's too dark to see at the bottom of the well so check we have the lamp
.if currentRoom = 4
..if lampIsOn and (item 1 of objectLocations=4 or item 1 of objectLocations=0)
...say "That lamp is really helping"
..else
...say "It's really dark in here"
...stop script
.##############################################


Room 4 is the bottom of the well. If we have the lamp, and its turned on, then we say something about how useful the lamp is, and then continue to display the normal room description.  However if we're in that room and we don't have a lit lamp, then we say that its really dark. There's a sword hidden at the bottom of the well, but you can't see it until you have the lamp with you.


Some slightly more complex game logic in the demo concerns the dragon. He lives in location 3, but won't let you go south from there. In the doMove script we have:

.####
.if currentRoom=3 and dragonIsEnemy and direction=3
..say "The dragon is in the way, and looks as if he doesn't want disturbed"
..stop script


.####

This is at the beginning of the doMove script, so stops anything happening. However there's some additional logic at the end of doMove, which means it happens as you enter a room:

.if currentRoom=3 and dragonIsEnemy and item 2 of objectLocations = 0
..say "The dragon gets excited!"
..say "'You've found my sword! Thank you so much!"
..say "The dragon takes the sword and runs off into the woods"
..replace item 2 of objectLocations with -1
..replace item 3 of objectLocations with -1
..set dragonIsEnemy to no

If we've just entered room 3, and the dragon is still an enemy, and we have the sword (item 2), then the dragon takes the sword and runs away with it (we set the location to -1 as we can never get to location -1, removing the object from the game). Now when we try to move south it'll work fine.


While I haven't gone through every script in the engine, most of the rest follow a similar pattern. It should be fairly straight forwards to add new commands, and add hooks into existing ones to make special stuff happen.

The code for adv. sniff (a generic version of the code), and demo.sniff (with a few simple game hooks in specific to the demo world) can be downloaded from the previous blog post.