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

Slide puzzle

< Лекция 3 || Лекция 4: 1234 || Лекция 5 >
Аннотация: 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.
Ключевые слова: board, Grid, with, tiling, Left, right, AND, ONE, blank space, random, player, slide, back, original, this, source coding, CAN, FROM, if, GET, error, look, Line, CHECK, Copy, paste, web form, SEE, book, previous, set, Top, ALL, Basic, LIKE, memory, Last, chapter, function call, VALUES, return, First, part, function, Object, Font, surface, used, Button, undo, new, create, Data, State, ITS, meaning, AS, change, second, Compare, data structure, off, integer, even, pass, mainboard, list, computer, require, algorithm, very, effort, memorize, example, perform, reverse order, DID, track, direction, tile, string, screen, draw, actual, end, event loop, separate, handling, explain, event handling, event, TIME, mouse, spot, release, coordinate, mouse button, NOT, still, CASE, additional, WHERE, method, area, tuple, NEXT, blank, store, USER, let, keyboard, arrow, statement, call, mouse click, neighboring, space, expression, python, evaluate, equal, exact, save, computer game, EXCEPT, hand, layout, UPDATE, animation, while, speed, Add, FAR, BIT, syntactics, explanation, entire, constant, internal, event queue, stay, program termination, consume, corner, column, row, size, Width, go, numbering, find, structure, NEST, PARAMETER, argument, review, conception, REFERENCES, EDGE, valid, slash, interpretation, split, long, parentheses, choose, restriction, random number, account, prevent, caller, remove, pixel, board space, nested loop, Height, keyword parameter, Position, adjustment, default, drawing, background, square, text, blit, positioning, amount, typing, center, point, transparent, color, scratch, CONDITION, border, Calculated, rectangle, thickness, length, compensate, finally, constant variable, calculate, comment, paint, running, adjacent, mean, half, slow, loop iteration, jump, include, FPS, correct, execution, scrambled, freeze, moment, SMART, section, SEQUENCE, iteration, game programming, duplicate, slice, Reverse, begin, general, programming, course, Write, ACTS, MOST, execution time, usage, true, scientific, 3d graphics, measure, programmer, readability, important, skill, trouble, FIX, HAVING, short, reuse, try, avoidance, reduce, debug, rearrangement, nanosecond, read, billion, sentence, practice, download

Drawing the Highlight


Рис. 4.1.

The board is a 4x4 grid with fifteen tiles (numbered 1 through 15 going left to right) and one blank space. The tiles start out in random positions, and the player must slide tiles around until the tiles are back in their original order.

Source Code to Slide Puzzle

This source code can be downloaded from http://invpy.com/slidepuzzle.py . 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/slidepuzzle to see if the differences between your code and the code in the book.

Second Verse, Same as the First

Much of the code in Wormy is similar to the previous games we've looked at, especially the constants being set at the start of the code.

1. # Slide Puzzle
2. # By Al Sweigart al@inventwithpython.com
3. # http://inventwithpython.com/pygame
4. # Creative Commons BY-NC-SA 3.0 US
5.
6. import pygame, sys, random
7. from pygame.locals import *
8.
9. # Create the constants (go ahead and experiment with different values)
10. BOARDWIDTH = 4  # number of columns in the board
11. BOARDHEIGHT = 4 # number of rows in the board
12. TILESIZE = 80
13. WINDOWWIDTH = 640
14. WINDOWHEIGHT = 480
15. FPS = 30
16. BLANK = None
17.
18. #                 R    G    B
19. BLACK =         (  0,   0,   0)
20. WHITE =         (255, 255, 255)
21. BRIGHTBLUE =    (  0,  50, 255)
22. DARKTURQUOISE = (  3,  54,  73)
23. GREEN =         (  0, 204,   0)
24.
25. BGCOLOR = DARKTURQUOISE
26. TILECOLOR = GREEN
27. TEXTCOLOR = WHITE
28. BORDERCOLOR = BRIGHTBLUE
29. BASICFONTSIZE = 20
30.
31. BUTTONCOLOR = WHITE
32. BUTTONTEXTCOLOR = BLACK
33. MESSAGECOLOR = WHITE
34.
35. XMARGIN = int((WINDOWWIDTH - (TILESIZE * BOARDWIDTH + (BOARDWIDTH - 1))) /
2)
36. YMARGIN = int((WINDOWHEIGHT - (TILESIZE * BOARDHEIGHT + (BOARDHEIGHT -
1))) / 2)
37.
38. UP = 'up'
39. DOWN = 'down'
40. LEFT = 'left'
41. RIGHT = 'right'

This code at the top of the program just handles all the basic importing of modules and creating constants. This is just like the beginning of the Memory Puzzle game from the last chapter.

Setting Up the Buttons

43. def main():
44.     global FPSCLOCK, DISPLAYSURF, BASICFONT, RESET_SURF, RESET_RECT,
NEW_SURF, NEW_RECT, SOLVE_SURF, SOLVE_RECT
45.
46.     pygame.init()
47.     FPSCLOCK = pygame.time.Clock()
48.     DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
49.     pygame.display.set_caption('Slide Puzzle')
50.     BASICFONT = pygame.font.Font('freesansbold.ttf', BASICFONTSIZE)
51.
52.     # Store the option buttons and their rectangles in OPTIONS.
53.     RESET_SURF, RESET_RECT = makeText('Reset',    TEXTCOLOR, TILECOLOR,
WINDOWWIDTH - 120, WINDOWHEIGHT - 90)
54.     NEW_SURF,   NEW_RECT   = makeText('New Game', TEXTCOLOR, TILECOLOR,
WINDOWWIDTH - 120, WINDOWHEIGHT - 60)
55.     SOLVE_SURF, SOLVE_RECT = makeText('Solve',    TEXTCOLOR, TILECOLOR,
WINDOWWIDTH - 120, WINDOWHEIGHT - 30)
56.
57.     mainBoard, solutionSeq = generateNewPuzzle(80)
58.     SOLVEDBOARD = getStartingBoard() # a solved board is the same as the
board in a start state.

Just like in the last chapter, the functions called from the main() function calls will be explained later in the chapter. For now, you just need to know what they do and what values they return. You don't need to know how they work.

The first part of the main() function will handle creating the window, Clock object, and Font object. The makeText() function is defined later in the program, but for now you just need to know that it returns a pygame.Surface object and pygame.Rect object which can be used to make clickable buttons. The Slide Puzzle game will have three buttons: a "Reset" button that will undo any moves the player has made, a "New" button that will create a new slide puzzle, and a "Solve" button that will solve the puzzle for the player.

We will need to have two board data structures for this program. One board will represent the current game state. The other board will have its tiles in the "solved" state, meaning that all the tiles are lined up in order. When the current game state's board is exactly the same as the solved board, then we know the player has won (We won't ever change this second one. It'll just be there to compare the current game state board to).

The generateNewPuzzle() will create a board data structure that started off in the ordered, solved state and then had 80 random slide moves performed on it (because we passed the integer 80 to it. If we want the board to be even more jumbled, then we can pass a larger integer to it). This will make the board into a randomly jumbled state that the player will have to solve (which will be stored in a variable named mainBoard). The generateNewBoard() also returns a list of all the random moves that were performed on it (which will be stored in a variable named solutionSeq).

Being Smart By Using Stupid Code

59.     allMoves = [] # list of moves made from the solved configuration

Solving a slide puzzle can be really tricky. We could program the computer to do it, but that would require us to figure out an algorithm that can solve the slide puzzle. That would be very difficult and involve a lot of cleverness and effort to put into this program.

Fortunately, there's an easier way. We could just have the computer memorize all the random slides it made when it created the board data structure, and then the board can be solved just by performing the opposite slide. Since the board originally started in the solved state, undoing all the slides would return it to the solved state.

For example, below we perform a "right" slide on the board on the left side of the page, which leaves the board in the state that is on the right side of the page:


Рис. 4.2.

After the right slide, if we do the opposite slide (a left slide) then the board will be back in the original state. So to get back to the original state after several slides, we just have to do the opposite slides in reverse order. If we did a right slide, then another right slide, then a down slide, we would have to do an up slide, left slide, and left slide to undo those first three slides. This is much easier than writing a function that can solve these puzzles simply by looking at the current state of them.

The Main Game Loop

61.     while True: # main game loop
62.         slideTo = None # the direction, if any, a tile should slide
63.         msg = '' # contains the message to show in the upper left corner.
64.         if mainBoard == SOLVEDBOARD:
65.             msg = 'Solved!'
66.
67.         drawBoard(mainBoard, msg)

In the main game loop, the slideTo variable will track which direction the player wants to slide a tile (it starts off at the beginning of the game loop as None and is set later) and the msg variable tracks what string to display at the top of the window. The program does a quick check on line 64 to see if the board data structure has the same value as the solved board data structure stored in SOLVEDBOARD. If so, then the msg variable is changed to the string 'Solved!'.

This won't appear on the screen until drawBoard() has been called to draw it to the DISPLAYSURF Surface object (which is done on line 67) and pygame.display.update() is called to draw the display Surface object on the actual computer screen (which is done on line 291 at the end of the game loop).

Clicking on the Buttons

69.         checkForQuit()
70.         for event in pygame.event.get(): # event handling loop
71.             if event.type == MOUSEBUTTONUP:
72.                 spotx, spoty = getSpotClicked(mainBoard, event.pos[0],
event.pos[1])
73.
74.                 if (spotx, spoty) == (None, None):
75.                     # check if the user clicked on an option button
76.                     if RESET_RECT.collidepoint(event.pos):
77.                         resetAnimation(mainBoard, allMoves) # clicked on
Reset button
78.                         allMoves = []
79.                     elif NEW_RECT.collidepoint(event.pos):
80.                         mainBoard, solutionSeq = generateNewPuzzle(80) #
clicked on New Game button
81.                         allMoves = []
82.                     elif SOLVE_RECT.collidepoint(event.pos):
83.                         resetAnimation(mainBoard, solutionSeq + allMoves)
# clicked on Solve button
84.                         allMoves = []

Before going into the event loop, the program calls checkForQuit() on line 69 to see if any QUIT events have been created (and terminates the program if there have). Why we have a separate function (the checkForQuit() function) for handling the QUIT events will be explained later. The for loop on line 70 executes the event handling code for any other event created since the last time pygame.event.get() was called (or since the program started, if pygame.event.get() has never been called before).

If the type of event was a MOUSEBUTTONUP event (that is, the player had released a mouse button somewhere over the window), then we pass the mouse coordinates to our getSpotClicked() function which will return the board coordinates of the spot on the board the mouse release happened. The event.pos[0] is the X coordinate and event.pos[1] is the Y coordinate.

If the mouse button release did not happen over one of the spaces on the board (but obviously still happened somewhere on the window, since a MOUSEBUTTONUP event was created), then getSpotClicked() will return None. If this is the case, we want to do an additional check to see if the player might have clicked on the Reset, New, or Solve buttons (which are not located on the board).

The coordinates of where these buttons are on the window are stored in the pygame.Rect objects that are stored in the RESET_RECT, NEW_RECT and SOLVE_RECT variables. We can pass the mouse coordinates from the Event object to the collidepoint() method. This method will return True if the mouse coordinates are within the Rect object's area and False otherwise.

Sliding Tiles with the Mouse

85.                 else:
86.                     # check if the clicked tile was next to the blank spot
87.
88.                     blankx, blanky = getBlankPosition(mainBoard)
89.                     if spotx == blankx + 1 and spoty == blanky:
90.                         slideTo = LEFT
91.                     elif spotx == blankx - 1 and spoty == blanky:
92.                         slideTo = RIGHT
93.                     elif spotx == blankx and spoty == blanky + 1:
94.                         slideTo = UP
95.                     elif spotx == blankx and spoty == blanky - 1:
96.                         slideTo = DOWN

If getSpotClicked() did not return (None, None), then it will have returned a tuple of two integer values that represent the X and Y coordinate of the spot on the board that was clicked. Then the if and elif statements on lines 89 to 96 check if the spot that was clicked is a tile that is next to the blank spot (otherwise the tile will have no place to slide).

Our getBlankPosition() function will take the board data structure and return the X and Y board coordinates of the blank spot, which we store in the variables blankx and blanky. If the spot the user clicked on was next to the blank space, we set the slideTo variable with the value that the tile should slide.

Sliding Tiles with the Keyboard

98.             elif event.type == KEYUP:
99.                 # check if the user pressed a key to slide a tile
100.                 if event.key in (K_LEFT, K_a) and isValidMove(mainBoard,
LEFT):
101.                     slideTo = LEFT
102.                 elif event.key in (K_RIGHT, K_d) and
isValidMove(mainBoard, RIGHT):
103.                     slideTo = RIGHT
104.                 elif event.key in (K_UP, K_w) and isValidMove(mainBoard,
UP):
105.                     slideTo = UP
106.                 elif event.key in (K_DOWN, K_s) and isValidMove(mainBoard,
DOWN):
107.                     slideTo = DOWN

We can also let the user slide tiles by pressing keyboard keys. The if and elif statements on lines 100 to 107 let the user set the slideTo variable by either pressing the arrow keys or the WASD keys (explained later). Each if and elif statement also has a call to isValidMove() to make sure that the tile can slide in that direction (We didn't have to make this call with the mouse clicks because the checks for the neighboring blank space did the same thing).

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