As I make progress through my bottom-up designed game, it is now time to look at how the player will respond to the game by repeating the light sequence. In the game console, the four light regions are physical buttons that the user can press. The closest approximation to that I could come up with on the Circuit Playground Express board is to use the capacitive touch sensors.
This board comes with seven capacitive touch sensors, labeled A1
through A7
. You can actually see the labels printed on the board itself. You may notice that there is also an A0
pad, but unfortunately that one is not a capacitive touch pad.
The following diagram shows how I'm going to map these seven touch sensors to the four light regions in the game:
You can see that the yellow color is at a disadvantage compared to the other regions because it only has one touch pad versus two pads each for the others. Unfortunately this is something that cannot be helped, because the design of this board has an asymmetric distribution of touch pads. If you feel it is more important to have perfectly symmetrical inputs, then you can just not use pads A3
, A4
and A7
, which would leave all regions with a single touch pad positioned exactly as the yellow one.
The cpx
module provides support for checking the state of each pad. For example, to check the state of pad A1
, you can use the expression cpx.touch_A1
. This is easily tested in a REPL session:
>>> from adafruit_circuitplayground.express import cpx >>> cpx.touch_A1 False
A value of False
indicates that the pad in question is currently not being touched. Now go ahead and touch the A1
pad with a finger and then repeat the above statement:
>>> from adafruit_circuitplayground.express import cpx >>> cpx.touch_A1 True
Back when I needed to map region numbers to colors or to sounds, I used lists. Now I need to map capacitive touch pads to region numbers, which is a little bit different. Since the pads have names and not numbers, this time I'm going to specify this mapping using a Python dictionary:
PAD_REGION = { 'A1': 0, # yellow region 'A2': 2, # red region 'A3': 2, # red region 'A4': 3, # green region 'A5': 3, # green region 'A6': 1, # blue region 'A7': 1, # blue region }
With this data structure, once I know which pad is being touched, I can easily find the region number it corresponds to. I can now construct a function, which I'm going to call read_region()
that returns the number of the region being touched by the player, or None
if none of the pads are touched. Here is a first implementation of this function:
def read_region(): if cpx.touch_A1: return PAD_REGION['A1'] elif cpx.touch_A2: return PAD_REGION['A2'] elif cpx.touch_A3: return PAD_REGION['A3'] elif cpx.touch_A4: return PAD_REGION['A4'] elif cpx.touch_A5: return PAD_REGION['A5'] elif cpx.touch_A6: return PAD_REGION['A6'] elif cpx.touch_A7: return PAD_REGION['A7']
Simple, right? Now I can replace the light demo code at the bottom of code.py with a slightly more interesting piece of code that waits for the user to touch one of the pads, and then lights the appropriate region:
while True: region = read_region() if region is not None: light_region(region)
This simple code snippet shows how powerful bottom-up development is. Since all the basic blocks of this game have now been implemented, it takes just four lines of code to make the board respond to user input in a similar way to the Simon game!
The read_region()
function is currently implemented in a very simplistic way, it just returns the region being touched or None
, which means that it does not wait for the player to touch a pad. An interesting improvement would be to have this function wait for the player to select a pad for a given amount of time. This can be accomplished by wrapping the chain of if statements that do the pad checking logic in a while-loop, and having the loop exit only after the specified time has passed without the player having chosen a region:
def read_region(timeout=30): val = 0 start_time = time.time() while time.time() - start_time < timeout: if cpx.touch_A1: val = PAD_REGION['A1'] elif cpx.touch_A2: val = PAD_REGION['A2'] elif cpx.touch_A3: val = PAD_REGION['A3'] elif cpx.touch_A4: val = PAD_REGION['A4'] elif cpx.touch_A5: val = PAD_REGION['A5'] elif cpx.touch_A6: val = PAD_REGION['A6'] elif cpx.touch_A7: val = PAD_REGION['A7'] return val
Here I have expanded the function to take a timeout
argument which specifies for how many seconds the function should wait for user input, with a default of 30 seconds. Inside the function I use the time.time()
function to get the current time in seconds, and then on each loop iteration I check how many seconds have passed by substracting that time from an updated reading of time.time()
. If this difference is larger than the requested timeout, then the loop exits, and the function returns None
. But if, on the other side, the player touches one of the pads, then the function immediately returns the region touched.
The benefit of adding this loop is that now the game logic (that I have yet to write!) does not need to get complicated with keeping track of time while it waits for player input. With this solution, if read_region()
returns None
it means that the player took too long to answer and has lost the game.
To make sure that you have the correct code, below you can see a complete copy of code.py with all the work done so far:
import time from adafruit_circuitplayground.express import cpx cpx.pixels.brightness = 0.1 # adjust NeoPixel brightness to your liking REGION_LEDS = ( (5, 6, 7), # yellow region (2, 3, 4), # blue region (7, 8, 9), # red region (0, 1, 2), # green region ) REGION_COLOR = ( (255, 255, 0), # yellow region (0, 0, 255), # blue region (255, 0, 0), # red region (0, 255, 0), # green region ) REGION_TONE = ( 252, # yellow region 209, # blue region 310, # red region 415, # green region ) PAD_REGION = { 'A1': 0, # yellow region 'A2': 2, # red region 'A3': 2, # red region 'A4': 3, # green region 'A5': 3, # green region 'A6': 1, # blue region 'A7': 1, # blue region } def light_region(region, duration=1): # turn the LEDs for the selected region on for led in REGION_LEDS[region]: cpx.pixels[led] = REGION_COLOR[region] # play a tone for the selected region cpx.start_tone(REGION_TONE[region]); # wait the requested amount of time time.sleep(duration) # stop the tone cpx.stop_tone() # turn the LEDs for the selected region off for led in REGION_LEDS[region]: cpx.pixels[led] = (0, 0, 0) def read_region(timeout=30): val = 0 start_time = time.time() while time.time() - start_time < timeout: if cpx.touch_A1: val = PAD_REGION['A1'] elif cpx.touch_A2: val = PAD_REGION['A2'] elif cpx.touch_A3: val = PAD_REGION['A3'] elif cpx.touch_A4: val = PAD_REGION['A4'] elif cpx.touch_A5: val = PAD_REGION['A5'] elif cpx.touch_A6: val = PAD_REGION['A6'] elif cpx.touch_A7: val = PAD_REGION['A7'] return val while True: region = read_region() if region is not None: light_region(region)
Page last edited March 08, 2024
Text editor powered by tinymce.