بازی که در زیر آموزش داده شده است، بازی لغزنده یا Slide Puzzle است. این بازی یکی دیگر از بازی هایی است که در این سری آموزشی یاد می گیریم. بیایید یادگیری را شروع کنیم.
صفحه بازی یک مربع 4x4 با پانزده خانه (از شماره 1 تا 15 که از چپ به راست هستند) و یک خانه خالی تشکیل شده است. خانه های شماره دار به صورت نامرتب در صفحه قرار می گیرند و بازیکن تا زمانی که خانه ها به ترتیب اصلی خود برنگشته اند، باید خانه ها را حرکت دهد.
کد این بازی را می توانید از نشانی اینترنتی http://invpy.com/slidepuzzle.py بارگیری کنید. کد کامل بازی در زیر نیز آمده است.
# Slide Puzzle # By Al Sweigart al@inventwithpython.com # http://inventwithpython.com/pygame # Released under a "Simplified BSD" license import pygame import sys import random from pygame.locals import * # Create the constants (go ahead and experiment with different values) BOARDWIDTH = 4 # number of columns in the board BOARDHEIGHT = 4 # number of rows in the board TILESIZE = 80 WINDOWWIDTH = 640 WINDOWHEIGHT = 480 FPS = 30 BLANK = None # R G B BLACK = (0, 0, 0) WHITE = (255, 255, 255) BRIGHTBLUE = (0, 50, 255) DARKTURQUOISE = (3, 54, 73) GREEN = (0, 204, 0) BGCOLOR = DARKTURQUOISE TILECOLOR = GREEN TEXTCOLOR = WHITE BORDERCOLOR = BRIGHTBLUE BASICFONTSIZE = 20 BUTTONCOLOR = WHITE BUTTONTEXTCOLOR = BLACK MESSAGECOLOR = WHITE XMARGIN = int((WINDOWWIDTH - (TILESIZE * BOARDWIDTH + (BOARDWIDTH - 1))) / 2) YMARGIN = int( (WINDOWHEIGHT - (TILESIZE * BOARDHEIGHT + (BOARDHEIGHT - 1))) / 2) UP = 'up' DOWN = 'down' LEFT = 'left' RIGHT = 'right' def main(): global FPSCLOCK, DISPLAYSURF, BASICFONT, RESET_SURF, RESET_RECT, NEW_SURF, NEW_RECT, SOLVE_SURF, SOLVE_RECT pygame.init() FPSCLOCK = pygame.time.Clock() DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) pygame.display.set_caption('Slide Puzzle') BASICFONT = pygame.font.Font('freesansbold.ttf', BASICFONTSIZE) # Store the option buttons and their rectangles in OPTIONS. RESET_SURF, RESET_RECT = makeText( 'Reset', TEXTCOLOR, TILECOLOR, WINDOWWIDTH - 120, WINDOWHEIGHT -90) NEW_SURF, NEW_RECT = makeText( 'New Game', TEXTCOLOR, TILECOLOR, WINDOWWIDTH - 120, WINDOWHEIGHT - 60) SOLVE_SURF, SOLVE_RECT = makeText( 'Solve', TEXTCOLOR, TILECOLOR, WINDOWWIDTH - 120, WINDOWHEIGHT - 30) mainBoard, solutionSeq = generateNewPuzzle(80) # a solved board is the same as the board in a start state. SOLVEDBOARD = getStartingBoard() allMoves = [] # list of moves made from the solved configuration while True: # main game loop slideTo = None # the direction, if any, a tile should slide # contains the message to show in the upper left corner. msg = 'Click tile or press arrow keys to slide.' if mainBoard == SOLVEDBOARD: msg = 'Solved!' drawBoard(mainBoard, msg) checkForQuit() for event in pygame.event.get(): # event handling loop if event.type == MOUSEBUTTONUP: spotx, spoty = getSpotClicked( mainBoard, event.pos[0], event.pos[1]) if (spotx, spoty) == (None, None): # check if the user clicked on an option button if RESET_RECT.collidepoint(event.pos): # clicked on Reset button resetAnimation(mainBoard, allMoves) allMoves = [] elif NEW_RECT.collidepoint(event.pos): mainBoard, solutionSeq = generateNewPuzzle( 80) # clicked on New Game button allMoves = [] elif SOLVE_RECT.collidepoint(event.pos): # clicked on Solve button resetAnimation(mainBoard, solutionSeq + allMoves) allMoves = [] else: # check if the clicked tile was next to the blank spot blankx, blanky = getBlankPosition(mainBoard) if spotx == blankx + 1 and spoty == blanky: slideTo = LEFT elif spotx == blankx - 1 and spoty == blanky: slideTo = RIGHT elif spotx == blankx and spoty == blanky + 1: slideTo = UP elif spotx == blankx and spoty == blanky - 1: slideTo = DOWN elif event.type == KEYUP: # check if the user pressed a key to slide a tile if event.key in (K_LEFT, K_a) and isValidMove(mainBoard, LEFT): slideTo = LEFT elif event.key in (K_RIGHT, K_d) and isValidMove(mainBoard, RIGHT): slideTo = RIGHT elif event.key in (K_UP, K_w) and isValidMove(mainBoard, UP): slideTo = UP elif event.key in (K_DOWN, K_s) and isValidMove(mainBoard, DOWN): slideTo = DOWN if slideTo: # show slide on screen slideAnimation(mainBoard, slideTo, 'Click tile or press arrow keys to slide.', 8) makeMove(mainBoard, slideTo) allMoves.append(slideTo) # record the slide pygame.display.update() FPSCLOCK.tick(FPS) def terminate(): pygame.quit() sys.exit() def checkForQuit(): for event in pygame.event.get(QUIT): # get all the QUIT events terminate() # terminate if any QUIT events are present for event in pygame.event.get(KEYUP): # get all the KEYUP events if event.key == K_ESCAPE: terminate() # terminate if the KEYUP event was for the Esc key pygame.event.post(event) # put the other KEYUP event objects back def getStartingBoard(): # Return a board data structure with tiles in the solved state. # For example, if BOARDWIDTH and BOARDHEIGHT are both 3, this function # returns [[1, 4, 7], [2, 5, 8], [3, 6, BLANK]] counter = 1 board = [] for x in range(BOARDWIDTH): column = [] for y in range(BOARDHEIGHT): column.append(counter) counter += BOARDWIDTH board.append(column) counter -= BOARDWIDTH * (BOARDHEIGHT - 1) + BOARDWIDTH - 1 board[BOARDWIDTH-1][BOARDHEIGHT-1] = BLANK return board def getBlankPosition(board): # Return the x and y of board coordinates of the blank space. for x in range(BOARDWIDTH): for y in range(BOARDHEIGHT): if board[x][y] == BLANK: return (x, y) def makeMove(board, move): # This function does not check if the move is valid. blankx, blanky = getBlankPosition(board) if move == UP: board[blankx][blanky], board[blankx][blanky + 1] = board[blankx][blanky + 1], board[blankx][blanky] elif move == DOWN: board[blankx][blanky], board[blankx][blanky - 1] = board[blankx][blanky - 1], board[blankx][blanky] elif move == LEFT: board[blankx][blanky], board[blankx + 1][blanky] = board[blankx + 1][blanky], board[blankx][blanky] elif move == RIGHT: board[blankx][blanky], board[blankx - 1][blanky] = board[blankx - 1][blanky], board[blankx][blanky] def isValidMove(board, move): blankx, blanky = getBlankPosition(board) return (move == UP and blanky != len(board[0]) - 1) or \ (move == DOWN and blanky != 0) or \ (move == LEFT and blankx != len(board) - 1) or \ (move == RIGHT and blankx != 0) def getRandomMove(board, lastMove=None): # start with a full list of all four moves validMoves = [UP, DOWN, LEFT, RIGHT] # remove moves from the list as they are disqualified if lastMove == UP or not isValidMove(board, DOWN): validMoves.remove(DOWN) if lastMove == DOWN or not isValidMove(board, UP): validMoves.remove(UP) if lastMove == LEFT or not isValidMove(board, RIGHT): validMoves.remove(RIGHT) if lastMove == RIGHT or not isValidMove(board, LEFT): validMoves.remove(LEFT) # return a random move from the list of remaining moves return random.choice(validMoves) def getLeftTopOfTile(tileX, tileY): left = XMARGIN + (tileX * TILESIZE) + (tileX - 1) top = YMARGIN + (tileY * TILESIZE) + (tileY - 1) return (left, top) def getSpotClicked(board, x, y): # from the x & y pixel coordinates, get the x & y board coordinates for tileX in range(len(board)): for tileY in range(len(board[0])): left, top = getLeftTopOfTile(tileX, tileY) tileRect = pygame.Rect(left, top, TILESIZE, TILESIZE) if tileRect.collidepoint(x, y): return (tileX, tileY) return (None, None) def drawTile(tilex, tiley, number, adjx=0, adjy=0): # draw a tile at board coordinates tilex and tiley, optionally a few # pixels over (determined by adjx and adjy) left, top = getLeftTopOfTile(tilex, tiley) pygame.draw.rect(DISPLAYSURF, TILECOLOR, (left + adjx, top + adjy, TILESIZE, TILESIZE)) textSurf = BASICFONT.render(str(number), True, TEXTCOLOR) textRect = textSurf.get_rect() textRect.center = left + int(TILESIZE / 2) + \ adjx, top + int(TILESIZE / 2) + adjy DISPLAYSURF.blit(textSurf, textRect) def makeText(text, color, bgcolor, top, left): # create the Surface and Rect objects for some text. textSurf = BASICFONT.render(text, True, color, bgcolor) textRect = textSurf.get_rect() textRect.topleft = (top, left) return (textSurf, textRect) def drawBoard(board, message): DISPLAYSURF.fill(BGCOLOR) if message: textSurf, textRect = makeText(message, MESSAGECOLOR, BGCOLOR, 5, 5) DISPLAYSURF.blit(textSurf, textRect) for tilex in range(len(board)): for tiley in range(len(board[0])): if board[tilex][tiley]: drawTile(tilex, tiley, board[tilex][tiley]) left, top = getLeftTopOfTile(0, 0) width = BOARDWIDTH * TILESIZE height = BOARDHEIGHT * TILESIZE pygame.draw.rect(DISPLAYSURF, BORDERCOLOR, (left - 5, top - 5, width + 11, height + 11), 4) DISPLAYSURF.blit(RESET_SURF, RESET_RECT) DISPLAYSURF.blit(NEW_SURF, NEW_RECT) DISPLAYSURF.blit(SOLVE_SURF, SOLVE_RECT) def slideAnimation(board, direction, message, animationSpeed): # Note: This function does not check if the move is valid. blankx, blanky = getBlankPosition(board) if direction == UP: movex = blankx movey = blanky + 1 elif direction == DOWN: movex = blankx movey = blanky - 1 elif direction == LEFT: movex = blankx + 1 movey = blanky elif direction == RIGHT: movex = blankx - 1 movey = blanky # prepare the base surface drawBoard(board, message) baseSurf = DISPLAYSURF.copy() # draw a blank space over the moving tile on the baseSurf Surface. moveLeft, moveTop = getLeftTopOfTile(movex, movey) pygame.draw.rect(baseSurf, BGCOLOR, (moveLeft, moveTop, TILESIZE, TILESIZE)) for i in range(0, TILESIZE, animationSpeed): # animate the tile sliding over checkForQuit() DISPLAYSURF.blit(baseSurf, (0, 0)) if direction == UP: drawTile(movex, movey, board[movex][movey], 0, -i) if direction == DOWN: drawTile(movex, movey, board[movex][movey], 0, i) if direction == LEFT: drawTile(movex, movey, board[movex][movey], -i, 0) if direction == RIGHT: drawTile(movex, movey, board[movex][movey], i, 0) pygame.display.update() FPSCLOCK.tick(FPS) def generateNewPuzzle(numSlides): # From a starting configuration, make numSlides number of moves (and # animate these moves). sequence = [] board = getStartingBoard() drawBoard(board, '') pygame.display.update() pygame.time.wait(500) # pause 500 milliseconds for effect lastMove = None for i in range(numSlides): move = getRandomMove(board, lastMove) slideAnimation(board, move, 'Generating new puzzle...', animationSpeed=int(TILESIZE / 3)) makeMove(board, move) sequence.append(move) lastMove = move return (board, sequence) def resetAnimation(board, allMoves): # make all of the moves in allMoves in reverse. revAllMoves = allMoves[:] # gets a copy of the list revAllMoves.reverse() for move in revAllMoves: if move == UP: oppositeMove = DOWN elif move == DOWN: oppositeMove = UP elif move == RIGHT: oppositeMove = LEFT elif move == LEFT: oppositeMove = RIGHT slideAnimation(board, oppositeMove, '', animationSpeed=int(TILESIZE / 2)) makeMove(board, oppositeMove) if __name__ == '__main__': main()
بیش تر کدهای برنامه همانند بازی پیشین است، به ویژه ثابت هایی که در آغاز کد تنظیم کردیم.
# Slide Puzzle # By Al Sweigart al@inventwithpython.com # http://inventwithpython.com/pygame # Released under a "Simplified BSD" license import pygame import sys import random from pygame.locals import * # Create the constants (go ahead and experiment with different values) BOARDWIDTH = 4 # number of columns in the board BOARDHEIGHT = 4 # number of rows in the board TILESIZE = 80 WINDOWWIDTH = 640 WINDOWHEIGHT = 480 FPS = 30 BLANK = None # R G B BLACK = (0, 0, 0) WHITE = (255, 255, 255) BRIGHTBLUE = (0, 50, 255) DARKTURQUOISE = (3, 54, 73) GREEN = (0, 204, 0) BGCOLOR = DARKTURQUOISE TILECOLOR = GREEN TEXTCOLOR = WHITE BORDERCOLOR = BRIGHTBLUE BASICFONTSIZE = 20 BUTTONCOLOR = WHITE BUTTONTEXTCOLOR = BLACK MESSAGECOLOR = WHITE XMARGIN = int((WINDOWWIDTH - (TILESIZE * BOARDWIDTH + (BOARDWIDTH - 1))) / 2) YMARGIN = int( (WINDOWHEIGHT - (TILESIZE * BOARDHEIGHT + (BOARDHEIGHT - 1))) / 2) UP = 'up' DOWN = 'down' LEFT = 'left' RIGHT = 'right'
کدی که در بالا آورده شده است وارد کردن کتابخانه ها و وابستگی های لازم است. همچنین در آن چندین متغییر و ثابت نیز تعریف شده است. اگر دقت کنید این کد بسیار شبیه به کد بازی یادمان است.
def main(): global FPSCLOCK, DISPLAYSURF, BASICFONT, RESET_SURF, RESET_RECT, NEW_SURF, NEW_RECT, SOLVE_SURF, SOLVE_RECT pygame.init() FPSCLOCK = pygame.time.Clock() DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT)) pygame.display.set_caption('Slide Puzzle') BASICFONT = pygame.font.Font('freesansbold.ttf', BASICFONTSIZE) # Store the option buttons and their rectangles in OPTIONS. RESET_SURF, RESET_RECT = makeText( 'Reset', TEXTCOLOR, TILECOLOR, WINDOWWIDTH - 120, WINDOWHEIGHT - 90) NEW_SURF, NEW_RECT = makeText( 'New Game', TEXTCOLOR, TILECOLOR, WINDOWWIDTH - 120, WINDOWHEIGHT - 60) SOLVE_SURF, SOLVE_RECT = makeText( 'Solve', TEXTCOLOR, TILECOLOR, WINDOWWIDTH - 120, WINDOWHEIGHT - 30) mainBoard, solutionSeq = generateNewPuzzle(80) # a solved board is the same as the board in a start state. SOLVEDBOARD = getStartingBoard()
همانند فصل پیشین، تابع هایی که در تابع main استفاده می شوند در آخر این قسمت توضیح داده می شوند. اکنون، باید بدانید که این تابع ها چه کاری انجام می دهند و چه مقدار هایی را برمی گردانند. لازم نیست بدانید که چگونه این کارها را انجام می دهد. نخستین قسمت از تابع main برای ساخت پنجره، شی Clock و شی Font است. تابع makeText در بعد در برنامه تعریف شده است، اما اکنون باید بدانید که این تابع یک شی pygame.Surface و pygame.Rect را بازمی گرداند که برای ساختن دکمه های قابل کلیک استفاده می شود. بازی لغزنده سه دکمه دارد:
برای این بازی به دو ساختمان داده board نیاز داریم. ساختمان داده نخست نمایان گر وضعیت کنونی بازی است. ساختمان داده دیگر خانه های خودش را در حالت "حل شده" یا "solved" خواهد داشت، به این معنی که همه خانه ها به ترتیب شماره مرتب شده اند. هنگامی که صفحه کنونی بازی دقیقا برابر با صفحه حل شده باشد، آن گاه بازیکن برنده شده است. (ساختمان داده دوم را تغییر نمی دهیم و فقط از آن برای مقایسه حالت کنونی بازی با ساختمان داده نخست استفاده می کنیم.)
تابع generateNewPuzzle ساختمان داده board را می سازد که در آغاز مرتب است و سپس 80 حرکت لغزشی تصادفی روی آن انجام می شود (زیرا ما عدد صحیح 80 را به تابع فرستاده ایم.اگر بخواهیم صفحه از این هم، آشفته تر شود، باید عدد بزرگتری را به تابع بفرستیم.) این کار باعث می شود که صفحه ای نامرتب ساخته شود (که در یک متغیر به نام mainBoard ذخیره می شود). تابع generateNewPuzzle همچنین آرایه ای از همه حرکت های تصادفی را که در متغیری به نام solutionSeq ذخیره می شود را برمی گرداند.
allMoves = [] # list of moves made from the solved configuration
بازی لغزنده واقعا می تواند مشکل باشد. می توانیم کامپیوتر را برای انجام آن برنامه ریزی کنیم، اما رایانه برای انجام این کار به یک الگوریتم نیاز دارد و کشف این الگوریتم بر عهده ما است.پیدا گردن این الگوریتم کاری بسیار سخت خواهد بود و هوشمندی زیادی را می خواهد.
خوشبختانه، روش ساده تری وجود دارد. می توانیم کاری کنیم که کامپیوتر همه حرکت های تصادفی را هنگام ساختن ساختمان داده board به یاد داشته باشد و سپس با استفاده از حرکت مخالف بتواند بازی را انجام دهد. از آن جا که board در ابتدا در حالت حل شده شروع به کار می کند، به عقب برگرداندن همه حرکت ها آن را به حالت حل شده باز می گرداند.به عنوان نمونه، در زیر خانه شماره 15 را از سمت راست به چپ حرکت می دهیم، و به شکل سمت راست می رسیم:
اگر پس از به سمت راست بردن خانه 15، آن را دوباره به چپ حرکت دهیم، صفحه به حالت اصلی برمی گردد. بنابراین برای بازگشت به حالت اولیه پس از چندین حرکت، فقط باید حرکت های مخالف را به ترتیب وارونه انجام دهیم. اگر دو حرکت به سمت راست و سپس یک حرکت به سمت به پایین داشته باشیم، آن گاه برای رسیدن به حالت نخست باید یک حرکت رو به بالا و سپس دو حرکت به سمت چپ داشته باشیم تا آن سه حرکت نخست را خنثی کنیم. این کار بسیار ساده تر از نوشتن یک تابع است که می تواند با بررسی وضعیت فعلی آن ها، این معما را حل کند.
while True: # main game loop slideTo = None # the direction, if any, a tile should slide # contains the message to show in the upper left corner. msg = 'Click tile or press arrow keys to slide.' if mainBoard == SOLVEDBOARD: msg = 'Solved!' drawBoard(mainBoard, msg)
در حلقه اصلی بازی، متغیر slideTo پیگیری می کند که بازیکن از چه جهتی می خواهد خانه را حرکت بدهد (در آغاز حلقه با None مقداردهی می شود و بعد تنظیم می شود) و متغیر msg برای این است که چه رشته ای در بالا پنجره نشان داده شود. برنامه یک بررسی سریع را انجام می دهد تا ببیند آیا ساختمان داده board دارای همان مقدار ساختمان داده SOLVEDBOARD است یا نه. اگر چنین باشد، متغیر msg به رشته '!Solved' تغییر می یابد. تا زمانی که drawBoard فراخوانی نشده است چیزی روی صفحه نمایش داده نمی شود و pygame.display.update برای کشیدن شی DISPLAYSURF در صفحه کامپیوتر که در انتهای حلقه بازی است فراخوانی می شود.
checkForQuit() for event in pygame.event.get(): # event handling loop if event.type == MOUSEBUTTONUP: spotx, spoty = getSpotClicked( mainBoard, event.pos[0], event.pos[1]) if (spotx, spoty) == (None, None): # check if the user clicked on an option button if RESET_RECT.collidepoint(event.pos): # clicked on Reset button resetAnimation(mainBoard, allMoves) allMoves = [] elif NEW_RECT.collidepoint(event.pos): mainBoard, solutionSeq = generateNewPuzzle( 80) # clicked on New Game button allMoves = [] elif SOLVE_RECT.collidepoint(event.pos): # clicked on Solve button resetAnimation(mainBoard, solutionSeq + allMoves) allMoves = []
پیش از ورود به حلقه رویداد، برنامه با فراخوانی تابع checkForQuit بررسی می کند آیا رویداد QUIT ایجاد شده است یا نه (و در صورت وجود، برنامه پایان می یابد). این که چرا یک تابع جداگانه checkForQuit برای رسیدگی به رویدادهای QUIT داریم در بعد توضیح داده خواهد شد. حلقه for کد مدیریت رویداد را برای هر رویداد اجرا می کند.
اگر نوع رویداد MOUSEBUTTONUP باشد (یعنی بازیکن در جایی روی پنجره، کلید موس را کلیک کرده باشد)، آن گاه باید مختصات موس را به تابع getSpotClicked بفرستیم که مختصات صفحه یا board را از روی نقطه ای که در آن موس رها شده است برمی گرداند. رویداد event.pos[0] مختصات X و رویداد event.pos[1] مختصات Y است.
اگر رها کردن دکمه موس روی یکی از فضاهای موجود در صفحه رخ ندهد (اما هنوز ممکن است جایی روی پنجره این اتفاق افتاده باشد، به خاطر این که یک رویداد MOUSEBUTTONUP ایجاد شده است)، آن گاه تابع getSpotClicked ، مقدار None را بر می گرداند. در این صورت، می خواهیم یک بررسی اضافی انجام دهیم تا ببینیم بازیکن روی دکمه های Reset، New یا Solve (که در صفحه قرار ندارند) کلیک کرده است یا نه.
جای و مختصات دکمه ها در پنجره، در اشیا pygame.Rect ذخیره می شوند.این اشیا در متغیرهای RESET_RECT، NEW_RECT و SOLVE_RECT ذخیره می شوند. می توانیم مختصات موس را از شی Event به متد collidepoint بفرستیم. اگر مختصات موس در ناحیه شی Rect قرار داشته باشد True و در غیر این صورت False را بر می گرداند.
else: # check if the clicked tile was next to the blank spot blankx, blanky = getBlankPosition(mainBoard) if spotx == blankx + 1 and spoty == blanky: slideTo = LEFT elif spotx == blankx - 1 and spoty == blanky: slideTo = RIGHT elif spotx == blankx and spoty == blanky + 1: slideTo = UP elif spotx == blankx and spoty == blanky - 1: slideTo = DOWN
اگرgetSpotClicked مقدار (None,None) را برنگرداند، آن گاه یک تاپل از دو مقدار صحیح که نشانگر مختصات X و Y نقطه ای که کلیک شده است را بر می گرداند. سپس عبارات if و elif در خطوط 89 تا 96 بررسی می کنند که آیا نقطه ای که بر روی آن کلیک شده خانه ای خالی در کنار خود دارد یا نه (در غیر این صورت خانه جایی برای لغزیدن ندارد). تابع getBlankPosition ساختمان داده board را می گیرد و مختصات X و Y صفحه یا board نقاط خالی را بر می گرداند، که در متغیرهای blankx و blanky ذخیره می شوند. اگر نقطه ای که کاربر روی آن کلیک کرده بود در کنار فضای خالی بود، متغیر slideTo را با مقدارخانه ای که باید بلغزد، تنظیم می کنیم.
elif event.type == KEYUP: # check if the user pressed a key to slide a tile if event.key in (K_LEFT, K_a) and isValidMove(mainBoard, LEFT): slideTo = LEFT elif event.key in (K_RIGHT, K_d) and isValidMove(mainBoard, RIGHT): slideTo = RIGHT elif event.key in (K_UP, K_w) and isValidMove(mainBoard, UP): slideTo = UP elif event.key in (K_DOWN, K_s) and isValidMove(mainBoard, DOWN): slideTo = DOWN
همچنین می توان با فشار دادن کلیدهای صفحه کلید، خانه ها را به حرکت درآوریم. عبارات if و elif به کاربر اجازه می دهد متغیر slideTo را با فشار دادن کلیدهای پیکانی یا کلیدهای WASD تنظیم کند (بعد توضیح داده شده است). همچنین هر عبارت if و elif تابع isValidMove را فراخوانی می کند تا اطمینان پیدا کند که خانه می تواند در آن جهت حرکت کند. (مجبور نیستیم با کلیک موس این فراخوانی را انجام دهیم زیرا بررسی های فضای خالی همسایه همین کار را انجام می دهد.)
عبارت event.key در (K_LEFT, K_a) یک ترفند پایتون برای ساده تر کردن کد است.یعنی اگر یکی از دو عبارت (K_LEFT, K_a) برابر با True بودند آن گاه event.key را برابر با True کن. دو عبارت زیر دقیقا به همین روش ارزیابی می شوند:
event.key in (K_LEFT, K_a) event.key == K_LEFT or event.key == K_a
کلیدهای WASD (که به طور خلاصه وازدی نامیده می شوند) معمولا در بازی های رایانه ای مورد استفاده قرار می گیرند تا همان کاری که کلیدهای پیکانی انجام می دهند را انجام بدهند، با این تفاوت که بازیکن با دست چپ (از آن جا که کلیدهای WASD در سمت چپ صفحه کلید هستند) از آن ها استفاده می کند. W برای بالا، A برای سمت چپ، S برای پایین و D برای سمت راست است. به راحتی می توانید این را به خاطر بسپارید زیرا کلیدهای WASD همان طرح کلیدهای پیکان را دارند :
if slideTo: # show slide on screen slideAnimation(mainBoard, slideTo, 'Click tile or press arrow keys to slide.', 8) makeMove(mainBoard, slideTo) allMoves.append(slideTo) # record the slide pygame.display.update() FPSCLOCK.tick(FPS)
اکنون که همه رویدادها به دست آمده اند، باید متغیرهای حالت بازی یا state بازی را به روز کنیم و سپس آن را روی صفحه نمایش دهیم. اگر slideTo مقداری داشته باشد (با کد رویداد موس یا با رویداد صفحه کلید) می توانیم slideAnimation را برای انجام پویانمایی لغزیدن فراخوانی کنیم. پارامترهای آن 1- ساختمان داده board یا صفحه،2- جهت لغزیدن، 3- پیامی برای نمایش در هنگام لغزیدن خانه ها و 4- سرعت لغزش هستند.
پس از برگشت، باید ساختمان داده واقعی board را بروزرسانی کنیم (که توسط تابع makeMove انجام می شود) و سپس حرکت ها را به آرایه allMoves که در بردارنده ی همه حرکت ها تا زمان حاضر است بیافزاییم. این کار به این صورت انجام می شود که اگر بازیکن روی دکمه Reset کلیک کند، می دانیم چگونه می توان همه حرکت های بازیکن را خنثی سازی کرد.
def terminate(): pygame.quit() sys.exit()
این کد تابع pygame.quit و sys.exit را فراخوانی می کند. این تابع به نوعی از شکر نگارش استفاده می کند و به جای این که دو تابع را به طور جداگانه فراخوانی کنیم، آن ها را تنها با یک بار فراخوانی کردن مورد استفاده قرار می دهیم.
def checkForQuit(): for event in pygame.event.get(QUIT): # get all the QUIT events terminate() # terminate if any QUIT events are present for event in pygame.event.get(KEYUP): # get all the KEYUP events if event.key == K_ESCAPE: terminate() # terminate if the KEYUP event was for the Esc key pygame.event.post(event) # put the other KEYUP event objects back
تابع checkForQuit رویدادهای QUIT و فشرده شدن کلید Esc را بررسی می کند و سپس تابع terminate را فراخوانی می کند. اما یافتن این رویدادها کمی پیچیده هستند و نیاز به روشن شدن دارند. pygame ساختمان داده آرایه خود را دارد و اشیا رویداد یا Event objects را به همان صورت که ساخته می شوند، به آرایه پیوست می کند. این ساختمان داده را صف رویداد می نامند. هنگامی که تابع pygame.event.get بدون پارامتر فراخوانی می شود، کل آرایه بازگردانده می شود. با این حال، می توان فقط ثابت هایی مانند QUIT را به pygame.event.get فرستاد، تا فقط رویدادهای QUIT در صورت وجود برگرداننده شوند. بقیه رویدادها برای فراخوانی های بعدی pygame.event.get در صف رویداد خواهند ماند.
باید یادآوری شود که صف رویداد Pygame فقط 127 رویداد را ذخیره می کند. اگر برنامه شما pygame.event.get را به اندازه کافی فراخوانی نکند و صف پر شود، آن گاه هیچ کدام از رویدادهای جدیدی که ساخته می شوند به صف رویداد افزوده نمی شوند. در صورت وقوع رویداد های QUIT در صف رویداد، برنامه پایان می یابد.
خط 125 تمام رویدادهای KEYUP را از صف رویداد خارج می کند و بررسی می کند آیا هیچ کدام از آن ها مربوط به کلید Esc است یا نه. اگر یکی از رویدادهای صف رویداد KEYUP باشد، برنامه پایان می یابد. با این وجود، ممکن است برای کلیدهای دیگری غیر از کلید Esc، رویدادهای KEYUP وجود داشته باشد. در این حالت، ما باید رویداد KEYUP را دوباره در صف رویداد Pygame قرار دهیم. می توانیم این کار را با تابع pygame.event.post انجام دهیم، که شی رویداد فرستاده شده به آن را به انتهای صف رویداد Pygame می افزاید. به این ترتیب، هنگامی که pygame.event.get فراخوانی می شود رویداد های KEYUP که Esc نیستند هنوز هم وجود خواهند داشت. در غیر این صورت فراخوانی checkForQuit همه رویدادهای KEYUP را به حساب می آورد و هرگز از این رویدادها استفاده نمی شود.تابع pygame.event.post نیز در صورتی مفید است که برنامه بخواهد اشیا رویداد را به صف رویداد Pygame بیافزاید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.