در این بخش ادامه بازی یادمان را از سر خواهیم گرفت و با نکته های بیش تری در بازی سازی آشنا خواهیم شد.
شاید متوجه شده باشید که ثابت های ALLCOLORS و ALLSHAPES به جای لیست، تاپل هستند. شاید از خود بپرسید که چه هنگام باید از تاپل ها و چه هنگام باید از لیست استفاده کنم؟ و چه تفاوتی میان آن ها وجود دارد ؟
در پاسخ باید بگویم که تاپل ها و لیست ها از هر نظر یکسان هستند به جز دو مورد:
ما اغلب لیست ها را تغییرپذیر می نامیم (به معنای این که می توانند تغییر می کنند) و تاپل ها را تغییر ناپذیر می نامیم (به این معنی که نمی توان آن ها را تغییر داد).
به کد زیر نگاه کنید :
با نگاه به کد بالا در می یابیم که اگر بخواهیم آیتم سوم تاپل را تغییر دهیم، با خطایی به صورت زیر رو به رو می شویم:
tuple' object does not support item assignment'
ویژگی تغییر ناپذیری تاپل دو فایده دارد.نخست این که کدی که از تاپل ها استفاده می کند کمی سریع تر از کدی است که از لیست ها استفاده می کند. اما اجرا شدن سریع تر کد شما به ازای چند نانو ثانیه چندان مهم نیست.
دومین فایده مهم مانند مزیت استفاده از ثابت ها است: این که هیچ گاه مقدار موجود در تاپل تغییر نخواهد کرد، بنابراین هر کسی که بعد کد را می خواند می تواند بگوید، "من می توانم انتظار داشته باشم که این تاپل همیشه یکسان باشد در غیر این صورت برنامه نویس می توانست از لیست استفاده کند." این کار همچنین باعث می شود که یک برنامه نویس که در آینده کد شما را می خواند، بگوید، "اگر مقدار لیست را ببینم، می دانم که می تواند در بعضی از قسمت های این برنامه تغییر کند در غیر این صورت، برنامه نویسی که این کد را می نوشت، از یک تاپل استفاده می کرد".
کلمه کلیدی global و چرا استفاده از متغیر های global بد است
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')
کد بالا آغاز تابع main است، که اصلی ترین قسمت برنامه در آن قرار دارد. همه توابعی که در این قسمت فراخوانی می شوند در بعد توضیح داده می شوند. خط اول تابع main یک عبارت global است. global یک کلمه کلیدی است و به دنبال آن نام متغیرهایی که با کاما از هم جدا شده اند آمده است. این متغیرها به عنوان متغیرهای سراسری یا global مشخص می شوند. متغیرهای global در سرتاسر برنامه قابل استفاده هستند. FPSCLOCK و DISPLAYSURF را به عنوان global تعریف می کنیم زیرا در چند تابع دیگر نیز از آن ها استفاده می شود. (اطلاعات بیشتر در http://invpy.com/scope است.)
چهار قانون ساده برای تعیین متغیر محلی (local) و سراسری (global) است وجود دارد:
در بیش تر موارد از متغیرهای سراسری (global) در توابع خود استفاده نمی کنیم. یک تابع مانند یک برنامه کوچک درون برنامه شما است که دارای ورودی های ویژه ای (پارامترها) و یک خروجی (مقدار برگشتی) است. اما تابعی که متغیرهای سراسری را فرا می خواند و آن ها را تغییر می دهد، ورودی و خروجی اضافی دارد. از آن جا که پیش از فراخوانی تابع، ممکن است متغیر سراسری در بسیاری از جا ها تغییر کرده باشد، یافتن اشکال دشوار خواهد بود.
mainBoard = getRandomizedBoard() revealedBoxes = generateRevealedBoxesData(False)
تابع getRandomizedBoard ساختمان داده ای است که وضعیت صفحه را نشان می دهد. تابع generateRevealedBoxesData نشان دهنده ترتیب پوشانده شدن جعبه ها است. مقدار برگشتی این دو تابع یک آرایه(لیست) دو بعدی است. اگر یک آرایه را در یک متغیر به نام spam ذخیره کنیم، می توانیم با براکت به مقدار درون آن دست یابی پیدا بکنیم مانند spam[2] که به مقدار سوم در این لیست اشاره می کند. اگر مقدار موجود در spam[2] خود یک آرایه باشد، می توانیم از یک جفت براکت دیگر برای بازیابی این مقدار در آن لیست استفاده کنیم. استفاده از این روش نماد گذاری آرایه های دوبعدی باعث می شود تا بتوان یک صفحه دو بعدی را به یک آرایه دو بعدی به آسانی نگاشت کنیم. از آن جایی که متغیر mainBoard شکل هایی را در خود ذخیره می کند، اگر می خواستیم شکل را در موقعیت (5، 4) در صفحه قرار دهیم، می توانیم از کد mainBoard[4][5] استفاده کنیم. از آن جایی که خود شکل ها به عنوان تاپل های دوتایی یعنی با شکل و رنگ ذخیره می شوند، ساختمان داده کامل یک آرایه دو بعدی از تاپل های دوتایی خواهد بود.این یک نمونه کوچک است. گمان کنید صفحه مانند زیر باشد:
آن گاه ساختمان داده مربوطه به شکل زیر خواهد بود:
mainBoard = [[(DONUT, BLUE), (LINES, BLUE), (SQUARE, ORANGE)], [(SQUARE, GREEN), (DONUT, BLUE), (DIAMOND, YELLOW)], [(SQUARE, GREEN), (OVAL, YELLOW), (SQUARE, ORANGE)], [(DIAMOND, YELLOW), (LINES, BLUE), (OVAL, YELLOW)]]
تا اینجا باید متوجه شده باشید که mainBoard[x][y] معادل با مختصه (x, y) در صفحه است. در همین حال، ساختمان داده revealedbox بر خلاف ساختمان داده board یک آرایه دو بعدی است و مقدار بولین دارد. اگر جعبه موجود در مختصات x،y به کنار رفته باشد، مقدار True و اگر جعبه پوشیده باشد مقدار False را دارد. فرستادن False به تابع generateRevealedBoxesData باعث می شود که همه مقادیر آن False شوند. (این تابع در بعد توضیح داده خواهد شد). از دو ساختمان داده mainBoard و revealedbox برای ردیابی وضعیت صفحه استفاده می شود.
firstSelection = None # stores the (x, y) of the first box clicked DISPLAYSURF.fill(BGCOLOR) startGameAnimation(mainBoard)
خط بالا یک متغیر به نام firstSelection با مقدار None را تعریف می کند. (None مقداری است که نشان دهنده نبودن مقدار باشد.) هنگامی که بازیکن روی یک جعبه کلیک می کند، برنامه باید بررسی کند آیا شکل کلیک شده نخستین شکل بوده است یا نه. اگر firstSelection دارای مقدار None باشد و شکلی که کلیک می شود شکل اول باشد مختصات XY شکل را در متغیر firstSelection به عنوان یک تاپل دو تایی ذخیره می کنیم. با کلیک بر روی جعبه دوم، یک تاپل خواهیم داشت و دیگر مقدار None در کار نخواهد بود. خط بعد تمام سطح را با رنگ پس زمینه رنگ می کند. این خط هم چنین بر روی هر چیزی که از قبل روی سطح بوده است را رنگ می زند و به ما یک صفحه تمیز و یک دست می دهد تا شروع به ترسیم کنیم.
اگر بازی یادمان را از پیش انجام داده باشید، متوجه خواهید شد که در آغاز بازی، همه جعبه ها به سرعت پنهان می شوند و به طور ناگهانی و در یک چشم بر هم زدنی آشکار می شوند و به بازیکن سرنخی در مورد جایگاه شکل ها می دهند. همه این رخداد ها در تابع startGameAnimation مدیریت می شود که در آینده توضیح داده می شوند.
مهم است که به بازیکن این سرنخ کوتاه را بدهید اما نباید مدت نمایش آن زیاد طول بکشد تا بازیکن به راحتی بتواند مکان شکل ها را به خاطر بسپارد. کورکورانه کلیک کردن شکل ها نیز کار جالبی به نظر نمی رسد و از زیبایی و هیجان انگیز بودن بازی می کاهد و حتی دیگر نمی توانیم نام این بازی را یادمان یا یادآوری بگذاریم.
while True: # main game loop mouseClicked = False DISPLAYSURF.fill(BGCOLOR) # drawing the window drawBoard(mainBoard, revealedBoxes)
حلقه بازی یک حلقه بی نهایت است و تا زمانی که بازی در حال انجام است، تکرار می شود. به یاد داشته باشید که حلقه بازی رویداد ها را مدیریت می کند، وضعیت بازی را به روزرسانی می کند و وضعیت بازی را روی صفحه رسم می کند.
وضعیت بازی برای برنامه یادمان در متغیرهای زیر ذخیره می شود:
در هر تکرار از حلقه بازی، متغیر mouseClicked مقداری از نوع بولین را در خود ذخیره می کند. اگر بازیکن در حین اجرا شدن حلقه بازی روی موس کلیک کند مقدار بولین True می شود (این بخشی از پی گیری وضعیت بازی است.) در خط بعدی، صفحه نمایش دوباره با رنگ پس زمینه رنگ می شود تا هر چیزی را که از پیش روی آن کشیده شده است پاک کند. سپس برای ترسیم وضعیت فعلی تابع drawBoard را فراخوانی می کنیم. تابع drawBoard دارای دو پارامتر board و ساختمان داده revealedBoxes است. (این خطوط بخشی از کار ترسیم و به روزرسانی صفحه هستند.) به یاد داشته باشید که توابع ترسیم فقط روی شی نمایش سطح کار ترسیم را انجام می دهند. تا زمانی که تابع pygame.display.update را فراخوانی نکنیم، شی سطح روی صفحه نمایش داده نمی شود. این کار را در پایان حلقه بازی انجام می دهیم.
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
حلقه for در خط 72 برای هر رویدادی که در آخرین تکرار حلقه بازی رخ داده است، کد را اجرا می کند. این حلقه، حلقه مدیریت رویداد نام دارد و روی لیست اشیا pygame.event که توسط pygame.event.get باز گردانده می شود حلقه می زند. اگر شی رویداد، QUIT یا یک رویداد KEYUP برای کلید Esc باشد، آن گاه برنامه متوقف می شود. در غیر این صورت، در صورت بروز یک رویداد MOUSEMOTION (یعنی مکان نما موس حرکت کرده بکند) یا رویداد MOUSEBUTTONUP (یعنی اگر موس از پیش کلیک شده باشد ولی اکنون رها شده است) باید موقعیت مکان نما موس در متغیرهای mousex و mousey ذخیره شود. اگر رویداد یک رویداد MOUSEBUTTONUP بود، باید mouseClicked نیز بر روی True تنظیم شود. هنگامی که همه رویدادها را کنترل و مدیریت کردیم، مقادیر ذخیره شده در mousex، mousey و mouseClicked، نشان دهنده ورودی های بازیکن خواهند بود. حال باید وضعیت بازی را به روز کنیم و نتایج را در صفحه نمایش دهیم.
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)
تابع getBoxAtPixel یک تاپل دوتایی از اعداد صحیح را برمی گرداند.این دو عدد صحیح نشانگر مختصات صفحه XY جعبه است که مختصات موس در بالای قرار می گیرد به عبارتی دیگر برای جعبه یک مختصات دیگر در نظر می گیریم و بررسی می کنیم که آیا مختصات موس در مختصات XY جعبه قرار می گیرد یا نه. این که تابع getBoxAtPixel چگونه این کار را انجام می دهد در آینده توضیح داده می شود. تنها چیزی که اکنون باید بدانیم این است که اگر مختصات mousex و mousey موس بالای یک جعبه بودند، یک تاپل از مختصات صفحه XY توسط این تابع بازگردانده می شوند و در boxx و boxy ذخیره می شوند.
اگر مکان نما موس، بالای هیچ کدام از جعبه ها نباشد (به عنوان نمونه، اگر از کنار صفحه یا در فاصله بین جعبه ها باشد)، آن گاه تاپل (None,None)، توسط تابع بازگردانده می شود و در boxx و boxy مقدار None ذخیره می شود. ما مواردی را می خواهیم که boxx و boxy دارای مقدار None نباشند، بنابراین خط های بعدی که پس از if در خط 83 آمده اند این مورد را بررسی می کنند. اگر اجرای این بخش در داخل این بلوک از کد قرار داشته باشد، می دانیم که مکان نمای کاربر بالای جعبه قرار دارد (و شاید موس را نیز کلیک کرده است.فهمیدن آن به مقدار ذخیره شده در mouseClicked بستگی دارد) عبارت if در خط 85 با خواندن مقدار ذخیره شده در revealedBoxes[boxx][boxy] بررسی می کند که آیا جعبه پوشانده شده است یا نه. اگر این مقدار False باشد، می دانیم جعبه پوشانده شده است. هر گاه موس بالای یک جعبه پوشیده شده باشد می خواهیم یک سایه روشن آبی در اطراف آن نمایش بدهیم تا بازیکن را آگاه کنیم که می تواند روی آن کلیک کند. این سایه روشن برای جعبه هایی که از پیش آشکار شده اند نخواهد بود. طراحی سایه روشن توسط تابع drawHighlightBox انجام می شود که در آینده توضیح داده می شود.
if not revealedBoxes[boxx][boxy] and mouseClicked: revealBoxesAnimation(mainBoard, [(boxx, boxy)]) revealedBoxes[boxx][boxy] = True # set the box as "revealed"
در خط بالا، بررسی می کنیم که مکان نما موس نه تنها بالای جعبه پوشانده شده است، بلکه این را هم بررسی می کنیم که موس کلیک کرده است یا نه.در این حالت، ما می خواهیم پویا نمایی آشکار سازی جعبه را با استفاده از تابع revealBoxesAnimation به کاربر نشان دهیم که در آینده توضیح داده می شود.توجه داشته باشید که فراخوانی این تابع فقط باعث اجرا شدن پویانمایی پدیدار شدن جعبه می شود. تا خط 89 این رویداد رخ نمی دهد یعنی تا هنگامی که هنوز revealedBoxes[boxx][boxy] را با True مقداردهی نکرده ایم. اگر خط 89 را کامنت کنید یعنی از اجرای آن جلوگیری کنید و سپس برنامه را اجرا کنید، متوجه خواهید شد که پس از کلیک روی جعبه، پویانمایی آشکارسازی جعبه نمایش داده می شود، اما پس از آن بی هیچ درنگی دوباره جعبه پوشانده می شود. دلیل این رخداد آن است که revealedBoxes[boxx][boxy] روی False تنظیم شده است، بنابراین در تکرار بعدی حلقه بازی، صفحه با این جعبه پوشیده شده کشیده خواهد شد.اجرا نشدن خط 89 باعث بروز اشکالات عجیب و غریب در برنامه ما خواهد شد.
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)
پیش از آن که اجرای برنامه به حلقه بازی برسد، متغیر firstSelection از پیش روی None تنظیم شده است. برنامه ما این مقدار را این گونه تفسیر می کند که هیچ جعبه ای کلیک نشده است، بنابراین اگر شرط خط 90 درست یا True باشد، این بدان معنی است که جعبه کلیک شده نخستین جعبه است.ما می خواهیم پویانمایی آشکار سازی را برای جعبه نمایش دهیم و سپس آن جعبه را آشکار شده نگه داریم. همچنین متغیر firstSelection را برای جعبه ای که کلیک شده است، با یک تاپل از مختصات جعبه مقداردهی کنیم.اگر این جعبه، جعبه دومی باشد که بازیکن روی آن کلیک کرده است، می خواهیم پویانمایی آشکار سازی را برای آن جعبه نمایش دهیم و سپس بررسی کنیم که آیا دو شکل در زیر جعبه ها با هم همسانی دارند یا خیر. تابع getShapeAndColor که در آینده توضیح داده می شوند نوع و رنگ شکل ها را بازیابی می کند.(این مقادیر بازیابی شده یکی از مقادیر در تاپل های ALLCOLORS و ALLSHAPES خواهند بود)
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
عبارت if در خط 97 بررسی می کند که شکل ها یا رنگ های دو شکل یکسان هستند یا نه. در صورت یکسان نبودن، می خواهیم با فراخوانی تابع pygame.time.wait(1000) بازی را برای 1000 میلی ثانیه (که برابر با 1 ثانیه است) متوقف کنیم تا بازیکن بتواند این فرصت را پیدا کند که این دو شکل را که با هم یکی نیستند ببیند و جای آن ها را به خاطر بسپارد. سپس پونمایی پنهان سازی را برای هر دو جعبه پخش کنیم. ما همچنین می خواهیم وضعیت بازی را به روز کنیم تا این جعبه ها به عنوان آشکار شده علامت گذاری نشوند (زیرا آن ها هنوز در زیر جعبه ها پنهان هستند).
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
در غیر این صورت، اگر شرط خط 97 نادرست بود، باید این دو شکل مطابقت داشته باشند. برنامه واقعا در این مرحله مجبور به انجام کار دیگری نیست. این فقط می تواند هر دو جعبه را در حالت آشکار باقی بگذارد. با این حال، این برنامه باید بررسی کند که آیا این آخرین جفت شکل در صفحه است که مطابقت داشته یا خیر. این کار در داخل تابع hasWon ما انجام می شود که اگر صفحه در حالت برنده شدن باشد True را برمی گرداند (یعنی حالتی که در آن همه جعبه ها نشان داده می شوند و دیگر پنهان نیستند). در این صورت، می خواهیم پویا نمایی "game won" یا "بازی برنده شد" را با فراخوانی تابع gameWonAnimation پخش کنیم، سپس کمی درنگ کنیم تا بازیکن از پیروزی خود لذت ببرد و سپس ساختمان داده ها را در mainboard و revealedBoxes بازنشانی یا پاک کند تا به این ترتیب بتوان یک بازی جدید را شروع کرد.خط 117 دوباره پویا نمایی "start game" یا آغاز بازی را پخش می کند. پس از آن، اجرای برنامه به طور معمول از طریق حلقه بازی ادامه می یابد و بازیکن می تواند بازی خود را تا زمان ترک برنامه ادامه دهد.مهم نیست که این دو جعبه مطابقت داشته باشند یا خیر، پس از کلیک بر روی جعبه دوم، خط 118 متغیر firstSelection را بر روی None تنظیم می کند تا جعبه بعدی که کلیک می شود، به عنوان نخستین جعبه تعبیر شود.
# Redraw the screen and wait a clock tick. pygame.display.update() FPSCLOCK.tick(FPS)
در این مرحله، وضعیت بازی بسته به ورودی بازیکن و آخرین حالت بازی رسم شده روی شی سطح نمایش DISPLAYSURF، به روز رسانی می شود. ما به پایان حلقه بازی رسیدیم، بنابراین تابع pygame.display.update را فراخوانی می کنیم تا شی سطح DISPLAYSURF را روی صفحه رایانه بکشیم. خط 9 مقدار FPS را بر روی عدد صحیح 30 قرار می دهد، به این معنی که می خواهیم بازی با (حداکثر) سرعت 30 فریم در ثانیه اجرا شود. اگر بخواهیم برنامه سریعتر اجرا شود، می توانیم این FPS را افزایش دهیم. اگر بخواهیم برنامه کندتر اجرا شود، می توانیم این مقدار را کاهش دهیم. حتی می توان آن را با یک مقدار اعشاری مانند 0.5 تنظیم کرد که برنامه را با نیم فریم در ثانیه اجرا می کند، یعنی یک فریم در هر دو ثانیه. برای اجرای 30 فریم در ثانیه، هر فریم باید در 30/1 ثانیه کشیده شود. این بدان معنی است که تابع pygame.display.update و تمام کدهای موجود در حلقه بازی باید در کمتر از 33.3 میلی ثانیه اجرا شوند. هر رایانه مدرن می تواند این کار را با زمان زیادی که باقی مانده است به راحتی انجام دهد.برای جلوگیری از اجرای سریع برنامه، متد tick شی pygame.Clock در FPSCLOCK را فراخوانی می کنیم تا مجبور شود برنامه را برای بقیه 33.3 میلی ثانیه متوقف کند.از آن جا که این کار در پایان حلقه بازی انجام می شود، به ما این اطمینان را می دهد که هر تکرار از حلقه بازی (حداقل) 33.3 میلی ثانیه طول می کشد. اگر بنا به دلایلی فراخوانی pygame.display.update و اجرای کد موجود در حلقه بازی بیش از 33.3 میلی ثانیه طول بکشد، متد tick اصلا درنگ نخواهد کرد و بلافاصله برمی گردد.
در این بخش مدام این جمله را خوانده اید که تابع های گوناگون در آینده در این فصل توضیح داده خواهند شد. اکنون که تابع main را پشت سر گذاشتیم و به طور کلی می دانیم که کارکرد برنامه چگونه است، بیاییم جزئیات سایر تابع های اشاره شده در بالا را که از داخل تابع main فراخوانده می شوند را بررسی کنیم.
def generateRevealedBoxesData(val): revealedBoxes = [] for i in range(BOARDWIDTH): revealedBoxes.append([val] * BOARDHEIGHT) return revealedBoxes
تابع generateRevealedBoxesData باید لیستی دو بعدی از مقادیر بولی بسازد. مقدار بولی مقداری خواهد بود که به عنوان پارامتر val به این تابع فرستاده داده می شود. در آغاز ساختمان داده را به عنوان یک لیست خالی در متغیر revealedBoxes ذخیره می کنیم. برای این که ساختمان داده را برای داشتن ساختاری مانند revealedBoxes[x][y] توانمند کنیم، باید مطمئن شویم که لیست داخلی نشان دهنده ستون ها است و نه سطرهای آن، در غیر این صورت، ساختمان داده دارای ساختار revealedBoxes[y][x] خواهد بود.حلقه for ستون ها را می سازد و سپس آن ها را به revealedBoxes پیوست می کند. ستون ها با استفاده از همانند سازی لیست ساخته می شوند، به گونه ای که لیست ستون ها همان اندازه مقدارهای val را خواهند داشت که BOARDHEIGHT دیکته می کند.
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))
ساختمان داده صفحه یا Board لیستی دوبعدی از تاپل ها است که در آن هر تاپل دو مقدار دارد : مقدار نخست برای نوع شکل و مقدار دوم رنگ آن است. اما ساخت این ساختمان داده کمی پیچیده است. ما باید دقیقا به اندازه تعداد جعبه های موجود در صفحه، شکل داشته باشیم و همچنین باید مطمئن باشیم که از هر نوع شکل با یک رنگ خاص فقط دو تا داریم. نخستین گام برای این کار ایجاد لیست با هر ترکیب احتمالی شکل و رنگ است. به خاطر بیاورید که ما در لیست های ALLCOLORS و ALLSHAPES لیستی از هر رنگ و شکل را داریم، بنابراین استفاده از حلقه های تودرتو در خط های 135 و 136 باعث می شود که هر شکل ممکن با هر رنگ ممکن را داشته باشیم. این ها هر کدام در متغیر icons در خط 137 به لیست افزوده شده اند.
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)
ممکن است ترکیبات احتمالی که از شکل و رنگ داریم، از فضایی که در صفحه موجود است بیش تر باشد. برای به دست آوردن تعداد فضاهای موجود در صفحه، باید BOARDWIDTH را در BOARDHEIGHT ضرب کنیم سپس آن عدد را به 2 تقسیم می کنیم زیرا از هر شکل یک جفت داریم پس در یک صفحه با فضای 70، ما فقط 35 شکل مختلف نیاز داریم. این عدد در متغیر numIconsUsed ذخیره می شود. خط 141 از برش لیست برای به دست آوردن تعداد نخستین numIconsUsed شکل های موجود در لیست استفاده می کند. (اگر فراموش کرده اید که چگونه برش لیست کار می کند، به نشانی http://invpy.com/slicing سری بزنید.) این لیست در خط 139 تغییر می باید، بنابراین در هر بازی همان شکل های پیشین را نخواهیم داشت.سپس این لیست با استفاده از عملگر * دوبرابر می شود، به طوری که از هر یک از این شکل ها دو تا خواهیم داشت. این لیست جدید، لیست قدیمی را در متغیر icons بازنویسی می کند. از آن جا که نیمه اول این لیست جدید با نیمه اول لیست قدیمی یکسان است، ما دوباره متد shuffle را فراخوانی می کنیم تا به طور تصادفی ترتیب شکل ها را تغییر بدهیم.
# 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
حال باید ساختمان داده لیست دو بعدی را برای صفحه بسازیم. این کار را می توانیم با حلقه های تودرتو که در تابع generateRevealedBoxesData است انجام دهیم.برای هر ستون روی صفحه، لیستی از شکل های تصادفی انتخاب شده را ایجاد می کنیم. همان طور که در خط 149 شکل ها را به ستون ها می افزاییم، در خط 150 آن ها را از قسمت جلوی لیست icons حذف می کنیم. به این ترتیب، هرچه لیست icons کوتاه تر و کوتاه تر می شود، icons[0] یک شکل دیگر برای اضافه کردن به ستون ها دارد. برای درک بهتر کد زیر را در پوسته اندرکنشی بنویسید. توجه کنید که چگونه del لیست myList را تغییر می دهد.
از آن جا که ما در حال حذف آیتم نخست لیست هستیم، بقیه آیتم ها به جلو حرکت می کنند به این ترتیب که آیتم بعدی در لیست به آیتم اول تبدیل می شود.خط 150 به همین روش کار می کند.
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
تابع splitIntoGroupsOf (که توسط تابعstartGameAnimation فراخوانی می شود) یک لیست را به یک لیست دو بعدی تبدیل می کند، که لیست داخلی حداکثر به اندازه groupSize آیتم خواهد داشت. (لیست بیرونی می تواند کمتر آیتم داشته باشد اگر تعداد کمتری از آیتم های groupSize باقی مانده باشد) فراخوانی range() در خط 159 از شکل سه پارامتری تابع range() استفاده می کند. (اگر با این شکل نا آشنا هستید، به نشانی http://invpy.com/range نگاهی بیندازید).
بگذارید از یک مثال استفاده کنیم. اگر طول لیست 20 باشد و پارامتر groupSize، 8 باشد، آن گاه range(0, len(theList), groupSize) مقداری که ارزیابی می کند range(0, 20, 8) است. این تابع به متغیر i مقادیر 0، 8 و 16 را برای سه تکرار حلقه for می دهد. عدد سوم نشان دهنده گام حلقه است. برش لیست در خط 160 با theList [i: i + groupSize] لیست هایی را ایجاد می کند که به لیست result افزوده می شوند. در هر تکرار که در آن i 0، 8 و 16 است (و groupSize برابر 8 است)، این عبارت برش دهنده لیست شامل theList[0:8]، سپس theList[8:16] در تکرار دوم، و سپس theList[16:24] در تکرار سوم خواهد بود.
توجه داشته باشید با این که بزرگترین ایندکس لیست در نمونک ما 19 است، لیست [16:24] خطای IndexError را ایجاد نمی کند، اگرچه 24 بزرگتر از 19 است. فقط یک قطعه لیست با موارد باقیمانده را ایجاد می کند. برش لیست، لیست اصلی ذخیره شده در theList را تغییر نمی دهد. فقط بخشی از آن را کپی می کند تا به یک لیست جدید مقداردهی شود. این مقدار جدید لیست، لیستی است که در متغیر result در خط 160 به لیست پیوست می شود. بنابراین وقتی resulte را در انتهای این تابع برمی گردانیم، لیستی دوبعدی را برمی گردانیم.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.