(рис 3.1)
In the Memory Puzzle game, several icons are
One for loop inside of another for loop. These are called for loops. 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 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 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
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 >>>
This
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
You'll probably
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 *
At the top of the program are comments about what the game is, who made it, and where the user
could find more information. There's also a note that the
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 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 BOXSIZE variable in our code we could just
type the integer 40 directly in the code. But there are two 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
Second, it makes the code more XMARGIN constant, which is how many pixels are on the side of the
entire board. It is a complicated looking expression, but you can
XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * (BOXSIZE + GAPSIZE))) / 2)
But if line 18 didn't use
XMARGIN = int((640 – (10 * (40 + 10))) / 2)
Now it becomes impossible to remember what
ZERO = 0 ONE = 1 TWO = 99999999 TWOANDTHREEQUARTERS = 2.75
Don't write code like that. That's just silly.
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
The assert statement with an expression basically says, "The programmer asserts that this
expression must be True, otherwise
If the product of the board width and height is
>>> 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
isOdd will be True.
Having your program
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 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
You want to add assert statements whenever there is some condition in your program that
must always, always, always be True. assert statements everywhere, but crashing often with asserts goes a long way in detecting the
true source of a bug.
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
It is a nice thing to make your code more
38. DONUT = 'donut' 39. SQUARE = 'square' 40. DIAMOND = 'diamond' 41. LINES = 'lines' 42. OVAL = 'oval'
The program also sets up
if shape == DONUT:
The shape variable will be set to one of the strings 'donut', 'square', ' 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 DUNOT.
This is good. Since the program has crashed on line 187, when we check that line it will be
if shape == 'dunot':
This is perfectly acceptable Python code, so it won't
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
You might have noticed that the ALLCOLORS and ALLSHAPES variables are
For an example of trying to change values in lists and
>>> listVal = [1, 1, 2, 3, 5, 8] >>> tupleVal = (1, 1, 2, 3, 5, 8) >>> listVal[4] = 'hello!' >>> listVal [1, 1, 2, 3, 'hello!', 8] >>> tupleVal[4] = 'hello!' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment >>> tupleVal (1, 1, 2, 3, 5, 8) >>> tupleVal[4] 5
Notice that when we try to change the item at index 2 in the
There is a silly benefit and an important benefit to
The important benefit to using
You can still assign a new
>>> tupleVal = (1, 2, 3) >>> tupleVal = (1, 2, 3, 4)
The tupleVal, and
Strings are also an immutable data type. You can use the
>>> strVal = 'Hello' >>> strVal[1] 'e' >>> strVal[1] = 'X' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'str' object does not support item assignment
Also, one minor details about
oneValueTuple = (42, )
If you forget this
variableA = (5 * 6) variableB = (5 * 6, )
The value that is stored in variableA is just the integer 30. However, the expression for
variableB's
You can convert between list and list() function and it will return a list form of that
function and it will return a
>>> spam = (1, 2, 3, 4) >>> spam = list(spam) >>> spam [1, 2, 3, 4] >>> spam = tuple(spam) >>> spam (1, 2, 3, 4) >>>
48. def main():
49. global FPSCLOCK, DISPLAYSURF
50. pygame.init()
51. FPSCLOCK = pygame.time.Clock()
52. DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
53.
54. mousex = 0 # used to store x coordinate of mouse event
55. mousey = 0 # used to store y coordinate of mouse event
56. pygame.display.set_caption('Memory Game')
This is the start of the main() function, which is where (main() function will be
Line 49 is a global statement. The global statement is the global keyword followed by a
main() function, those names are not for are the main() function will persist outside the main() function. We are
marking the FPSCLOCK and DISPLAYSURF variables as global because they are used in several
other functions in the program. (More info is at http://invpy.com/scope).
There are four simple rules to
You generally want to
Having a function as a separate mini-program that doesn't use
Basically, using
In the games in this book, pygame.init() main() function, they are set in the main() function and must be global for
other functions to see them. But the
If you don't
58. mainBoard = getRandomizedBoard() 59. revealedBoxes = generateRevealedBoxesData(False)
The getRandomizedBoard() function returns a generateRevealedBoxesData() function returns a
If we have a list value stored in a variable named , we could access a value in that list with
the to is itself a list, then we could use another set of , which would . Using the this notation of lists of lists makes it variable will store icons in it, if we
wanted to get the icon on the board at the position (4, 5) then we could just use the expression
. Since the icons themselves are stored as two-item
Here's an small example. Say the board looked like this:
(рис 3.2)
The corresponding
mainBoard = [[(DONUT, BLUE), (LINES, BLUE), (SQUARE, ORANGE)], [(SQUARE, GREEN), (DONUT, BLUE), (DIAMOND, YELLOW)], [(SQUARE, GREEN), (OVAL, YELLOW), (SQUARE, ORANGE)], [(DIAMOND, YELLOW), (LINES, BLUE), (OVAL, YELLOW)]]
(If your book is in black and white, you can see a color version of the above picture at
http://invpy.com/memoryboard). You'll notice that will correspond to the
icon at the (x, y) coordinate on the board.
Meanwhile, the "True if the box at that x, y coordinate is
False if it is False to the
generateRevealedBoxesData() function sets all of the Boolean values to False (This
function is
These two
61. firstSelection = None # stores the (x, y) of the first box clicked. 62. 63. DISPLAYSURF.fill(BGCOLOR) 64. startGameAnimation(mainBoard)
Line 61 sets up a variable called firstSelection with the value None. (None is the value
that represents a lack of a value. It is the only value of the data type, NoneType. More info at
http://invpy.com/None). When the player clicks on an icon on the board, the program needs to
track if this was the first icon of the pair that was clicked on or the second icon. If
firstSelection is None, the click was on the first icon and we store the XY coordinates in
the firstSelection variable as a None, which is how the program tracks
that it is the second icon click. Line 63 fills the entire
If you've played the Memory Puzzle game, you'll notice that at the beginning of the game, all of
the boxes are quickly startGameAnimation() function,
which is
It's important to give the player this sneak
66. while True: # main game loop 67. mouseClicked = False 68. 69. DISPLAYSURF.fill(BGCOLOR) # drawing the window 70. drawBoard(mainBoard, revealedBoxes)
The game loop is an
The game state for the Memory Puzzle program is stored in the following variables:
On each iteration of the game loop in the Memory Puzzle program, the mouseClicked variable
stores a Boolean value that is True if the player has clicked the
On line 69, the drawBoard() to draw the current state of the
board based on the board and "
Remember that our drawing functions only draw on the in-memory display pygame.display.update(), which is done at the end of the game loop on line 121.
72. for event in pygame.event.get(): # event handling loop 73. if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE): 74. pygame.quit() 75. sys.exit() 76. elif event.type == MOUSEMOTION: 77. mousex, mousey = event.pos 78. elif event.type == MOUSEBUTTONUP: 79. mousex, mousey = event.pos 80. mouseClicked = True
The for loop on line 72 pygame.Event objects returned by the pygame.event.get() call.
If the event object was a either a QUIT event or a KEYUP event for the Esc key, then the program
should terminate. Otherwise, in the event of a MOUSEMOTION event (that is, the MOUSEBUTTONUP event (that is, a mousex and mousey
variables. If this was a MOUSEBUTTONUP event, mouseClicked should also be set to True.
Once we have handled all of the events, the values stored in mousex, mousey, and
mouseClicked will tell us any input that player has given us. Now we should update the game
state and draw the results to the screen.
82. boxx, boxy = getBoxAtPixel(mousex, mousey) 83. if boxx != None and boxy != None: 84. # The mouse is currently over a box. 85. if not revealedBoxes[boxx][boxy]: 86. drawHighlightBox(boxx, boxy)
The getBoxAtPixel() function will return a getBoxAtPixel()
does this is mousex and mousey
coordinates were over a box, a boxx and boxy. If the (None, None) is returned by
the function and boxx and boxy will both have None stored in them.
We are only interested in the case where boxx and boxy do not have None in them, so the next
several lines of code are in the block following the if statement on line 83 that checks for this
case. If execution has come inside this block, we know the user has the mouseClicked).
The if statement on line 85 checks if the box is revealedBoxes[boxx][boxy]. If it is False, then we know the box is drawHighlightBox() function,
which is
87. if not revealedBoxes[boxx][boxy] and mouseClicked: 88. revealBoxesAnimation(mainBoard, [(boxx, boxy)]) 89. revealedBoxes[boxx][boxy] = True # set the box as "revealed"
On line 87, we check if the revealBoxesAnimation() function (which is, as with all the other functions main() calls,
revealedBoxes[boxx][boxy] = True that the
If you comment out line 89 and then run the program, you’ll notice that after clicking on a box
the revealedBoxes[boxx][boxy] is still set to False, so on the next iteration of the
game loop, the board is drawn with this box
90. if firstSelection == None: # the current box was the first box clicked 91. firstSelection = (boxx, boxy) 92. else: # the current box was the second box clicked 93. # Check if there is a match between the two icons. 94. icon1shape, icon1color = getShapeAndColor(mainBoard, firstSelection[0], firstSelection[1]) 95. icon2shape, icon2color = getShapeAndColor(mainBoard, boxx, boxy)
Before the execution entered the game loop, the firstSelection variable was set to None.
Our program will True, that means this is the first of the two possibly matching boxes that was clicked. We
want to play the firstSelection variable to a
If this is the second box the player has clicked on, we want to play the getShapeAndColor()
function (ALLCOLORS and ALLSHAPES
97. if icon1shape != icon2shape or icon1color != icon2color: 98. # Icons don't match. Re-cover up both selections. 99. pygame.time.wait(1000) # 1000 milliseconds= 1 sec 100. coverBoxesAnimation(mainBoard, [(firstSelection[0], firstSelection[1]), (boxx, boxy)]) 101. revealedBoxes[firstSelection[0]][firstSelection [1]] = False 102. revealedBoxes[boxx][boxy] = False
The if statement on line 97 checks if either the shapes or colors of the two icons don’t match. If
this is the case, then we want to pause the game for 1000 pygame.time.wait(1000) so that the player has a chance to see that the
two icons don’t match. Then the "cover up" animation plays for both boxes. We also want to
update the game state to mark these boxes as not
103. elif hasWon(revealedBoxes): # check if all pairs found 104. gameWonAnimation(mainBoard) 105. pygame.time.wait(2000) 106. 107. # Reset the board 108. mainBoard = getRandomizedBoard() 109. revealedBoxes = generateRevealedBoxesData(False) 110. 111. # Show the fully unrevealed board for a second. 112. drawBoard(mainBoard, revealedBoxes) 113. pygame.display.update() 114. pygame.time.wait(1000) 115. 116. # Replay the start game animation. 117. startGameAnimation(mainBoard) 118. firstSelection = None # reset firstSelection variable
Otherwise, if line 97’s condition was False, then the two icons must be a match. The program
doesn’t really have to do anything else to the boxes at that point: it can just leave both boxes in
the hasWon() function, which returns True if the
board is in a winning state (that is, all of the boxes are
If that is the case, we want to play the "game won" animation by calling
gameWonAnimation(), then pause slightly to let the player revel in their victory, and then
reset the and revealedBoxes to start a new game.
Line 117 plays the "start game" animation again. After that, the program execution will just loop through the game loop as usual, and the player can continue playing until they quit the program.
No matter if the two boxes were matching or not, after the second box was clicked line 118 will
set the firstSelection variable back to None so that the next box the player clicks on will
be
120. # Redraw the screen and wait a clock tick 121. pygame.display.update() 122. FPSCLOCK.tick(FPS)
At this point, the game state has been updated depending on the player’s input, and the latest
game state has been drawn to the DISPLAYSURF display pygame.display.update() to draw the DISPLAYSURF
Line 9 set the
In order to run at 30 frames per second, each frame must be drawn in 1/30 th
of a second. This
means that pygame.display.update() and all the code in the game loop must execute in
under 33.3 method of the
pygame.Clock object in FPSCLOCK to have to it pause the program for the rest of the 33.3
Since this is done at the very end of the game loop, it ensures that each iteration of the game loop
takes (at least) 33.3 pygame.display.update() call
and the code in the game loop takes longer than 33.3 method will
not wait at all and immediately return.
I’ve kept saying that the other functions would be main() function and you have an main().
125. def generateRevealedBoxesData(val): 126. revealedBoxes = [] 127. for i in range(BOARDWIDTH): 128. revealedBoxes.append([val] * BOARDHEIGHT) 129. return revealedBoxes
The generateRevealedBoxesData() function needs to create a list of lists of Boolean
values. The Boolean value will just be the one that is passed to the function as the val parameter.
We start the revealedBoxes variable.
In order to make the revealedBoxes[x][y] structure, we need to
make sure that the inner lists represent the vertical columns of the board and not the horizontal
rows. Otherwise, the revealedBoxes[y][x] structure.
The for loop will create the columns and then append them to revealedBoxes. The columns
are created using list replication, so that the column list has as many val values as the
BOARDHEIGHT dictates.
132. def getRandomizedBoard(): 133. # Get a list of every possible shape in every possible color. 134. icons = [] 135. for color in ALLCOLORS: 136. for shape in ALLSHAPES: 137. icons.append( (shape, color) )
The board
The first step to do this is to create a list with every possible combination of shape and color.
ALLCOLORS and ALLSHAPES, so for loops on lines 135 and 136 will go through every possible shape for every possible color.
These are each added to the list in the icons variable on line 137.
139. random.shuffle(icons) # randomize the order of the icons list 140. numIconsUsed = int(BOARDWIDTH * BOARDHEIGHT / 2) # calculate how many icons are needed 141. icons = icons[:numIconsUsed] * 2 # make two of each 142. random.shuffle(icons)
But remember, there may be more possible combinations than spaces on the board. We need to
calculate the number of spaces on the board by multiplying BOARDWIDTH by BOARDHEIGHT.
Then we numIconsUsed.
Line 141 uses list slicing to grab the first numIconsUsed number of icons in the list. (If you’ve
forgotten how list slicing works, check out http://invpy.com/slicing .) This list has been shuffled
on line 139, so it won’t always be the same icons each game. Then this list is replicated by using
the * operator so that there are two of each of the icons. This new doubled up list will overwrite
the old list in the icons variable. Since the first shuffle() method again to randomly mix up the order of the icons.
144. # Create the board data structure, with randomly placed icons. 145. board = [] 146. for x in range(BOARDWIDTH): 147. column = [] 148. for y in range(BOARDHEIGHT): 149. column.append(icons[0]) 150. del icons[0] # remove the icons as we assign them 151. board.append(column) 152. return board
Now we need to create a list of lists for
loops just like the generateRevealedBoxesData() function did. For each column on the
board, we will create a list of randomly selected icons. As we add icons to the column, on line
149 we will then delete them from the front of the icons list on line 150. This way, as the
icons list gets shorter and shorter, icons[0] will have a different icon to add to the columns.
To picture this better, type the following code into the interactive shell. Notice how the del
statement changes the myList list.
>>> myList = ['cat', 'dog', 'mouse', 'lizard'] >>> del myList[0] >>> myList ['dog', 'mouse', 'lizard'] >>> del myList[0] >>> myList ['mouse', 'lizard'] >>> del myList[0] >>> myList ['lizard'] >>> del myList[0] >>> myList [] >>>
Because we are deleting the item at the front of the list, the other items shift forward so that the next item in the list becomes the new "first" item. This is the same way line 150 works.
155. def splitIntoGroupsOf(groupSize, theList): 156. # splits a list into a list of lists, where the inner lists have at 157. # most groupSize number of items. 158. result = [] 159. for i in range(0, len(theList), groupSize): 160. result.append(theList[i:i + groupSize]) 161. return result
The splitIntoGroupsOf() function (which will be called by the
startGameAnimation() function) splits a list into a list of lists, where the inner lists have
groupSize number of items in them (The last list could have less if there are less than
groupSize items left over).
The call to range() on line 159 uses the three-parameter form of range() (If you are
unfamiliar with this form, take a look at http://invpy.com/range ). Let’s use an example. If the
length of the list is 20 and the groupSize parameter is 8, then range(0,len(theList), groupSize) evaluates to range(0, 20, 8). This will give the i
variable the values 0, 8, and 16 for the three iterations of the for loop.
The list slicing on line 160 with theList[i:i + groupSize] creates the lists that are
added to the result list. On each iteration where i is 0, 8, and 16 (and groupSize is 8), this
list slicing expression would be theList[0:8], then theList[8:16] on the second
iteration, and then theList[16:24] on the third iteration.
Note that even though the largest index of theList would be 19 in our example,
theList[16:24] won’t raise an IndexError error even though 24 is larger than 19. It will
just create a list slice with the remaining items in the list. List slicing doesn’t theList. It just copies a result variable on line 160. So
when we return result at the end of this function, we are returning a list of lists.
164. def leftTopCoordsOfBox(boxx, boxy): 165. # Convert board coordinates to pixel coordinates 166. left = boxx * (BOXSIZE + GAPSIZE) + XMARGIN 167. top = boxy * (BOXSIZE + GAPSIZE) + YMARGIN 168. return (left, top)
The game loop is an
You should be familiar with
Here's a picture of the game and the two different
(рис 3.3)
The leftTopCoordsOfBox() function will take box coordinates and return pixel
coordinates. Because a box takes up multiple pixels on the screen, we will always return the
single pixel at the top left leftTopCoordsOfBox() function will often be used when we need pixel coordinates for
drawing these boxes.
171. def getBoxAtPixel(x, y): 172. for boxx in range(BOARDWIDTH): 173. for boxy in range(BOARDHEIGHT): 174. left, top = leftTopCoordsOfBox(boxx, boxy) 175. boxRect = pygame.Rect(left, top, BOXSIZE, BOXSIZE) 176. if boxRect.collidepoint(x, y): 177. return (boxx, boxy) 178. return (None, None)
We will also need a function to convert from pixel coordinates (which the collidepoint() method that you can pass X and Y
coordinates too and it will return True if the coordinates are inside (that is, collide with) the Rect
object's area.
In order to find which box the collidepoint() method on a Rect object with those coordinates.
When collidepoint() returns True, we know we have found the box that was clicked on or
moved over and will return the box coordinates. If none of them return True, then the
getBoxAtPixel() function will return the value (None, None). This None because the getBoxAtPixel() is
181. def drawIcon(shape, color, boxx, boxy): 182. quarter = int(BOXSIZE * 0.25) # syntactic sugar 183. half = int(BOXSIZE * 0.5) # syntactic sugar 184. 185. left, top = leftTopCoordsOfBox(boxx, boxy) # get pixel coords from board coords
The drawIcon() function will draw an icon (with the specified shape and color) at the
space whose coordinates are given in the boxx and boxy parameters. Each possible shape has a
different set of Pygame drawing if and elif
statements to
The X and Y coordinates of the left and top edge of the box can be obtained by calling the
leftTopCoordsOfBox() function. The width and height of the box are both set in the
BOXSIZE constant. However, many of the shape drawing quarter and
. We could just as easily have the code int(BOXSIZE * 0.25) instead of the variable
quarter, but this way the code becomes easier to read since it is more obvious what quarter
means rather than int(BOXSIZE * 0.25).
Such variables are an example of getRandomizedBoard() function, we could have easily made the code on lines 140 and
line 141 into a single line of code. But it's easier to read as two separate lines.) We don't need to
have the extra quarter and variables, but having them makes the code easier to read.
Code that is
186. # Draw the shapes 187. if shape == DONUT: 188. pygame.draw.circle(DISPLAYSURF, color, (left + half, top + half), half - 5) 189. pygame.draw.circle(DISPLAYSURF, BGCOLOR, (left + half, top + half), quarter - 5) 190. elif shape == SQUARE: 191. pygame.draw.rect(DISPLAYSURF, color, (left + quarter, top + quarter, BOXSIZE - half, BOXSIZE - half)) 192. elif shape == DIAMOND: 193. pygame.draw.polygon(DISPLAYSURF, color, ((left + half, top), (left + BOXSIZE - 1, top + half), (left + half, top + BOXSIZE - 1), (left, top + half))) 194. elif shape == LINES: 195. for i in range(0, BOXSIZE, 4): 196. pygame.draw.line(DISPLAYSURF, color, (left, top + i), (left + i, top)) 197. pygame.draw.line(DISPLAYSURF, color, (left + i, top + BOXSIZE - 1), (left + BOXSIZE - 1, top + i)) 198. elif shape == OVAL: 199. pygame.draw.ellipse(DISPLAYSURF, color, (left, top + quarter, BOXSIZE, half))
Each of the donut, square,
202. def getShapeAndColor(board, boxx, boxy): 203. # shape value for x, y spot is stored in board[x][y][0] 204. # color value for x, y spot is stored in board[x][y][1] 205. return board[boxx][boxy][0], board[boxx][boxy][1]
The getShapeAndColor() function only has one line. You might wonder why we would
want a function instead of just typing in that one line of code whenever we need it. This is done
for the same
It's shape, color = getShapeAndColor() does.
But if you looked a code like shape, color = board[boxx][boxy][0], board[boxx][boxy][1], it would be a bit more difficult to figure out.
208. def drawBoxCovers(board, boxes, coverage): 209. # Draws boxes being covered/revealed. "boxes" is a list 210. # of two-item lists, which have the x y spot of the box. 211. for box in boxes: 212. left, top = leftTopCoordsOfBox(box[0], box[1]) 213. pygame.draw.rect(DISPLAYSURF, BGCOLOR, (left, top, BOXSIZE, BOXSIZE)) 214. shape, color = getShapeAndColor(board, box[0], box[1]) 215. drawIcon(shape, color, box[0], box[1]) 216. if coverage > 0: # only draw the cover if there is an coverage 217. pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, coverage, BOXSIZE)) 218. pygame.display.update() 219. FPSCLOCK.tick(FPS)
The drawBoxCovers() function has three parameters: the board
Since we want to use the same drawing code for each box in the boxes parameter, we will use a
for loop on line 211 so we execute the same code on each box in the boxes list. Inside this
for loop, the code should do three things: draw the background color (to paint over anything that
was there before), draw the icon, then draw however much of the leftTopCoordsOfBox() function will return the pixel coordinates of the top
left if statement on line 216 makes sure that if the number in
happens to be less than 0, we won't call the pygame.draw.rect() function.
When the parameter is 0, there is no is set to
20, there is a 20 pixel wide set to is the number in BOXSIZE, where the entire icon is completely
drawBoxCovers() is going to be called from a separate loop than the game loop. Because of
this, it needs to have its own calls to pygame.display.update() and
FPSCLOCK. to display the animation (This does mean that while inside this loop,
there is no code being run to handle any events being generated. That's fine, since the cover and
222. def revealBoxesAnimation(board, boxesToReveal): 223. # Do the "box reveal" animation. 224. for coverage in range(BOXSIZE, (-REVEALSPEED) - 1, - REVEALSPEED): 225. drawBoxCovers(board, boxesToReveal, coverage) 226. 227. 228. def coverBoxesAnimation(board, boxesToCover): 229. # Do the "box cover" animation. 230. for coverage in range(0, BOXSIZE + REVEALSPEED, REVEALSPEED): 231. drawBoxCovers(board, boxesToCover, coverage)
Remember that an animation is simply just displaying different images for brief revealBoxesAnimation() and coverBoxesAnimation() only need to draw an icon
with a drawBoxCovers() which can do this, and then have our animation drawBoxCovers() for each frame of animation. As we saw in the last section,
drawBoxCovers() makes a call to pygame.display.update() and
FPSCLOCK. itself.
To do this, we'll set up a for loop to make decreasing (in the case of
revealBoxesAnimation()) or increasing (in the case of coverBoxesAnimation())
numbers for the converage parameter. The amount that the variable will
REVEALSPEED constant. On line 12 we set this
constant to 8, meaning that on each call to drawBoxCovers(), the
Think of it like climbing stairs. If on each step you take, you climbed one stair, then it would take a normal amount of time to climb the entire staircase. But if you climbed two stairs at a time on each step (and the steps took just as long as before), you could climb the entire staircase twice as fast. If you could climb the staircase 8 stairs at a time, then you would climb the entire staircase 8 times as fast.
234. def drawBoard(board, revealed>): 235. # Draws all of the boxes in their covered or revealed state. 236. for boxx in range(BOARDWIDTH): 237. for boxy in range(BOARDHEIGHT): 238. left, top = leftTopCoordsOfBox(boxx, boxy) 239. if not revealed[boxx][boxy]: 240. # Draw a covered box. 241. pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, BOXSIZE, BOXSIZE)) 242. else: 243. # Draw the (revealed) icon. 244. shape, color = getShapeAndColor(board, boxx, boxy) 245. drawIcon(shape, color, boxx, boxy)
The drawBoard() function makes a call to drawIcon() for each of the boxes on the board.
The for loops on lines 236 and 237 will loop through every possible X and Y coordinate
for the boxes, and will either draw the icon at that location or draw a white square instead (to
represent a
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 pygame.draw.rect() to make a rectangle with a width of 4 pixels.
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 for loops on lines 257 and 258 will add (X, Y) boxes
variable.
We will
To change up the boxes each time a game starts, we will call the random.shuffle() function
to randomly shuffle the order of the
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.
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 revealBoxesAnimation() and coverBoxesAnimation() functions will draw over the
spaces of these
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 coverBoxesAnimation() wil
animate the for loop goes to the next
iteration to animate the next set of 8 boxes.
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
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 "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 for
loops like this:
for x in revealedBoxes:
for y in revealedBoxes[x]:
if False == revealedBoxes[x][y]:
return False
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 main() function.
First, this lets you have main()
function would have to become
Second, this also lets you import the program so that you can call and test 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
That’s why the code is in a separate function that we have named main(). Then we check the
__name__ to see if we should call the main() function or not. This
variable is automatically set by the Python '__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
A lot of the suggestions in this
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
As an example, here is an
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.
This for loops,
One
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
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.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.