ساخت بازی ستاره‌بری یا Star Pusher در پایتون (بخش دوم)

Making Star Pusher Games with Python - Part 2

17 آبان 1400
Making-Star-Pusher-Games-with-Python---Part-2

پیش گفتار

بخش دو بازی ستاره بری را در این بخش ادامه خواهیم داد. این بازی پیش رفته است.بکوشید مطالب را با دقت بخوانید و حتما تمرین داشته باشید. کد بازی را می توانید از http://invpy.com/starpusher.py دانلود کنید. فایل مرحله ها را می توانید از http://invpy.com/starPusherLevels.txt بارگیری کنید. کاشی ها را می توانید از http://invpy.com/starPusherImages.zip بارگیری کنید.

# Convert the adjoined walls into corner tiles.
for x in range(len(mapObjCopy)):
    for y in range(len(mapObjCopy[0])):

        if mapObjCopy[x][y] == '#':
            if (isWall(mapObjCopy, x, y-1) and isWall(mapObjCopy, x+1, y)) or \
               (isWall(mapObjCopy, x+1, y) and isWall(mapObjCopy, x, y+1)) or \
               (isWall(mapObjCopy, x, y+1) and isWall(mapObjCopy, x-1, y)) or \
               (isWall(mapObjCopy, x-1, y) and isWall(mapObjCopy, x, y-1)):
                mapObjCopy[x][y] = 'x'

        elif mapObjCopy[x][y] == ' ' and random.randint(0, 99) < OUTSIDE_DECORATION_PCT:
            mapObjCopy[x][y] = random.choice(
                list(OUTSIDEDECOMAPPING.keys()))

return mapObjCopy

دستور بزرگ و چند خطی if در خط 301 بررسی می کند که آیا کاشی در مختصات XY یک کاشی در گوشه ای است یا نه. این کار را با بررسی این که آیا دیواری در مجاورت آن وجود دارد یا نه انجام می دهد. در این صورت، رشته '#' در شی map که نشان دهنده یک دیوار معمولی است به یک رشته 'x' که نشان دهنده یک کاشی در گوشه دیوار است، تغییر می کند.

def isBlocked(mapObj, gameStateObj, x, y):
    """Returns True if the (x, y) position on the map is
    blocked by a wall or star, otherwise return False."""

    if isWall(mapObj, x, y):
        return True

    elif x < 0 or x >= len(mapObj) or y < 0 or y >= len(mapObj[x]):
        return True  # x and y aren't actually on the map.

    elif (x, y) in gameStateObj['stars']:
        return True  # a star is blocking

    return False

سه حالت وجود دارد که در آن یک فضای روی نقشه مسدود می شود اگر یک ستاره، یک دیوار باشد یا این که مختصات فضا از لبه های نقشه گذشته باشد. تابع isBlocked این سه مورد را بررسی می کند و اگر مختصات XY مسدود شده باشد True و گر نه False را برمی گرداند.

def makeMove(mapObj, gameStateObj, playerMoveTo):
    """Given a map and game state object, see if it is possible for the
    player to make the given move. If it is, then change the player's
    position (and the position of any pushed star). If not, do nothing.

    Returns True if the player moved, otherwise False."""

    # Make sure the player can move in the direction they want.
    playerx, playery = gameStateObj['player']

    # This variable is "syntactic sugar". Typing "stars" is more
    # readable than typing "gameStateObj['stars']" in our code.
    stars = gameStateObj['stars']

    # The code for handling each of the directions is so similar aside
    # from adding or subtracting 1 to the x/y coordinates. We can
    # simplify it by using the xOffset and yOffset variables.
    if playerMoveTo == UP:
        xOffset = 0
        yOffset = -1
    elif playerMoveTo == RIGHT:
        xOffset = 1
        yOffset = 0
    elif playerMoveTo == DOWN:
        xOffset = 0
        yOffset = 1
    elif playerMoveTo == LEFT:
        xOffset = -1
        yOffset = 0

    # See if the player can move in that direction.
    if isWall(mapObj, playerx + xOffset, playery + yOffset):
        return False
    else:
        if (playerx + xOffset, playery + yOffset) in stars:
            # There is a star in the way, see if the player can push it.
            if not isBlocked(mapObj, gameStateObj, playerx + (xOffset*2), playery + (yOffset*2)):
                # Move the star.
                ind = stars.index((playerx + xOffset, playery + yOffset))
                stars[ind] = (stars[ind][0] + xOffset, stars[ind][1] + yOffset)
            else:
                return False
        # Move the player upwards.
        gameStateObj['player'] = (playerx + xOffset, playery + yOffset)
        return True

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

def startScreen():
    """Display the start screen (which has the title and instructions)
    until the player presses a key. Returns None."""

    # Position the title image.
    titleRect = IMAGESDICT['title'].get_rect()
    topCoord = 50  # topCoord tracks where to position the top of the text
    titleRect.top = topCoord
    titleRect.centerx = HALF_WINWIDTH
    topCoord += titleRect.height

    # Unfortunately, Pygame's font & text system only shows one line at
    # a time, so we can't use strings with \n newline characters in them.
    # So we will use a list with each line in it.
    instructionText = ['Push the stars over the marks.',
                       'Arrow keys to move, WASD for camera control, P to change character.',
                       'Backspace to reset level, Esc to quit.',
                       'N for next level, B to go back a level.']

تابع startScreen  باید چند متن مختلف را در مرکز پنجره نشان دهد. ما هر خط را به عنوان یک رشته در لیست instructionText ذخیره خواهیم کرد. تصویر عنوان در IMAGESDICT['title']  به عنوان یک شی سطح ذخیره شده است و از star_title.png بارگذاری می شود و در 50 پیکسل بالای پنجره قرار خواهد گرفت. دلیل آن این است که عدد صحیح 50 در متغیر topCoord در خط 383 ذخیره شده است. متغیر topCoord موقعیت Y تصویر عنوان و متن را ردیابی می کند. محور X همیشه به گونه ای تنظیم می شود که تصاویر و متن متمرکز شود. در خط 386، متغیر topCoord با توجه به اندازه ارتفاع آن تصویر افزایش می یابد. به این ترتیب ما می توانیم تصویر را تغییر دهیم و لازم نیست کد صفحه شروع تغییر کند.

# Start with drawing a blank color to the entire window:
DISPLAYSURF.fill(BGCOLOR)

# Draw the title image to the window:
DISPLAYSURF.blit(IMAGESDICT['title'], titleRect)

# Position and draw the text.
for i in range(len(instructionText)):
    instSurf = BASICFONT.render(instructionText[i], 1, TEXTCOLOR)
    instRect = instSurf.get_rect()
    topCoord += 10  # 10 pixels will go in between each line of text.
    instRect.top = topCoord
    instRect.centerx = HALF_WINWIDTH
    topCoord += instRect.height  # Adjust for the height of the line.
    DISPLAYSURF.blit(instSurf, instRect)

خط 400 جایی است که تصویر عنوان در قسمت Surface نمایش و کشیده داده می شود.حلقه for در خط 403، هر رشته ای را در حلقه  می کشد و آن را نشان می دهد. متغیر topCoord همواره با اندازه متن نشان داده شده پیشین (خط 409) و 10 پیکسل اضافی روی خط 406، افزایش می یابد تا شکافی 10 پیکسلی بین خطوط متن وجود داشته باشد.

while True:  # Main loop for the start screen.
    for event in pygame.event.get():
        if event.type == QUIT:
            terminate()
        elif event.type == KEYDOWN:
            if event.key == K_ESCAPE:
                terminate()
            return  # user has pressed a key, so return.

    # Display the DISPLAYSURF contents to the actual screen.
    pygame.display.update()
    FPSCLOCK.tick()

یک حلقه بازی در startScreen وجود دارد که از خط 412 شروع می شود و رویدادهایی را مدیریت می کند که نشان دهنده ی این هستند که برنامه باید پایان پذیرد یا از تابع startScreen برگردد. تا زمانی که بازیکن این کار را نکند، حلقه با فراخوانی pygame.display.update  و  FPSCLOCK.tick کاری می کند که صفحه شروع روی صفحه، نمایش داده شود.

ساختمان داده ها در Star Pusher

Star Pusher دارای ساختمان داده های مرحله ها، نقشه ها و حالت بازی یا game state است.

ساختمان داده Game State (وضعیت بازی)

شی وضعیت بازی یا game state یک دیکشنری با سه کلید است : 'player'، 'stepCounter' و 'stars' است.مقدار کلید'player' برای موقعیت XY فعلی بازیکن، یک تاپل از دو عدد صحیح خواهد بود.مقدار کلید 'stepCounter' تعداد حرکت های بازیکن را در خود دارد.با دیدن این عدد بازیکن  سعی می کند پازل را در آینده با مراحل کمتری حل کند.مقدار کلید 'stars'  لیستی از مقادیر تاپل از مقادیر XY برای هر یک از ستاره های موجود در مرحله فعلی است.

ساختمان داده  نقشه یا Map

ساختمان داده map یک آرایه 2 بعدی است که در آن دو اندیس استفاده شده مختصات X و Y نقشه را نشان می دهند. مقدار هر اندیس در لیست 2 بعدی، یک رشته تک کاراکتری است که عنوان هر مکان را نشان می دهد:

  • '#' – یک دیوار چوبی.
  • 'x' – یک دیوار کناری.
  • '@' – نقطه فضای یا آغازین حرکت برای بازیکن.
  • '.' – یک فضای هدف.
  • '$'  – فضایی که ستاره در آغاز مرحله در آن قرار می گیرد.
  • '+'  – یک فضا با هدف و فضای اولیه برای بازیکن.
  • '*'  – یک فضا با هدف و ستاره در ابتدای مرحله.
  • '  '  – یک فضای بیرونی با علف.
  • 'o'  – فضای داخلی کف (این یک حرف کوچک O است، نه یک صفر).
  • '1'  – یک سنگ روی علف.
  • '2'  – یک درخت کوتاه روی علف.
  • '3'  – یک درخت بلند روی علف.
  • '4'  – یک درخت زشت روی علف.

ساختمان داده Level ها (مرحله ها)

شی level شامل یک شی وضعیت بازی، یک شی نقشه و چند مقدار دیگر است. شی level خود یک دیکشنری با کلیدهای زیر است:

  • مقدار کلید "width" یک عدد صحیح از طول کاشی های کل نقشه است.
  • مقدار کلید "height" یک عدد صحیح از عرض کاشی های کل نقشه است.
  • مقدار کلید "mapObj" شی نقشه برای این مرحله است.
  • مقدار کلید "goals" لیستی از تاپل های دوتایی با مختصات XY هر فضای هدف روی نقشه است.
  • مقدار کلید "startState" یک شی وضعیت بازی است که برای نشان دادن موقعیت شروع ستاره ها و بازیکن در ابتدای مرحله استفاده می شود.

خواندن و نوشتن فایل های متنی

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

فایل های متنی و باینری

فایل های متنی فایل هایی هستند که شامل داده های ساده هستند. فایل های متنی توسط برنامه Notepad، Gedit on Ubuntu و TextEdit در Mac OS X در Windows ایجاد می شوند. بسیاری از برنامه های دیگر به نام ویرایشگرهای متنی وجود دارند که می توانند فایل های متنی را ایجاد و تغییر دهند.

تفاوت بین ویرایشگران متن و پردازشگرهای کلمه (مانند مایکروسافت ورد، یا OpenOffice Writer یا iWork Pages) در این است که آن ها فقط متن دارند. شما نمی توانید فونت، اندازه یا رنگ متن را تنظیم کنید. (IDLE بطور خودکار رنگ متن را بر اساس نوع کد پایتون تنظیم می کند، اما شما نمی توانید این کار را خودتان انجام دهید، بنابراین هنوز یک ویرایشگر متن است.) تفاوت بین متن و فایل های باینری برای این بازی مهم نیست اما می توانید در مورد آن در http://invpy.com/textbinary  جستجو کنید. تنها چیزی که باید بدانید این است که بازی Star Pusher فقط با فایل های متنی سر و کار دارد.

درباره فرمت فایل Star Pusher

فایل متنی level باید دارای یک فرمت خاص باشد. کدام کاراکترها نمایانگر دیوارها، ستاره ها یا موقعیت شروع بازیکن هستند؟ اگر نقشه برای چندین مرحله داریم، چگونه می توانیم بگوییم نقشه یک مرحله به پایان می رسد و مرحله بعدی شروع می شود؟ اگر فایل مرحله را از http://invpy.com/starPusherLevels.txt بارگیری کنید و آن را در یک ویرایشگر متن باز کنید، چیزی شبیه به این خواهید دید:

; Star Pusher (Sokoban clone)

; http://inventwithpython.com/blog

; By Al Sweigart al@inventwithpython.com

;

; Everything after the ; is a comment and will be ignored by the game that

; reads in this file.

;

; The format is described at:

; http://sokobano.de/wiki/index.php?title=Level_format

;   @ - The starting position of the player.

;   $ - The starting position for a pushable star.

;   . - A goal where a star needs to be pushed.

;   + - Player & goal

;   * - Star & goal

;  (space) - an empty open space.

;   # - A wall.

;

; Level maps are separated by a blank line (I like to use a ; at the start

; of the line since it is more visible.)

;

; I tried to use the same format as other people use for their Sokoban games,

; so that loading new levels is easy. Just place the levels in a text file

; and name it "starPusherLevels.txt" (after renaming this file, of course).



; Starting demo level:

 ########

##      #

#   .   #

#   $   #

# .$@$. #

####$   #

   #.   #

   #   ##

   #####

comment ها در بالا فرمت فایل را توضیح می دهند. در هنگام اجرای بازی مرحله اول به صورت زیر به نظر خواهد رسید:

def readLevelsFile(filename):
    assert os.path.exists(
        filename), 'Cannot find the level file: %s' % (filename)

اگر فایل فرستاده شده به تابع وجود داشته باشد آنگاه os.path.exists مقدار True را برمی گرداند.

 

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

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

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