It is time for our annual Halloween project. This time, it’s a game with Head the Skull and Ron the Pumpkin, in a race to detect the spooky particles that are emitted when they collide at speed. Just like many of the world’s colliders, it works by sending Head one way round a ring of accelerators, and Ron in the opposite direction. All you have to do is to press the foot switch at the instant they pass each other, once they have accelerated up to full speed. You know when they are up to speed because a speed-monitoring LED at the centre of the collider changes from red to green.
The Head 'n Ron collider project first appeared in The MagPi #62 and was written by Mike Cook.
Halloween Head 'n Ron collider: The circuit
We used two very cheap sets of string lights, obtained from our local pound shop, as shown in Figure 1.
Surprisingly, they each cost a pound. The LEDs are wired in parallel, and utilise the internal resistance of the batteries to keep the LED current in check. In this project, we are going to control them from the GPIO ports of the Raspberry Pi, and provide a real resistor in series with each LED. This project uses all but one of the GPIO pins on the connector, including the I2C HAT identification pins. The LEDs themselves are rather interesting, as they are designed with a 360-degree viewing angle. They use a cone-shaped indent which acts as a reflecting surface, spreading the light all round, as shown in Figure 2.
The schematic for our game is shown in Figure 3, and is pretty straightforward, using LEDs and a switch. The common cathode tri-colour LED is simply a red and a green LED in one package. As there is no data sheet for the string light LEDs, we had to experiment to find out which resistor values produced which levels of current. The forward voltage of the LEDs is close to the 3V3 of the GPIO pins, so we only need low-value resistors to run the white and orange LEDs at 5 mA and 4 mA respectively.
At a cursory glance, you might think that we are overloading the current capability of the GPIO pins (51 mA in total), but the project will only illuminate one LED at a time for the Head and Ron groups, plus one of the two speed LEDs. We could have opted for full buffering and driven the LEDs at 5 V, along with a much higher current, but in the end we chose the simplest and cheapest option. If you want to develop other games with more LEDs, we would recommend using a buffer, such as the ULN2003A.
The construction of the project is shown in the steps, and takes a bit longer than you might think.
Halloween game for Raspberry Pi
The game takes its inspiration from other forms of large colliders. The two streams go round in opposite directions. When they meet, they are diverted into the same path, and they collide. You have a foot switch to press when the two streams are at the same point.
Watch out – with 12 positions per circle, and moving at the same speed, there may never be a time when the two streams are at the same point. The trick is to make them move at different rates. What’s more, it takes time for the streams to accelerate to a colliding speed, and this is signalled when the central LED turns from red to green. If you press the foot switch at the right time, as shown in Figure 4, the screech of fundamental spooky particles is released, and the collider starts again – only this time it goes faster! Once you miss, the round ends, and you’ll be given a score based on the number of correct hits.
Halloween project for Raspberry Pi: Head 'n ron software
The software uses the Pygame framework to produce the sound. Make a directory called Sounds in the same directory as the program, and copy into it the files Screech.wav and Spiral.wav from the directory /
usr/share/scratch/Media/Sounds/Electronic
Most of the action is controlled by the playRound function, which contains a state machine that decides which (if any) of the streams to move, based on the elapsed time clock. It returns a true or false signal to indicate whether or not the round is over. The variables rInterval and hInterval control the speed of each stream, and the variable speedThreshold determines when they reach a speed suitable for collision. Altering the speedInc variable controls how quickly the streams accelerate up to speed.
Taking it further
You can adjust the variable controlling the final speed of the game, but at its fastest it is quite tricky. The initial slow start ensures that everyone can manage at least one hit. You could add extra sound effects, such a clunky whine as the accelerator ramps up, or a spooky cackle if you miss.
Making the large Head ‘n Ron collider
>step-01: Making the board
Take a piece of 6 mm thick MDF, 330×330 mm in size. Drill a hole in the centre and mark two circles of 150 mm and 120 mm radius, centred on the same point. We made a strip of Perspex with three holes, and used a screw in the centre and a pencil in each hole to draw the circles. Then we then used a 30-degree set square and a ruler to mark the position of the mounting holes, and drilled them with a 3 mm drill bit. Next, we glued on 20 mm stripboard to make a tray, painted it all with a flat black paint, and filed a slot on the right-hand side for the foot switch cable. Finally, we filed a slot on the back edge, near position 10, for the 40-way ribbon cable.
>step-02: Prepare the LEDs
Separate the LEDs by starting at the end of the string and snipping off the two wires, close to the next LED. Repeat this until all the LEDs have two long and two very short wires coming from them. Identify the anode and cathode by applying a voltage (3 to 5 V) through a 470R resistor. The cathode will be the one at the negative terminal when the LED lights up. Identify the cathode with a blob of correcting fluid, or by making the wire 5 mm shorter than the anode. Finally, trim off the shrink insulation from the sides to reveal the two short stubs of wire. Cut these as short as you can so that they are covered by the insulation. This should leave two longer wires.
>step-03: Mounting the LED shells
Separate the two halves of the plastic skull and pumpkin LED covers. Drill a 3 mm hole in the base of the back half of each cover, and attach each one to the top of the box using a 10 mm M3 screw and nut. For positions 9 and 11, use a 10 mm threaded pillar instead of a nut, as these will act as a mounting for the stripboard. Drill a 5 mm hole in front of each half shell, as close to it as you can, and insert an LED of the appropriate colour. Fix the LEDs in place with hot-melt glue from the underside of the board. Finally, cut holes in the bases of the remaining front-half covers to fit around the LEDs, and clip the front and back of each cover back together.
>step-04: Wiring it up
Mark the positions (Head 1–12 and Ron 1–12) on the underside with a pencil so you can identify the LEDs. Wire the GPIO connectors to the appropriate LEDs through the appropriate resistors. We used surface-mount resistors on the back of the stripboard, mainly because we had them in stock at the right value – there is no advantage to using surface-mount resistors over through-hole resistors here. Don’t forget to add the jack for the foot pedal as well – it doesn’t matter which way around you attach it to GPIO 2. Use the threaded pillars to attach the stripboard to the underside of the game, and the ribbon cable to attach the game to the Raspberry Pi. Do not underestimate the length of time it will take to complete this step – it took us over four hours, including using heat-shrink sleeving to extend the LED wires.
import pygame,time, random import RPi.GPIO as io pygame.init() pygame.mixer.quit() pygame.mixer.init(frequency=22050, size=-16, channels=2, buffer=512) random.seed() def main(): global score, rightSpeed, speedThreshold, hInterval, rInterval global countR, countH print("Head n Ron collider game") print("Ctrl C to quit") init() while 1: clr() countR = random.randint(0,11) countH = random.randint(0,11) score = 0 hInterval = 2.0 ; rInterval = 1.5 speedThreshold = 1.0 io.output(speed[0],1) io.output(speed[1],0) rightSpeed = False while playRound(): # keep going until you miss pass miss.play() print("Score",score) a = input("Press return to play again") def playRound(): global countH, countR, lastRon, lastHead, overlap, score global rightSpeed, rInterval, hInterval, speedThreshold done = False if time.time() - lastRon > rInterval: countR = moveR(countR) lastRon = time.time() if countR == countH: if io.input(2) ==1 : overlap = True if not rightSpeed and rInterval > speedInc: rInterval -= speedInc hInterval -= speedInc if rInterval < speedThreshold: io.output(speed[0],0) io.output(speed[1],1) rightSpeed = True if time.time() - lastHead > hInterval: countH = moveH(countH) lastHead = time.time() if countR == countH: if io.input(2) ==1 : overlap = True if countR != countH and overlap: # have they passed overlap = False if io.input(2) ==0 : if overlap and rightSpeed: print("bang") bang.play() while pygame.mixer.get_busy(): pass overlap = False # make go faster if speedThreshold > speedInc: speedThreshold -= 0.1 #change colliding speed hInterval = 2.0 ; rInterval = 1.5 score +=1 io.output(speed[0],1) io.output(speed[1],0) rightSpeed = False time.sleep(0.8) else: if not rightSpeed: print("not going fast enough") else: print("not in the right position") done = True return not done def moveH(count): io.output(heads[count],0) count += 1 if count > 11: count = 0 io.output(heads[count],1) return count def moveR(count): io.output(rons[count],0) count -= 1 if count < 0: count = 11 io.output(rons[count],1) return count def init(): global heads, rons, speed, hInterval, rInterval global countH, countR, lastRon, lastHead, overlap global rightSpeed, speedInc, speedThreshold, bang, miss countH = 4 ; countR = 0 lastRon = time.time() lastHead = time.time() overlap = False ; rightSpeed = False heads = [14,15,18,23,24,25,8,7,12,16,20,21] rons = [3,4,17,27,22,10,9,11,5,6,13,19] speed = [0,1] #speed LED hInterval = 2.0 ; rInterval = 1.5 speedInc = 0.1 speedThreshold = 1.0 io.setwarnings(False) io.setmode(io.BCM) io.setup(heads, io.OUT) io.setup(rons, io.OUT) io.setup(speed, io.OUT) io.setup(2, io.IN) # foot switch bang = pygame.mixer.Sound("sounds/Screech.wav") miss = pygame.mixer.Sound("sounds/Spiral.wav") def clr(): for led in range(0,12): io.output(heads[led],0) io.output(rons[led],0) io.output(speed[0],0) io.output(speed[1],0) # Main program logic: if __name__ == '__main__': try: main() finally: clr() pygame.mixer.quit() pygame.quit() # close pygame