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

Memory puzzle

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

Tuples vs. Lists, Immutable vs. Mutable

You might have noticed that the ALLCOLORS and ALLSHAPES variables are tuples instead of lists. When do we want to use tuples and when do we want to use lists? And what's the difference between them anyway?

Tuples and lists are the same in every way except two: tuples use parentheses instead of square brackets, and the items in tuples cannot be modified (but the items in lists can be modified). We often call lists mutable (meaning they can be changed) and tuples immutable (meaning they cannot be changed).

For an example of trying to change values in lists and tuples, look at the following code:

>>> 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 tuple, Python gives us an error message saying that tuple objects do not support "item assignment".

There is a silly benefit and an important benefit to tuple's immutability. The silly benefit is that code that uses tuples is slightly faster than code that uses lists. (Python is able to make some optimizations knowing that the values in a tuple will never change.) But having your code run a few nanoseconds faster is not important.

The important benefit to using tuples is similar to the benefit of using constant variables: it's a sign that the value in the tuple will never change, so anyone reading the code later will be able to say, "I can expect that this tuple will always be the same. Otherwise the programmer would have used a list." This also lets a future programmer reading your code say, "If I see a list value, I know that it could be modified at some point in this program. Otherwise, the programmer who wrote this code would have used a tuple."

You can still assign a new tuple value to a variable:

>>> tupleVal = (1, 2, 3)
>>> tupleVal = (1, 2, 3, 4)

The reason this code works is because the code isn't changing the (1, 2, 3) tuple on the second line. It is assigning an entirely new tuple (1, 2, 3, 4) to the tupleVal, and overwriting the old tuple value. You cannot however, use the square brackets to modify an item in the tuple.

Strings are also an immutable data type. You can use the square brackets to read a single character in a string, but you cannot change a single character in a string:

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

One Item Tuples Need a Trailing Comma

Also, one minor details about tuples: if you ever need to write code about a tuple that has one value in it, then it needs to have a trailing comma in it, such as this:

oneValueTuple = (42, )

If you forget this comma (and it is very easy to forget), then Python won't be able to tell the difference between this and a set of parentheses that just change the order of operations. For example, look at the following two lines of code:

variableA = (5 * 6)
variableB = (5 * 6, )

The value that is stored in variableA is just the integer 30. However, the expression for variableB's assignment statement is the single-item tuple value (30, ). Blank tuple values do not need a comma in them, they can just be a set of parentheses by themselves: ().

Converting Between Lists and Tuples

You can convert between list and tuple values just like you can convert between string and integer values. Just pass a tuple value to the list() function and it will return a list form of that tuple value. Or, pass a list value to the tuple() function and it will return a tuple form of that list value. Try typing the following into the interactive shell:

>>> spam = (1, 2, 3, 4)
>>> spam = list(spam)
>>> spam
[1, 2, 3, 4]
>>> spam = tuple(spam)
>>> spam
(1, 2, 3, 4)
>>>

The global statement, and Why Global Variables are Evil

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 (oddly enough) the main part of the game code is. The functions called in the main() function will be explained later in this chapter.

Line 49 is a global statement. The global statement is the global keyword followed by a comma-delimited list of variable names. These variable names are then marked as global variables. Inside the main() function, those names are not for local variables that might just happen to have the same name as global variables. They are the global variables. Any values assigned to them in 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 determine if a variable is local or global:

  1. If there is a global statement for a variable at the beginning of the function, then the variable is global.
  2. If the name of a variable in a function has the same name as a global variable and the function never assigns the variable a value, then that variable is the global variable.
  3. If the name of a variable in a function has the same name as a global variable and the function does assign the variable a value, then that variable is a local variable.
  4. If there isn't a global variable with the same name as the variable in the function, then that variable is obviously a local variable.

You generally want to avoid using global variables inside functions. A function is supposed to be like a mini-program inside your program with specific inputs (the parameters) and an output (the return value). But a function that reads and writes to global variables has additional inputs and output. Since the global variable could have been modified in many places before the function was called, it can be tricky to track down a bug involving a bad value set in the global variable.

Having a function as a separate mini-program that doesn't use global variables makes it easier to find bugs in your code, since the parameters of the function are clearly known. It also makes changing the code in a function easier, since if the new function works with the same parameters and gives the same return value, it will automatically work with the rest of the program just like the old function.

Basically, using global variables might make it easier to write your program but they generally make it harder to debug.

In the games in this book, global variables are mostly used for variables that would be global constants that never change, but need the pygame.init() function called first. Since this happens in the main() function, they are set in the main() function and must be global for other functions to see them. But the global variables are used as constants and don't change, so they are less likely to cause confusing bugs.

If you don't understand this, don't worry. Just write your code so that you pass in values to functions rather than have the functions read global variables as a general rule.

Data Structures and 2D Lists

58.     mainBoard = getRandomizedBoard()
59.     revealedBoxes = generateRevealedBoxesData(False)

The getRandomizedBoard() function returns a data structure that represents the state of the board. The generateRevealedBoxesData() function returns a data structure that represents which boxes are covered, respectively. The return values of these functions are two dimensional (2D) lists, or lists of lists. A list of lists of lists of values would be a 3D list. Another word for two or more dimensional lists is a multidimensional list.

If we have a list value stored in a variable named spam, we could access a value in that list with the square brackets, such as spam[2] to retrieve the third value in the list. If the value at spam[2] is itself a list, then we could use another set of square brackets to retrieve a value in that list. This would look like, for example, spam[2][4], which would retrieve the fifth value in the list that is the third value in spam. Using the this notation of lists of lists makes it easy to map a 2D board to a 2D list value. Since the mainBoard 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 mainBoard[4][5]. Since the icons themselves are stored as two-item tuples with the shape and color, the complete data structure is a list of list of two-item tuples. Whew!

Here's an small example. Say the board looked like this:


Рис. 3.2.

The corresponding data structure would be:

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 mainBoard[x][y] will correspond to the icon at the (x, y) coordinate on the board.

Meanwhile, the "revealed boxes" data structure is also a 2D list, except instead of two-item tuples like the board data structure, it has Boolean values: True if the box at that x, y coordinate is revealed, and False if it is covered up. Passing False to the generateRevealedBoxesData() function sets all of the Boolean values to False (This function is explained in detail later).

These two data structures are used to keep track of the state of the game board.

The "Start Game" Animation

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 tuple of two integers (one for the X value, the other for Y). On the second click the value will be this tuple and not None, which is how the program tracks that it is the second icon click. Line 63 fills the entire surface with the background color. This will also paint over anything that used to be on the surface, which gives us a clean slate to start drawing graphics on.

If you've played the Memory Puzzle game, you'll notice that at the beginning of the game, all of the boxes are quickly covered and uncovered randomly to give the player a sneak peek at which icons are under which boxes. This all happens in the startGameAnimation() function, which is explained later in this chapter.

It's important to give the player this sneak peek (but not long enough of a peek to let the player easily memorize the icon locations), because otherwise they would have no clue where any icons are. Blindly clicking on the icons isn't as much fun as having a little hint to go on.

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