ساخت بازی لغزنده در پایتون (بخش دوم)

Slide Puzzle Game in Python - Part 2

10 آبان 1400
Slide-Puzzle-Game-in-Python---Part-2

پیش گفتار

در این بخش بازی لغزنده را به پایان می بریم.سعی کنید حداقل یک بار آن را بخوانید سپس شروع به کدنویسی کنید.

ساختن ساختمان داده Board

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

تابع getStartingBoard ساختمان داده ای را می سازد که یک صفحه solved را نشان می دهد، که در آن همه خانه های شماره گذاری شده به ترتیب هستند و خانه خالی در گوشه سمت راست پایین قرار دارد. این کار با حلقه های تودرتو انجام می شود، درست همانند ساختمان داده board در بازی یادمان. با این حال، توجه داشته باشید که ستون اول قرار نیست [1, 2, 3]  باشد بلکه در عوض [1, 4, 7] خواهد بود. این امر به این دلیل است که اعداد روی خانه ها به صورت سطری افزایش می یابند نه ستونی. با پایین رفتن از ستون، اعداد به اندازه عرض صفحه افزایش می یابند (که در ثابت BOARDWIDTH ذخیره می شود). ما از متغیر counter استفاده خواهیم کرد تا عددی را که باید روی خانه بعدی باشد، ردیابی کنیم. هنگامی که شماره گذاری خانه های موجود در ستون به پایان رسید، باید در شروع ستون بعدی counter  را تنظیم کنیم.

دنبال نکردن فضای خالی

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)

هر وقت کد ما نیاز به یافتن مختصات XY از فضای خالی داشته باشد، به جای این که فضای خالی پس از هر لغزیدن را ردیابی کنیم، می توانیم یک تابع ایجاد کنیم که در کل صفحه بگردد و مختصات فضای خالی را پیدا کند. مقدار None در ساختمان داده  board  برای نشان دادن فضای خالی استفاده می شود. کد موجود در getBlankPosition به سادگی با استفاده از حلقه های تو در تو درمی یابد که فضای موجود در صفحه فضای خالی کجا است.

حرکت کردن با ویرایش ساختمان داده Board

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]

ساختمان داده در پارامتر  board یک لیست دو بعدی است که کلیه خانه ها را نشان می دهد. هر زمان که بازیکن حرکت کند، برنامه باید این ساختمان داده را به روز کند. آن چه رخ می دهد این است که مقدار خانه یا tile با مقدار فضای خالی جایگزین می شود. تابع makeMove لازم نیست هیچ مقداری را برگرداند، زیرا پارامتر board دارای یک مرجع لیست برای آرگومان خود است.این بدان معنی است که هر تغییری که در این تابع برایboard  ایجاد کنیم، روی لیست مقدارهایی که برای makeMove فرستاده ایم تاثیر می گذارد.(می توانید مفهوم منابع را در http://invpy.com/references مرور کنید.)

چه هنگامی از Assertion استفاده نکنیم

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)

تابع isValidMove یک ساختمان داده board و حرکتی (move) که بازیکن خواهد کرد را می گیرد. اگر حرکت معتبر باشد  True، وگرنه False را برمی گرداند.به عنوان نمونه، شما نمی توانید صدها بار در یک ردیف یک خانه را به سمت چپ بکشید، زیرا در نهایت فضای خالی در لبه قرار خواهد گرفت و دیگر خانه هایی برای لغزاندن به سمت چپ وجود ندارد. این که یک حرکت معتبر باشد یا خیر، بستگی به جای فضای خالی دارد. این تابع برای یافتن مختصات X و Y نقاط خالی، تابع getBlankPosition را فراخوانی می کند.

اسلش ها یا / در پایان سه سطر اول به مفسر پایتون می گوید که این پایان خط نیست (حتی اگر این اسلش در انتهای خط باشد). این کار ما را توانمند می کند که یک خط کد را در چندین خط تقسیم کنیم تا زیباتر به نظر برسد، به جای این که یک خط غیرقابل خواندن بسیار طولانی داشته باشید. از آن جا که قسمت هایی از این عبارت در داخل پرانتز با عملگرها به هم متصل شده اند، فقط یکی از آن ها باید True باشد تا کل عبارت True باشد. هر یک از این قسمت ها بررسی می کنند که حرکت موردنظر چیست و سپس می بیند که آیا مختصات فضای خالی اجازه حرکت را می دهد یا خیر.

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)

در آغاز بازی با ساختمان داده board  در حالت حل شده، مرتب شده شروع می کنیم و پازل را با خانه هایی که در مکان هایی تصادفی هستند می کشیم. برای اینکه بفهمیم کدام یک از چهار جهت را بکشیم، تابع getRandomMove را فراخوانی می کنیم. همچنین می توانستیم از تابع random.choice استفاده کنیم و به آن تاپل (UP, DOWN, LEFT, RIGHT) را بفرستیم تا پایتون به طور تصادفی یک مقدار جهت را برای ما انتخاب کند. اما بازی لغزنده محدودیت کمی دارد که مانع از انتخاب یک عدد کاملا تصادفی می شود.اگر یک پازل اسلاید داشتید و یک خانه را به سمت چپ بکشید و سپس همان خانه را به سمت راست بکشید، با همان صفحه ای که در آغاز کار داشتید رو به رو می شوید. ساخت چنین اسلایدی که فقط به دنبال اسلاید مخالف خود است بی معنی است. همچنین، اگر فضای خالی در گوشه پایین سمت راست باشد، حرکت دادن یک خانه به سمت بالا یا چپ غیر ممکن است.

کد در getRandomMove  این فاکتورها را در نظر می گیرد. برای جلوگیری از این که تابع آخرین حرکتی که انجام شده است را انتخاب نکند، فراخوانی کننده ی تابع می تواند یک مقدار جهت به پارامتر lastMove را بفرستد. خط 181 با لیستی از هر چهار مقدار جهت ذخیره شده در متغیر validMoves  آغاز می شود. مقدار lastMove (اگر با None تنظیم نشده باشد) از validMoves حذف می شود. بسته به این که فضای خالی در لبه صفحه باشد، خطوط 184 تا 191 مقادیر جهت دیگر را از لیست lastMove حذف می کنند.از مقادیری که در lastMove باقیمانده است، یکی از آن ها به طور تصادفی با فراخوانی random.choice انتخاب می شود و برگردانده می شود.

تبدیل مختصات خانه (Tile) به مختصات پیکسلی

def getLeftTopOfTile(tileX, tileY):
    left = XMARGIN + (tileX * TILESIZE) + (tileX - 1)
    top = YMARGIN + (tileY * TILESIZE) + (tileY - 1)
    return (left, top)

تابع getLeftTopOfTile مختصات صفحه را به مختصات پیکسل تبدیل می کند. برای مختصات XY صفحه که آن را می فرستیم، تابع مختصات پیکسلی XY پیکسل را در سمت چپ بالای فضای آن محاسبه می کند و سپس برمی گرداند.

تبدیل مختصات خانه (Tile) به مختصات صفحه

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)

تابع getSpotClicked خلاف کار تابع getLeftTopOfTile را انجام می دهد و مختصات پیکسلی را به مختصات صفحه تبدیل می کند. حلقه های تو در تو در خطوط 205 و 206 در هر مختصات XY ممکن board حلقه می زنند و اگر مختصات پیکسلی که در آن حلقه زدند همان فضای موجود در صفحه باشد، آنگاه مختصات board را برمی گرداند. از آن جا که همه خانه ها دارای یک عرض و ارتفاع هستند که در ثابت TILESIZE تنظیم شده است، می توانیم با گرفتن مختصات پیکسل در گوشه بالا سمت چپ فضای صفحه، یک شی Rect ایجاد کنیم که فضای موجود در صفحه را نشان می دهد و سپس از collidepoint() Rect برای دیدن این که مختصات پیکسل در ناحیه آن شی Rect قرار دارند یا نه استفاده کنیم.اگر مختصات پیکسلی که گذر داده می شود از فضای صفحه ای بیش تر نبود، مقدار (None, None)   بازگردانده می شود.

کشیدن خانه (Tile)

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)

تابع drawTile یک خانه شماره گذاری شده بر روی صفحه را ترسیم می کند. پارامترهای tilex  و tiley  مختصات صفحه ای خانه است. پارامتر  number رشته ای از شماره خانه ها است (مانند "3" یا "12"). پارامترهای کلمه کلیدی adjx و adjy برای ایجاد تنظیمات جزئی در موقعیت خانه است. به عنوان نمونه، اگر adjx 5 باشد باعث می شود خانه 5 پیکسل در سمت راست فضای tilex  و tiley روی صفحه قرار گیرد. اگر adjx 10 باشد باعث می شود خانه 10 پیکسل در سمت چپ فضا ظاهر شود.

این مقادیر تنظیم زمانی مفید خواهد بود که بخواهیم خانه را در میانه لغزیدن خود بکشیم. اگر در هنگام فراخوانی drawTile هیچ مقداری برای این آرگومان ها فرستاده نشود، به طور پیش فرض آن ها روی 0 تنظیم می شوند. این بدان معنی است که آن ها دقیقا در فضای صفحه قرار می گیرند که توسط tilex و tiley داده شده است.

توابع ترسیم Pygame فقط از مختصات پیکسلی استفاده می کند، بنابراین خط 217 مختصات صفحه ذخیره شده در tixx و tiley را به پیکسل تبدیل می کند، که آن ها را در متغیرهای left و top ذخیره خواهیم کرد (به خاطر این که getLeftTopOfTile مختصات گوشه بالا سمت چپ را برمی گرداند).پشت زمینه مربع شکل خود را با فراخوانی pygame.draw.rect می کشیم و در حین این کار اگر کد به تنظیم موقعیت خانه نیاز داشته باشد مقادیر adjx و adjy را به سمت چپ و بالا، می افزاییم.

خطوط 219 تا 222 شی سطح یا Surface را می سازند که متن شماره را بر روی آن کشیده می شود. یک شی Rect برای شی Surface قرار گرفته و سپس از آن برای کشیدن شی Surface به سطح Surface استفاده می شود. تابع drawTile ،pygame.display.update را فراخوانی نمی کند، زیرا فراخوانی کننده drawTile احتمالا می خواهد پیش از ظاهر شدن روی صفحه، خانه های بیشتری را برای بقیه صفحه بکشد.

نمایش متن روی صفحه

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)

تابع makeText با ساختن شی های Surface و Rect برای یافتن جای متن روی صفحه کار می کند. به جای انجام همه این فراخوانی در هنگان نمایش متن، می توانیم makeText را فراخوانی کنیم. با این کار کدهای کمتری خواهیم نوشت. (گرچه drawTile توابع render و get_rect را برای قرار دادن متن شی Surface به جای نقطه چپ بالا در مرکز فراخوانی می کند .این تابع همچنین از یک رنگ شفاف نیز استفاده می کند.)

کشیدن صفحه

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

این تابع رسم همه صفحه و خانه های آن را بر عهده دارد. متد fill روی هر چیزی را که از پیش روی DISPLAYSURF کشیده شده است، ترسیم می کند تا دوباره بتوان از آغاز کار کرد. خط های 235 تا 237 کار نوشتن پیام در بالای پنجره را بر دوش دارند در گام بعدی، تابع drawTile و همچنین حلقه های تودرتو برای کشیدن هر خانه در داخل شی سطح نمایش یا DISPLAYSURF استفاده می شوند.

 
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)

خطوط 244 تا 247 خطوط اطراف خانه ها را می کشد. گوشه بالا سمت چپ صفحه 5 پیکسل در سمت چپ و 5 پیکسل در بالای گوشه سمت چپ بالای خانه در مختصات صفحه (0، 0) خواهد بود. عرض و ارتفاع border از عرض خانه و ارتفاع صفحه محاسبه می شود (که در ثابت های BOARDWIDTH و BOARDHEIGHT ذخیره می شود) که در اندازه خانه ها ضرب می شود و در ثابت TILESIZE ذخیره می شود. مستطیلی که در خط 247 می کشیم، 4 پیکسل  ضخامت خواهد داشت، بنابراین boarder را 5 پیکسل به سمت چپ و بالا جابجا خواهیم کرد که متغیرهای left و top قرار می گیرند، بنابراین ضخامت خط روی خانه ها نمی افتد. همچنین به عرض و طول 11 پیکسل می افزاییم (5 پیکسل از این 11 پیکسل برای جبران جابجایی مستطیل به سمت چپ و بالا هستند).

نمایش دکمه ها

DISPLAYSURF.blit(RESET_SURF, RESET_RECT)
DISPLAYSURF.blit(NEW_SURF, NEW_RECT)
DISPLAYSURF.blit(SOLVE_SURF, SOLVE_RECT)

در آخر، دکمه ها را بیرون از صفحه بازی می کشیم. متن و جای این دکمه ها هرگز تغییر نمی کند، به همین دلیل در آغاز تابع main در ثابت ها ذخیره می شوند.

حرکت دادن خانه ها

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

نخستین چیزی که کد پویا نمایی لغزاندن یا sliding نیاز به محاسبه آن دارد  این است که فضای خالی خانه متحرک در کجا قرار دارد. comment  در خط 255 به ما یادآوری می کند کدی که متد slideAnimation را فراخوانی می کند باید اطمینان حاصل کند که اسلایدی که برای پارامتر direction می فرستد یک حرکت معتبر است. مختصات فضای خالی از فراخوانی getBlankPosition به دست می آید. از این مختصات و جهت اسلایدها می توانیم مختصات XY  صفحه  را از خانه ای بفهمیم که به لغزش در خواهد آمد. این مختصات در متغیرهای movex  و movey  ذخیره می شود.

متد copy

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

متد copy از اشیا Surface یک شی جدید Surface همانی که تصویر روی آن کشیده می شود را بر می گرداند. اما آن ها دو شی جداگانه سطح هستند. پس از فراخوانی متد copy، اگر با تابع blit یا دیگر توابع ترسیم Pygame روی یک سطح Surface  بکشیم، تصویر را در شی دیگر Surface تغییر نمی دهد. ما این نسخه را در متغیر baseSurf در خط 273 ذخیره می کنیم.

در گام پسین، فضای خالی دیگری را روی خانه ای که می لغزانیم می کشیم. دلیل این امر این است که وقتی هر فریم از انیمیشن لغزاندن را می کشیم، خانه  لغزیده شده را بر قسمت های مختلفی از شی سطح baseSurf یا baseSurf Surface object می کشیم. اگر خانه متحرک را روی سطح baseSurf خالی نکنیم، وقتی خانه  لغزیده شده را می کشیم، هنوز هم سر جای خود وجود دارد. در این حالت، سطح baseSurf به شکل زیر به نظر خواهد رسید:

پس از کشیدن خانه شماره 9 به بالا، تصویری همانند زیر را خواهیم دید :

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)

برای ترسیم فریم های انیمیشن لغزاندن، باید سطح baseSurf را روی سطح نمایشگر بکشیم، سپس در هر فریم انیمیشن خانه لغزنده را نزدیک تر و نزدیک تر به موقعیت نهایی خود که در آن فضای خالی بوده است، قرار دهیم. فضای بین دو خانه مجاور به اندازه یک خانه واحد است، که آن را در TILESIZE ذخیره کرده ایم. این کد از یک حلقه for برای رفتن از 0 به TILESIZE استفاده می کند.

معمولا این بدان معنی است که ما خانه را 0 پیکسل بالا تر می کشیم، سپس در فریم بعدی خانه را 1 پیکسل بالا تر روی آن می کشیم، سپس 2 پیکسل، سپس 3 و  به همین ترتیب. هر یک از این فریم ها 1/30 ام ثانیه طول می کشد. اگر TILESIZE را روی 80 تنظیم کرده باشید (همان طور که برنامه این کار را در خط 12 انجام می دهد)، آنگاه کشیدن خانه دو و نیم ثانیه طول می کشد، که در واقع خیلی کند است. بنابراین در عوض، یک حلقه for برای تکرار از 0 تا TILESIZE با چند پیکسل در هر فریم خواهیم داشت. تعداد پیکسل هایی که از آن پرش می کنند در animationSpeed ذخیره می شوند، که در هنگام فراخوانی slideAnimation فراخوانی به آن گذر داده می شود. به عنوان نمونه، اگر animationSpeed ،8 باشد و TILESIZE ثابت روی 80 تنظیم شود، سپس حلقه  و

range(0, TILESIZE, animationSpeed) متغیر i را با مقادیر 0، 8، 16، 24، 32، 40 مقداردهی می کند. ، 48، 56، 64، 72. (شامل 80 نمی شود زیرا تابع range به عنوان آرگومان دوم بالا می رود، اما شامل آن نیست.) این بدان معنی است که کل انیمیشن کشویی در 10 فریم انجام می شود، این بدان معنی است این در 10/30th  ثانیه (یک سوم ثانیه) انجام می شود زیرا این بازی در 30 FPS انجام می شود.

خطوط 282 تا 289 اطمینان حاصل می کند که خانه لغزیده شده را در جهت صحیح می کشیم (با توجه به مقداری که متغیر direction  دارد). پس از پخش شدن انیمیشن، این تابع باز می گردد. توجه کنید در حالی که این انیمیشن در حال پخش شدن است، هر رویدادی که توسط کاربر ایجاد می شود، مدیریت نمی شوند. این رویدادها پی در پی اجرا خواهند شد تا هنگامی که اجرای بعدی به خط 70 در main یا کد موجود در تابع  checkForQuit برسد.

ساخت یک پازل جدید

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

تابع generateNewPuzzle در آغاز هر بازی جدید فراخوانی می شود. با فراخوانی generateNewPuzzle ساختمان داده board را ایجاد می کند و سپس بصورت تصادفی آن را بر می زند. چند خط نخست generateNewPuzzle صفحه را  می سازند و سپس آن را روی screen می کشند (نیم ثانیه درنگ کنید تا بازیکن بتواند صفحه تازه را ببیند).

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)

پارامتر numSlides نشان می دهد که تابع چگونه بسیاری از این حرکات تصادفی را انجام می دهد. کد برای انجام حرکات تصادفی عبارت است از فراخوانی getRandomMove در خط 305 برای بدست آوردن حرکت، سپس فراخوانی getRandomMove تا انیمیشن را روی صفحه پخش کند. از آن جا که انجام انیمیشن لغراندن در واقع ساختمان داده های board را به روز نمی کند، ما با فراخوانی makeMove در خط 307 صفحه را به روز می کنیم. باید هر یک از حرکات تصادفی را که انجام شده است، ردیابی کنیم تا بازیکن بتواند بعدا بر روی دکمه Solve کلیک کند و برنامه را برای خنثی سازی همه این حرکات تصادفی آماده کند. بنابراین این حرکت ها به ترتیب به لیست حرکات یا moves به در خط 308 پیوست می شود.

سپس حرکت تصادفی را در متغیری به نام lastMove ذخیره می کنیم که برای تکرار بعدی به getRandomMove گذر داده می شود. این مانع از خنثی کردن حرکت تصادفی بعدی که اخیرا انجام داده ایم می شود. همه این موارد باید به تعداد numSlides روی بدهد، بنابراین خطوط 305 تا 309 را درون حلقه for قرار می دهیم. هنگامی که صفحه در حال تکه تکه شدن است، ساختمان داده های board و همچنین لیست حرکت های تصادفی روی آن را باز می گردیم.

پویانمایی صفحه Reset

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)

وقتی بازیکن روی  Reset یا  Solve کلیک می کند، برنامه باید همه حرکاتی را که در صفحه انجام شده است را خنثی سازی کند. لیست مقادیر جهت اسلایدها به عنوان آرگومان برای پارامتر allMoves گذر داده می شود.

خط 315 از لیست برش لیست برای ایجاد کپی از لیست allMoves استفاده می کند. به یاد داشته باشید که اگر پیش از  : عددی را مشخص نکنید پایتون فرض می کند که این برش باید از همان ابتدای لیست شروع شود. و اگر شماره ای را بعد از این کار مشخص نکردید، پس پایتون فرض می کند که این برش باید به انتهای لیست ادامه یابد. بنابراین  allMoves [:] یک برش لیست از کل لیست allMoves می سازد. این باعث می شود یک نسخه از لیست واقعی به جای نسخه ای از مرجع لیست، در revAllMoves ذخیره شود. (برای جزئیات بیشتر به  http://invpy.com/references مراجعه کنید.)

برای خنثی کردن همه حرکات در allMoves، باید حرکت مخالف حرکات را در allMoves و به ترتیب برعکس انجام دهیم. یک متد لیست به نام reverse وجود دارد که ترتیب آیتم ها را در یک لیست وارونه می کند. ما این کار را در لیست revAllMoves در خط 316 فراخوانی می کنیم.

حلقه for در خط 318 در لیست مقادیر جهت دار تکرار می شود. به یاد داشته باشید، ما حرکت مخالف را می خواهیم، بنابراین عبارات if و elif از خط 319 تا 326 مقدار جهت گیری صحیح را در متغیر oppositeMove تنظیم می کنند. سپس برای اجرای پویانمایی slideAnimation را و برای به روز آوری ساختمان  داده board makeMove را فراخوانی می کنیم.

if __name__ == '__main__':
    main()

دقیقا مانند بازی یادمان، پس از این که تمام عبارات def برای ایجاد همه توابع اجرا شد، main را فراخوانی می کنیم. این همه برنامه Slide Puzzle است! اما بگذارید در مورد برخی از مفاهیم برنامه نویسی عمومی که در این بازی آمده اند صحبت کنیم.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری بازی‌سازی با پایتون توصیه می‌کند:
نویسنده شوید

دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.