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

# Memory puzzle

< Лекция 2 || Лекция 3: 12345 || Лекция 4 >
Аннотация: Coding memory puzzle and the use of a for loop inside of another for loop.

### How to Play Memory Puzzle

Рис. 3.1.

In the Memory Puzzle game, several icons are covered up by white boxes. There are two of each icon. The player can click on two boxes to see what icon is behind them. If the icons match, then those boxes remain uncovered. The player wins when all the boxes on the board are uncovered. To give the player a hint, the boxes are quickly uncovered once at the beginning of the game.

### Nested for Loops

One concept that you will see in Memory Puzzle (and most of the games in this book) is the use of a for loop inside of another for loop. These are called nested for loops. Nested for loops are handy for going through every possible combination of two lists. Type the following into the interactive shell:

```>>> for x in [0, 1, 2, 3, 4]:
...     for y in ['a', 'b', 'c']:
...         print(x, y)
...
0 a
0 b
0 c
1 a
1 b
1 c
2 a
2 b
2 c
3 a
3 b
3 c
4 a
4 b
4 c
>>>
```

There are several times in the Memory Puzzle code that we need to iterate through every possible X and Y coordinate on the board. We'll use nested for loops to make sure that we get every combination. Note that the inner for loop (the for loop inside the other for loop) will go through all of its iterations before going to the next iteration of the outer for loop. If we reverse the order of the for loops, the same values will be printed but they will be printed in a different order. Type the following code into the interactive shell, and compare the order it prints values to the order in the previous nested for loop example:

```>>> for y in ['a', 'b', 'c']:
...     for x in [0, 1, 2, 3, 4]:
...         print(x, y)
...
0 a
1 a
2 a
3 a
4 a
0 b
1 b
2 b
3 b
4 b
0 c
1 c
2 c
3 c
4 c
>>>
```

### Source Code of Memory Puzzle

Go ahead and first type in the entire program into IDLE's file editor, save it as memorypuzzle.py, and run it. If you get any error messages, look at the line number that is mentioned in the error message and check your code for any typos. You can also copy and paste your code into the web form at http://invpy.com/diff/memorypuzzle to see if the differences between your code and the code in the book.

You'll probably pick up a few ideas about how the program works just by typing it in once. And when you're done typing it in, you can then play the game for yourself.

### Credits and Imports

```1. # Memory Puzzle
2. # By Al Sweigart al@inventwithpython.com
3. # http://inventwithpython.com/pygame
4. # Released under a "Simplified BSD" license
5.
6. import random, pygame, sys
7. from pygame.locals import *
```

This program makes use of many functions in other modules, so it imports those modules on line 6. Line 7 is also an import statement in the from (module name) import * format, which means you do not have to type the module name in front of it. There are no functions in the pygame.locals module, but there are several constant variables in it that we want to use such as MOUSEMOTION, KEYUP, or QUIT Using this style of import statement, we only have to type MOUSEMOTION rather than pygame.locals.MOUSEMOTION.

```9. FPS = 30 # frames per second, the general speed of the program
10. WINDOWWIDTH = 640 # size of window's width in pixels
11. WINDOWHEIGHT = 480 # size of windows' height in pixels
12. REVEALSPEED = 8 # speed boxes' sliding reveals and covers
13. BOXSIZE = 40 # size of box height & width in pixels
14. GAPSIZE = 10 # size of gap between boxes in pixels
```

The game programs in this book use a lot of constant variables. You might not realize why they're so handy. For example, instead of using the BOXSIZE variable in our code we could just type the integer 40 directly in the code. But there are two reasons to use constant variables. First, if we ever wanted to change the size of each box later, we would have to go through the entire program and find and replace each time we typed 40. By just using the BOXSIZE constant, we only have to change line 13 and the rest of the program is already up to date. This is much better, especially since we might use the integer value 40 for something else besides the size of the white boxes, and changing that 40 accidentally would cause bugs in our program.

Second, it makes the code more readable. Go down to the next section and look at line 18. This sets up a calculation for the XMARGIN constant, which is how many pixels are on the side of the entire board. It is a complicated looking expression, but you can carefully piece out what it means. Line 18 looks like this:

`XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * (BOXSIZE + GAPSIZE))) / 2)`

But if line 18 didn't use constant variables, it would look like this:

`XMARGIN = int((640 – (10 * (40 + 10))) / 2)`

Now it becomes impossible to remember what exactly the programmer intended to mean. These unexplained numbers in the source code are often called magic numbers. Whenever you find yourself entering magic numbers, you should consider replacing them with a constant variable instead. To the Python interpreter, both of the previous lines are the exact same. But to a human programmer who is reading the source code and trying to understand how it works, the second version of line 18 doesn't make much sense at all! Constants really help the readability of source code. Of course, you can go too far replacing numbers with constant variables. Look at the following code:

```ZERO = 0
ONE = 1
TWO = 99999999
TWOANDTHREEQUARTERS = 2.75
```

Don't write code like that. That's just silly.

### Sanity Checks with assert Statements

```15. BOARDWIDTH = 10 # number of columns of icons
16. BOARDHEIGHT = 7 # number of rows of icons
17. assert (BOARDWIDTH * BOARDHEIGHT) % 2 == 0, 'Board needs to have an even
number of boxes for pairs of matches.'
18. XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * (BOXSIZE + GAPSIZE))) / 2)
19. YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * (BOXSIZE + GAPSIZE))) / 2)
```

The assert statement on line 15 ensures that the board width and height we've selected will result in an even number of boxes (since we will have pairs of icons in this game). There are three parts to an assert statement: the assert keyword, an expression which, if False, results in crashing the program. The third part (after the comma after the expression) is a string that appears if the program crashes because of the assertion.

The assert statement with an expression basically says, "The programmer asserts that this expression must be True, otherwise crash the program". This is a good way of adding a sanity check to your program to make sure that if the execution ever passes an assertion we can at least know that that code is working as expected.

### Telling If a Number is Even or Odd

If the product of the board width and height is divided by two and has a remainder of 0 (the % modulus operator evaluates what the remainder is) then the number is even. Even numbers divided by two will always have a remainder of zero. Odd numbers divided by two will always have a remainder of one. This is a good trick to remember if you need your code to tell if a number is even or odd:

```>>> isEven = someNumber % 2 == 0
>>> isOdd = someNumber % 2 != 0
```

In the above case, if the integer in someNumber was even, then isEven will be True. If it was odd, then isOdd will be True.

### Crash Early and Crash Often!

Having your program crash is a bad thing. It happens when your program has some mistake in the code and cannot continue. But there are some cases where crashing a program early can avoid worse bugs later.

If the values we chose for BOARDWIDTH and BOARDHEIGHT that we chose on line 15 and 16 result in a board with an odd number of boxes (such as if the width were 3 and the height were 5), then there would always be one left over icon that would not have a pair to be matched with. This would cause a bug later on in the program, and it could take a lot of debugging work to figure out that the real source of the bug is at the very beginning of the program. In fact, just for fun, try commenting out the assertion so it doesn't run, and then setting the BOARDWIDTH and BOARDHEIGHT constants both to odd numbers. When you run the program, it will immediately show an error happening on a line 149 in mcodeorypuzzle.py, which is in getRandomizedBoard() function!

```Traceback (most recent call last):
File "C:\book2svn\src\memorypuzzle.py", line 292, in <module>
main()
File "C:\book2svn\src\memorypuzzle.py", line 58, in main
mainBoard = getRandomizedBoard()
File "C:\book2svn\src\memorypuzzle.py", line 149, in getRandomizedBoard
columns.append(icons[0])
IndexError: list index out of range
```

We could spend a lot of time looking at getRandomizedBoard() trying to figure out what's wrong with it before realizing that getRandomizedBoard() is perfectly fine: the real source of the bug was on line 15 and 16 where we set the BOARDWIDTH and BOARDHEIGHT constants.

The assertion makes sure that this never happens. If our code is going to crash, we want it to crash as soon as it detects something is terribly wrong, because otherwise the bug may not become apparent until much later in the program. Crash early!

You want to add assert statements whenever there is some condition in your program that must always, always, always be True. Crash often! You don't have to go overboard and put assert statements everywhere, but crashing often with asserts goes a long way in detecting the true source of a bug. Crash early and crash often! (In your code that is. Not, say, when riding a pony).

### Making the Source Code Look Pretty

```21. #            R    G    B
22. GRAY     = (100, 100, 100)
23. NAVYBLUE = ( 60,  60, 100)
24. WHITE    = (255, 255, 255)
25. RED      = (255,   0,   0)
26. GREEN    = (  0, 255,   0)
27. BLUE     = (  0,   0, 255)
28. YELLOW   = (255, 255,   0)
29. ORANGE   = (255, 128,   0)
30. PURPLE   = (255,   0, 255)
31. CYAN     = (  0, 255, 255)
32.
33. BGCOLOR = NAVYBLUE
34. LIGHTBGCOLOR = GRAY
35. BOXCOLOR = WHITE
36. HIGHLIGHTCOLOR = BLUE
```

Remember that colors in Pygame are represented by a tuple of three integers from 0 to 255. These three integers represent the amount of red, green, and blue in the color which is why these tuples are called RGB values. Notice the spacing of the tuples on lines 22 to 31 are such that the R, G, and B integers line up. In Python the indentation (that is, the space at the beginning of the line) is needs to be exact, but the spacing in the rest of the line is not so strict. By spacing the integers in the tuple out, we can clearly see how the RGB values compare to each other. (More info on spacing and indentation is as http://invpy.com/whitespace ).

It is a nice thing to make your code more readable this way, but don't bother spending too much time doing it. Code doesn't have to be pretty to work. At a certain point, you'll just be spending more time typing spaces than you would have saved by having readable tuple values.

### Using Constant Variables Instead of Strings

```38. DONUT = 'donut'
39. SQUARE = 'square'
40. DIAMOND = 'diamond'
41. LINES = 'lines'
42. OVAL = 'oval'
```

The program also sets up constant variables for some strings. These constants will be used in the data structure for the board, tracking which spaces on the board have which icons. Using a constant variable instead of the string value is a good idea. Look at the following code, which comes from line 187:

```if shape == DONUT:
```

The shape variable will be set to one of the strings 'donut', 'square', 'diamond', 'lines', or 'oval' and then compared to the DONUT constant. If we made a typo when writing line 187, for example, something like this:

```if shape == DUNOT:
```

Then Python would crash, giving an error message saying that there is no variable named DUNOT. This is good. Since the program has crashed on line 187, when we check that line it will be easy to see that the bug was caused by a typo. However, if we were using strings instead of constant variables and made the same typo, line 187 would look like this:

```if shape == 'dunot':
```

This is perfectly acceptable Python code, so it won't crash at first when you run it. However, this will lead to weird bugs later on in our program. Because the code does not immediately crash where the problem is caused, it can be much harder to find it.

### Making Sure We Have Enough Icons

```44. ALLCOLORS = (RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, CYAN)
45. ALLSHAPES = (DONUT, SQUARE, DIAMOND, LINES, OVAL)
46. assert len(ALLCOLORS) * len(ALLSHAPES) * 2 >= BOARDWIDTH * BOARDHEIGHT,
"Board is too big for the number of shapes/colors defined."
```

In order for our game program to be able to create icons of every possible color and shape combination, we need to make a tuple that holds all of these values. There is also another assertion on line 46 to make sure that there are enough color/shape combinations for the size of the board we have. If there isn't, then the program will crash on line 46 and we will know that we either have to add more colors and shapes, or make the board width and height smaller. With 7 colors and 5 shapes, we can make 35 (that is, 7 x 5) different icons. And because we'll have a pair of each icon, that means we can have a board with up to 70 (that is, 35 x 2, or 7 x 5 x 2) spaces.

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

### Студенты

Элмурод Жураев
 Узбекистан
Константин Кривко
 Россия, Благовещенск