Chapter 8
In This Chapter
Making more projects with the Light Fantastic keypad
Finding hidden treasure and learning the resistor color code
Making a color sequence sliding block puzzle
Trying your skill at matching colors
Battling logic to get all the lights out
Having built the Light Fantastic in Chapter 7, you’re ready to have some fun with it. This chapter presents four games, along with ideas for variants and more complex puzzles. Each one is colorful fun.
A few notes before we begin: The Light Fantastic consists of illuminated push buttons. Each one can be set to a range of colors too subtle for your eye to distinguish. In scientific terms, this is known as better than a “just noticeable difference.” The color of each LED is defined by writing a value of the red, green, and blue components of the color into a buffer. When the buffer has been set to what you want to display, it’s transferred to the LEDs with the show
call. The push buttons and LED positions should match up, as shown in Figure 8-1.
The sequence numbers start at 0 and go to 15, but there is an alternative way of describing a position: with a pair of x- and y-coordinates. This is quite a handy thing to do when you’re looking for an adjacent position rather than just the next one in the sequence. In Figure 8-1, you can see how we can convert a sequence number to x- and y-coordinates and back again. You may not have come across the %
operation before, but in Python (and many other languages), it’s the modulus operation. That means, “Do an integer division, but just give the remainder.” Note that the divide operator (/
) returns just an integer; if the two numbers involved are integers, it throws away the reminder.
Finally, in order to save on your fingers some functions that are the same in all programs are not repeated. Instead, after the function definition is a note saying where the function can be copied and pasted from. All these programs require you to enter the IDLE editor by using gksudo idle
from a command-line window. The programs interact with the user through the Python console window, so keep that the active window (the one with the keyboard focus). All the programs run in an infinite loop, so when you want to quit one, press Ctrl+C. If that doesn’t appear to work, click with your mouse on the Python console window to give it the keyboard focus.
Let the games begin!
Treasure Hunt (see Figure 8-2) is a simple game with an educational motive. A treasure has been hidden in one of the squares, and you have to find it. If you guess right and press the right square, it flashes. If you press the wrong square, the square lights up in a color that tells you how many squares you are away, in terms of horizontal plus vertical distance (diagonal distances don’t count).
Notice in the diagram that the distance of every square has a number in it. On the Light Fantastic, there are only colors, so the colors that light up are the distance numbers in the resistor color code colors. So in addition to playing a game, you’re learning the resistor color code (well, at least up to six).
Resistors have their values marked in colored bands, with each color representing a number. They are as follows:
0 Black
1 Brown
2 Red
3 Orange
4 Yellow
5 Green
6 Blue
7 Violet
8 Gray
9 White
You need to learn these codes if you’re going to work with electronics. Unfortunately, LEDs aren’t good at producing brown and orange, and brown can be confused, exactly as it can be with real resistors. It’s easy enough to fiddle around with the numbers if you don’t like our rendering of the colors.
The code for this game is given in Listing 8-1.
Listing 8-1: Treasure Hunt
#!/usr/bin/env python
# NeoPixel Light Fantastic
# Treasure Hunt
# Author: Mike Cook
#
import time, random
import wiringpi2 as io
from neopixel import *
print"if program quits here start IDLE with 'gksudo idle' from command line"
io.wiringPiSetupGpio()
print"OK no crash" ; print" "
pinList = [9,24,10,23,7,8,11,25] # pins for keyboard
# black=0, brown=1, red=2, orange=3, yellow=4, green=5, blue=6
distanceC = [ Color(0,0,0), Color(45,139,0), Color(0,255,0),
Color(120,255,0), Color(255,255,0), Color(200,0,0),
Color(0,0,200) ]
treasure = 0 # location of the treasure
strip = Adafruit_NeoPixel(16,18,800000,5,False)
def main():
initGPIO()
strip.begin()
print"Treasure hunt - find the hidden treasure"
print"pressing a key will show the distance to the treasure"
wipe() ; key = -1
while True:
setBoard() # set up colors to use
while key != treasure:
while keyPressed() == False :
pass
newKey = getKey()
if newKey != -1 :
key = newKey
while keyPressed(): # wait for release
pass
makeMove(key)
print"puzzle complete - any key for new game"
while keyPressed() == False :
pass
while keyPressed(): # wait for release
pass
time.sleep(0.5)
print"play"
def initGPIO():
# see Listing 7-1 for this function
def keyPressed():
# see Listing 7-1 for this function
def getKey():
# see Listing 7-1 for this function
def wipe():
# see Listing 7-1 for this function
def setBoard():
global treasure
wipe()
treasure = random.randint(0,15)
#uncomment to cheat
#print" treasure at",treasure
#strip.setPixelColor(treasure, Color(128, 128, 128))
#strip.show()
def makeMove(move):
distX = abs((move % 4) - (treasure % 4))
distY = abs((move / 4) - (treasure / 4))
distance = distX + distY
if move != treasure:
strip.setPixelColor(move, distanceC[distance])
strip.show()
else:
print"found it"
flashTreasure()
def flashTreasure():
for i in range(0,7):
strip.setPixelColor(treasure, Color(0,0,0))
strip.show()
time.sleep(0.3)
strip.setPixelColor(treasure, Color(0,255,255))
strip.show()
time.sleep(0.3)
# Main program logic follows:
if __name__ == '__main__':
main()
The Colors are defined in a list called distanceC
. For these 5mm packaged versions of the WS2812b LEDs, the Color
function takes in the color components green, red, and blue. Note that black is defined even though it isn’t used in this game, so all the other colors have a list index, which is the same as the color’s value in the resistor color code. The main function sets up the LEDs and the switches and prints the instructions to the console.
The setBoard
function picks a random square to hide the treasure in. Note that there are some cheat lines commented out with #
that will show the square number in the console and even light up the treasure square as a light gray color.
When the main
function has a key press, the key number is passed to the makeMove
function. This function first calculates the distance to the treasure by adding up the x displacement and the y displacement; then it checks to see if the move has found the treasure. If it has, the flashTreasure
function is called. True to its name, it alternates the treasure square between black (unlit) and magenta (a color not otherwise in the game).
If the treasure hasn’t been found, the key you pressed is illuminated with a color equal to the distance. The important point here is the use of a list to define a color whose index is the distance color.
This game is a colorful twist on the sliding-block puzzle game. Normally, you have to get numbered squares into an ascending order, but here it’s much trickier. You have to get the colors in the right sequence according to the H value in the HSV color space (see Chapter 7). The correct color sequence is shown at the start; then the colors are scrambled up. Normally, you would have no chance of remembering 15 colors from just one showing, so here there are two ways you can get a hint:
In a normal move, if you press a key that’s adjacent to the blank space, it swaps position as you would expect. However, if you press a key that’s on the same column or row as the blank, all the colors are pushed up from where you pressed into the blank space, and the blank appears where you made the move.
Listing 8-2 shows the code for this game.
Listing 8-2: Sliding Block Puzzle
#!/usr/bin/env python
# NeoPixel Light Fantastic
# Sliding block puzzle
# Author: Mike Cook
#
import time, random
import wiringpi2 as io
from neopixel import *
print"if program quits here start IDLE with 'gksudo idle' from command line"
io.wiringPiSetupGpio()
print"OK no crash" ; print" "
pinList = [9,24,10,23,7,8,11,25] # pins for keyboard
gameOrder =[ 0,1,2,3,4,5,6,7,8,9,10,11,12,13,15,14 ] # working order
lightC = [ Color(0,0,0) for i in range(0,16) ]
strip = Adafruit_NeoPixel(16,18,800000,5,False)
def main():
initGPIO()
strip.begin()
print"Sliding block puzzle - get the lights in the right order"
print"pressing the unlit block will blink blocks in the wrong place"
print"pressing a key that does not result in a shift will show the right order"
wipe() ; key = 1
while True:
setBoard() # set up colors to use
while not finished():
while keyPressed() == False :
pass
newKey = getKey()
if newKey != -1:
key = newKey
while keyPressed(): # wait for release
pass
makeMove(key)
showSet()
print"puzzle complete - any key for new game"
while keyPressed() == False :
pass
while keyPressed(): # wait for release
pass
def initGPIO():
# see Listing 7-1 for this function
def keyPressed():
# see Listing 7-1 for this function
def getKey():
# see Listing 7-1 for this function
def wipe():
# see Listing 7-1 for this function
def colorH(angle):
# see Listing 7-2 for this function
def setBoard():
global gameOrder,lightC
random.shuffle(gameOrder) # mix up the board
h = random.randint(0,255)
hInc = random.randint(16,35)
for i in range(0,15):
lightC[i] = colorH(h)
h += hInc
showSol()
def showSol():
for i in range(0,16):
strip.setPixelColor(i, lightC[i])
time.sleep(0.08)
strip.show()
time.sleep(1.0)
showSet()
def showSet():
for i in range(0,16):
#print"game order ",gameOrder[i]
strip.setPixelColor(i, lightC[gameOrder[i]])
strip.show()
def finished():
done = True
for i in range(0,16):
#print i," Game order ", gameOrder[i]
if gameOrder[i] != i:
done = False
return done
def showCorrect(): #blink incorrect squares
for blink in range (0,3):
for i in range(0,16):
if gameOrder[i] != i:
strip.setPixelColor(i,Color(0,0,0))
strip.show()
time.sleep(0.5)
showSet()
time.sleep(0.3)
def makeMove(move):
if lightC[gameOrder[move]] == Color(0,0,0):
#blank key pressed
showCorrect()
else:
#print"not blank"
x = move % 4
y = move / 4
blank = findBlank()
if blank[0] == x:
shuffle(4,blank[1],y,blank[2],move)
elif blank[1] == y:
shuffle(1,blank[0],x,blank[2],move)
else:
#print" no alignment with blank"
wipe()
showSol() # show what you are aiming for
def shuffle(incSize,distance,target,blankPos,move): #move into blank
global gameOrder
inc = incSize
if distance > target:
inc = -incSize
while blankPos != move:
temp = gameOrder[blankPos]
gameOrder[blankPos] = gameOrder[blankPos + inc]
gameOrder[blankPos + inc] = temp
blankPos += inc
def findBlank():
blank =(-1,-1,-1)
for i in range(0,16):
if lightC[gameOrder[i]] == Color(0,0,0):
blank = (i % 4, i / 4, i)
if blank == (-1,-1,-1):
# this should never happen
print"error blank not found"
return blank
# Main program logic follows:
if __name__ == '__main__':
main()
The code follows the overall structure of the previous game in terms of initialization. Here, there are two lists: gameOrder
defines the order you have to arrange the colors in, and lightC
defines the current colors. At this initial stage, it’s set so that they’re all black.
The setBoard
function first mixes up the gameOrder
list, which defines the initial startup position. Then the lightC
list is populated by a succession of colors defined by a randomly chosen initial h
angle and incremented by a randomly chosen value, incH
. When that’s finished, the showSol
function shows the solution, by simply showing the pixels in the order of the lightC
list.
The while not finished( ):
line calls the finished
function, which returns a true
when the puzzle is complete. It does this by checking that the gameOrder
list matches the sequence 0 to 15. If any entry in the list fails, the logic variable done
is set to false
, and it’s this variable that is returned by the function.
So, assuming the puzzle is not complete, the main
function waits for a key press. When it gets one, it calls the makeMove
function, which looks to see what sort of move has been made. If it’s the blank key, it will call the showCorrect
function and blink the positions that don’t contain the correct color. It does that by alternately blanking the pixels that don’t correspond to the right order and then showing the current state of the board with the showSet
function.
If the move wasn’t the blank key, it works out the x- and y-coordinates of the move key and then calls the findBlank
function, which, as its name implies, returns a tuple (a list of numbers in one variable) of the x- and y-coordinates of the blank space. If the blank space is at the same x value as the move, the shuffle
function is called. This takes the colors between the blank space and the move and shuffles them up one. This function copes with moving the colors along both the x-axis and the y-axis, depending on what’s needed. We started out by writing two functions — one to shuffle in the x-axis and the other for the y-axis — but they looked so similar. So we combined them into one and let the axis be defined by the parameters passed to it. This make for very efficient use of code, but it can be a bit tricky to follow at first.
If there is no alignment in either the x- or y-coordinate between the blank space and the move, this is an invalid move and the code responds by showing the solution — that is, the sequence of colors you’re aiming for.
When the puzzle is complete, a message is output to the console. On pressing any key, a new game is set up.
Because this is a much longer game than the first one, we suggest that you base any scoring on time rather than the number of moves made. You may want to incorporate an “I give up” combination of keys.
Mike Cook wrote this game specifically to annoy someone in an online forum. He was using LEDs without any form of current control and claimed that the intensity of the LEDs had not diminished in three months of continuous operation. It turned out he wasn’t measuring it in any way but claimed he could tell by looking. Mike said he couldn’t remember the brightness over three seconds let alone three months, so he designed this game to prove it. This is a Light Fantastic version that deals not only with brightness but also with color.
The way this game works is that the center four switches light up for just over a second. Then all goes dark, and two seconds later the perimeter lights are lit up and you have to press the one of the same color as the one lit up in the center. Then the center color, the matching perimeter color, and your guess, if different, flash. If you got it right, there is only one perimeter light flashing — your choice. If you got it wrong, you can see the difference between the color you chose and the central colors. Just to confirm things, a console message is produced as well. If you want to have another look at the colors, you can press one of the central four keys for a sneaky reminder.
The code for this program is shown in Listing 8-3.
Listing 8-3: Color Match
#!/usr/bin/env python
# NeoPixel Light Fantastic
# Color Match
# Author: Mike Cook
#
import time, random
import wiringpi2 as io
from neopixel import *
print"if program quits here start IDLE with 'gksudo idle' from command line"
io.wiringPiSetupGpio()
print"OK no crash" ; print" "
pinList = [9,24,10,23,7,8,11,25] # pins for keyboard
colorOrder = [0,1,2,3,7,11,15,14,13,12,8,4]
colorRange = [ Color(0,0,0) for i in range(0,12) ]
strip = Adafruit_NeoPixel(16,18,800000,5,False)
cheat = False ; target = 0
def main():
initGPIO()
strip.begin()
print"Color Match"
print"the center four lights will flash a single color"
print"then you press the match on the outside"
wipe() ; key = 1
while True:
guess = True
setBoard() # set up colors to choose from
while guess:
while keyPressed() == False :
pass
newKey = getKey()
if newKey != -1:
key = newKey
while keyPressed(): # wait for release
pass
guess = makeMove(key)
print"another go"
time.sleep(1.5)
def initGPIO():
# see Listing 7-1 for this function
def keyPressed():
# see Listing 7-1 for this function
def getKey():
# see Listing 7-1 for this function
def wipe():
# see Listing 7-1 for this function
def colorH(angle):
# see Listing 7-1 for this function
def setBoard():
global colorRange,target
wipe()
h = random.randint(0,255)
hInc = 8 # sets how hard it is
for i in range(0,12):
colorRange[i] = colorH(h)
h += hInc
target = random.randint(0,10)
showReminder()
if cheat :
print target
def showSet():
for i in range(0,12):
strip.setPixelColor(colorOrder[i], colorRange[i])
strip.show()
def showTarget():
strip.setPixelColor(5, colorRange[target])
strip.setPixelColor(6, colorRange[target])
strip.setPixelColor(9, colorRange[target])
strip.setPixelColor(10, colorRange[target])
strip.show()
def showReminder():
wipe()
time.sleep(0.4)
showTarget()
time.sleep(0.9)
wipe()
time.sleep(1.5)
showSet()
def makeMove(move):
guess = True
if move == 5 or move == 6 or move == 9 or move == 10:
showReminder()
else:
guess = False # flash guess and color and right color
if colorOrder[target] == move:
print"Yes right"
else:
print"No wrong"
for t in range(0,6):
wipe()
time.sleep(0.2)
strip.setPixelColor(move, colorRange[colorOrder.index(move)])
strip.setPixelColor(colorOrder[target], colorRange[target])
showTarget()
time.sleep(0.4)
return guess
# Main program logic follows:
if __name__ == '__main__':
main()
Again, following the same template as before, the colorOrder
list has in it the keypad’s numbers for the perimeter of the Light Fantastic display. The colorRange
list is used to hold the colors to display. The main
function starts by initializing things and printing out the instructions. The setBoard
function generates a range of colors to act as the potential target and then chooses one of them at random. Although the initial point in the HSV color space is chosen at random, the hInc
or increment value is fixed. This effectively controls the change between adjacent colors; a value in the listing is one that we found to give a just noticeable difference over the whole range, with blue color changes being the hardest to detect. Make this value bigger for an easier game.
After the colors have been defined, the setBoard
function calls the showRemainder
function. This clears the key colors, shows the target color with the showTarget
function, wipes that, and then shows the colors around the outside of the keypad with the showSet
function.
After all that, back in the main
function, the program looks for a key press. There are two actions that can happen as a result of pressing a key: One is to make a guess as to the correct color, and the other is to request a review of the target color. This is decided in the makeMove
function. If the move is one of the central keys, it calls the showRemainder
function just like at the end of the setBoard
function. Then it returns a True
value to inform the main function that this round has not yet finished.
If, however, your move is one of the outer keys, that’s taken as a valid answer, and a check is made to see if the key you pressed is where the target color was in the colorOrder
list. Then your success in matching the color is printed out to the console. Finally, the target color, your guess, and the correct result are flashed. Note that there are two lines that define your guess and the correct color. These are:
strip.setPixelColor(move, colorRange[colorOrder.index(move)])
strip.setPixelColor(colorOrder[target], colorRange[target])
If you’re correct, these two lines will result in setting the same pixel number to the same color. The use of colorOrder.index(move)
is a reversal of how you normally use lists. This returns a number that gives the position of where the value of move
is in the list.
Perhaps the biggest change you can make is in the generation of colors. The eye is much less sensitive to the amounts of blue in a color than to red or green. You could change the H increment value according to the initial H starting point so that if H were clear of the blue content — that is, below a value of 170 — then the hue increment could be smaller. You could change the way the choice colors are generated so that it gets harder as more and more correct answers are given and drops down to easy if a mistake is made. Each degree of difficulty could be marked with a level number. Then you could introduce an element of competition in how high a level you can get.
Lights Out is a fantastic puzzle. We normally implement it on a 3 x 3 grid, but here it’s on the 4 x 4 grid of the Light Fantastic. The idea is to turn out all the lights by pressing keys. The snag is that when you press one key, not only is that key inverted but those surrounding it are also inverted. It basically inverts a cross pattern of adjacent keys, but that’s clipped if the key is close to the edge. This is shown in Figure 8-3.
The game works on two levels: First, it’s about getting all the lights out. But when you get better, the aim is to get the lights out in the minimum number of moves.
You can’t just generate any random collection of lights — it has to be a pattern that is solvable. To do this is remarkably simple: You start off with a finished representation of the board and make a number of random moves to generate the start position. The number of moves it took to generate the start position is the number of moves you need to get back to the end position. Each move is fully reversible, so if you press a key twice, you get back to your original position. That applies no matter what keys you press and in what order. Any sequence of key presses is reversed by the same keys in a different order. All you need to do in order to make sure you’re getting the minimum number of moves when you’re setting up the board is not use any key twice. You can set up a board that is solvable in any number of key presses you like. It turns out that for one or two moves, it’s trivial but for three or more moves it becomes increasingly difficult.
The code for the Lights Out game is shown in Listing 8-4.
Listing 8-4: Lights Out
# #!/usr/bin/env python
# NeoPixel Light Fantastic
# Lights Out
# Author: Mike Cook
#
import time, random
import wiringpi2 as io
from neopixel import *
print"if program quits here start IDLE with 'gksudo idle' from command line"
io.wiringPiSetupGpio()
print"OK no crash" ; print" "
pinList = [9,24,10,23,7,8,11,25] # pins for keyboard
litColor = Color(255,0,0)
lightC = [ Color(0,0,0) for i in range(0,16) ]
strip = Adafruit_NeoPixel(16,18,800000,5,False)
cheat = True
def main():
initGPIO()
strip.begin()
print"Lights Out - remove all the lights"
print"pressing a key will invert the light and others surrounding it"
playLevel = int(raw_input("Enter the level 3 to 8 "))
if playLevel < 3 or playLevel > 8 :
playLevel = random.randint(3,8)
print"Setting level to ",playLevel
wipe() ; key = 1
while True:
turn = 0
print"this can be completed in",playLevel,"moves"
setBoard(playLevel) # set up colors to use
while not finished():
while keyPressed() == False :
pass
newKey = getKey()
if newKey != -1:
key = newKey
while keyPressed(): # wait for release
pass
makeMove(key,True)
turn += 1
print"You have had",turn,"turns"
if turn > playLevel:
print"taking more than you should"
if turn == playLevel:
print"Well done - minimum number of turns"
print"puzzle complete - any key for new game"
while keyPressed() == False :
pass
while keyPressed(): # wait for release
pass
def initGPIO():
# see Listing 7-1 for this function
def keyPressed():
# see Listing 7-1 for this function
def getKey():
# see Listing 7-1 for this function
def wipe():
# see Listing 7-1 for this function
def colorH(angle):
# see Listing 7-1 for this function
def setBoard(level):
global lightC,litColor
for i in range(0,strip.numPixels()):
lightC[i] = Color(0, 0, 0)
h = random.randint(0,255)
litColor = colorH(h)
moves = [random.randint(0,15)]
move = moves[0]
makeMove(move,False)
for m in range (0,level-1):
while move in moves:
move = random.randint(0,15)
moves.extend([move])
makeMove(move,False)
showSet()
if cheat :
print moves
def showSet():
for i in range(0,16):
strip.setPixelColor(i, lightC[i])
strip.show()
def finished():
done = True
for i in range(0,16):
if lightC[i] != Color(0,0,0):
done = False
return done
def makeMove(move,play):
toggleColor(move,play)
y = move / 4
if move -4 >= 0:
toggleColor(move -4,play)
if move +4 < 16:
toggleColor(move +4,play)
if ((move -1) / 4) == y:
toggleColor(move -1,play)
if ((move +1) / 4) == y:
toggleColor(move +1,play)
def toggleColor(led,play):
global lightC
if play: # playing the game
if lightC[led] == Color(0,0,0):
lightC[led] = litColor
strip.setPixelColor(led, litColor)
else:
strip.setPixelColor(led, Color(0,0,0))
lightC[led] = Color(0,0,0)
strip.show()
time.sleep(0.2)
else: # setup the board
if lightC[led] == Color(0,0,0):
lightC[led] = litColor
else:
lightC[led] = Color(0,0,0)
# Main program logic follows:
if __name__ == '__main__':
main()
You should be seeing a pattern in these programs by now. Many of the functions have the same names but do different things, depending on the game. This time, the state of the board is represented by the list lightC
. This needs checking to make sure they’re all out. One difference here is that you need to type in the game level on the keyboard at the start of the game. Once it’s set, it’ll be the same for all subsequent games.
The loop in the main
function that plays the game prints a reminder as to how many moves the board can be completed in. Then the setBoard
function is the one that plays the reverse game to generate the starting position. First, the lightC
list is cleared. Then a random color for the game is chosen. Next, a list of moves is generated. Notice that after the first move, the while
loop keeps generating random numbers until it finds one that is not in the list of moves
. This ensures that the same key is never used more than once. After each unique move has been generated, the makeMove
function is called. This takes two parameters: one containing the move and the other containing a logic variable that determines if the game is being played or set up.
The makeMove
function further identifies which positions need to be inverted (or “toggled” as it’s called in electronics). These positions are the move, and the positions above, below, left, and right if they’re places on the board. Each one identified calls up the toggleColor
function, which in the setup phase simply sets the move position lightC
list to the opposite of what it is already. Finally, the setBoard
function calls the showSet
function to display the board. Then if the cheat
variable has been set to true
, it prints out a list of moves you have to make.
When the main
function has set up the game, the code loops reading the keys and checking for completion. After each key press, a reminder is given of how many turns you’ve had. Then when all the lights are out, a congratulation message is printed if you did it in the minimum number of turns.
The biggest change you can make is to change the logic. One such change is that you can restrict the keys you’re permitted to press to just the keys that are currently lit. This changes the whole feel of the game.
In addition to restricting what can key be pressed, you could change the patterns of inversions depending on the key pressed. For example, a corner key could invert all four keys in the corner and a side middle key could invert the whole row or column. This can be as asymmetrical as you want. As long as the setup function follows the play logic, the whole concept will work. If you make it too complex, though, you’ll have a hard time explaining the rules to the players.
Finally, you could define an ending state to the board, which is not all the lights out. Some of them could be, say, red. Then play in another color — say, green — and have the red lights toggle between blue and red and the others between green and off. Although it may sound complex, it’s exactly the same game, just much harder to play.
There is no need to stop with these four games — there are a whole host of uses you can put the Light Fantastic to. The options we present in this chapter are just a few to give you some inspiration.
The Light Fantastic interface lends itself well to all sorts of variations of the “Whack-a-Mole” game, where lights come on and you have to press the keys as quickly as possible to turn them off. You could have some colors the player should whack and other that the player shouldn’t.
You can also make a colorful version of a plumbing game where you have to unblock a drain by maneuvering pipe blocks into place to make the water flow.
How about a snake game that wraps around top and bottom, as well as left and right, of the playing area?
Then there’s tic-tac-toe. Normally it’s played on a 3-x-3 grid, but there’s nothing stopping you from using a 4-x-4 grid. In fact, how about four in a row?
Finally, you don’t have to stop at games. You can use the keys to control just about anything, from media players to musical instruments. You have the tools now. Let your imagination flow!
18.191.21.86