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

Slide puzzle

< Лекция 3 || Лекция 4: 1234 || Лекция 5 >

Creating a New Puzzle

295. def generateNewPuzzle(numSlides):
296.     # From a starting configuration, make numSlides number of moves (and
297.     # animate these moves).
298.     sequence = []
299.     board = getStartingBoard()
300.     drawBoard(board, '')
301.     pygame.display.update()
302.     pygame.time.wait(500) # pause 500 milliseconds for effect

The generateNewPuzzle() function will be called at the start of each new game. It will create a new board data structure by calling getStartingBoard() and then randomly scramble it. The first few lines of generateNewPuzzle() get the board and then draw it to the screen (freezing for half a second to let the player see the fresh board for a moment).

303.     lastMove = None
304.     for i in range(numSlides):
305.         move = getRandomMove(board, lastMove)
306.         slideAnimation(board, move, 'Generating new puzzle...',
int(TILESIZE / 3))
307.         makeMove(board, move)
308.         sequence.append(move)
309.         lastMove = move
310.     return (board, sequence)

The numSlides parameter will show tell the function how many of these random moves to make. The code for doing a random move is the getRandomMove() call on line 305 to get the move itself, then call slideAnimation() to perform the animation on the screen. Because doing the slide animation does not actually update the board data structure, we update the board by calling makeMove() on line 307.

We need to keep track of each of the random moves that was made so that the player can click the "Solve" button later and have the program undo all these random moves. (The "Being Smart By Using Stupid Code" section talks about why and how we do this.) So the move is appended to the list of moves in sequence on line 308.

Then we store the random move in a variable called lastMove which will be passed to getRandomMove() on the next iteration. This prevents the next random move from undoing the random move we just performed.

All of this needs to happen numSlides number of times, so we put lines 305 to 309 inside a for loop. When the board is done being scrambled, then we return the board data structure and also the list of the random moves made on it.

Animating the Board Reset

313. def resetAnimation(board, allMoves):
314.     # make all of the moves in allMoves in reverse.
315.     revAllMoves = allMoves[:] # gets a copy of the list
316.     revAllMoves.reverse()
317.
318.     for move in revAllMoves:
319.         if move == UP:
320.             oppositeMove = DOWN
321.         elif move == DOWN:
322.             oppositeMove = UP
323.         elif move == RIGHT:
324.             oppositeMove = LEFT
325.         elif move == LEFT:
326.             oppositeMove = RIGHT
327.         slideAnimation(board, oppositeMove, '', int(TILESIZE / 2))
328.         makeMove(board, oppositeMove)

When the player clicks on "Reset" or "Solve", the Slide Puzzle game program needs to undo all of the moves that were made to the board. The list of directional values for the slides will be passed as the argument for the allMoves parameter.

Line 315 uses list slicing to create a duplicate of the allMoves list. Remember that if you don't specify a number before the :, then Python assumes the slice should start from the very beginning of the list. And if you don't specify a number after the :, then Python assumes the slice should keep going to the very end of the list. So allMoves[:] creates a list slice of the entire allMoves list. This makes a copy of the actual list to store in revAllMoves, rather than just a copy of the list reference. (See http://invpy.com/references for details.)

To undo all the moves in allMoves, we need to perform the opposite move of the moves in allMoves, and in reverse order. There is a list method called reverse() which will reverse the order of the items in a list. We call this on the revAllMoves list on line 316.

Thefor loop on line 318 iterates over the list of directional values. Remember, we want the opposite move, so the if and elif statements from line 319 to 326 set the correct directional value in the oppositeMove variable. Then we call slideAnimation() to perform the animation, and makeMove() to update the board data structure.

331. if __name__ == '__main__':
332.     main()

Just like in the Memory Puzzle game, after all the def statements have been executed to create all the functions, we call the main() function to begin the meat of the program.

That's all there is to the Slide Puzzle program! But let's talk about some general programming concepts that came up in this game.

Time vs. Memory Tradeoffs

Of course, there are a few different ways to write the Slide Puzzle game so that it looks and acts the exact same way even though the code is different. There are many different ways the a program that does a task could be written. The most common differences are making tradeoffs between execution time and memory usage.

Usually, the faster a program can run, the better it is. This is especially true with programs that need to do a lot of calculations, whether they are scientific weather simulators or games with a large amount of detailed 3D graphics to draw. It's also good to use the least amount of memory possible. The more variables and the larger the lists your program uses, the more memory it takes up (You can find out how to measure your program's memory usage and execution time at http://invpy.com/profiling).

Right now, the programs in this book aren't big and complicated enough where you have to worry about conserving memory or optimizing the execution time. But it can be something to consider as you become a more skilled programmer.

For example, consider the getBlankPosition() function. This function takes time to run, since it goes through all the possible board coordinates to find where the blank space is. Instead, we could just have a blankspacex and blankspacey variable which would have these XY coordinates so we would not have to look through the entire board each time we want to know where it was (We would also need code that updates the blankspacex and blankspacey variables whenever a move is done. This code could go in makeMove()). Using these variables would take up more memory, but they would save you on execution time so your program would run faster.

Another example is that we keep a board data structure in the solved state in the SOLVEDBOARD variable, so that we can compare the current board to SOLVEDBOARD to see if the player has solved the puzzle. Each time we wanted to do this check, we could just call the getStartingBoard() function and compare the returned value to the current board. Then we would not need the SOLVEDBOARD variable. This would save us a little bit of memory, but then our program would take longer to run because it is re-creating the solved-state board data structure each time we do this check.

There is one thing you must remember though. Writing code that is readable is a very important skill. Code that is "readable" is code that is easy to understand, especially by programmers who did not write the code. If another programmer can look at your program's source code and figure out what it does without much trouble, then that program is very readable. Readability is important because when you want to fix bugs or add new features to your program (and bugs and ideas for new features always come up), then having a readable program makes those tasks much easier.

Nobody Cares About a Few Bytes

Also, there is one thing that might seem kind of silly to say in this book because it seem obvious, but many people wonder about it. You should know that using short variable names like x or num instead of longer, more descriptive variable names like blankx or numSlides does not save you any memory when your program actually runs. Using these longer variable names is better because they'll make your program more readable.

You might also come up with some clever tricks that do save a few bytes of memory here and there. One trick is that when you no longer need a variable, you can reuse that variable name for a different purpose instead of just using two differently named variables.

Try to avoid the temptation to do this. Usually, these tricks reduce code readability and make it harder to debug your programs. Modern computers have billions of bytes of memory, and saving a few bytes here and there really isn't worth making the code more confusing for human programmers.

Nobody Cares About a Few Million Nanoseconds

Similarly, there are times when you can rearrange your code in some way to make it slightly faster by a few nanoseconds. These tricks also usually make the code harder to read. When you consider that several billion nanoseconds have passed in the time it takes you to read this sentence, saving a few nanoseconds of execution time in your program won't be noticed by the player.

Summary

This chapter hasn't introduced any new Pygame programming concepts that the Memory Puzzle game didn't use, aside from using the copy() method of Surface objects. Just knowing a few different concepts will let you create completely different games.

For practice, you can download buggy versions of the Sliding Puzzle program from http://invpy.com/buggy/slidepuzzle.

< Лекция 3 || Лекция 4: 1234 || Лекция 5 >