Chapter 8

Games for the Light Fantastic

In This Chapter

arrow Making more projects with the Light Fantastic keypad

arrow Finding hidden treasure and learning the resistor color code

arrow Making a color sequence sliding block puzzle

arrow Trying your skill at matching colors

arrow 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.

remember If you haven’t built the Light Fantastic yet, be sure to turn back to Chapter 7. You won’t get very far in this chapter otherwise.

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.

image

Figure 8-1: Light Fantastic position mapping.

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

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).

image

Figure 8-2: Treasure Hunt.

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.

tip You can do more with this game if you like. For example, you could make a more elaborate win celebration, maybe taking the elements of the demo in Chapter 7. You could keep a total of the best score — that is, the fewest moves. You could play the game with a time element, where the score is not simply the number of moves it took you but a measure of the amount of time it took. Because this is such a simple game, you could change it so that you played a number of games — say, six — and it gave you an average score. Finally, you could extend it to a Battleship type of game.

Sliding Block Puzzle

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:

  • Press the blank square, and all the colors that are not in the correct order will blink. This is great for keeping track of how well you’re doing.
  • Press a key that is not in line horizontally or vertically with the blank space. This move would normally be invalid, but here it’s a request to repeat the display of the final sequence you’re aiming for.

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.

tip You can experiment with changing the range of numbers used for the incH variable. Making it a small number makes the game much more difficult, with blue colors starting to look very similar. Making this value too big means you lose any sense of blending between adjacent colors.

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.

Color Match

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.

tip There are a few things you can tinker with in this program. First, you can alter the sleep delays so you have longer to wait before you see the choice of colors. Unsurprisingly, the longer you have to wait, the harder the game. The order of the color choice also is constantly increasing around the perimeter in order of increasing H color value. You can scramble that by applying a random shuffle to the colorOrder 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

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.

image

Figure 8-3: Lights Out logic.

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.

tip As always, there are a number of improvements and changes you can make to this basic game. For example, you may want to add a reset button so you can restart the same pattern if you’ve exceeded the minimum number of turns and want another shot at the same pattern. You need to add an extra list to permanently store the start position in order for this to work.

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.

remember When generating moves, to get to the starting position you need to filter out those moves that land on a lit position. This is exactly the opposite of how you would play the game. It needs to be opposite so your play can undo what the setup has done.

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.

Exploring a World of Possibilities

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!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.21.86