Import the Libraries
First, the CircuitPython libraries are imported. The adafruit_led_animation library is being used as a way to easily access different colors for the NeoPixels.
import time import random import board import neopixel import digitalio import adafruit_led_animation.color as color
Next, the button's pin is setup.
# button pin setup button = digitalio.DigitalInOut(board.D5) button.direction = digitalio.Direction.INPUT button.pull = digitalio.Pull.UP
Followed by the NeoPixel setup.
# neopixel setup pixel_pin = board.D6 num_pixels = 61 pixels = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.2, auto_write=False)
Three functions are brought in to continue the NeoPixel setup. All three of them are classic NeoPixel animations: rainbow_cycle
and color_chase
.
# wheel and rainbow_cycle setup def wheel(pos): if pos < 0 or pos > 255: return (0, 0, 0) if pos < 85: return (255 - pos * 3, pos * 3, 0) if pos < 170: pos -= 85 return (0, 255 - pos * 3, pos * 3) pos -= 170 return (pos * 3, 0, 255 - pos * 3) def rainbow_cycle(wait): for j in range(255): for i in range(num_pixels): rc_index = (i * 256 // 10) + j pixels[i] = wheel(rc_index & 255) pixels.show() time.sleep(wait) # color_chase setup def color_chase(c, wait): for i in range(num_pixels): pixels[i] = c time.sleep(wait) pixels.show() time.sleep(0.5)
These animation functions are followed by the game_over()
function. This function allows for the NeoPixel strip to use color_chase
to turn off the NeoPixels and then blink the strip red when you lose a level in the game. The reason for setting it up as a function rather than in the loop
is to keep the loop easier to read since there will be other things going on.
# function to blink the neopixels when you lose def game_over(): color_chase(color.BLACK, 0.05) pixels.fill(color.RED) pixels.show() time.sleep(0.5) pixels.fill(color.BLACK) pixels.show() time.sleep(0.5) pixels.fill(color.RED) pixels.show() time.sleep(0.5) pixels.fill(color.BLACK) pixels.show() time.sleep(0.5) pixels.fill(color.RED) pixels.show() time.sleep(1)
Next are the variables and state machines that will be used in the loop. Their functions are commented next to them.
# variables and states pixel = 0 # time.monotonic() holder num = 0 # chaser NeoPixel position last_num = 0 # previous chaser position now_color = 0 # chaser NeoPixel color next_color = 1 # target NeoPixel color speed = 0.1 # default speed for chaser level = 0.005 # speed increase increment final_level = 0.001 # final level speed new_target = True # state to denote a new level button_state = False # button debouncing state
The last piece of setup before the loop is the NeoPixel colors. Using the adafruit_led_animation library, you can insert colors easily to assign to the NeoPixels without having to determine RGB values.
This array of colors will be used to cycle through colors as you advance through the game, using now_color
and next_color
to index your position in the array. now_color
will be the color of the chaser pixel and next_color
will be the color of the target pixel.
# neopixel colors colors = [color.RED, color.ORANGE, color.YELLOW, color.GREEN, color.TEAL, color.CYAN, color.BLUE, color.PURPLE, color.MAGENTA, color.GOLD, color.AQUA, color.PINK]
The loop begins with an if
statement for button debouncing.
# button debouncing if not button.value and not button_state: button_state = True
x
, y
and z
hold the target pixel positions. y
is setup to hold a random integer within the range of 5
and 55
. This position is reset every time you advance a level. x
and z
are setup to be on either side of y
.
x
and z
are setup to be white to highlight the target pixel, which is setup to be the next_color
index in the colors
array.
# if new level starting.. if new_target: # randomize target location y = int(random.randint(5, 55)) x = int(y - 1) z = int(y + 1) new_target = False print(x, y, z) pixels[x] = color.WHITE pixels[y] = colors[next_color] pixels[z] = color.WHITE
Game play begins using time.monotonic()
instead of time.sleep()
to delay the loop. time.sleep()
delays the entire loop, where as when you use time.monotonic()
, the timing is tracked without stopping the entire loop.
# delay without time.sleep() if (pixel + speed) < time.monotonic():
Before getting into hitting pressing the button to hit the target NeoPixel, there are a few things that need to happen in the code in order for the chaser pixel to move.
First, when the chaser pixel (tracked with num
) moves forward, the previous pixel is turned off. This previous pixel is tracked with last_num
.
# turn off pixel behind chaser if num > 0: last_num = num - 1 pixels[last_num] = color.BLACK pixels.show()
You also want to keep the target pixels their preset colors, even as the chaser pixel passes them.
# keep target pixels their colors when the chaser passes if last_num in (x, y, z): pixels[x] = color.WHITE pixels[y] = colors[next_color] pixels[z] = color.WHITE
How does the chaser pixel move though? While the position of the chaser pixel is less than the total number of NeoPixels, it advances by one pixel position.
# move chaser pixel by one if num < num_pixels: pixels[num] = colors[now_color] pixels.show() #print(num) #print("target is", y) num += 1
When the chaser reaches the end of the NeoPixel strip, num
is reset to 0
to send the chaser back to the beginning of the strip.
# send chaser back to the beginning of the circle if num == num_pixels: last_num = num - 1 pixels[last_num] = color.BLACK pixels.show() num = 0
Using the button, you'll try and line-up the chaser pixel with the target pixel to score. When you score, all of the NeoPixels will light-up with the next_color
, which the target pixel had been showing.
The position of the chaser pixel is reset to 0
and the speed
is increased by the increment stored in level
. The now_color
and next_color
indexes are also increased by 1
. Finally, new_target
is set to True
in order to setup a new target pixel.
# if the chaser hits the target... if last_num in [x, y, z] and not button.value: button_state = False # fills with the next color pixels.fill(colors[next_color]) pixels.show() print(num) print(x, y, z) # chaser resets num = 0 time.sleep(0.5) pixels.fill(color.BLACK) pixels.show() # speed increases for next level speed = speed - level # color updates next_color = next_color + 1 if next_color > 11: next_color = 0 now_color = now_color + 1 if now_color > 11: now_color = 0 # setup for new target new_target = True print("speed is", speed) print("button is", button.value)
If you miss the target pixel with your button press, all of the NeoPixels will light-up with the now_color
. This is the same color that the chaser pixel had been.
Then, the game_over()
function animates the NeoPixels by using color_chase
to turn all of the pixels off and then flash them all red.
To set things up for a new game, the position of the chaser pixel is reset to 0
and the speed
is reset to its default value. The now_color
and next_color
indexes are also reset. Finally, new_target
is set to True
in order to setup a new target pixel.
# if the chaser misses the target... if last_num not in [x, y, z] and not button.value: button_state = False print(num) print(x, y, z) # fills with current chaser color pixels.fill(colors[now_color]) pixels.show() # function to flash all pixels red game_over() # chaser is reset num = 0 pixels.fill(color.BLACK) pixels.show() # speed is reset to default speed = 0.1 # colors are reset next_color = 1 now_color = 0 # setup for new target new_target = True print("speed is", speed) print("button is", button.value)
If you happen to be an expert at the NeoPixel Run Game, you'll defeat all of the targets with your chaser pixel and eventually run out of levels. You'll know you've won when all of the NeoPixels animate using the classic rainbow_cycle()
animation.
After some fun rainbows, all of the game parameters are reset to their defaults so that you can begin playing again.
# when you have beaten all the levels... if speed < final_level: # rainbows! rainbow_cycle(0.01) time.sleep(1) # chaser is reset num = 0 pixels.fill(color.BLACK) pixels.show() # speed is reset to default speed = 0.1 # colors are reset next_color = 1 now_color = 0 # setup for new target new_target = True
The last line of the loop updates pixel
to grab the current time.monotonic()
time.
# time.monotonic() is reset for the delay pixel = time.monotonic()
Page last edited March 08, 2024
Text editor powered by tinymce.