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

Memory Game - Part 1

06 آبان 1400
Memory-Game---Part-1

پیش گفتار

در این قسمت آموزش ساخت بازی حافظه (یادمان) را یاد خواهیم گرفت. تصویر این بازی در زیر آمده است.

بازی یادمان

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

در بازی یادمان باید بتوانیم هر مختصات ممکن X و Y را داشته باشیم. برای اطمینان از این که هر ترکیب ممکن را بدست آورده ایم، از حلقه های تو در تو استفاده خواهیم کرد. توجه داشته باشید که حلقه داخلی (حلقه ای که درون حلقه ی دیگر است) پیش از رفتن به تکرار بعدی حلقه بیرونی، همه تکرارهای خود را انجام می دهد و سپس حلقه بیرونی وارد دور بعدی خود می شود.

کد بازی یادمان را می توانید از نشانی http://invpy.com/memorypuzzle.py بارگیری کنید. احتمالا با نگاه کردن به کد برنامه ایده هایی را در مورد چگونگی ساخت آن به دست آورده اید ولی شتاب نکنید. بیایید با هم نخستین بازی در این کتاب را خوب بنگریم و سپس آن را بسازیم.

# Memory Puzzle
# By Al Sweigart al@inventwithpython.com
# http://inventwithpython.com/pygame
# Released under a "Simplified BSD" license

import random
import pygame
import sys
from pygame.locals import *

FPS = 30  # frames per second, the general speed of the program
WINDOWWIDTH = 640  # size of window's width in pixels
WINDOWHEIGHT = 480  # size of windows' height in pixels
REVEALSPEED = 8  # speed boxes' sliding reveals and covers
BOXSIZE = 40  # size of box height & width in pixels
GAPSIZE = 10  # size of gap between boxes in pixels
BOARDWIDTH = 10  # number of columns of icons
BOARDHEIGHT = 7  # number of rows of icons
assert (BOARDWIDTH * BOARDHEIGHT) % 2 == 0, 'Board needs to have an even number of boxes for pairs of matches.'
XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * (BOXSIZE + GAPSIZE))) / 2)
YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * (BOXSIZE + GAPSIZE))) / 2)

#            R    G    B
GRAY = (100, 100, 100)
NAVYBLUE = (60,  60, 100)
WHITE = (255, 255, 255)
RED = (255,   0,   0)
GREEN = (0, 255,   0)
BLUE = (0,   0, 255)
YELLOW = (255, 255,   0)
ORANGE = (255, 128,   0)
PURPLE = (255,   0, 255)
CYAN = (0, 255, 255)

BGCOLOR = NAVYBLUE
LIGHTBGCOLOR = GRAY
BOXCOLOR = WHITE
HIGHLIGHTCOLOR = BLUE

DONUT = 'donut'
SQUARE = 'square'
DIAMOND = 'diamond'
LINES = 'lines'
OVAL = 'oval'

ALLCOLORS = (RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, CYAN)
ALLSHAPES = (DONUT, SQUARE, DIAMOND, LINES, OVAL)
assert len(ALLCOLORS) * len(ALLSHAPES) * 2 >= BOARDWIDTH * \
    BOARDHEIGHT, "Board is too big for the number of shapes/colors defined."


def main():
    global FPSCLOCK, DISPLAYSURF
    pygame.init()
    FPSCLOCK = pygame.time.Clock()
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))

    mousex = 0  # used to store x coordinate of mouse event
    mousey = 0  # used to store y coordinate of mouse event
    pygame.display.set_caption('Memory Game')

    mainBoard = getRandomizedBoard()
    revealedBoxes = generateRevealedBoxesData(False)

    firstSelection = None  # stores the (x, y) of the first box clicked.

    DISPLAYSURF.fill(BGCOLOR)
    startGameAnimation(mainBoard)

    while True:  # main game loop
        mouseClicked = False

        DISPLAYSURF.fill(BGCOLOR)  # drawing the window
        drawBoard(mainBoard, revealedBoxes)

        for event in pygame.event.get():  # event handling loop
            if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
                pygame.quit()
                sys.exit()
            elif event.type == MOUSEMOTION:
                mousex, mousey = event.pos
            elif event.type == MOUSEBUTTONUP:
                mousex, mousey = event.pos
                mouseClicked = True

        boxx, boxy = getBoxAtPixel(mousex, mousey)
        if boxx != None and boxy != None:
            # The mouse is currently over a box.
            if not revealedBoxes[boxx][boxy]:
                drawHighlightBox(boxx, boxy)
            if not revealedBoxes[boxx][boxy] and mouseClicked:
                revealBoxesAnimation(mainBoard, [(boxx, boxy)])
                revealedBoxes[boxx][boxy] = True  # set the box as "revealed"
                if firstSelection == None:  # the current box was the first box clicked
                    firstSelection = (boxx, boxy)
                else:  # the current box was the second box clicked
                    # Check if there is a match between the two icons.
                    icon1shape, icon1color = getShapeAndColor(
                        mainBoard, firstSelection[0], firstSelection[1])
                    icon2shape, icon2color = getShapeAndColor(
                        mainBoard, boxx, boxy)

                    if icon1shape != icon2shape or icon1color != icon2color:
                        # Icons don't match. Re-cover up both selections.
                        pygame.time.wait(1000)  # 1000 milliseconds = 1 sec
                        coverBoxesAnimation(
                            mainBoard, [(firstSelection[0], firstSelection[1]), (boxx, boxy)])
                        revealedBoxes[firstSelection[0]
                                      ][firstSelection[1]] = False
                        revealedBoxes[boxx][boxy] = False
                    elif hasWon(revealedBoxes):  # check if all pairs found
                        gameWonAnimation(mainBoard)
                        pygame.time.wait(2000)

                        # Reset the board
                        mainBoard = getRandomizedBoard()
                        revealedBoxes = generateRevealedBoxesData(False)

                        # Show the fully unrevealed board for a second.
                        drawBoard(mainBoard, revealedBoxes)
                        pygame.display.update()
                        pygame.time.wait(1000)

                        # Replay the start game animation.
                        startGameAnimation(mainBoard)
                    firstSelection = None  # reset firstSelection variable

        # Redraw the screen and wait a clock tick.
        pygame.display.update()
        FPSCLOCK.tick(FPS)


def generateRevealedBoxesData(val):
    revealedBoxes = []
    for i in range(BOARDWIDTH):
        revealedBoxes.append([val] * BOARDHEIGHT)
    return revealedBoxes


def getRandomizedBoard():
    # Get a list of every possible shape in every possible color.
    icons = []
    for color in ALLCOLORS:
        for shape in ALLSHAPES:
            icons.append((shape, color))

    random.shuffle(icons)  # randomize the order of the icons list
    # calculate how many icons are needed
    numIconsUsed = int(BOARDWIDTH * BOARDHEIGHT / 2)
    icons = icons[:numIconsUsed] * 2  # make two of each
    random.shuffle(icons)

    # Create the board data structure, with randomly placed icons.
    board = []
    for x in range(BOARDWIDTH):
        column = []
        for y in range(BOARDHEIGHT):
            column.append(icons[0])
            del icons[0]  # remove the icons as we assign them
        board.append(column)
    return board


def splitIntoGroupsOf(groupSize, theList):
    # splits a list into a list of lists, where the inner lists have at
    # most groupSize number of items.
    result = []
    for i in range(0, len(theList), groupSize):
        result.append(theList[i:i + groupSize])
    return result


def leftTopCoordsOfBox(boxx, boxy):
    # Convert board coordinates to pixel coordinates
    left = boxx * (BOXSIZE + GAPSIZE) + XMARGIN
    top = boxy * (BOXSIZE + GAPSIZE) + YMARGIN
    return (left, top)


def getBoxAtPixel(x, y):
    for boxx in range(BOARDWIDTH):
        for boxy in range(BOARDHEIGHT):
            left, top = leftTopCoordsOfBox(boxx, boxy)
            boxRect = pygame.Rect(left, top, BOXSIZE, BOXSIZE)
            if boxRect.collidepoint(x, y):
                return (boxx, boxy)
    return (None, None)


def drawIcon(shape, color, boxx, boxy):
    quarter = int(BOXSIZE * 0.25)  # syntactic sugar
    half = int(BOXSIZE * 0.5)  # syntactic sugar

    # get pixel coords from board coords
    left, top = leftTopCoordsOfBox(boxx, boxy)
    # Draw the shapes
    if shape == DONUT:
        pygame.draw.circle(DISPLAYSURF, color,
                           (left + half, top + half), half - 5)
        pygame.draw.circle(DISPLAYSURF, BGCOLOR,
                           (left + half, top + half), quarter - 5)
    elif shape == SQUARE:
        pygame.draw.rect(DISPLAYSURF, color, (left + quarter,
                                              top + quarter, BOXSIZE - half, BOXSIZE - half))
    elif shape == DIAMOND:
        pygame.draw.polygon(DISPLAYSURF, color, ((left + half, top), (left + BOXSIZE - 1,
                                                                      top + half), (left + half, top + BOXSIZE - 1), (left, top + half)))
    elif shape == LINES:
        for i in range(0, BOXSIZE, 4):
            pygame.draw.line(DISPLAYSURF, color,
                             (left, top + i), (left + i, top))
            pygame.draw.line(DISPLAYSURF, color, (left + i,
                                                  top + BOXSIZE - 1), (left + BOXSIZE - 1, top + i))
    elif shape == OVAL:
        pygame.draw.ellipse(DISPLAYSURF, color,
                            (left, top + quarter, BOXSIZE, half))


def getShapeAndColor(board, boxx, boxy):
    # shape value for x, y spot is stored in board[x][y][0]
    # color value for x, y spot is stored in board[x][y][1]
    return board[boxx][boxy][0], board[boxx][boxy][1]


def drawBoxCovers(board, boxes, coverage):
    # Draws boxes being covered/revealed. "boxes" is a list
    # of two-item lists, which have the x & y spot of the box.
    for box in boxes:
        left, top = leftTopCoordsOfBox(box[0], box[1])
        pygame.draw.rect(DISPLAYSURF, BGCOLOR, (left, top, BOXSIZE, BOXSIZE))
        shape, color = getShapeAndColor(board, box[0], box[1])
        drawIcon(shape, color, box[0], box[1])
        if coverage > 0:  # only draw the cover if there is an coverage
            pygame.draw.rect(DISPLAYSURF, BOXCOLOR,
                             (left, top, coverage, BOXSIZE))
    pygame.display.update()
    FPSCLOCK.tick(FPS)


def revealBoxesAnimation(board, boxesToReveal):
    # Do the "box reveal" animation.
    for coverage in range(BOXSIZE, (-REVEALSPEED) - 1, -REVEALSPEED):
        drawBoxCovers(board, boxesToReveal, coverage)


def coverBoxesAnimation(board, boxesToCover):
    # Do the "box cover" animation.
    for coverage in range(0, BOXSIZE + REVEALSPEED, REVEALSPEED):
        drawBoxCovers(board, boxesToCover, coverage)


def drawBoard(board, revealed):
    # Draws all of the boxes in their covered or revealed state.
    for boxx in range(BOARDWIDTH):
        for boxy in range(BOARDHEIGHT):
            left, top = leftTopCoordsOfBox(boxx, boxy)
            if not revealed[boxx][boxy]:
                # Draw a covered box.
                pygame.draw.rect(DISPLAYSURF, BOXCOLOR,
                                 (left, top, BOXSIZE, BOXSIZE))
            else:
                # Draw the (revealed) icon.
                shape, color = getShapeAndColor(board, boxx, boxy)
                drawIcon(shape, color, boxx, boxy)


def drawHighlightBox(boxx, boxy):
    left, top = leftTopCoordsOfBox(boxx, boxy)
    pygame.draw.rect(DISPLAYSURF, HIGHLIGHTCOLOR, (left - 5,
                                                   top - 5, BOXSIZE + 10, BOXSIZE + 10), 4)


def startGameAnimation(board):
    # Randomly reveal the boxes 8 at a time.
    coveredBoxes = generateRevealedBoxesData(False)
    boxes = []
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            boxes.append((x, y))
    random.shuffle(boxes)
    boxGroups = splitIntoGroupsOf(8, boxes)

    drawBoard(board, coveredBoxes)
    for boxGroup in boxGroups:
        revealBoxesAnimation(board, boxGroup)
        coverBoxesAnimation(board, boxGroup)


def gameWonAnimation(board):
    # flash the background color when the player has won
    coveredBoxes = generateRevealedBoxesData(True)
    color1 = LIGHTBGCOLOR
    color2 = BGCOLOR

    for i in range(13):
        color1, color2 = color2, color1  # swap colors
        DISPLAYSURF.fill(color1)
        drawBoard(board, coveredBoxes)
        pygame.display.update()
        pygame.time.wait(300)


def hasWon(revealedBoxes):
    # Returns True if all the boxes have been revealed, otherwise False
    for i in revealedBoxes:
        if False in i:
            return False  # return False if any boxes are covered.
    return True


if __name__ == '__main__':
    main()

وارد کردن کتابخانه ها

# Memory Puzzle
# By Al Sweigart al@inventwithpython.com
# http://inventwithpython.com/pygame
# Released under a "Simplified BSD" license

import random
import pygame
import sys
from pygame.locals import *

در بالای برنامه اطلاعاتی درمورد نام بازی، نویسنده آن و این که کاربر از کجا می تواند اطلاعات بیشتری در مورد آن پیدا کند وجود دارد. این برنامه از توابع موجود در ماژول های pygame استفاده می کند، بنابراین این ماژول ها را با دستور import وارد برنامه می کنیم تا بتوانیم از آن ها استفاده کنیم. هیچ تابعی در ماژول pygame.locals وجود ندارد، اما چندین ثابت در آن هست که برای نوشتن برنامه به آن ها نیاز دازیم مانند MOUSEMOTION ،KEYUP یاQUIT.

چرا اعداد جادویی بد هستند

FPS = 30  # frames per second, the general speed of the program
WINDOWWIDTH = 640  # size of window's width in pixels
WINDOWHEIGHT = 480  # size of windows' height in pixels
REVEALSPEED = 8  # speed boxes' sliding reveals and covers
BOXSIZE = 40  # size of box height & width in pixels
GAPSIZE = 10  # size of gap between boxes in pixels

برنامه های موجود در این سری آموزشی از ثابت های زیادی استفاده می کنند. شاید نفهمید که چرا آن ها تا این اندازه مفید هستند. به عنوان نمونه، به جای استفاده از متغیر BOXSIZE می توانیم عدد صحیح 40 را مستقیما در برنامه به کار ببریم. اما دو دلیل برای استفاده از متغیرهای ثابت وجود دارد که در زیر به آن ها نگاهی می اندازیم:

  1. نخست، اگر  بخواهیم اندازه هر جعبه را تغییر دهیم، مجبور می شویم که همه برنامه را به دنبال مقدار 40 بگردیم تا بتوانیم آن را با مقدار جدید آن جایگزین کنیم. با استفاده از ثابت BOXSIZE، فقط باید BOXSIZE را تغییر دهیم و به این ترتیب بقیه برنامه را کاری نداریم. پس این راه بهتر است، چون ممکن است علاوه بر اندازه جعبه های سفید، از عدد 40 برای موارد دیگری هم استفاده کرده باشیم و با تغییر دادن 40 به طور ناخواسته باعث بروز اشکال در برنامه شویم.
  2. دوم این که این روش برنامه را خواناتر می کند. به قسمت بعدی بروید و به خط 18 نگاه کنید. خط زیر یک محاسبه برای به دست آوردن ثابت XMARGIN است. تعداد پیکسل های کنار صفحه اصلی بازی (صفحه ای که در آن جعبه ها و شکل ها قرار می گیرند) در ثابت XMARGIN قرار می گیرد. این عبارت پیچیده به نظر می رسد، اما می توان به دقت مفهوم آن را بیان کرد. خط 18 در زیر آمده است:
XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * (BOXSIZE + GAPSIZE))) / 2)

اما اگر کد بالا از ثابت ها استفاده نمی کرد، به  صورت زیر به نظر می رسید:

XMARGIN = int((640 – (10 * (40 + 10))) / 2) 

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

بررسی خردمندانه با استفاده از assert

BOARDWIDTH = 10  # number of columns of icons
BOARDHEIGHT = 7  # number of rows of icons
assert (BOARDWIDTH * BOARDHEIGHT) % 2 == 0, 'Board needs to have an even number of boxes for pairs of matches.'
XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * (BOXSIZE + GAPSIZE))) / 2)
YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * (BOXSIZE + GAPSIZE))) / 2)

عبارت assert در بالا تضمین می کند عرض و ارتفاعی که برای صفحه انتخاب می کنیم یک عدد زوج خواهد بود.

هر عبارت assert سه بخش دارد:

  1. کلمه کلیدی assert
  2. عبارتی که باید ارزیابی شود. نتیجه ی این ارزیابی true یا false است. اگر مقدار آن false باشد، باعث اشکال در برنامه می شود.
  3.  اگر در قسمت دوم assert مشکلی وجود داشته باشد قسمت سوم برای رسیدگی به این مشکل نوشته می شود. این قسمت پس از کاما می آید و رشته ای است که، به عنوان یک پیام به کاربر نمایش داده می شود.

در واقع assert این را می گوید که : "برنامه نویس ادعا می کند این عبارت باید صحیح باشد چون اگر نباشد باعث بروز  خطا در برنامه می شود." این یک روش خوب برای بررسی خردمندانه برنامه است تا مطمئن شویم در صورتی که اجرای برنامه یک assert را رد می کند حداقل می توان مطمئن بود کد همان طور که انتظار می رود کار می کند.به عبارت دیگر اگر بخواهیم در هنگام پدید آمدن خطا برنامه ی ما متوقف نشود روش خردمندانه این است که از assert ها استفاده کنیم.

بررسی زوج یا فرد بودن عدد

اگر حاصل ضرب ارتفاع در عرض صفحه به دو تقسیم شود و باقیمانده 0 شود پس عدد به دست آمده زوج است (عملگر  ٪ میزان باقی مانده را ارزیابی می کند). باقیمانده تقسیم اعداد زوج بر دو همیشه صفر خواهد بود و باقیمانده تقسیم اعداد فرد بر دو همیشه یک است . این یک روش خوب برای تعیین زوج  یا فرد بودن یک عدد  است :

isEven = someNumber %  2 == 0    عدد زوج است

isOdd = someNumber  %  2 != 0    عدد فرد است

در مثال بالا، اگر someNumber زوج باشد، isEven دارای مقدار True خواهد شد و اگر فرد باشد، isOdd دارای مقدار True خواهد شد.

خراب شدن اختیاری برنامه!

درهم شکستن برنامه چیز بدی است. درهم شکستن برنامه هنگامی رخ می دهد که برنامه شما خطایی داشته باشد و نتواند به کار خود ادامه دهد. اما مواردی وجود دارد که خراب شدن اختیاری و زود هنگام برنامه می تواند از بروز اشکال های بدتر در آینده جلوگیری کند. اگر مقادیری که برای BOARDWIDTH و BOARDHEIGHT انتخاب می کنیم  عددی فرد باشند آن گاه حاصل ضرب آن ها عددی فرد خواهد شد که در نتیجه آن تعداد جعبه ها هم فرد می شود (مانند عرض 3 و ارتفاع 5 باشد) اگر تعداد جعبه ها فرد باشد و از آن جایی که از هر شکل دو تا وجود دارد که باید با هم جفت شوند پس همیشه یکی از این جعبه ها باقی خواهد ماند. این کار باعث ایجاد اشکال در برنامه می شود و باید وقت زیادی را صرف کنیم تا بفهمیم که ریشه ی واقعی این اشکال در همان آغاز برنامه است. برای دیدن این حقیقت به چشم خود، عبارت assert را comment کنید تا اجرا نشود، و سپس ثابت های BOARDWIDTH و BOARDHEIGHT را با اعداد فرد مقدار دهی کنید(مانند 5 و 7). هنگامی که برنامه را اجرا می کنید، بلافاصله خطایی رخ می دهد  که ریشه اصلی آن در تابع getRandomizedBoard است.

 

باید زمان زیادی را صرف جستجو در تابع getRandomizedBoard بکنیم تا اشتباه آن را پیدا کنیم در حالی که ریشه واقعی اشکال در ثابت های BOARDWIDTH و BOARDHEIGHT در ابتدای برنامه است. استفاده از assert اطمینان می دهد که چنین اتفاقی هرگز رخ نمی دهد.

زیباتر ساختن کد برنامه

#            R    G    B
GRAY = (100, 100, 100)
NAVYBLUE = (60,  60, 100)
WHITE = (255, 255, 255)
RED = (255,   0,   0)
GREEN = (0, 255,   0)
BLUE = (0,   0, 255)
YELLOW = (255, 255,   0)
ORANGE = (255, 128,   0)
PURPLE = (255,   0, 255)
CYAN = (0, 255, 255)

BGCOLOR = NAVYBLUE
LIGHTBGCOLOR = GRAY
BOXCOLOR = WHITE
HIGHLIGHTCOLOR = BLUE

به یاد داشته باشید که رنگ ها در Pygame با یک تاپل سه تایی از اعداد صحیح 0 تا 255 نشان داده می شوند. این سه عدد میزان رنگ قرمز، سبز و آبی را نشان می دهند به همین دلیل به این تاپل از مقادیر RGB  (red green blue) می گویند. توجه داشته باشید که فاصله درج شده در تاپل ها در خطوط 22 تا 31 به گونه ای است که این اعداد صحیح در زیر حروف RGB  قرار بگیرند. در پایتون تورفتگی (یعنی فضای موجود در ابتدای خط) باید بسیار دقیق باشد، اما فاصله در بقیه خط چندان مهم نیست. با فاصله دادن اعداد صحیح در تاپل، می توانیم به وضوح ببینیم که مقادیر RGB با یکدیگر چگونه مقایسه می شوند(اطلاعات بیشتر در مورد فاصله و تورفتگی در آدرس اینترنتی http://invpy.com/whitespace  است).

استفاده از ثابت ها به جای رشته ها

DONUT = 'donut'
SQUARE = 'square'
DIAMOND = 'diamond'
LINES = 'lines'
OVAL = 'oval'

در بالا رشته ها را از نوع ثابت تعریف کرده ایم. از این ثابت ها در ساختمان داده ها برای صفحه استفاده می شود و هم چنین برای ردیابی این که فضاهای موجود در صفحه مربوط به چه شکل هایی هستند به کار می رود. استفاده از ثابت به جای مقدار رشته ای روش خوبی است. اگر هنگام نوشتن خط 187 اشتباه تایپی مانند زیر داشته باشیم :

if shape == DUNUT:

متغیر shape  با یکی از رشته های 'donut'، 'square'، 'diamond'، 'lines' یا 'oval' مقداردهی می شود و سپس با ثابت DONUT مقایسه می شود. اگر هنگام نوشتن خط 187 اشتباه تایپی داشته باشیم، همانند کد زیر:

if shape == DUNOT:

برنامه متوقف می شود و پیغام خطایی می دهد که هیچ متغیری به نام DUNOT وجود ندارد. از آن جا که در خط 187 خطا داریم، هنگامی که آن خط را بررسی می کنیم، می توان فهمید که این اشکال توسط یک typo (اشتباه تایپی) ایجاد شده است. اما اگر به جای ثابت ها از رشته ها استفاده می کردیم و همان اشتباه تایپی را می ساختیم، خط 187 مانند زیر می شد:

if shape == 'dunot':

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

آیا به اندازه کافی شکل داریم؟

ALLCOLORS = (RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, CYAN)
ALLSHAPES = (DONUT, SQUARE, DIAMOND, LINES, OVAL)
assert len(ALLCOLORS) * len(ALLSHAPES) * 2 >= BOARDWIDTH * \
    BOARDHEIGHT, "Board is too big for the number of shapes/colors defined."

برای این که برنامه ما بتواند شکل هایی از هر ترکیب ممکن رنگ و شکل ایجاد کند، باید تاپلی درست کنیم که تمام این مقادیر را در خود جای دهد(پس دو تاپل برای این کار نیاز داریم یک تاپل برای همه ی رنگ ها و یک تاپل ها برای همه ی شکل ها) .assert دوم در این برنامه برای مطمئن کردن ما است از این که ترکیب های کافی از رنگ و شکل داریم (این ترکیب ها متناسب با اندازه صفحه است). اگر به میزان لازم جفت رنگ و شکل وجود نداشته باشد، برنامه در خط 46 دچار خطا می شود و می دانیم که یا باید رنگ ها و شکل های بیشتری اضافه کنیم، یا عرض و ارتفاع صفحه را کم تر کنیم. با 7 رنگ و 5 شکل که در برنامه آن را مشخص کردیم می توانیم 35 (5*7) شکل گوناگون درست کنیم و از آن جایی که ما یک جفت از هر شکل داریم، صفحه ای خواهیم داشت که 70  (2*5*7) شکل دارد.

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

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

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