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, 12 August 2014

Gamebuino

Earlier this year Aurelien Rodot launched and IndieGoGo campaign so he could build 100 hand-held mini-console's inspired by the original gameboy, but powered by an Arduino. By the time the campaign finished, he was committed to build 1000! He spent over a month assembling and shipping them!!!

The result is one of the best Arduino's since the Uno. While the fixed (limited) hardware spec might seem at odds with the DIY principles, having a single device to program for makes sharing projects massively easier. and makes it a great device if you want to write games in Sniff.  We took the Gamebuino and our Bounce Out game to Scratch@MIT2014, and the reaction was literally "where can I get one of these?". Unfortunatly right now you can't - the original campaign is over and Aureleien is too burnt out to build any more just yet, but he has hinted that more might be made available in the future. Of course you could build your own (I'm working on a project to build something based on the design), as it uses completely standard components - it just won't look as nice.

The Gamebuino has 8 buttons (connected directly to digital inputs), a light dependant resistor (to control the backlight), a speaker, an SD card, a small lipo battery/charger and a Nokia 5110 screen (with controllable backlight). All of those are easily supported in Sniff, so it only took a few hours to get everything running sweetly. As of Release 8 you can compile Sniff for Gamebuino using the gb-sniff command.

Here's a walk through of the code for our first Gamebuino game "Bounce Out", which is basically Breakout with gravity!

video

To handle all the buttons you can use the Gamebuino device.

make gamebuino device
make buttonA boolean
make buttonB boolean
make buttonC boolean
make buttonUp boolean
make buttonDown boolean
make buttonLeft boolean
make buttonRight boolean
make batteryLevel number
make lightLevel number

when start
.forever
..tell gamebuino to "checkButtons"
..wait 0.05 secs

make quitTimer number

#backlight and reset
when start
.forever
..wait until buttonC
..set lightLevel to 1-lightLevel
..tell gamebuino to "setBackLight"
..set quitTimer to timer
..repeat until not buttonC
...if timer-quitTimer > 1
....tell gamebuino to "quit"

Here we've set up a loop which is going to keep checking the buttons regularly so we can just use the values elsewhere. ButtonC is the yellow button that's normally used to quit a game. We wait until the button is pressed, and toggle the backlight on or off. We then wait until the button is released - if we wait more than a second, then we bail out of the game, back to the loader (which is one of the smartest boot loaders in Arduino-land: it can load hex files from the SD card!). Because Sniff supports doing multiple things at the same time we can just leave this running, so we can turn the backlight on or off, and exit the game regardless of the rest of the code.

make spi device
make display nokia device
make displayX number
make displayY number
make displayColor number
make displayFlush boolean
make message string

when start
.set displayFlush to no
.forever
..set displayColor to 0
..tell display to "clear"
..
..set displayColor to 777
..set displayX to 16
..set displayY to 30
..set message to "Bounce Out!"
..tell display to "show"
..set displayX to 5
..set displayY to 10
..set message to "Hi Score:"
..tell display to "show"
..set displayX to 50
..set message to [ hiScore ]
..tell display to "show"
..tell display to "flush"
..
..wait until not buttonA
..wait until buttonA
..
..broadcast play and wait
..
..if score > hiScore
...set hiScore to score
..
..set displayY to 20
..repeat 10
...set displayX to 14
...tell display to "move"
...set displayX to 66
...tell display to "hfill"
...change displayY by 1
..set displayX to 16
..set displayY to 21
..set displayColor to 0
..set message to "Game Over"
..tell display to "show"
..tell display to "flush"
..
..wait until not buttonA
..wait until buttonA

That looks like a lot of code, but its all really simple. We make a Nokia display device, and then draw a welcome screen. We wait until buttonA is pressed, and then launch the main game loop by broadcasting play. When that's done we check if its a new high score, and display the "game over" message.

If you've used the Nokia (or other) Sniff graphics screens before you'll notice we've added something new: When you're writing games, its useful to have what's known as "double buffering" - we draw on a hidden screen, updating all the separate bits, then tell the display to display everything all at once. This reduces flickering and makes it possible to generate smooth animation. We've therefore updated the Nokia display to support this kind of hidden drawing. By default all drawing is immediately sent to the screen, but the line ".set displayFlush to no" turns this off, so no drawing appears until we tell the display to flush. By default all drawing is flushed immediately so you don't need to worry about it, but to get fast updates for games like this you should display auto-flushing, and send everything at the end of the frame. This is currently only supported on the Nokia device, but it works really well, so it should start appearing in other displays where it makes sense.

The ability of Sniff (and Scratch) to do multiple things at the same time is incredibly useful. We can move the bat by just adding:
#move the bat
when start
.forever
..set batXVel to batXVel*0.5
..if buttonRight and batXPos < 78
...change batXPos by 2
...change batXVel by 0.5
..if buttonLeft and batXPos > 5
...change batXPos by -2
...change batXVel by -0.5
..wait 0.05 secs

We use the same trick to separate the drawing into a separate "refresh" script, so our main game code lives in the play script, but never has to worry about drawing! At the beginning of the play script we broadcast refresh, to start the main drawing code. The refresh runs until the variable refreshStop is set to YES. It then sets it to NO.

when refresh
.repeat until refreshStop
..#DO LOTS OF DRAWING HERE
..#ITS BORING SO IVE NOT INCLUDED IT
.set refreshStop to no

The actually drawing is just lots of moves and draws, so I've missed it out. You can see at the end of the play method it sets refreshStop to be YES, then waits for it to become NO, so that it tells refresh to stop, and waits until it actually has stopped.

when play
.set score to 0
.set level to 1
.set batXPos to pick random 10 to 73
.set ballXPos to pick random 10 to 73
.set ballYPos to 40
.set ballXVel to 0
.set ballYVel to 0
.set boostAvailable to no
.delete all of blockData
.broadcast refresh
.repeat until ballYPos < 0
..repeat level
...add pick random 3 to 6 to blockData #Size
...add pick random 5 to 78 to blockData #X
...add pick random 10 to 30 to blockData #Y
..repeat until length of blockData = 0 or ballYPos < 0
...#bounce off BAT
...if ballYPos+ballYVel < 2
....if ballYVel>-1.5
.....set boostAvailable to yes
....if abs of (ballXPos-batXPos)<6
.....if buttonA and boostAvailable
......set ballYVel to 2.5
......set boostAvailable to no
.....else
......set ballYVel to -ballYVel*0.9
.....set ballXVel to ballXVel*0.8+batXVel
.....change score by 0.01*((abs of batXVel) + (abs of ballXVel))+0.001 
...
...#BOUNCE OFF SIDE WALLS
...if ballXPos+ballXVel<0 or ballXPos+ballXVel>82
....set ballXVel to -ballXVel*0.8
...
...#BLOCLKS
...set pCounter to 1
...repeat until pCounter >length of blockData
....set size to item pCounter of blockData
....if abs of ((ballXPos+ballXVel) - item pCounter+1 of blockData)<size
.....if abs of ((ballYPos+ballYVel) - item pCounter+2 of blockData)<size
......#WE HAVE A HIT
......if not abs of (ballXPos - item pCounter+1 of blockData)<size
.......set ballXVel to -ballXVel*0.8
......if not abs of (ballYPos - item pCounter+2 of blockData)<size
.......set ballYVel to -ballYVel*0.8
......delete item pCounter of blockData
......delete item pCounter of blockData
......delete item pCounter of blockData
......change score by 0.1
....change pCounter by 3
...
...change ballXPos by ballXVel
...change ballYPos by ballYVel
...change ballYVel by -0.1
...wait 0.05 secs
..
..if length of blockData = 0
...change level by 1
...change score by 1
.
.set refreshStop to yes
.wait until not refreshStop

There's probably too much here to go through in detail, but basically we set up the game in its initial state, and load up the blocks list with the size, and position of each block. Originally we had three lists, but that used too much memory, so we use one list to store all of the block info.

The core loop repeats until we either destroy all the blocks, or loose the ball. The main loop has to check if the ball is about to hit anything, and if it does it reverses the balls velocity. At the end of the loop it changes the balls position by its velocity. It also changes the balls Y velocity by -0.1: this is what gives us gravity.

And that's it. Bounce Out is 290 lines of code which is pretty large. What's amazing is that it runs really smoothly on the AVR, and the display refresh is really smooth. Sniff's parallelism turns out to be really great for coding up simple games as we can just handle each aspect of the game by itself, and let the interactions take care of themselves. I was able to get the game up and running in a couple of hours, and then a couple more hours of tweaking to get just right. Writing it was actually a big learning experience - it felt a bit like being a test pilot (without the fear of imminent death aspect). We've built our aircraft, and think you know what it can do, but you have to fly it for the first time, to learn how it really handles. You wouldn't write a game in this style of code in any other language, so writing Bounce Out really was a lesson in programming Sniff.


Here's the full Source Code, or you can download a compiled hex file. These are slightly different from the version that is in the current Sniff release, as a few things were tidied up, but here are functional differences. I've deliberately not talked about the sound library (which is used by BounceOut to make simple beeps), as its a whole post by itself.

No comments:

Post a Comment