Опубликован: 06.08.2013 | Доступ: свободный | Студентов: 974 / 64 | Длительность: 27:51:00
Лекция 3:

Memory puzzle

< Лекция 2 || Лекция 3: 12345 || Лекция 4 >

Drawing the Highlight

248. def drawHighlightBox(boxx, boxy):
249.     left, top = leftTopCoordsOfBox(boxx, boxy)
250.     pygame.draw.rect(DISPLAYSURF, HIGHLIGHTCOLOR, (left - 5, top - 5,
BOXSIZE + 10, BOXSIZE + 10), 4)

To help the player recognize that they can click on a covered box to reveal it, we will make a blue outline appear around a box to highlight it. This outline is drawn with a call to pygame.draw.rect() to make a rectangle with a width of 4 pixels.

The "Start Game" Animation

253. def startGameAnimation(board):
254.     # Randomly reveal the boxes 8 at a time.
255.     coveredBoxes = generateRevealedBoxesData(False)
256.     boxes = []
257.     for x in range(BOARDWIDTH):
258.         for y in range(BOARDHEIGHT):
259.             boxes.append( (x, y) )
260.     random.shuffle(boxes)
261.     boxGroups = splitIntoGroupsOf(8, boxes)

The animation that plays at the beginning of the game gives the player a quick hint as to where all the icons are located. In order to make this animation, we have to reveal and cover up groups of boxes one group after another. To do this, first we’ll create a list of every possible space on the board. The nested for loops on lines 257 and 258 will add (X, Y) tuples to a list in the boxes variable.

We will reveal and cover up the first 8 boxes in this list, then the next 8, then the next 8 after that, and so on. However, since the order of the (X, Y) tuples in boxes would be the same each time, then the same order of boxes would be displayed (Try commenting out line 260 and then running to program a few times to see this effect).

To change up the boxes each time a game starts, we will call the random.shuffle() function to randomly shuffle the order of the tuples in the boxes list. Then when we reveal and cover up the first 8 boxes in this list (and each group of 8 boxes afterwards), it will be random group of 8 boxes.

To get the lists of 8 boxes, we call our splitIntoGroupsOf() function, passing 8 and the list in boxes. The list of lists that the function returns will be stored in a variable named boxGroups.

Revealing and Covering the Groups of Boxes

263.     drawBoard(board, coveredBoxes)
264.     for boxGroup in boxGroups:
265.         revealBoxesAnimation(board, boxGroup)
266.         coverBoxesAnimation(board, boxGroup)

First, we draw the board. Since every value in coveredBoxes is set to False, this call to drawBoard() will end up drawing only covered up white boxes. The revealBoxesAnimation() and coverBoxesAnimation() functions will draw over the spaces of these white boxes.

The for loop will go through each of the inner lists in the boxGroups lists. We pass these to revealBoxesAnimation(), which will perform the animation of the white boxes being pulled away to reveal the icon underneath. Then the call to coverBoxesAnimation() wil animate the white boxes expanding to cover up the icons. Then the for loop goes to the next iteration to animate the next set of 8 boxes.

The "Game Won" Animation

269. def gameWonAnimation(board):
270.     # flash the background color when the player has won
271.     coveredBoxes = generateRevealedBoxesData(True)
272.     color1 = LIGHTBGCOLOR
273.     color2 = BGCOLOR
274.
275.     for i in range(13):
276.         color1, color2 = color2, color1 # swap colors
277.         DISPLAYSURF.fill(color1)
278.         drawBoard(board, coveredBoxes)
279.         pygame.display.update()
280.         pygame.time.wait(300)

When the player has uncovered all of the boxes by matching every pair on the board, we want to congratulate them by flashing the background color. The for loop will draw the color in the color1 variable for the background color and then draw the board over it. However, on each iteration of the for loop, the values in color1 and color2 will be swapped with each other on line 276. This way the program will alternate between drawing two different background colors.

Remember that this function needs to call pygame.display.update() to actually make the DISPLAYSURF surface appear on the screen.

Telling if the Player Has Won

283. def hasWon(revealedBoxes):
284.     # Returns True if all the boxes have been revealed, otherwise False
285.     for i in revealedBoxes:
286.         if False in i:
287.             return False # return False if any boxes are covered.
288.     return True

The player has won the game when all of the icon pairs have been matched. Since the "revealed" data structure gets values in it set to True as icons have been matched, we can simply loop through every space in revealedBoxes looking for a False value. If even one False value is in revealedBoxes, then we know there are still unmatched icons on the board.

Note that because revealedBoxes is a list of lists, the for loop on line 285 will set the inner list as the values of i. But we can use the in operator to search for a False value in the entire inner list. This way we don’t need to write an additional line of code and have two nested for loops like this:

for x in revealedBoxes:
  for y in revealedBoxes[x]:
    if False == revealedBoxes[x][y]:
      return False

Why Bother Having a main() Function?

291. if __name__ == '__main__':
292.     main()

It may seem pointless to have a main() function, since you could just put that code in the global scope at the bottom of the program instead, and the code would run the exact same. However, there are two good reasons to put them inside of a main() function.

First, this lets you have local variables whereas otherwise the local variables in the main() function would have to become global variables. Limiting the number of global variables is a good way to keep the code simple and easier to debug (See the "Why Global Variables are Evil" section in this chapter).

Second, this also lets you import the program so that you can call and test individual functions. If the mcodeorypuzzle.py file is in the C:\Python32 folder, then you can import it from the interactive shell. Type the following to test out the splitIntoGroupsOf() and getBoxAtPixel() functions to make sure they return the correct return values:

>>> import memorypuzzle
>>> memorypuzzle.splitIntoGroupsOf(3, [0,1,2,3,4,5,6,7,8,9])
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
>>> memorypuzzle.getBoxAtPixel(0, 0)
(None, None)
>>> memorypuzzle.getBoxAtPixel(150, 150)
(1, 1)

When a module is imported, all of the code in it is run. If we didn’t have the main() function, and had its code in the global scope, then the game would have automatically started as soon as we imported it, which really wouldn’t let us call individual functions in it.

That’s why the code is in a separate function that we have named main(). Then we check the built-in Python variable __name__ to see if we should call the main() function or not. This variable is automatically set by the Python interpreter to the string '__main__' if the program itself is being run and 'mcodeorypuzzle' if it is being imported. This is why the main() function is not run when we executed the import mcodeorypuzzle statement in the interactive shell.

This is a handy technique for being able to import the program you are working on from the interactive shell and make sure individual functions are returning the correct values by testing them one call at a time.

Why Bother With Readability?

A lot of the suggestions in this chapter haven’t been about how to write programs that computers can run so much as how to write programs that programmers can read. You might not understand why this is important. After all, as long as the code works, who cares if it is hard or easy for human programmers to read?

However, the important thing to realize about software is that it is rarely ever left alone. When you are creating your own games, you will rarely be "done" with the program. You will always get new ideas for game features you want add, or find new bugs with the program. Because of this, it is important that your program is readable so that you can look at the code and understand it. And understanding the code is the first step to changing it to add more code or fix bugs.

As an example, here is an obfuscated version of the Memory Puzzle program that was made entirely unreadable. If you type it in (or download it from http://invpy.com/memorypuzzle_obfuscated.py) and run it you will find it runs exactly the same as the code at the beginning of this chapter. But if there was a bug with this code, it would be impossible to read the code and understand what’s going on, much less fix the bug.

The computer doesn’t mind code as unreadable as this. It’s all the same to it.

1    import random, pygame, sys
2    from pygame.locals import *
3    def hhh():
4        global a, b
5        pygame.init()
6        a = pygame.time.Clock()
7        b = pygame.display.set_mode((640, 480))
8        j = 0
9        k = 0
10       pygame.display.set_caption('Memory Game')
11       i = c()
12       hh = d(False)
13       h = None
14       b.fill((60, 60, 100))
15       g(i)
16       while True:
17           e = False
18           b.fill((60, 60, 100))
19           f(i, hh)
20           for eee in pygame.event.get():
21               if eee.type == QUIT or (eee.type == KEYUP and eee.key == K_ESCAPE):
22                   pygame.quit()
23                   sys.exit()
24               elif eee.type == MOUSEMOTION:
25                   j, k = eee.pos
26               elif eee.type == MOUSEBUTTONUP:
27                   j, k = eee.pos
28                   e = True
29           bb, ee = m(j, k)
30           if bb != None and ee != None:
31               if not hh[bb][ee]:
32                   n(bb, ee)
33               if not hh[bb][ee] and e:
34                   o(i, [(bb, ee)])
35                   hh[bb][ee] = True
36                   if h == None:
37                       h = (bb, ee)
38                   else:
39                       q, fff = s(i, h[0], h[1])
40                       r, ggg = s(i, bb, ee)
41                       if q != r or fff != ggg:
42                           pygame.time.wait(1000)
43                           p(i, [(h[0], h[1]), (bb, ee)])
44                           hh[h[0]][h[1]] = False
45                           hh[bb][ee] = False
46                       elif ii(hh):
47                          jj(i)
48                          pygame.time.wait(2000)
49                          i = c()
50                          hh = d(False)
51                          f(i, hh)
52                          pygame.display.update()
53                          pygame.time.wait(1000)
54                         g(i)
55                      h = None
56          pygame.display.update()
57          a.tick(30)
58  def d(ccc):
59      hh = []
60      for i in range(10):
61          hh.append([ccc] * 7)
62      return hh
63  def c():
64      rr = []
65      for tt in ((255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0), (255, 128, 0), (255, 0, 255), (0, 255, 255)):
66          for ss in ('a', 'b', 'c', 'd', 'e'):
67              rr.append( (ss, tt) )
68     random.shuffle(rr)
69      rr = rr[:35] * 2
70      random.shuffle(rr)
71      bbb = []
72      for x in range(10):
73          v = []
74          for y in range(7):
75              v.append(rr[0])
76              del rr[0]
77          bbb.append(v)
78      return bbb
79  def t(vv, uu):
80      ww = []
81      for i in range(0, len(uu), vv):
82          ww.append(uu[i:i + vv])
83      return ww
84  def aa(bb, ee):
85      return (bb * 50 + 70, ee * 50 + 65)
86  def m(x, y):
87      for bb in range(10):
88          for ee in range(7):
89              oo, ddd = aa(bb, ee)
90              aaa = pygame.Rect(oo, ddd, 40, 40)
91              if aaa.collidepoint(x, y):
92                  return (bb, ee)
93      return (None, None)
94  def w(ss, tt, bb, ee):
95      oo, ddd = aa(bb, ee)
96      if ss == 'a':
97          pygame.draw.circle(b, tt, (oo + 20, ddd + 20), 15)
98          pygame.draw.circle(b, (60, 60, 100), (oo + 20, ddd + 20), 5)
99      elif ss == 'b':
100          pygame.draw.rect(b, tt, (oo + 10, ddd + 10, 20, 20))
101      elif ss == 'c':
102          pygame.draw.polygon(b, tt, ((oo + 20, ddd), (oo + 40 - 1, ddd + 20), (oo + 20, ddd + 40 - 1), (oo, ddd + 20)))
103      elif ss == 'd':
104          for i in range(0, 40, 4):
105              pygame.draw.line(b, tt, (oo, ddd + i), (oo + i, ddd))
106              pygame.draw.line(b, tt, (oo + i, ddd + 39), (oo + 39, ddd + i))
107      elif ss == 'e':
108          pygame.draw.ellipse(b, tt, (oo, ddd + 10, 40, 20))
109  def s(bbb, bb, ee):
110      return bbb[bb][ee][0], bbb[bb][ee][1]
111  def dd(bbb, boxes, gg):
112      for box in boxes:
113          oo, ddd = aa(box[0], box[1])
114          pygame.draw.rect(b, (60, 60, 100), (oo, ddd, 40, 40))
115          ss, tt = s(bbb, box[0], box[1])
116          w(ss, tt, box[0], box[1])
117          if gg > 0:
118              pygame.draw.rect(b, (255, 255, 255), (oo, ddd, gg, 40))
119      pygame.display.update()
120      a.tick(30)
121  def o(bbb, cc):
122      for gg in range(40, (-8) - 1, -8):
123          dd(bbb, cc, gg)
124  def p(bbb, ff):
125      for gg in range(0, 48, 8):
126          dd(bbb, ff, gg)
127  def f(bbb, pp):
128      for bb in range(10):
129          for ee in range(7):
130              oo, ddd = aa(bb, ee)
131              if not pp[bb][ee]:
132                  pygame.draw.rect(b, (255, 255, 255), (oo, ddd, 40, 40))
133              else:
134                  ss, tt = s(bbb, bb, ee)
135                  w(ss, tt, bb, ee)
136  def n(bb, ee):
137      oo, ddd = aa(bb, ee)
138      pygame.draw.rect(b, (0, 0, 255), (oo - 5, ddd - 5, 50, 50), 4)
139  def g(bbb):
140      mm = d(False)
141      boxes = []
142      for x in range(10):
143          for y in range(7):
144              boxes.append( (x, y) )
145      random.shuffle(boxes)
146      kk = t(8, boxes)
147      f(bbb, mm)
148      for nn in kk:
149          o(bbb, nn)
150          p(bbb, nn)
151  def jj(bbb):
152      mm = d(True)
153      tt1 = (100, 100, 100)
154      tt2 = (60, 60, 100)
155      for i in range(13):
156          tt1, tt2 = tt2, tt1
157          b.fill(tt1)
158          f(bbb, mm)
159          pygame.display.update()
160          pygame.time.wait(300)
161  def ii(hh):
162      for i in hh:
163          if False in i:
164              return False
165      return True
166  if __name__ == '__main__':
167      hhh()

Never write code like this. If you program like this while facing the mirror in a bathroom with the lights turned off, the ghost of Ada Lovelace will come out of the mirror and throw you into the jaws of a Jacquard loom.

Summary, and a Hacking Suggestion

This chapter covers the entire explanation of how the Memory Puzzle program works. Read over the chapter and the source code again to understand it better. Many of the other game programs in this book make use of the same programming concepts (like nested for loops, syntactic sugar, and different coordinate systems in the same program) so they won’t be explained again to keep this book short.

One idea to try out to understand how the code works is to intentionally break it by commenting out random lines. Doing this to some of the lines will probably cause a syntactic error that will prevent the script from running at all. But commenting out other lines will result in weird bugs and other cool effects. Try doing this and then figure out why a program has the bugs it does.

This is also the first step in being able to add your own secret cheats or hacks to the program. By breaking the program from what it normally does, you can learn how to change it to do something neat effect (like secretly giving you hints on how to solve the puzzle). Feel free to experiment. You can always save a copy of the unchanged source code in a different file if you want to play the regular game again.

In fact, if you’d like some practice fixing bugs, there are several versions of this game’s source code that have small bugs in them. You can download these buggy versions from http://invpy.com/buggy/memorypuzzle. Try running the program to figure out what the bug is, and why the program is acting that way.

< Лекция 2 || Лекция 3: 12345 || Лекция 4 >